Add api for fetching memberships
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Philipp Lang 2023-09-12 16:54:13 +02:00
parent 85f0d6c515
commit 3d154c4154
17 changed files with 252 additions and 199 deletions

View File

@ -23,7 +23,7 @@ class MemberController extends Controller
'data' => MemberResource::collection(Member::search($filter->search)->query(
fn ($q) => $q->select('*')
->withFilter($filter)
->with(['payments.subscription', 'memberships', 'courses', 'subscription', 'leaderMemberships', 'ageGroupMemberships'])
->with(['payments.subscription', 'courses', 'subscription', 'leaderMemberships', 'ageGroupMemberships'])
->withPendingPayment()
->ordered()
)->paginate(15)),

View File

@ -73,10 +73,10 @@ class MemberResource extends JsonResource
'has_nami' => null !== $this->nami_id,
'children_phone' => $this->children_phone,
'payments' => PaymentResource::collection($this->whenLoaded('payments')),
'memberships' => MembershipResource::collection($this->whenLoaded('memberships')),
'pending_payment' => $this->pending_payment ? number_format($this->pending_payment / 100, 2, ',', '.') . ' €' : null,
'age_group_icon' => $this->ageGroupMemberships->first()?->subactivity->slug,
'courses' => CourseMemberResource::collection($this->whenLoaded('courses')),
'memberships' => MembershipResource::collection($this->whenLoaded('memberships')),
'nationality' => new NationalityResource($this->whenLoaded('nationality')),
'region' => new RegionResource($this->whenLoaded('region')),
'full_address' => $this->fullAddress,
@ -105,6 +105,7 @@ class MemberResource extends JsonResource
'lat' => $this->lat,
'lon' => $this->lon,
'links' => [
'membership_index' => route('member.membership.index', ['member' => $this->getModel()]),
'show' => route('member.show', ['member' => $this->getModel()]),
'edit' => route('member.edit', ['member' => $this->getModel()]),
],

View File

@ -0,0 +1,33 @@
<?php
namespace App\Membership\Actions;
use App\Member\Member;
use App\Member\Membership;
use App\Membership\MembershipResource;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Pagination\LengthAwarePaginator;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class ApiIndexAction
{
use AsAction;
/**
* @return Collection<int, Membership>
*/
public function handle(Member $member): Collection
{
return $member->memberships;
}
public function asController(Member $member): AnonymousResourceCollection
{
return MembershipResource::collection($this->handle($member))
->additional([
'meta' => MembershipResource::memberMeta($member)
]);
}
}

View File

@ -6,7 +6,7 @@ use App\Maildispatcher\Actions\ResyncAction;
use App\Member\Member;
use App\Member\Membership;
use App\Setting\NamiSettings;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
@ -32,16 +32,15 @@ class MembershipDestroyAction
}
}
public function asController(Member $member, Membership $membership, ActionRequest $request, NamiSettings $settings): RedirectResponse
public function asController(Membership $membership, NamiSettings $settings): JsonResponse
{
$this->handle(
$member,
$membership->member,
$membership,
$settings,
);
ResyncAction::dispatch();
return redirect()->back();
return response()->json([]);
}
}

View File

@ -4,11 +4,10 @@ namespace App\Membership\Actions;
use App\Activity;
use App\Maildispatcher\Actions\ResyncAction;
use App\Member\Member;
use App\Member\Membership;
use App\Subactivity;
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\In;
use Lorisleiva\Actions\ActionRequest;
@ -20,8 +19,6 @@ class MembershipUpdateAction
public function handle(Membership $membership, Activity $activity, ?Subactivity $subactivity, ?Carbon $promisedAt): Membership
{
$from = now()->startOfDay();
$membership->update([
'activity_id' => $activity->id,
'subactivity_id' => $subactivity ? $subactivity->id : null,
@ -56,7 +53,7 @@ class MembershipUpdateAction
];
}
public function asController(Member $member, Membership $membership, ActionRequest $request): RedirectResponse
public function asController(Membership $membership, ActionRequest $request): JsonResponse
{
$this->handle(
$membership,
@ -67,6 +64,6 @@ class MembershipUpdateAction
ResyncAction::dispatch();
return redirect()->back();
return response()->json([]);
}
}

View File

@ -2,6 +2,10 @@
namespace App\Membership;
use App\Activity;
use App\Lib\HasMeta;
use App\Member\Data\NestedGroup;
use App\Member\Member;
use Illuminate\Http\Resources\Json\JsonResource;
/**
@ -28,6 +32,33 @@ class MembershipResource extends JsonResource
'human_date' => $this->from->format('d.m.Y'),
'promised_at' => $this->promised_at?->format('Y-m-d'),
'is_active' => $this->isActive(),
'links' => [
'update' => route('membership.update', ['membership' => $this->getModel()]),
'destroy' => route('membership.destroy', ['membership' => $this->getModel()]),
]
];
}
/**
* @return array<string, mixed>
*/
public static function memberMeta(Member $member): array
{
$activities = Activity::with('subactivities')->get();
return [
'links' => [
'store' => route('member.membership.store', ['member' => $member]),
],
'groups' => NestedGroup::cacheForSelect(),
'activities' => $activities->map(fn ($activity) => ['id' => $activity->id, 'name' => $activity->name]),
'subactivities' => $activities->mapWithKeys(fn ($activity) => [$activity->id => $activity->subactivities->map(fn ($subactivity) => ['id' => $subactivity->id, 'name' => $subactivity->name])]),
'default' => [
'group_id' => $member->group_id,
'activity_id' => null,
'subactivity_id' => null,
'promised_at' => null,
],
];
}
}

View File

@ -63,10 +63,11 @@ class MembershipFactory extends Factory
public function in(string $activity, int $activityNamiId, ?string $subactivity = null, ?int $subactivityNamiId = null): self
{
$instance = $this->for(Activity::factory()->name($activity)->inNami($activityNamiId));
$activityModel = Activity::factory()->name($activity)->inNami($activityNamiId)->create();
$instance = $this->for($activityModel);
if ($subactivity) {
$instance = $instance->for(Subactivity::factory()->name($subactivity)->inNami($subactivityNamiId));
$instance = $instance->for(Subactivity::factory()->name($subactivity)->inNami($subactivityNamiId)->hasAttached($activityModel));
}
return $instance;

View File

@ -1,10 +1,7 @@
<template>
<div v-tooltip="longLabel" class="flex space-x-2 items-center">
<div class="border-2 rounded-full w-5 h-5 flex items-center justify-center"
:class="value ? (dark ? 'border-green-500' : 'border-green-700') : dark ? 'border-red-500' : 'border-red-700'">
<ui-sprite :src="value ? 'check' : 'close'"
:class="value ? (dark ? 'text-green-600' : 'text-green-800') : dark ? 'text-red-600' : 'text-red-800'"
class="w-3 h-3 flex-none"></ui-sprite>
<div class="border-2 rounded-full w-5 h-5 flex items-center justify-center" :class="value ? (dark ? 'border-green-500' : 'border-green-700') : dark ? 'border-red-500' : 'border-red-700'">
<ui-sprite :src="value ? 'check' : 'close'" :class="value ? (dark ? 'text-green-600' : 'text-green-800') : dark ? 'text-red-600' : 'text-red-800'" class="w-3 h-3 flex-none"></ui-sprite>
</div>
<div class="text-gray-400 text-xs" v-text="label"></div>
</div>
@ -18,8 +15,8 @@ export default {
type: Boolean,
},
label: {
required: true,
type: String,
default: () => '',
},
longLabel: {
default: function () {

View File

@ -2,24 +2,24 @@
<div class="sidebar flex flex-col group is-bright">
<page-header title="Mitgliedschaften" @close="$emit('close')">
<template #toolbar>
<page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue
Mitgliedschaft</page-toolbar-button>
<page-toolbar-button v-if="single !== null" color="primary" icon="undo"
@click.prevent="cancel">Zurück</page-toolbar-button>
<page-toolbar-button v-if="single === null" color="primary" icon="plus" @click.prevent="create">Neue Mitgliedschaft</page-toolbar-button>
<page-toolbar-button v-if="single !== null" color="primary" icon="undo" @click.prevent="single = null">Zurück</page-toolbar-button>
</template>
</page-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-select id="group_id" v-model="single.group_id" name="group_id" :options="groups" label="Gruppierung"
required></f-select>
<f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="activities"
label="Tätigkeit" required></f-select>
<f-select v-if="single.activity_id" id="subactivity_id" v-model="single.subactivity_id" name="subactivity_id"
:options="subactivities[single.activity_id]" label="Untertätigkeit" size="sm"></f-select>
<f-switch id="has_promise" :model-value="single.promised_at !== null" label="Hat Versprechen"
@update:modelValue="single.promised_at = $event ? '2000-02-02' : null"></f-switch>
<f-text v-show="single.promised_at !== null" id="promised_at" v-model="single.promised_at" type="date"
label="Versprechensdatum" size="sm"></f-text>
<f-select id="group_id" v-model="single.group_id" name="group_id" :options="data.meta.groups" label="Gruppierung" required></f-select>
<f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="data.meta.activities" label="Tätigkeit" required></f-select>
<f-select
v-if="single.activity_id"
id="subactivity_id"
v-model="single.subactivity_id"
name="subactivity_id"
:options="data.meta.subactivities[single.activity_id]"
label="Untertätigkeit"
></f-select>
<f-switch id="has_promise" :model-value="single.promised_at !== null" label="Hat Versprechen" @update:modelValue="single.promised_at = $event ? '2000-02-02' : null"></f-switch>
<f-text v-show="single.promised_at !== null" id="promised_at" v-model="single.promised_at" type="date" label="Versprechensdatum" size="sm"></f-text>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
@ -33,18 +33,14 @@
<th></th>
</thead>
<tr v-for="(membership, index) in value.memberships" :key="index">
<tr v-for="(membership, index) in data.data" :key="index">
<td v-text="membership.activity_name"></td>
<td v-text="membership.subactivity_name"></td>
<td v-text="membership.human_date"></td>
<td><ui-boolean-display :value="membership.is_active" dark></ui-boolean-display></td>
<td class="flex">
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="
single = membership;
mode = 'edit';
"><ui-sprite src="pencil"></ui-sprite></a>
<i-link href="#" class="inline-flex btn btn-danger btn-sm"
@click.prevent="remove(membership)"><ui-sprite src="trash"></ui-sprite></i-link>
<td class="flex space-x-1">
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil"></ui-sprite></a>
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite src="trash"></ui-sprite></a>
</td>
</tr>
</table>
@ -52,70 +48,47 @@
</div>
</template>
<script>
export default {
props: {
value: {},
activities: {},
subactivities: {},
groups: {},
},
data: function () {
return {
mode: null,
single: null,
};
<script setup>
import {ref, inject, onBeforeMount} from 'vue';
const axios = inject('axios');
const props = defineProps({
value: {
type: Object,
required: true,
},
});
computed: {
def() {
return {
group_id: this.value.group_id,
activity_id: null,
subactivity_id: null,
promised_at: null,
};
},
},
const single = ref(null);
const data = ref({
meta: {},
data: [],
});
methods: {
create() {
this.mode = 'create';
this.single = { ...this.def };
},
cancel() {
this.mode = this.single = null;
},
remove(membership) {
this.$inertia.delete(`/member/${this.value.id}/membership/${membership.id}`);
},
async function reload() {
data.value = (await axios.post(props.value.links.membership_index)).data;
}
accept(payment) {
this.$inertia.patch(`/member/${this.value.id}/payment/${payment.id}`, { ...payment, status_id: 3 });
},
onBeforeMount(async () => {
await reload();
});
openLink(link) {
if (link.disabled) {
return;
}
function create() {
single.value = JSON.parse(JSON.stringify(data.value.meta.default));
}
window.open(link.href);
},
function edit(membership) {
single.value = JSON.parse(JSON.stringify(membership));
}
submit() {
var _self = this;
async function submit() {
single.value.id ? await axios.patch(single.value.links.update, single.value) : await axios.post(data.value.meta.links.store, single.value);
await reload();
single.value = null;
}
var options = {
onSuccess() {
_self.single = null;
_self.mode = null;
},
};
this.mode === 'create'
? this.$inertia.post(`/member/${this.value.id}/membership`, this.single, options)
: this.$inertia.patch(`/member/${this.value.id}/membership/${this.single.id}`, this.single, options);
},
},
};
async function remove(membership) {
await axios.delete(membership.links.destroy);
await reload();
}
</script>

View File

@ -1,20 +1,20 @@
<template>
<div class="sidebar flex flex-col group is-bright">
<page-header @close="$emit('close')" title="Zahlungen">
<page-header title="Zahlungen" @close="$emit('close')">
<template #toolbar>
<page-toolbar-button @click.prevent="create" color="primary" icon="plus" v-if="single === null">Neue Zahlung</page-toolbar-button>
<page-toolbar-button @click.prevent="cancel" color="primary" icon="undo" v-if="single !== null">Zurück</page-toolbar-button>
<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" name="subscription_id" :options="subscriptions" v-model="single.subscription_id" label="Beitrag" required></f-select>
<f-select id="status_id" name="status_id" :options="statuses" v-model="single.status_id" label="Status" required></f-select>
<f-select id="subscription_id" v-model="single.subscription_id" name="subscription_id" :options="subscriptions" label="Beitrag" required></f-select>
<f-select id="status_id" v-model="single.status_id" name="status_id" :options="statuses" label="Status" required></f-select>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="grow" v-else>
<div v-else class="grow">
<table class="custom-table custom-table-light custom-table-sm text-sm">
<thead>
<th>Nr</th>
@ -30,28 +30,28 @@
<td class="flex">
<a
href="#"
class="inline-flex btn btn-warning btn-sm"
@click.prevent="
single = payment;
mode = 'edit';
"
class="inline-flex btn btn-warning btn-sm"
><ui-sprite src="pencil"></ui-sprite
></a>
<i-link v-show="!payment.is_accepted" href="#" @click.prevent="accept(payment)" class="inline-flex btn btn-success btn-sm"><ui-sprite src="check"></ui-sprite></i-link>
<i-link href="#" @click.prevent="remove(payment)" class="inline-flex btn btn-danger btn-sm"><ui-sprite src="trash"></ui-sprite></i-link>
<i-link v-show="!payment.is_accepted" href="#" class="inline-flex btn btn-success btn-sm" @click.prevent="accept(payment)"><ui-sprite src="check"></ui-sprite></i-link>
<i-link href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(payment)"><ui-sprite src="trash"></ui-sprite></i-link>
</td>
</tr>
</table>
</div>
<div class="flex flex-col pb-6 px-6">
<a
href="#"
@click.prevent="openLink(link)"
:class="{disabled: link.disabled}"
target="_BLANK"
v-for="(link, index) in value.payment_links"
:key="index"
href="#"
:class="{disabled: link.disabled}"
target="_BLANK"
class="mt-1 text-center btn btn-primary"
@click.prevent="openLink(link)"
v-text="link.label"
></a>
</div>
@ -60,6 +60,12 @@
<script>
export default {
props: {
value: {},
subscriptions: {},
statuses: {},
},
data: function () {
return {
mode: null,
@ -107,11 +113,5 @@ export default {
});
},
},
props: {
value: {},
subscriptions: {},
statuses: {},
},
};
</script>

View File

@ -77,7 +77,7 @@
<ui-label :value="member.pending_payment" fallback="---"></ui-label>
</td>
<td>
<actions :member="member" @sidebar="openSidebar(index, $event)" @remove="remove(member)"></actions>
<actions :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)"></actions>
</td>
</tr>
</table>
@ -93,7 +93,7 @@
<ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment"
fallback=""></ui-label>
</div>
<actions class="mt-2" :member="member" @sidebar="openSidebar(index, $event)" @remove="remove(member)">
<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">
<i-link v-tooltip="`Details`" :href="member.links.show"><ui-sprite src="chevron"
@ -106,12 +106,11 @@
<ui-pagination class="mt-4" :value="meta" :only="['data']"></ui-pagination>
</div>
<member-payments v-if="single !== null && sidebar === 'payment.index'" :subscriptions="meta.subscriptions"
:statuses="meta.statuses" :value="data[single]" @close="closeSidebar"></member-payments>
<member-memberships v-if="single !== null && sidebar === 'membership.index'" :groups="meta.groups"
:activities="meta.formActivities" :subactivities="meta.formSubactivities" :value="data[single]"
@close="closeSidebar"></member-memberships>
<member-courses v-if="single !== null && sidebar === 'courses.index'" :courses="meta.courses" :value="data[single]"
<member-payments v-if="single !== null && single.type === 'payment'" :subscriptions="meta.subscriptions"
:statuses="meta.statuses" :value="single.model" @close="closeSidebar"></member-payments>
<member-memberships v-if="single !== null && single.type === 'membership'" :activities="meta.formActivities"
:subactivities="meta.formSubactivities" :value="single.model" @close="closeSidebar"></member-memberships>
<member-courses v-if="single !== null && single.type === 'courses'" :courses="meta.courses" :value="single.model"
@close="closeSidebar"></member-courses>
</page-layout>
</template>
@ -125,7 +124,6 @@ import Actions from './index/Actions.vue';
import { indexProps, useIndex } from '../../composables/useIndex.js';
import { ref, defineProps } from 'vue';
const sidebar = ref(null);
const single = ref(null);
const deleting = ref(null);
@ -147,12 +145,13 @@ async function remove(member) {
.catch(() => (deleting.value = null));
}
function openSidebar(index, name) {
single.value = index;
sidebar.value = name;
function openSidebar(type, model) {
single.value = {
type: type,
model: model,
};
}
function closeSidebar() {
single.value = null;
sidebar.value = null;
}
</script>

View File

@ -1,16 +1,14 @@
<template>
<div class="flex space-x-1">
<i-link :href="member.links.show" class="inline-flex btn btn-primary btn-sm" v-tooltip="`Details`"><ui-sprite src="eye"></ui-sprite></i-link>
<i-link :href="`/member/${member.id}/edit`" class="inline-flex btn btn-warning btn-sm" v-tooltip="`Bearbeiten`"><ui-sprite src="pencil"></ui-sprite></i-link>
<a href="#" v-tooltip="`Zahlungen`" v-show="hasModule('bill')" @click.prevent="$emit('sidebar', 'payment.index')" class="inline-flex btn btn-info btn-sm"
><ui-sprite src="money"></ui-sprite
></a>
<a href="#" v-tooltip="`Ausbildungen`" v-show="hasModule('courses')" @click.prevent="$emit('sidebar', 'courses.index')" class="inline-flex btn btn-info btn-sm"
<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="`Bearbeiten`" :href="`/member/${member.id}/edit`" class="inline-flex btn btn-warning btn-sm"><ui-sprite src="pencil"></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>
<a v-show="hasModule('courses')" v-tooltip="`Ausbildungen`" href="#" class="inline-flex btn btn-info btn-sm" @click.prevent="$emit('sidebar', 'courses')"
><ui-sprite src="course"></ui-sprite
></a>
<a href="#" v-tooltip="`Mitgliedschaften`" @click.prevent="$emit('sidebar', 'membership.index')" class="inline-flex btn btn-info btn-sm"><ui-sprite src="user"></ui-sprite></a>
<a :href="member.efz_link" v-show="member.efz_link" class="inline-flex btn btn-info btn-sm" v-tooltip="`EFZ Formular`"><ui-sprite src="report"></ui-sprite></a>
<a href="#" @click.prevent="$emit('remove')" class="inline-flex btn btn-danger btn-sm" v-tooltip="`Entfernen`"><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>
</template>

View File

@ -35,6 +35,7 @@ use App\Member\Actions\MemberResyncAction;
use App\Member\Actions\MemberShowAction;
use App\Member\Actions\SearchAction;
use App\Member\MemberController;
use App\Membership\Actions\ApiIndexAction;
use App\Membership\Actions\ApiListAction;
use App\Membership\Actions\MembershipDestroyAction;
use App\Membership\Actions\MembershipStoreAction;
@ -59,6 +60,7 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::post('/api/member/search', SearchAction::class)->name('member.search');
Route::post('/api/membership/member-list', ApiListAction::class)->name('membership.member-list');
Route::post('/api/membership/sync', SyncAction::class)->name('membership.sync');
Route::post('/api/member/{member}/membership', ApiIndexAction::class)->name('member.membership.index');
Route::get('/initialize', InitializeFormAction::class)->name('initialize.form');
Route::post('/initialize', InitializeAction::class)->name('initialize.store');
Route::resource('member', MemberController::class)->except('show', 'destroy');
@ -72,9 +74,9 @@ Route::group(['middleware' => 'auth:web'], function (): void {
->name('member.singlepdf');
Route::get('/sendpayment', [SendpaymentController::class, 'create'])->name('sendpayment.create');
Route::get('/sendpayment/pdf', [SendpaymentController::class, 'send'])->name('sendpayment.pdf');
Route::post('/member/{member}/membership', MembershipStoreAction::class)->name('membership.store');
Route::patch('/member/{member}/membership/{membership}', MembershipUpdateAction::class)->name('membership.store');
Route::delete('/member/{member}/membership/{membership}', MembershipDestroyAction::class)->name('membership.destroy');
Route::post('/member/{member}/membership', MembershipStoreAction::class)->name('member.membership.store');
Route::patch('/membership/{membership}', MembershipUpdateAction::class)->name('membership.update');
Route::delete('/membership/{membership}', MembershipDestroyAction::class)->name('membership.destroy');
Route::resource('member.course', CourseController::class);
Route::get('/member/{member}/efz', ShowEfzDocumentAction::class)->name('efz');
Route::get('/member/{member}/resync', MemberResyncAction::class)->name('member.resync');

View File

@ -8,8 +8,6 @@ use App\Member\Member;
use App\Member\Membership;
use App\Payment\Payment;
use App\Subactivity;
use Carbon\Carbon;
use Generator;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\RequestFactories\Child;
use Tests\TestCase;
@ -22,7 +20,7 @@ class IndexTest extends TestCase
{
$this->withoutExceptionHandling()->login()->loginNami();
$group = Group::factory()->create();
Member::factory()->defaults()->for($group)->create([
$member = Member::factory()->defaults()->for($group)->create([
'firstname' => '::firstname::',
'address' => 'Kölner Str 3',
'zip' => 33333,
@ -36,6 +34,8 @@ class IndexTest extends TestCase
$this->assertInertiaHas(false, $response, 'data.data.0.has_nami');
$this->assertInertiaHas('Kölner Str 3, 33333 Hilden', $response, 'data.data.0.full_address');
$this->assertInertiaHas($group->id, $response, 'data.data.0.group_id');
$this->assertInertiaHas(null, $response, 'data.data.0.memberships');
$this->assertInertiaHas(route('member.membership.index', ['member' => $member]), $response, 'data.data.0.links.membership_index');
}
public function testFieldsCanBeNull(): void
@ -82,29 +82,6 @@ class IndexTest extends TestCase
$this->assertInertiaHas(false, $response, 'data.data.2.is_leader');
}
public function membershipDataProvider(): Generator
{
yield [now()->subMonths(2), null, true];
yield [now()->subMonths(2), now()->subDay(), false];
yield [now()->addDays(2), null, false];
}
/**
* @dataProvider membershipDataProvider
*/
public function testItShowsIfMembershipIsActive(Carbon $from, ?Carbon $to, bool $isActive): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()
->defaults()
->has(Membership::factory()->in('€ LeiterIn', 455, 'Pfadfinder', 15)->state(['from' => $from, 'to' => $to]))
->create();
$response = $this->get('/member');
$this->assertInertiaHas($isActive, $response, 'data.data.0.memberships.0.is_active');
}
public function testItHasNoEfzLinkWhenAddressIsMissing(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
@ -155,28 +132,6 @@ class IndexTest extends TestCase
$this->assertInertiaHas('€ Mitglied', $response, "data.meta.formActivities.{$activity->id}");
}
public function testItShowsActivityAndSubactivityNamesOfMember(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$group = Group::factory()->create();
$member = Member::factory()
->defaults()
->has(Membership::factory()->for($group)->in('€ Mitglied', 122, 'Wölfling', 234)->from('2022-11-02'))
->create();
$response = $this->get('/member');
$this->assertInertiaHas([
'activity_id' => $member->memberships->first()->activity_id,
'subactivity_id' => $member->memberships->first()->subactivity_id,
'activity_name' => '€ Mitglied',
'subactivity_name' => 'Wölfling',
'human_date' => '02.11.2022',
'group_id' => $group->id,
'id' => $member->memberships->first()->id,
], $response, 'data.data.0.memberships.0');
}
public function testItReturnsPayments(): void
{
$this->withoutExceptionHandling()->login()->loginNami();

View File

@ -44,9 +44,9 @@ class DestroyTest extends TestCase
->inNami(6)
->create();
$response = $this->from('/member')->delete("/member/{$member->id}/membership/{$member->memberships->first()->id}");
$response = $this->delete("/membership/{$member->memberships->first()->id}");
$response->assertRedirect('/member');
$response->assertOk();
$this->assertEquals(1506, $member->fresh()->version);
$this->assertDatabaseMissing('memberships', [
'member_id' => $member->id,
@ -65,9 +65,8 @@ class DestroyTest extends TestCase
->inNami(6)
->create();
$response = $this->from('/member')->delete("/member/{$member->id}/membership/{$member->memberships->first()->id}");
$response = $this->delete("/membership/{$member->memberships->first()->id}");
$response->assertRedirect('/member');
$this->assertDatabaseMissing('memberships', [
'member_id' => $member->id,
]);

View File

@ -0,0 +1,68 @@
<?php
namespace Tests\Feature\Membership;
use App\Group;
use App\Member\Member;
use App\Member\Membership;
use Generator;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class IndexTest extends TestCase
{
use DatabaseTransactions;
public function testItShowsActivityAndSubactivityNamesOfMember(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$group = Group::factory()->create(['name' => 'aaaaaaaa']);
$member = Member::factory()
->defaults()
->for($group)
->has(Membership::factory()->for($group)->in('€ Mitglied', 122, 'Wölfling', 234)->from('2022-11-02'))
->create();
$membership = $member->memberships->first();
$this->postJson("/api/member/{$member->id}/membership")
->assertJsonPath('data.0.activity_id', $membership->activity_id)
->assertJsonPath('data.0.subactivity_id', $membership->subactivity_id)
->assertJsonPath('data.0.activity_name', '€ Mitglied')
->assertJsonPath('data.0.subactivity_name', 'Wölfling')
->assertJsonPath('data.0.human_date', '02.11.2022')
->assertJsonPath('data.0.group_id', $group->id)
->assertJsonPath('data.0.id', $membership->id)
->assertJsonPath('data.0.links.update', route('membership.update', ['membership' => $membership]))
->assertJsonPath('data.0.links.destroy', route('membership.destroy', ['membership' => $membership]))
->assertJsonPath('meta.default.activity_id', null)
->assertJsonPath('meta.default.group_id', $group->id)
->assertJsonPath('meta.groups.0.id', $group->id)
->assertJsonPath('meta.activities.0.id', $membership->activity_id)
->assertJsonPath("meta.subactivities.{$membership->activity_id}.0.id", $membership->subactivity_id)
->assertJsonPath('meta.links.store', route('member.membership.store', ['member' => $member]));
}
public function membershipDataProvider(): Generator
{
yield [now()->subMonths(2), null, true];
yield [now()->subMonths(2), now()->subDay(), false];
yield [now()->addDays(2), null, false];
}
/**
* @dataProvider membershipDataProvider
*/
public function testItShowsIfMembershipIsActive(Carbon $from, ?Carbon $to, bool $isActive): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()
->defaults()
->has(Membership::factory()->in('€ LeiterIn', 455, 'Pfadfinder', 15)->state(['from' => $from, 'to' => $to]))
->create();
$this->postJson("/api/member/{$member->id}/membership")
->assertJsonPath('data.0.is_active', $isActive);
}
}

View File

@ -37,11 +37,11 @@ class UpdateTest extends TestCase
$membership = $member->memberships->first();
$response = $this->from('/member')->patch(
"/member/{$member->id}/membership/{$membership->id}",
"/membership/{$membership->id}",
MembershipRequestFactory::new()->promise(now())->in($membership->activity, $membership->subactivity)->create()
);
$response->assertRedirect('/member');
$response->assertOk();
$this->assertDatabaseHas('memberships', [
'member_id' => $member->id,
'activity_id' => $activity->id,