Mod InvoicePosition in member overview

This commit is contained in:
Philipp Lang 2023-12-17 21:13:52 +01:00
parent 703c74a9f4
commit 2e8c41d5d9
13 changed files with 170 additions and 233 deletions

View File

@ -0,0 +1,31 @@
<?php
namespace App\Invoice\Actions;
use App\Invoice\Models\InvoicePosition;
use App\Invoice\Resources\InvoicePositionResource;
use App\Member\Member;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Lorisleiva\Actions\Concerns\AsAction;
class PaymentPositionIndexAction
{
use AsAction;
/**
* @return Collection<int, InvoicePosition>
*/
public function handle(Member $member): Collection
{
return $member->load('invoicePositions.invoice')->invoicePositions;
}
public function asController(Member $member): JsonResponse
{
return response()->json([
'data' => InvoicePositionResource::collection($this->handle($member)),
]);
}
}

View File

@ -24,6 +24,8 @@ class InvoicePositionResource extends JsonResource
'price' => $this->price, 'price' => $this->price,
'member_id' => $this->member_id, 'member_id' => $this->member_id,
'description' => $this->description, 'description' => $this->description,
'invoice' => new InvoiceResource($this->whenLoaded('invoice')),
'price_human' => number_format($this->price / 100, 2, ',', '') . ' €',
]; ];
} }
} }

View File

@ -109,7 +109,7 @@ class MemberResource extends JsonResource
'group_name' => $this->group->name, 'group_name' => $this->group->name,
'links' => [ 'links' => [
'membership_index' => route('member.membership.index', ['member' => $this->getModel()]), 'membership_index' => route('member.membership.index', ['member' => $this->getModel()]),
'payment_index' => route('member.payment.index', ['member' => $this->getModel()]), 'invoiceposition_index' => route('member.invoice-position.index', ['member' => $this->getModel()]),
'course_index' => route('member.course.index', ['member' => $this->getModel()]), 'course_index' => route('member.course.index', ['member' => $this->getModel()]),
'show' => route('member.show', ['member' => $this->getModel()]), 'show' => route('member.show', ['member' => $this->getModel()]),
'edit' => route('member.edit', ['member' => $this->getModel()]), 'edit' => route('member.edit', ['member' => $this->getModel()]),

View File

@ -1,31 +0,0 @@
<?php
namespace App\Payment\Actions;
use App\Member\Member;
use App\Payment\Payment;
use App\Payment\PaymentResource;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Lorisleiva\Actions\Concerns\AsAction;
class IndexAction
{
use AsAction;
/**
* @return Collection<int, Payment>
*/
public function handle(Member $member): Collection
{
return $member->payments()->with('subscription')->get();
}
public function asController(Member $member): AnonymousResourceCollection
{
return PaymentResource::collection($this->handle($member))
->additional([
'meta' => PaymentResource::memberMeta($member),
]);
}
}

View File

@ -26,6 +26,11 @@ class InvoicePositionFactory extends Factory
]; ];
} }
public function description(string $description): self
{
return $this->state(['description' => $description]);
}
public function price(int $price): self public function price(int $price): self
{ {
return $this->state(['price' => $price]); return $this->state(['price' => $price]);

View File

@ -0,0 +1,35 @@
<template>
<page-header title="Zahlungen" @close="$emit('close')"> </page-header>
<div class="grow">
<table class="custom-table custom-table-light custom-table-sm text-sm">
<thead>
<th>Beschreibung</th>
<th>Status</th>
<th>Beitrag</th>
</thead>
<tr v-for="(position, index) in data" :key="index">
<td v-text="position.description"></td>
<td v-text="position.invoice.status"></td>
<td v-text="position.price_human"></td>
</tr>
</table>
</div>
</template>
<script setup>
defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js';
const props = defineProps({
url: {
type: String,
required: true,
},
});
const { data, reload } = useApiIndex(props.url, 'payment');
await reload();
</script>

View File

@ -1,68 +0,0 @@
<template>
<page-header title="Zahlungen" @close="$emit('close')">
<template #toolbar>
<page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue
Zahlung</page-toolbar-button>
<page-toolbar-button v-if="single !== null" color="primary" icon="undo"
@click.prevent="cancel">Zurück</page-toolbar-button>
</template>
</page-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-text id="nr" v-model="single.nr" label="Jahr" required></f-text>
<f-select id="subscription_id" v-model="single.subscription_id" name="subscription_id" :options="meta.subscriptions"
label="Beitrag" required></f-select>
<f-select id="status_id" v-model="single.status_id" name="status_id" :options="meta.statuses" label="Status"
required></f-select>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div v-else class="grow">
<table class="custom-table custom-table-light custom-table-sm text-sm">
<thead>
<th>Nr</th>
<th>Status</th>
<th>Beitrag</th>
<th></th>
</thead>
<tr v-for="(payment, index) in data" :key="index">
<td v-text="payment.nr"></td>
<td v-text="payment.status_name"></td>
<td v-text="payment.subscription.name"></td>
<td class="flex">
<a v-show="payment.links.show" :href="payment.links.show"
class="inline-flex btn btn-success btn-sm"><ui-sprite src="eye"></ui-sprite></a>
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(payment)"><ui-sprite
src="pencil"></ui-sprite></a>
<button v-show="!payment.is_accepted" class="inline-flex btn btn-success btn-sm"
@click.prevent="accept(payment)"><ui-sprite src="check"></ui-sprite></button>
<button class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(payment)"><ui-sprite
src="trash"></ui-sprite></button>
</td>
</tr>
</table>
</div>
</template>
<script setup>
defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js';
const props = defineProps({
url: {
type: String,
required: true,
},
});
const { axios, data, meta, reload, cancel, single, create, edit, submit, remove } = useApiIndex(props.url, 'payment');
async function accept(payment) {
await axios.patch(payment.links.update, { ...payment, status_id: 3 });
await reload();
}
await reload();
</script>

View File

@ -1,17 +1,23 @@
<template> <template>
<page-layout page-class="pb-6"> <page-layout page-class="pb-6">
<template #toolbar> <template #toolbar>
<page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Mitglied anlegen</page-toolbar-button> <page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Mitglied
<page-toolbar-button v-if="hasModule('bill')" :href="meta.links.allpayment" color="primary" icon="invoice">Rechnungen erstellen</page-toolbar-button> anlegen</page-toolbar-button>
<page-toolbar-button v-if="hasModule('bill')" :href="meta.links.sendpayment" color="info" icon="envelope">Rechnungen versenden</page-toolbar-button> <page-toolbar-button v-if="hasModule('bill')" :href="meta.links.allpayment" color="primary"
icon="invoice">Rechnungen erstellen</page-toolbar-button>
<page-toolbar-button v-if="hasModule('bill')" :href="meta.links.sendpayment" color="info"
icon="envelope">Rechnungen versenden</page-toolbar-button>
</template> </template>
<ui-popup v-if="deleting !== null" heading="Mitglied löschen?" @close="deleting.reject()"> <ui-popup v-if="deleting !== null" heading="Mitglied löschen?" @close="deleting.reject()">
<div> <div>
<p class="mt-4">Das Mitglied "{{ deleting.member.fullname }}" löschen?</p> <p class="mt-4">Das Mitglied "{{ deleting.member.fullname }}" löschen?</p>
<p class="mt-2">Alle Zuordnungen (Ausbildungen, Rechnungen, Zahlungen, Tätigkeiten) werden ebenfalls entfernt.</p> <p class="mt-2">Alle Zuordnungen (Ausbildungen, Rechnungen, Zahlungen, Tätigkeiten) werden ebenfalls
<ui-note v-if="!deleting.member.has_nami" class="mt-5" type="warning"> Dieses Mitglied ist nicht in NaMi vorhanden und wird daher nur in der AdReMa gelöscht werden. </ui-note> entfernt.</p>
<ui-note v-if="!deleting.member.has_nami" class="mt-5" type="warning"> Dieses Mitglied ist nicht in NaMi
vorhanden und wird daher nur in der AdReMa gelöscht werden. </ui-note>
<ui-note v-if="deleting.member.has_nami" class="mt-5" type="danger"> <ui-note v-if="deleting.member.has_nami" class="mt-5" type="danger">
Dieses Mitglied ist in NaMi vorhanden und wird daher in NaMi abgemeldet werden. Sofern "Datenweiterverwendung" eingeschaltet ist, wird das Mitglied auf inaktiv gesetzt. Dieses Mitglied ist in NaMi vorhanden und wird daher in NaMi abgemeldet werden. Sofern
"Datenweiterverwendung" eingeschaltet ist, wird das Mitglied auf inaktiv gesetzt.
</ui-note> </ui-note>
<div class="grid grid-cols-2 gap-3 mt-6"> <div class="grid grid-cols-2 gap-3 mt-6">
<a href="#" class="text-center btn btn-danger" @click.prevent="deleting.resolve">Mitglied loschen</a> <a href="#" class="text-center btn btn-danger" @click.prevent="deleting.resolve">Mitglied loschen</a>
@ -20,45 +26,22 @@
</div> </div>
</ui-popup> </ui-popup>
<page-filter breakpoint="xl"> <page-filter breakpoint="xl">
<f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text> <f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm"
<f-switch v-show="hasModule('bill')" id="ausstand" :model-value="getFilter('ausstand')" label="Nur Ausstände" size="sm" @update:model-value="setFilter('ausstand', $event)"></f-switch> @update:model-value="setFilter('search', $event)"></f-text>
<f-multipleselect <f-switch v-show="hasModule('bill')" id="ausstand" :model-value="getFilter('ausstand')" label="Nur Ausstände"
id="group_ids" size="sm" @update:model-value="setFilter('ausstand', $event)"></f-switch>
:options="meta.groups" <f-multipleselect id="group_ids" :options="meta.groups" :model-value="getFilter('group_ids')"
:model-value="getFilter('group_ids')" label="Gruppierungen" size="sm" name="group_ids"
label="Gruppierungen" @update:model-value="setFilter('group_ids', $event)"></f-multipleselect>
size="sm" <f-select v-show="hasModule('bill')" id="billKinds" name="billKinds" :options="meta.billKinds"
name="group_ids" :model-value="getFilter('bill_kind')" label="Rechnung" size="sm"
@update:model-value="setFilter('group_ids', $event)" @update:model-value="setFilter('bill_kind', $event)"></f-select>
></f-multipleselect> <f-multipleselect id="activity_ids" :options="meta.filterActivities" :model-value="getFilter('activity_ids')"
<f-select label="Tätigkeiten" size="sm" name="activity_ids"
v-show="hasModule('bill')" @update:model-value="setFilter('activity_ids', $event)"></f-multipleselect>
id="billKinds" <f-multipleselect id="subactivity_ids" :options="meta.filterSubactivities"
name="billKinds" :model-value="getFilter('subactivity_ids')" label="Untertätigkeiten" size="sm" name="subactivity_ids"
:options="meta.billKinds" @update:model-value="setFilter('subactivity_ids', $event)"></f-multipleselect>
:model-value="getFilter('bill_kind')"
label="Rechnung"
size="sm"
@update:model-value="setFilter('bill_kind', $event)"
></f-select>
<f-multipleselect
id="activity_ids"
:options="meta.filterActivities"
:model-value="getFilter('activity_ids')"
label="Tätigkeiten"
size="sm"
name="activity_ids"
@update:model-value="setFilter('activity_ids', $event)"
></f-multipleselect>
<f-multipleselect
id="subactivity_ids"
:options="meta.filterSubactivities"
:model-value="getFilter('subactivity_ids')"
label="Untertätigkeiten"
size="sm"
name="subactivity_ids"
@update:model-value="setFilter('subactivity_ids', $event)"
></f-multipleselect>
<button class="btn btn-primary label mr-2" @click.prevent="exportMembers"> <button class="btn btn-primary label mr-2" @click.prevent="exportMembers">
<ui-sprite class="w-3 h-3 xl:mr-2" src="save"></ui-sprite> <ui-sprite class="w-3 h-3 xl:mr-2" src="save"></ui-sprite>
<span class="hidden xl:inline">Exportieren</span> <span class="hidden xl:inline">Exportieren</span>
@ -107,11 +90,14 @@
<div class="text-xs text-gray-200" v-text="member.full_address"></div> <div class="text-xs text-gray-200" v-text="member.full_address"></div>
<div class="flex items-center mt-1 space-x-4"> <div class="flex items-center mt-1 space-x-4">
<tags :member="member"></tags> <tags :member="member"></tags>
<ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment" fallback=""></ui-label> <ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment"
fallback=""></ui-label>
</div> </div>
<actions class="mt-2" :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)"> </actions> <actions class="mt-2" :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)">
</actions>
<div class="absolute right-0 top-0 h-full flex items-center mr-2"> <div class="absolute right-0 top-0 h-full flex items-center mr-2">
<i-link v-tooltip="`Details`" :href="member.links.show"><ui-sprite src="chevron" class="w-6 h-6 text-teal-100 -rotate-90"></ui-sprite></i-link> <i-link v-tooltip="`Details`" :href="member.links.show"><ui-sprite src="chevron"
class="w-6 h-6 text-teal-100 -rotate-90"></ui-sprite></i-link>
</div> </div>
</ui-box> </ui-box>
</div> </div>
@ -121,27 +107,30 @@
</div> </div>
<ui-sidebar v-if="single !== null" @close="closeSidebar"> <ui-sidebar v-if="single !== null" @close="closeSidebar">
<member-payments v-if="single.type === 'payment'" :url="single.model.links.payment_index" @close="closeSidebar"></member-payments> <member-invoice-positions v-if="single.type === 'invoicePosition'"
<member-memberships v-if="single.type === 'membership'" :url="single.model.links.membership_index" @close="closeSidebar"></member-memberships> :url="single.model.links.invoiceposition_index" @close="closeSidebar"></member-invoice-positions>
<member-courses v-if="single.type === 'courses'" :url="single.model.links.course_index" @close="closeSidebar"></member-courses> <member-memberships v-if="single.type === 'membership'" :url="single.model.links.membership_index"
@close="closeSidebar"></member-memberships>
<member-courses v-if="single.type === 'courses'" :url="single.model.links.course_index"
@close="closeSidebar"></member-courses>
</ui-sidebar> </ui-sidebar>
</page-layout> </page-layout>
</template> </template>
<script setup> <script setup>
import MemberPayments from './MemberPayments.vue'; import MemberInvoicePositions from './MemberInvoicePositions.vue';
import MemberMemberships from './MemberMemberships.vue'; import MemberMemberships from './MemberMemberships.vue';
import MemberCourses from './MemberCourses.vue'; import MemberCourses from './MemberCourses.vue';
import Tags from './Tags.vue'; import Tags from './Tags.vue';
import Actions from './index/Actions.vue'; import Actions from './index/Actions.vue';
import {indexProps, useIndex} from '../../composables/useIndex.js'; import { indexProps, useIndex } from '../../composables/useIndex.js';
import {ref, defineProps} from 'vue'; import { ref, defineProps } from 'vue';
const single = ref(null); const single = ref(null);
const deleting = ref(null); const deleting = ref(null);
const props = defineProps(indexProps); const props = defineProps(indexProps);
var {router, data, meta, getFilter, setFilter, filterString, reloadPage} = useIndex(props.data, 'member'); var { router, data, meta, getFilter, setFilter, filterString, reloadPage } = useIndex(props.data, 'member');
function exportMembers() { function exportMembers() {
window.open(`/member-export?filter=${filterString.value}`); window.open(`/member-export?filter=${filterString.value}`);
@ -149,7 +138,7 @@ function exportMembers() {
async function remove(member) { async function remove(member) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
deleting.value = {resolve, reject, member}; deleting.value = { resolve, reject, member };
}) })
.then(() => { .then(() => {
router.delete(`/member/${member.id}`); router.delete(`/member/${member.id}`);

View File

@ -1,14 +1,19 @@
<template> <template>
<div class="flex space-x-1"> <div class="flex space-x-1">
<i-link v-tooltip="`Details`" :href="member.links.show" class="inline-flex btn btn-primary btn-sm"><ui-sprite src="eye"></ui-sprite></i-link> <i-link v-tooltip="`Details`" :href="member.links.show" class="inline-flex btn btn-primary btn-sm"><ui-sprite
<i-link v-tooltip="`Bearbeiten`" :href="member.links.edit" class="inline-flex btn btn-warning btn-sm"><ui-sprite src="pencil"></ui-sprite></i-link> src="eye"></ui-sprite></i-link>
<a v-show="hasModule('bill')" v-tooltip="`Zahlungen`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'payment')"><ui-sprite src="money"></ui-sprite></a> <i-link v-tooltip="`Bearbeiten`" :href="member.links.edit" class="inline-flex btn btn-warning btn-sm"><ui-sprite
<a v-show="hasModule('courses')" v-tooltip="`Ausbildungen`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'courses')" src="pencil"></ui-sprite></i-link>
><ui-sprite src="course"></ui-sprite <a v-show="hasModule('bill')" v-tooltip="`Zahlungen`" href="#" class="inline-flex btn btn-info btn-sm"
></a> @click.prevent="$emit('sidebar', 'invoicePosition')"><ui-sprite src="money"></ui-sprite></a>
<a v-tooltip="`Mitgliedschaften`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'membership')"><ui-sprite src="user"></ui-sprite></a> <a v-show="hasModule('courses')" v-tooltip="`Ausbildungen`" href="#" class="inline-flex btn btn-info btn-sm"
<a v-show="member.efz_link" v-tooltip="`EFZ Formular`" :href="member.efz_link" class="inline-flex btn btn-info btn-sm"><ui-sprite src="report"></ui-sprite></a> @click.prevent="$emit('sidebar', 'courses')"><ui-sprite src="course"></ui-sprite></a>
<a v-tooltip="`Entfernen`" href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="$emit('remove')"><ui-sprite src="trash"></ui-sprite></a> <a v-tooltip="`Mitgliedschaften`" href="#" class="inline-flex btn btn-info btn-sm"
@click.prevent="$emit('sidebar', 'membership')"><ui-sprite src="user"></ui-sprite></a>
<a v-show="member.efz_link" v-tooltip="`EFZ Formular`" :href="member.efz_link"
class="inline-flex btn btn-info btn-sm"><ui-sprite src="report"></ui-sprite></a>
<a v-tooltip="`Entfernen`" href="#" class="inline-flex btn btn-danger btn-sm"
@click.prevent="$emit('remove')"><ui-sprite src="trash"></ui-sprite></a>
</div> </div>
</template> </template>
@ -17,6 +22,6 @@ defineProps({
member: { member: {
type: Object, type: Object,
required: true, required: true,
} },
}); });
</script> </script>

View File

@ -29,6 +29,7 @@ use App\Invoice\Actions\InvoiceDestroyAction;
use App\Invoice\Actions\InvoiceIndexAction; use App\Invoice\Actions\InvoiceIndexAction;
use App\Invoice\Actions\InvoiceUpdateAction; use App\Invoice\Actions\InvoiceUpdateAction;
use App\Invoice\Actions\MassStoreAction; use App\Invoice\Actions\MassStoreAction;
use App\Invoice\Actions\PaymentPositionIndexAction;
use App\Maildispatcher\Actions\CreateAction; use App\Maildispatcher\Actions\CreateAction;
use App\Maildispatcher\Actions\DestroyAction; use App\Maildispatcher\Actions\DestroyAction;
use App\Maildispatcher\Actions\EditAction; use App\Maildispatcher\Actions\EditAction;
@ -107,7 +108,6 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/group', ListAction::class)->name('group.index'); Route::get('/group', ListAction::class)->name('group.index');
// ---------------------------------- payment ---------------------------------- // ---------------------------------- payment ----------------------------------
Route::get('/member/{member}/payment', PaymentIndexAction::class)->name('member.payment.index');
Route::get('/payment/{payment}/pdf', DisplayPdfAction::class)->name('payment.pdf'); Route::get('/payment/{payment}/pdf', DisplayPdfAction::class)->name('payment.pdf');
Route::post('/member/{member}/payment', PaymentStoreAction::class)->name('member.payment.store'); Route::post('/member/{member}/payment', PaymentStoreAction::class)->name('member.payment.store');
Route::patch('/payment/{payment}', PaymentUpdateAction::class)->name('payment.update'); Route::patch('/payment/{payment}', PaymentUpdateAction::class)->name('payment.update');
@ -122,6 +122,10 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::patch('/invoice/{invoice}', InvoiceUpdateAction::class)->name('invoice.update'); Route::patch('/invoice/{invoice}', InvoiceUpdateAction::class)->name('invoice.update');
Route::delete('/invoice/{invoice}', InvoiceDestroyAction::class)->name('invoice.destroy'); Route::delete('/invoice/{invoice}', InvoiceDestroyAction::class)->name('invoice.destroy');
// ----------------------------- invoice-position ------------------------------
Route::get('/member/{member}/invoice-position', PaymentPositionIndexAction::class)->name('member.invoice-position.index');
// --------------------------------- membership -------------------------------- // --------------------------------- membership --------------------------------
Route::get('/member/{member}/membership', MembershipIndexAction::class)->name('member.membership.index'); Route::get('/member/{member}/membership', MembershipIndexAction::class)->name('member.membership.index');
Route::post('/member/{member}/membership', MembershipStoreAction::class)->name('member.membership.store'); Route::post('/member/{member}/membership', MembershipStoreAction::class)->name('member.membership.store');

View File

@ -0,0 +1,30 @@
<?php
namespace Tests\Feature\InvoicePosition;
use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\Models\Invoice;
use App\Invoice\Models\InvoicePosition;
use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class IndexTest extends TestCase
{
use DatabaseTransactions;
public function testItShowsInvoicePositions(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()
->has(InvoicePosition::factory()->for(Invoice::factory()->status(InvoiceStatus::SENT))->description('lala b')->price(5566))
->defaults()->create();
$this->get(route('member.invoice-position.index', ['member' => $member]))
->assertJsonPath('data.0.description', 'lala b')
->assertJsonPath('data.0.price_human', '55,66 €')
->assertJsonPath('data.0.id', $member->invoicePositions->first()->id)
->assertJsonPath('data.0.invoice.status', 'Rechnung gestellt');
}
}

View File

@ -37,7 +37,7 @@ class IndexTest extends TestCase
$this->assertInertiaHas($group->id, $response, 'data.data.0.group_id'); $this->assertInertiaHas($group->id, $response, 'data.data.0.group_id');
$this->assertInertiaHas(null, $response, 'data.data.0.memberships'); $this->assertInertiaHas(null, $response, 'data.data.0.memberships');
$this->assertInertiaHas(url("/member/{$member->id}/membership"), $response, 'data.data.0.links.membership_index'); $this->assertInertiaHas(url("/member/{$member->id}/membership"), $response, 'data.data.0.links.membership_index');
$this->assertInertiaHas(url("/member/{$member->id}/payment"), $response, 'data.data.0.links.payment_index'); $this->assertInertiaHas(url("/member/{$member->id}/invoice-position"), $response, 'data.data.0.links.invoiceposition_index');
$this->assertInertiaHas(url("/member/{$member->id}/course"), $response, 'data.data.0.links.course_index'); $this->assertInertiaHas(url("/member/{$member->id}/course"), $response, 'data.data.0.links.course_index');
$this->assertInertiaHas([ $this->assertInertiaHas([
'id' => $member->subscription->id, 'id' => $member->subscription->id,

View File

@ -1,65 +0,0 @@
<?php
namespace Tests\Feature\Payment;
use App\Invoice\BillDocument;
use App\Invoice\DocumentFactory;
use App\Member\Member;
use App\Payment\Payment;
use App\Payment\Subscription;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Collection;
use Tests\RequestFactories\Child;
use Tests\TestCase;
class IndexTest extends TestCase
{
use DatabaseTransactions;
public function testItShowsPayments(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()
->has(Payment::factory()->notPaid()->nr('2019')->subscription('Free', [
new Child('a', 1000),
new Child('b', 50),
]))
->defaults()->create();
$payment = $member->payments->first();
$this->get("/member/{$member->id}/payment")
->assertJsonPath('data.0.subscription.name', 'Free')
->assertJsonPath('data.0.subscription.id', $payment->subscription->id)
->assertJsonPath('data.0.subscription.amount', 1050)
->assertJsonPath('data.0.subscription_id', $payment->subscription->id)
->assertJsonPath('data.0.status_name', 'Nicht bezahlt')
->assertJsonPath('data.0.nr', '2019')
->assertJsonPath('data.0.links.show', null)
->assertJsonPath('data.0.links.update', url("/payment/{$payment->id}"))
->assertJsonPath('data.0.links.destroy', url("/payment/{$payment->id}"))
->assertJsonPath('meta.statuses.0.name', 'Nicht bezahlt')
->assertJsonPath('meta.statuses.0.id', $payment->status->id)
->assertJsonPath('meta.subscriptions.0.id', Subscription::first()->id)
->assertJsonPath('meta.subscriptions.0.name', Subscription::first()->name)
->assertJsonPath('meta.links.store', url("/member/{$member->id}/payment"));
}
public function testItShowsPaymentLink(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()
->has(Payment::factory()->notPaid()->nr('2019')->subscription('Free', [
new Child('a', 1000),
new Child('b', 50),
]))
->defaults()->create();
/** @var Collection<int|string, Member> */
$members = collect([$member]);
app(DocumentFactory::class)->afterSingle(BillDocument::fromMembers($members), $members);
$this->get("/member/{$member->id}/payment")
->assertJsonPath('data.0.links.show', route('payment.pdf', ['payment' => $member->payments->first()]));
}
}