Add Backend for displaying memberships
continuous-integration/drone/push Build is failing Details

This commit is contained in:
philipp lang 2025-06-13 16:04:04 +02:00
parent 878de3f566
commit 66b6a549c3
10 changed files with 203 additions and 4 deletions

29
app/Lib/Data/DateData.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Lib\Data;
use Spatie\LaravelData\Normalizers\Normalizer;
use App\Lib\Normalizers\DateNormalizer;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\WithTransformer;
use App\Lib\Transformers\DateTransformer;
use Carbon\Carbon;
class DateData extends Data
{
public function __construct(
#[WithTransformer(DateTransformer::class)]
public Carbon $raw,
public string $human,
) {}
/**
* @return array<int, class-string<Normalizer>>
*/
public static function normalizers(): array
{
return [
DateNormalizer::class,
];
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Lib\Data;
use Spatie\LaravelData\Data;
class RecordData extends Data {
public function __construct(
public int $id,
public string $name,
) {}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Lib\Normalizers;
use Spatie\LaravelData\Normalizers\Normalizer;
use Carbon\Carbon;
class DateNormalizer implements Normalizer
{
/**
* @return array<string, mixed>
*/
public function normalize(mixed $value): ?array
{
if (!$value instanceof Carbon) {
return null;
}
return [
'raw' => $value,
'human' => $value->format('d.m.Y'),
];
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Lib\Transformers;
use Spatie\LaravelData\Transformers\Transformer;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;
use Carbon\Carbon;
class DateTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value, TransformationContext $context): string
{
return Carbon::parse($value)->format('Y-m-d');
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Member\Data;
use Spatie\LaravelData\Data;
use App\Lib\Data\DateData;
use App\Lib\Data\RecordData;
use App\Member\Membership;
class MembershipData extends Data
{
public function __construct(
public int $id,
public RecordData $activity,
public ?RecordData $subactivity,
public RecordData $group,
public ?DateData $promisedAt,
public DateData $from,
public bool $isActive,
public array $links,
) {}
public static function fromModel(Membership $membership): static
{
return static::factory()->withoutMagicalCreation()->from([
'id' => $membership->id,
'activity' => $membership->activity,
'subactivity' => $membership->subactivity,
'isActive' => $membership->isActive(),
'from' => $membership->from,
'group' => $membership->group,
'promisedAt' => $membership->promised_at,
'links' => [
'update' => route('membership.update', $membership),
'destroy' => route('membership.destroy', $membership),
]
]);
}
}

View File

@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Lorisleiva\Actions\Concerns\AsAction;
class IndexAction
class MemberIndexAction
{
use AsAction;

View File

@ -0,0 +1,23 @@
<?php
namespace App\Membership\Actions;
use App\Member\Data\MembershipData;
use App\Member\Membership;
use Inertia\Inertia;
use Inertia\Response;
use Lorisleiva\Actions\Concerns\AsAction;
use Spatie\LaravelData\PaginatedDataCollection;
class MembershipIndexAction
{
use AsAction;
public function asController(): Response
{
return Inertia::render(
'membership/Index',
MembershipData::collect(Membership::orderByRaw('member_id, activity_id, subactivity_id')->paginate(20), PaginatedDataCollection::class)->wrap('data')
);
}
}

View File

@ -73,7 +73,9 @@ use App\Membership\Actions\IndexAction as MembershipIndexAction;
use App\Membership\Actions\ListForGroupAction;
use App\Membership\Actions\MassListAction;
use App\Membership\Actions\MassStoreAction;
use App\Membership\Actions\MemberIndexAction;
use App\Membership\Actions\MembershipDestroyAction;
use App\Membership\Actions\MembershipIndexAction as ActionsMembershipIndexAction;
use App\Membership\Actions\MembershipStoreAction;
use App\Membership\Actions\MembershipUpdateAction;
use App\Payment\SubscriptionController;
@ -140,13 +142,14 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/member/{member}/invoice-position', PaymentPositionIndexAction::class)->name('member.invoice-position.index');
// --------------------------------- membership --------------------------------
Route::get('/member/{member}/membership', MembershipIndexAction::class)->name('member.membership.index');
Route::get('/member/{member}/membership', MemberIndexAction::class)->name('member.membership.index');
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::post('/api/membership/member-list', ListForGroupAction::class)->name('membership.member-list');
Route::post('/api/membership/masslist', MassStoreAction::class)->name('membership.masslist.store');
Route::get('/membership/masslist', MassListAction::class)->name('membership.masslist.index');
Route::get('/membership', ActionsMembershipIndexAction::class)->name('membership.index');
// ----------------------------------- group ----------------------------------
Route::get('/group', GroupIndexAction::class)->name('group.index');

View File

@ -63,7 +63,7 @@ it('cannot create membership when activity and subactivity doesnt belong togethe
])->assertJsonValidationErrors(['activity_id' => 'Tätigkeit ist nicht vorhanden.']);
});
it('testItDeletesAMembership', function() {
it('deletes a membership', function() {
MembershipDestroyAction::partialMock()->shouldReceive('handle')->once();
MembershipStoreAction::partialMock()->shouldReceive('handle')->never();
ResyncAction::partialMock()->shouldReceive('handle')->once();
@ -73,7 +73,7 @@ it('testItDeletesAMembership', function() {
MassStoreAction::run($member->memberships->first()->group, $member->memberships->first()->activity, $member->memberships->first()->subactivity, []);
});
it('testItRollsbackWhenDeletionFails', function() {
it('rolls back when deletion fails', function() {
app(MembershipFake::class)
->shows(3, ['id' => 55])
->shows(3, ['id' => 56])

View File

@ -0,0 +1,49 @@
<?php
namespace Tests\Feature\Membership;
use App\Activity;
use App\Group;
use App\Member\Data\MembershipData;
use App\Member\Member;
use App\Member\Membership;
use App\Subactivity;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Inertia\Testing\AssertableInertia as Assert;
uses(DatabaseTransactions::class);
mutates(MembershipData::class);
it('lists memberships of users', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$activity = Activity::factory()
->hasAttached(Subactivity::factory()->name('SubAct'))
->name('Act')
->create();
$group = Group::factory()->name('GG')->create();
$member = Member::factory()->defaults()
->for($group)
->has(Membership::factory()->for($activity)->for($activity->subactivities->first())->for($group))
->male()
->name('Max Muster')
->create();
$activity->subactivities()->first();
$this->callFilter('membership.index', [])
->assertInertia(fn(Assert $page) => $page
->has('data.0', fn(Assert $page) => $page
->where('activity.name', 'Act')
->where('subactivity.name', 'SubAct')
->where('group.name', 'GG')
->where('promisedAt', null)
->where('links.update', route('membership.update', $member->memberships->first()))
->where('links.destroy', route('membership.destroy', $member->memberships->first()))
->etc()
)->has('meta', fn (Assert $page) => $page
->where('current_page', 1)
->etc()
)
);
});