Wip: Member view
continuous-integration/drone/push Build is passing Details

next: Schulungen darstellen
This commit is contained in:
philipp lang 2022-11-19 00:09:53 +01:00
parent 30592a1dc9
commit 90bdc28e23
18 changed files with 386 additions and 29 deletions

View File

@ -48,6 +48,7 @@ class MemberPullAction
}
try {
$region = Region::firstWhere('nami_id', $this->member->region_id ?: -1);
$m = Member::updateOrCreate(['nami_id' => $this->member->id], [
'firstname' => $this->member->firstname,
'lastname' => $this->member->lastname,
@ -70,7 +71,7 @@ class MemberPullAction
'group_id' => Group::firstOrCreate(['nami_id' => $this->member->group_id], ['nami_id' => $this->member->group_id, 'name' => $this->member->group_name])->id,
'gender_id' => optional(Gender::firstWhere('nami_id', $this->member->gender_id ?: -1))->id,
'confession_id' => optional(Confession::firstWhere('nami_id', $this->member->confession_id ?: -1))->id,
'region_id' => optional(Region::firstWhere('nami_id', $this->member->region_id ?: -1))->id,
'region_id' => $region && !$region->is_null ? $region->id : null,
'country_id' => optional(Country::where('nami_id', $this->member->country_id)->first())->id,
'subscription_id' => $this->getSubscriptionId($this->member),
'nationality_id' => Nationality::where('nami_id', $this->member->nationality_id)->firstOrFail()->id,

View File

@ -18,7 +18,12 @@ class MemberShowAction
public function handle(Member $member): array
{
return [
'data' => new MemberResource($member->load('memberships')->load('payments.subscription')),
'data' => new MemberResource($member
->load('memberships')
->load('payments.subscription')
->load('nationality')
->load('region')
),
'toolbar' => [['href' => route('member.index'), 'label' => 'Zurück', 'color' => 'primary', 'icon' => 'undo']],
];
}
@ -28,6 +33,6 @@ class MemberShowAction
session()->put('menu', 'member');
session()->put('title', 'Mitglied '.$member->fullname);
return Inertia::render('member/Show', $this->handle($member));
return Inertia::render('member/ShowView', $this->handle($member));
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Member;
use Illuminate\Http\Resources\Json\JsonResource;
class GenderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -202,7 +202,10 @@ class Member extends Model
public function region(): BelongsTo
{
return $this->belongsTo(Region::class);
return $this->belongsTo(Region::class)->withDefault([
'name' => '-- kein --',
'nami_id' => null,
]);
}
public function confession(): BelongsTo

View File

@ -78,6 +78,7 @@ class MemberRequest extends FormRequest
'nationality_id' => 'nullable|exists:nationalities,id',
'children_phone' => '',
'fax' => '',
'other_country' => '',
];
}

View File

@ -3,6 +3,8 @@
namespace App\Member;
use App\Course\Resources\CourseResource;
use App\Member\Resources\NationalityResource;
use App\Member\Resources\RegionResource;
use App\Membership\MembershipResource;
use App\Payment\PaymentResource;
use Illuminate\Http\Resources\Json\JsonResource;
@ -36,6 +38,7 @@ class MemberResource extends JsonResource
'subscription_id' => $this->subscription_id,
'subscription_name' => $this->subscription_name,
'gender_id' => $this->gender_id,
'gender_name' => $this->gender?->name ?: 'keine Angabe',
'further_address' => $this->further_address,
'work_phone' => $this->work_phone,
'mobile_phone' => $this->mobile_phone,
@ -59,6 +62,9 @@ class MemberResource extends JsonResource
'pending_payment' => $this->pending_payment ? number_format($this->pending_payment / 100, 2, ',', '.').' €' : null,
'age_group_icon' => $this->ageGroupMemberships->first()?->subactivity->slug,
'courses' => CourseResource::collection($this->whenLoaded('courses')),
'nationality' => new NationalityResource($this->whenLoaded('nationality')),
'region' => new RegionResource($this->whenLoaded('region')),
'full_address' => $this->fullAddress,
'efz' => $this->efz,
'efz_link' => $this->getEfzLink(),
'ps_at' => $this->ps_at,

View File

@ -0,0 +1,28 @@
<?php
namespace App\Member\Resources;
use App\Nationality;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Nationality
*/
class NationalityResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'name' => $this->name,
'nami_id' => $this->nami_id,
'id' => $this->id,
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Member\Resources;
use App\Region;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Region
*/
class RegionResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'name' => $this->name,
'nami_id' => $this->nami_id,
'id' => $this->id,
];
}
}

View File

@ -21,4 +21,14 @@ class GenderFactory extends Factory
'nami_id' => $this->faker->numberBetween(100, 200),
];
}
public function name(string $name): self
{
return $this->state(['name' => $name]);
}
public function inNami(int $namiId): self
{
return $this->state(['nami_id' => $namiId]);
}
}

View File

@ -25,6 +25,11 @@ class NationalityFactory extends Factory
];
}
public function name(string $name): self
{
return $this->state(['name' => $name]);
}
public function inNami(int $namiId): self
{
return $this->state(['nami_id' => $namiId]);

View File

@ -22,4 +22,14 @@ class RegionFactory extends Factory
'is_null' => false,
];
}
public function name(string $name): self
{
return $this->state(['name' => $name]);
}
public function inNami(int $namiId): self
{
return $this->state(['nami_id' => $namiId]);
}
}

View File

@ -0,0 +1,9 @@
<template>
<div class="text-gray-300">
<slot></slot>
</div>
</template>
<script>
export default {};
</script>

View File

@ -0,0 +1,38 @@
<template>
<div>
<div class="text-gray-400 text-xs" v-text="label"></div>
<div class="text-gray-200 text-sm">
<span v-if="!type" v-text="value"></span>
<a
:href="`mailto:${value}`"
class="text-teal-500 hover:text-teal-300 transition-all"
v-text="value"
v-if="type === 'email'"
></a>
<a
:href="`tel:${value}`"
class="text-teal-500 hover:text-teal-300 transition-all"
v-text="value"
v-if="type === 'tel'"
></a>
</div>
</div>
</template>
<script>
export default {
props: {
type: {
default: function () {
return null;
},
},
value: {
required: true,
},
label: {
required: true,
},
},
};
</script>

View File

@ -1,23 +0,0 @@
<template>
<div></div>
</template>
<script>
export default {
data: function () {
return {
inner: {},
};
},
methods: {},
props: {
data: {},
},
created() {
this.inner = this.data;
},
};
</script>

View File

@ -0,0 +1,89 @@
<template>
<div class="p-6 grid gap-6 this-grid">
<!-- ****************************** Stammdaten ******************************* -->
<div class="bg-gray-800 p-3 grid grid-cols-2 justify-start gap-3 rounded-lg">
<heading class="col-span-full">Stammdaten</heading>
<key-value label="Geschlecht" :value="inner.gender_name"></key-value>
<key-value label="Vorname" :value="inner.firstname"></key-value>
<key-value label="Nachname" :value="inner.lastname"></key-value>
<key-value label="Adresse" :value="inner.full_address"></key-value>
<key-value label="Geburtsdatum" :value="inner.birthday_human"></key-value>
<key-value label="Alter" :value="inner.age"></key-value>
<key-value label="Bundesland" :value="inner.region.name"></key-value>
<key-value label="Nationalität" :value="inner.nationality.name"></key-value>
<key-value
v-show="inner.other_country"
label="Andere Staatsangehörigkeit"
:value="inner.other_country"
></key-value>
</div>
<!-- ******************************** Kontakt ******************************** -->
<div class="bg-gray-800 p-3 grid justify-start gap-3 rounded-lg">
<heading class="col-span-full">Kontakt</heading>
<key-value
v-show="inner.main_phone"
label="Telefon Eltern"
:value="inner.main_phone"
type="tel"
></key-value>
<key-value
v-show="inner.mobile_phone"
label="Handy Eltern"
:value="inner.mobile_phone"
type="tel"
></key-value>
<key-value
v-show="inner.work_phone"
label="Telefon Eltern geschäftlich"
:value="inner.work_phone"
type="tel"
></key-value>
<key-value
v-show="inner.children_phone"
label="Telefon Kind"
:value="inner.children_phone"
type="tel"
></key-value>
<key-value v-show="inner.email" label="E-Mail-Adresse Kind" :value="inner.email" type="email"></key-value>
<key-value
v-show="inner.email_parents"
label="E-Mail-Adresse Eltern"
:value="inner.email_parents"
type="email"
></key-value>
<key-value v-show="inner.fax" label="Fax" :value="inner.fax" type="tel"></key-value>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
inner: {},
};
},
methods: {},
props: {
data: {},
},
components: {
'key-value': () => import('./KeyValue'),
'heading': () => import('./Heading'),
},
created() {
this.inner = this.data;
},
};
</script>
<style scoped>
.this-grid {
grid-template-columns: max-content max-content 1fr;
}
</style>

View File

@ -5,6 +5,8 @@ namespace Tests\Feature\Member;
use App\Actions\MemberPullAction;
use App\Member\Member;
use App\Member\Membership;
use App\Nationality;
use App\Region;
use App\Setting\NamiSettings;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
@ -63,4 +65,48 @@ class MemberPullActionTest extends TestCase
'member_id' => $member->id,
]);
}
public function testRegionIdIsSetToNull(): void
{
Nationality::factory()->inNami(1054)->create();
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]);
app(MemberFake::class)->shows(55, 123, [
'gruppierungId' => 55,
'id' => 123,
'regionId' => 999,
]);
app(MembershipFake::class)->fetches(123, []);
app(CourseFake::class)->fetches(123, []);
$this->withoutExceptionHandling()->login()->loginNami();
app(MemberPullAction::class)
->api(app(NamiSettings::class)->login())
->member(55, 123)
->execute();
$this->assertDatabaseHas('members', [
'region_id' => null,
]);
}
public function testItSetsNormalAttributes(): void
{
Nationality::factory()->inNami(1054)->create();
$region = Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => false]);
app(MemberFake::class)->shows(55, 123, [
'regionId' => 999,
]);
app(MembershipFake::class)->fetches(123, []);
app(CourseFake::class)->fetches(123, []);
$this->withoutExceptionHandling()->login()->loginNami();
app(MemberPullAction::class)
->api(app(NamiSettings::class)->login())
->member(55, 123)
->execute();
$this->assertDatabaseHas('members', [
'region_id' => $region->id,
]);
}
}

View File

@ -2,9 +2,16 @@
namespace Tests\Feature\Member;
use App\Fee;
use App\Gender;
use App\Group;
use App\Member\Member;
use App\Member\Membership;
use App\Nationality;
use App\Payment\Payment;
use App\Payment\Subscription;
use App\Region;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
@ -12,17 +19,56 @@ class ShowTest extends TestCase
{
use DatabaseTransactions;
public function setUp(): void
{
parent::setUp();
Carbon::setTestNow(Carbon::parse('2006-01-01 15:00:00'));
}
public function testItShowsSingleMember(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()
->defaults()
->has(Membership::factory()->in('€ LeiterIn', 5, 'Jungpfadfinder', 88)->state(['created_at' => '2022-11-19 05:00:00']))
->has(Payment::factory()->notPaid()->nr('2019')->subscription('Free', 1050))
->defaults()->create(['firstname' => 'Max']);
->for(Gender::factory()->name('Herr'))
->for(Region::factory()->name('NRW'))
->create([
'birthday' => '1991-04-20',
'address' => 'Itterstr 3',
'zip' => '42719',
'location' => 'Solingen',
'firstname' => 'Max',
'other_country' => 'other',
'main_phone' => '+49 212 1266775',
'mobile_phone' => '+49 212 1266776',
'work_phone' => '+49 212 1266777',
'children_phone' => '+49 212 1266778',
'email' => 'a@b.de',
'email_parents' => 'b@c.de',
'fax' => '+49 212 1255674',
]);
$response = $this->get("/member/{$member->id}");
$this->assertInertiaHas('Max', $response, 'data.firstname');
$this->assertInertiaHas([
'birthday_human' => '20.04.1991',
'age' => 14,
'firstname' => 'Max',
'gender_name' => 'Herr',
'full_address' => 'Itterstr 3, 42719 Solingen',
'region' => ['name' => 'NRW'],
'other_country' => 'other',
'main_phone' => '+49 212 1266775',
'mobile_phone' => '+49 212 1266776',
'work_phone' => '+49 212 1266777',
'children_phone' => '+49 212 1266778',
'email' => 'a@b.de',
'email_parents' => 'b@c.de',
'fax' => '+49 212 1255674',
], $response, 'data');
$this->assertInertiaHas([
'activity_name' => '€ LeiterIn',
'id' => $member->memberships->first()->id,
@ -38,4 +84,25 @@ class ShowTest extends TestCase
'nr' => '2019',
], $response, 'data.payments.0');
}
public function testItShowsMinimalSingleMember(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()
->for(Group::factory())
->for(Nationality::factory()->name('deutsch'))
->for(Subscription::factory()->for(Fee::factory()))
->create(['firstname' => 'Max']);
$response = $this->get("/member/{$member->id}");
$this->assertInertiaHas([
'region' => ['name' => '-- kein --'],
'nationality' => ['name' => '-- kein --'],
'gender_name' => 'keine Angabe',
'nationality' => [
'name' => 'deutsch',
],
], $response, 'data');
}
}

View File

@ -53,6 +53,21 @@ class UpdateTest extends TestCase
$response->assertRedirect("/member/{$member->id}/edit?conflict=1");
}
public function testItUpdatesContact(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = $this->member(['nami_id' => null]);
$this->fakeRequest();
$response = $this
->from("/member/{$member->id}")
->patch("/member/{$member->id}", array_merge($member->getAttributes(), [
'other_country' => 'englisch',
]));
$this->assertEquals('englisch', $member->fresh()->other_country);
}
public function testItUpdatesCriminalRecord(): void
{
$this->withoutExceptionHandling()->login()->loginNami();