Add Membership management
This commit is contained in:
parent
a134be5f5b
commit
2e9ab78203
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Group\Actions;
|
||||
|
||||
use App\Activity;
|
||||
use App\Group;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class ListAction
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function asController(): Response
|
||||
{
|
||||
session()->put('menu', 'group');
|
||||
session()->put('title', 'Gruppen');
|
||||
$activities = Activity::with('subactivities')->get();
|
||||
|
||||
return Inertia::render('group/Index', [
|
||||
'activities' => $activities->pluck('name', 'id'),
|
||||
'subactivities' => $activities->mapWithKeys(fn (Activity $activity) => [$activity->id => $activity->subactivities()->pluck('name', 'id')]),
|
||||
'groups' => Group::pluck('name', 'id'),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -17,16 +17,17 @@ class SearchAction
|
|||
/**
|
||||
* @return LengthAwarePaginator<int, Member>
|
||||
*/
|
||||
public function handle(FilterScope $filter): LengthAwarePaginator
|
||||
public function handle(FilterScope $filter, int $perPage): LengthAwarePaginator
|
||||
{
|
||||
return Member::search($filter->search)->query(fn ($q) => $q->select('*')
|
||||
->withFilter($filter)
|
||||
->ordered()
|
||||
)->paginate(15);
|
||||
return Member::search($filter->search)->query(
|
||||
fn ($q) => $q->select('*')
|
||||
->withFilter($filter)
|
||||
->ordered()
|
||||
)->paginate($perPage);
|
||||
}
|
||||
|
||||
public function asController(ActionRequest $request): AnonymousResourceCollection
|
||||
{
|
||||
return MemberResource::collection($this->handle(FilterScope::fromRequest($request->input('filter', ''))));
|
||||
return MemberResource::collection($this->handle(FilterScope::fromRequest($request->input('filter', '')), $request->input('per_page', 15)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,10 +67,11 @@ class FilterScope extends Filter
|
|||
}
|
||||
|
||||
if (false === $this->hasFullAddress) {
|
||||
$query->where(fn ($q) => $q
|
||||
->orWhere('address', '')->orWhereNull('address')
|
||||
->orWhere('zip', '')->orWhereNull('zip')
|
||||
->orWhere('location', '')->orWhereNull('location')
|
||||
$query->where(
|
||||
fn ($q) => $q
|
||||
->orWhere('address', '')->orWhereNull('address')
|
||||
->orWhere('zip', '')->orWhereNull('zip')
|
||||
->orWhere('location', '')->orWhereNull('location')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class MemberResource extends JsonResource
|
|||
'subscription' => new SubscriptionResource($this->whenLoaded('subscription')),
|
||||
'gender_id' => $this->gender_id,
|
||||
'gender_name' => $this->gender?->name ?: 'keine Angabe',
|
||||
'fullname' => ($this->gender ? $this->gender->salutation.' ' : '').$this->fullname,
|
||||
'fullname' => ($this->gender ? $this->gender->salutation . ' ' : '') . $this->fullname,
|
||||
'further_address' => $this->further_address,
|
||||
'work_phone' => $this->work_phone,
|
||||
'mobile_phone' => $this->mobile_phone,
|
||||
|
@ -74,7 +74,7 @@ class MemberResource extends JsonResource
|
|||
'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,
|
||||
'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')),
|
||||
'nationality' => new NationalityResource($this->whenLoaded('nationality')),
|
||||
|
|
|
@ -53,6 +53,14 @@ class Membership extends Model
|
|||
return $this->belongsTo(Subactivity::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Member, self>
|
||||
*/
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Member::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder<Membership> $query
|
||||
*
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Membership\Actions;
|
||||
|
||||
use App\Member\Membership;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class ApiListAction
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function asController(ActionRequest $request): JsonResponse
|
||||
{
|
||||
return response()->json(Membership::active()->where([
|
||||
'group_id' => $request->group_id,
|
||||
'activity_id' => $request->activity_id,
|
||||
'subactivity_id' => $request->subactivity_id,
|
||||
])->pluck('member_id'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Membership\Actions;
|
||||
|
||||
use App\Activity;
|
||||
use App\Group;
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
use App\Lib\Queue\TracksJob;
|
||||
use App\Maildispatcher\Actions\ResyncAction;
|
||||
use App\Member\Member;
|
||||
use App\Member\Membership;
|
||||
use App\Setting\NamiSettings;
|
||||
use App\Subactivity;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class SyncAction
|
||||
{
|
||||
use AsAction;
|
||||
use TracksJob;
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'group_id' => 'required|numeric|exists:groups,id',
|
||||
'activity_id' => 'required|numeric|exists:activities,id',
|
||||
'subactivity_id' => 'required|numeric|exists:subactivities,id',
|
||||
'members' => 'array',
|
||||
'members.*' => 'numeric|exists:members,id',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $members
|
||||
*/
|
||||
public function handle(Group $group, Activity $activity, Subactivity $subactivity, array $members): void
|
||||
{
|
||||
DB::transaction(function () use ($activity, $subactivity, $group, $members) {
|
||||
$attributes = [
|
||||
'group_id' => $group->id,
|
||||
'activity_id' => $activity->id,
|
||||
'subactivity_id' => $subactivity->id,
|
||||
];
|
||||
|
||||
Membership::where($attributes)->active()->whereNotIn('member_id', $members)->get()
|
||||
->each(fn ($membership) => MembershipDestroyAction::run($membership->member, $membership, app(NamiSettings::class)));
|
||||
|
||||
collect($members)
|
||||
->except(Membership::where($attributes)->active()->pluck('member_id'))
|
||||
->map(fn ($memberId) => Member::findOrFail($memberId))
|
||||
->each(fn ($member) => MembershipStoreAction::run(
|
||||
$member,
|
||||
$activity,
|
||||
$subactivity,
|
||||
$group,
|
||||
null,
|
||||
app(NamiSettings::class),
|
||||
));
|
||||
|
||||
|
||||
ResyncAction::dispatch();
|
||||
});
|
||||
}
|
||||
|
||||
public function asController(ActionRequest $request): void
|
||||
{
|
||||
/**
|
||||
* @var array{members: array<int, int>, group_id: int, activity_id: int, subactivity_id: int}
|
||||
*/
|
||||
$input = $request->validated();
|
||||
|
||||
$this->startJob(
|
||||
Group::findOrFail($input['group_id']),
|
||||
Activity::findOrFail($input['activity_id']),
|
||||
Subactivity::findOrFail($input['subactivity_id']),
|
||||
$input['members'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $parameters
|
||||
*/
|
||||
public function jobState(WithJobState $jobState, ...$parameters): WithJobState
|
||||
{
|
||||
return $jobState
|
||||
->before('Gruppen werden aktualisiert')
|
||||
->after('Gruppen aktualisiert')
|
||||
->failed('Aktualisieren von Gruppen fehlgeschlagen');
|
||||
}
|
||||
|
||||
public function jobChannel(): string
|
||||
{
|
||||
return 'group';
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 15 KiB |
|
@ -3,14 +3,14 @@
|
|||
<div class="text-sm text-gray-500" v-html="desc"></div>
|
||||
<div class="-mx-1 items-baseline" :class="{hidden: value.last_page == 1, flex: value.last_page > 1}">
|
||||
<div class="pl-1 pr-3 text-gray-500 text-sm">Seite:</div>
|
||||
<div class="px-1" v-for="(link, index) in links" :key="index">
|
||||
<div v-for="(link, index) in links" :key="index" class="px-1">
|
||||
<button
|
||||
href="#"
|
||||
@click.prevent="goto(link)"
|
||||
class="rounded text-sm w-8 h-8 text-primary-100 flex items-center justify-center leading-none shadow"
|
||||
:key="index"
|
||||
v-text="link.page"
|
||||
href="#"
|
||||
class="rounded text-sm w-8 h-8 text-primary-100 flex items-center justify-center leading-none shadow"
|
||||
:class="{'bg-primary-700': link.current, 'bg-primary-900': !link.current}"
|
||||
@click.prevent="goto(link)"
|
||||
v-text="link.page"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -32,22 +32,6 @@ export default {
|
|||
type: Boolean,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goto(page) {
|
||||
if (this.$attrs.onReload) {
|
||||
this.$emit('reload', page.page);
|
||||
return;
|
||||
}
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
params.set('page', page.page);
|
||||
|
||||
this.$inertia.visit(window.location.pathname + '?' + params.toString(), {
|
||||
only: this.only,
|
||||
preserveState: this.preserve,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
links() {
|
||||
|
@ -69,5 +53,21 @@ export default {
|
|||
return `${this.value.from} - ${this.value.to} von ${this.value.total} Einträgen`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goto(page) {
|
||||
if (this.$attrs.onReload) {
|
||||
this.$emit('reload', page.page);
|
||||
return;
|
||||
}
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
params.set('page', page.page);
|
||||
|
||||
this.$inertia.visit(window.location.pathname + '?' + params.toString(), {
|
||||
only: this.only,
|
||||
preserveState: this.preserve,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {ref, computed, onBeforeUnmount} from 'vue';
|
||||
import {router} from '@inertiajs/vue3';
|
||||
import {useToast} from 'vue-toastification';
|
||||
const toast = useToast();
|
||||
import useQueueEvents from './useQueueEvents.js';
|
||||
|
||||
export function useIndex(props, siteName) {
|
||||
const {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
||||
const rawProps = JSON.parse(JSON.stringify(props));
|
||||
const inner = {
|
||||
data: ref(rawProps.data),
|
||||
|
@ -61,22 +61,8 @@ export function useIndex(props, siteName) {
|
|||
};
|
||||
}
|
||||
|
||||
function handleJobEvent(event, type = 'success') {
|
||||
if (event.message) {
|
||||
toast[type](event.message);
|
||||
}
|
||||
if (event.reload) {
|
||||
reload(false);
|
||||
}
|
||||
}
|
||||
|
||||
window.Echo.channel('jobs').listen('\\App\\Lib\\Events\\ClientMessage', (e) => handleJobEvent(e));
|
||||
window.Echo.channel(siteName)
|
||||
.listen('\\App\\Lib\\Events\\JobStarted', (e) => handleJobEvent(e))
|
||||
.listen('\\App\\Lib\\Events\\JobFinished', (e) => handleJobEvent(e))
|
||||
.listen('\\App\\Lib\\Events\\JobFailed', (e) => handleJobEvent(e, 'error'));
|
||||
onBeforeUnmount(() => window.Echo.leave(siteName));
|
||||
onBeforeUnmount(() => window.Echo.leave('jobs'));
|
||||
startListener(;
|
||||
onBeforeUnmount(() => stopListener());
|
||||
|
||||
return {
|
||||
data: inner.data,
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import {useToast} from 'vue-toastification';
|
||||
const toast = useToast();
|
||||
|
||||
function handleJobEvent(event, type = 'success', reloadCallback) {
|
||||
if (event.message) {
|
||||
toast[type](event.message);
|
||||
}
|
||||
if (event.reload) {
|
||||
reloadCallback();
|
||||
}
|
||||
}
|
||||
|
||||
export default function (siteName, reloadCallback) {
|
||||
return {
|
||||
startListener: function () {
|
||||
window.Echo.channel('jobs').listen('\\App\\Lib\\Events\\ClientMessage', (e) => handleJobEvent(e, 'success', reloadCallback));
|
||||
window.Echo.channel(siteName)
|
||||
.listen('\\App\\Lib\\Events\\JobStarted', (e) => handleJobEvent(e, 'success', reloadCallback))
|
||||
.listen('\\App\\Lib\\Events\\JobFinished', (e) => handleJobEvent(e, 'success', reloadCallback))
|
||||
.listen('\\App\\Lib\\Events\\JobFailed', (e) => handleJobEvent(e, 'error', reloadCallback));
|
||||
},
|
||||
stopListener() {
|
||||
window.Echo.leave(siteName);
|
||||
window.Echo.leave('jobs');
|
||||
},
|
||||
};
|
||||
}
|
|
@ -2,26 +2,25 @@
|
|||
<v-notification class="fixed z-40 right-0 bottom-0 mb-3 mr-3"></v-notification>
|
||||
|
||||
<!-- ******************************** Sidebar ******************************** -->
|
||||
<div
|
||||
class="fixed z-40 bg-gray-800 p-6 w-56 top-0 h-screen border-r border-gray-600 border-solid flex flex-col justify-between transition-all"
|
||||
<div class="fixed z-40 bg-gray-800 p-6 w-56 top-0 h-screen border-r border-gray-600 border-solid flex flex-col justify-between transition-all"
|
||||
:class="{
|
||||
'-left-[14rem]': !menuStore.isShifted,
|
||||
'left-0': menuStore.isShifted,
|
||||
}"
|
||||
>
|
||||
}">
|
||||
<div class="grid gap-2">
|
||||
<v-link href="/" menu="dashboard" icon="loss">Dashboard</v-link>
|
||||
<v-link href="/member" menu="member" icon="user">Mitglieder</v-link>
|
||||
<v-link href="/subscription" v-show="hasModule('bill')" menu="subscription" icon="money">Beiträge</v-link>
|
||||
<v-link v-show="hasModule('bill')" href="/subscription" menu="subscription" icon="money">Beiträge</v-link>
|
||||
<v-link href="/contribution" menu="contribution" icon="contribution">Zuschüsse</v-link>
|
||||
<v-link href="/activity" menu="activity" icon="activity">Tätigkeiten</v-link>
|
||||
<v-link href="/group" menu="group" icon="group">Gruppen</v-link>
|
||||
<v-link href="/maildispatcher" menu="maildispatcher" icon="at">Mail-Verteiler</v-link>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<v-link href="/setting" menu="setting" icon="setting">Einstellungen</v-link>
|
||||
<v-link @click.prevent="$inertia.post('/logout')" icon="logout" href="/logout">Abmelden</v-link>
|
||||
<v-link icon="logout" href="/logout" @click.prevent="$inertia.post('/logout')">Abmelden</v-link>
|
||||
</div>
|
||||
<a href="#" @click.prevent="menuStore.hide()" v-if="menuStore.hideable" class="absolute right-0 top-0 mr-2 mt-2">
|
||||
<a v-if="menuStore.hideable" href="#" class="absolute right-0 top-0 mr-2 mt-2" @click.prevent="menuStore.hide()">
|
||||
<ui-sprite src="close" class="w-5 h-5 text-gray-300"></ui-sprite>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -31,19 +30,19 @@
|
|||
|
||||
<script>
|
||||
import VLink from './_VLink.vue';
|
||||
import {menuStore} from '../stores/menuStore.js';
|
||||
import { menuStore } from '../stores/menuStore.js';
|
||||
import VNotification from '../components/VNotification.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VNotification,
|
||||
VLink,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
menuStore: menuStore(),
|
||||
};
|
||||
},
|
||||
components: {
|
||||
VNotification,
|
||||
VLink,
|
||||
},
|
||||
|
||||
computed: {
|
||||
filterMenu() {
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<page-layout>
|
||||
<template #right>
|
||||
<f-save-button form="actionform"></f-save-button>
|
||||
</template>
|
||||
<form id="actionform" class="grow p-3" @submit.prevent="submit">
|
||||
<div class="flex space-x-3">
|
||||
<f-select :model-value="meta.activity_id" :options="props.activities" label="Tätigkeit" size="sm" name="activity_id" @update:model-value="setActivityId"></f-select>
|
||||
<f-select
|
||||
:model-value="meta.subactivity_id"
|
||||
:options="props.subactivities[meta.activity_id]"
|
||||
name="subactivity_id"
|
||||
label="Untertätigkeit"
|
||||
size="sm"
|
||||
@update:model-value="reload('subactivity_id', $event)"
|
||||
></f-select>
|
||||
<f-select :model-value="meta.group_id" :options="props.groups" label="Gruppierung" size="sm" name="group_id" @update:model-value="reload('group_id', $event)"></f-select>
|
||||
</div>
|
||||
<div class="grid gap-2 grid-cols-6 mt-4">
|
||||
<f-switch v-for="member in members.data" :id="`member-${member.id}`" :key="member.id" v-model="selected" :value="member.id" :label="member.fullname" size="sm"></f-switch>
|
||||
</div>
|
||||
<div v-if="members.meta.last_page" class="px-6">
|
||||
<ui-pagination class="mt-4" :value="members.meta" :only="['data']" @reload="reloadReal($event, false)"></ui-pagination>
|
||||
</div>
|
||||
</form>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script lang="js" setup>
|
||||
import { onBeforeUnmount, ref, defineProps, reactive, inject } from 'vue';
|
||||
import useQueueEvents from '../../composables/useQueueEvents.js';
|
||||
const {startListener, stopListener} = useQueueEvents('group', () => null);
|
||||
const axios = inject('axios');
|
||||
|
||||
startListener();
|
||||
onBeforeUnmount(() => stopListener());
|
||||
|
||||
const meta = reactive({
|
||||
activity_id: null,
|
||||
subactivity_id: null,
|
||||
group_id: null,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
activities: {
|
||||
type: Object,
|
||||
default: () => { },
|
||||
},
|
||||
subactivities: {
|
||||
type: Object,
|
||||
default: () => { },
|
||||
},
|
||||
groups: {
|
||||
type: Object,
|
||||
default: () => { },
|
||||
},
|
||||
});
|
||||
|
||||
const members = ref({ meta: {}, data: [] });
|
||||
const selected = ref([]);
|
||||
|
||||
async function reload(key, v) {
|
||||
meta[key] = v;
|
||||
|
||||
reloadReal(1, true);
|
||||
}
|
||||
|
||||
async function reloadReal(page, update) {
|
||||
if (meta.activity_id && meta.subactivity_id && meta.group_id) {
|
||||
const memberResponse = await axios.post('/api/member/search', {
|
||||
page: page,
|
||||
per_page: 80,
|
||||
});
|
||||
members.value = memberResponse.data;
|
||||
|
||||
if (update) {
|
||||
const membershipResponse = await axios.post('/api/membership/member-list', meta);
|
||||
selected.value = membershipResponse.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setActivityId(id) {
|
||||
meta.subactivity_id = null;
|
||||
await reload('activity_id', id);
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
await axios.post('/api/membership/sync', {
|
||||
...meta,
|
||||
members: selected.value,
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -15,6 +15,7 @@ use App\Contribution\Actions\ValidateAction as ContributionValidateAction;
|
|||
use App\Course\Controllers\CourseController;
|
||||
use App\Dashboard\Actions\IndexAction as DashboardIndexAction;
|
||||
use App\Efz\ShowEfzDocumentAction;
|
||||
use App\Group\Actions\ListAction;
|
||||
use App\Initialize\Actions\InitializeAction;
|
||||
use App\Initialize\Actions\InitializeFormAction;
|
||||
use App\Initialize\Actions\NamiGetSearchLayerAction;
|
||||
|
@ -34,9 +35,11 @@ use App\Member\Actions\MemberResyncAction;
|
|||
use App\Member\Actions\MemberShowAction;
|
||||
use App\Member\Actions\SearchAction;
|
||||
use App\Member\MemberController;
|
||||
use App\Membership\Actions\ApiListAction;
|
||||
use App\Membership\Actions\MembershipDestroyAction;
|
||||
use App\Membership\Actions\MembershipStoreAction;
|
||||
use App\Membership\Actions\MembershipUpdateAction;
|
||||
use App\Membership\Actions\SyncAction;
|
||||
use App\Payment\Actions\AllpaymentPageAction;
|
||||
use App\Payment\Actions\AllpaymentStoreAction;
|
||||
use App\Payment\PaymentController;
|
||||
|
@ -54,6 +57,8 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
|||
Route::post('/nami/get-search-layer', NamiGetSearchLayerAction::class)->name('nami.get-search-layer');
|
||||
Route::post('/nami/search', NamiSearchAction::class)->name('nami.search');
|
||||
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::get('/initialize', InitializeFormAction::class)->name('initialize.form');
|
||||
Route::post('/initialize', InitializeAction::class)->name('initialize.store');
|
||||
Route::resource('member', MemberController::class)->except('show', 'destroy');
|
||||
|
@ -98,4 +103,7 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
|||
Route::patch('/maildispatcher/{maildispatcher}', MaildispatcherUpdateAction::class)->name('maildispatcher.update');
|
||||
Route::post('/maildispatcher', MaildispatcherStoreAction::class)->name('maildispatcher.store');
|
||||
Route::delete('/maildispatcher/{maildispatcher}', DestroyAction::class)->name('maildispatcher.destroy');
|
||||
|
||||
// ----------------------------------- group -----------------------------------
|
||||
Route::get('/group', ListAction::class)->name('group.index');
|
||||
});
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\EndToEnd;
|
||||
|
||||
use App\Activity;
|
||||
use App\Group;
|
||||
use App\Maildispatcher\Actions\ResyncAction;
|
||||
use App\Member\Member;
|
||||
use App\Member\Membership;
|
||||
use App\Membership\Actions\MembershipDestroyAction;
|
||||
use App\Membership\Actions\MembershipStoreAction;
|
||||
use App\Membership\Actions\SyncAction;
|
||||
use App\Subactivity;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Tests\TestCase;
|
||||
use Throwable;
|
||||
use Zoomyboy\LaravelNami\Fakes\MembershipFake;
|
||||
|
||||
class SyncActionTest extends TestCase
|
||||
{
|
||||
|
||||
use DatabaseMigrations;
|
||||
|
||||
public function testItFiresActionJobWhenUsingController(): void
|
||||
{
|
||||
Queue::fake();
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
$member = Member::factory()->defaults()->create();
|
||||
$activity = Activity::factory()->create();
|
||||
$subactivity = Subactivity::factory()->create();
|
||||
$group = Group::factory()->create();
|
||||
|
||||
$this->postJson('/api/membership/sync', [
|
||||
'members' => [$member->id],
|
||||
'activity_id' => $activity->id,
|
||||
'subactivity_id' => $subactivity->id,
|
||||
'group_id' => $group->id,
|
||||
]);
|
||||
SyncAction::assertPushed(fn ($action, $params) => $params[0]->is($group) && $params[1]->is($activity) && $params[2]->is($subactivity) && $params[3][0] === $member->id);
|
||||
}
|
||||
|
||||
public function testItCreatesAMembership(): void
|
||||
{
|
||||
MembershipDestroyAction::partialMock()->shouldReceive('handle')->never();
|
||||
MembershipStoreAction::partialMock()->shouldReceive('handle')->once();
|
||||
$member = Member::factory()->defaults()->create();
|
||||
$activity = Activity::factory()->create();
|
||||
$subactivity = Subactivity::factory()->create();
|
||||
$group = Group::factory()->create();
|
||||
|
||||
SyncAction::run($group, $activity, $subactivity, [$member->id]);
|
||||
}
|
||||
|
||||
public function testItDeletesAMembership(): void
|
||||
{
|
||||
MembershipDestroyAction::partialMock()->shouldReceive('handle')->once();
|
||||
MembershipStoreAction::partialMock()->shouldReceive('handle')->never();
|
||||
ResyncAction::partialMock()->shouldReceive('handle')->once();
|
||||
|
||||
$member = Member::factory()->defaults()->has(Membership::factory()->inLocal('Leiter*in', 'Rover'))->create();
|
||||
|
||||
SyncAction::run($member->memberships->first()->group, $member->memberships->first()->activity, $member->memberships->first()->subactivity, []);
|
||||
}
|
||||
|
||||
public function testItRollsbackWhenDeletionFails(): void
|
||||
{
|
||||
app(MembershipFake::class)
|
||||
->shows(3, ['id' => 55])
|
||||
->shows(3, ['id' => 56])
|
||||
->destroysSuccessfully(3, 55)
|
||||
->failsDeleting(3, 56);
|
||||
$this->login()->loginNami();
|
||||
|
||||
$member = Member::factory()->defaults()->inNami(3)
|
||||
->has(Membership::factory()->in('Leiter*in', 10, 'Rover', 11)->inNami(55))
|
||||
->has(Membership::factory()->in('Leiter*in', 10, 'Jungpfadfinder', 12)->inNami(56))
|
||||
->create();
|
||||
|
||||
try {
|
||||
SyncAction::run($member->memberships->first()->group, $member->memberships->first()->activity, $member->memberships->first()->subactivity, []);
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
$this->assertDatabaseCount('memberships', 2);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Group;
|
||||
|
||||
use App\Activity;
|
||||
use App\Group;
|
||||
use App\Subactivity;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\TestCase;
|
||||
|
||||
class IndexTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function testItDisplaysAllActivitiesAndSubactivities(): void
|
||||
{
|
||||
$this->login()->loginNami();
|
||||
|
||||
$leiter = Activity::factory()->name('Leiter*in')->hasAttached(Subactivity::factory()->name('Rover'))->create();
|
||||
$intern = Activity::factory()->name('Intern')->hasAttached(Subactivity::factory()->name('Lager'))->create();
|
||||
$group = Group::factory()->create();
|
||||
|
||||
$response = $this->get('/group');
|
||||
|
||||
$this->assertInertiaHas('Leiter*in', $response, "activities.{$leiter->id}");
|
||||
$this->assertInertiaHas('Intern', $response, "activities.{$intern->id}");
|
||||
$this->assertInertiaHas('Rover', $response, "subactivities.{$leiter->id}.{$leiter->subactivities->first()->id}");
|
||||
$this->assertInertiaHas('Lager', $response, "subactivities.{$intern->id}.{$intern->subactivities->first()->id}");
|
||||
$this->assertInertiaHas($group->name, $response, "groups.{$group->id}");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue