Compare commits

..

95 Commits

Author SHA1 Message Date
philipp lang a6050b1e07 --wip-- [skip ci] 2025-01-26 16:39:25 +01:00
philipp lang c085de4761 Extract RendersAlpine trait 2025-01-26 16:39:25 +01:00
philipp lang 5d6add1976 Add alpineJS Support for label field 2025-01-26 16:39:25 +01:00
philipp lang f461c37ccc Add route for contribution 2025-01-26 16:39:25 +01:00
philipp lang cd4bd971eb Register memberSearch in app.js 2025-01-26 16:39:25 +01:00
philipp lang 8024fb7e58 Fix namespace 2025-01-26 16:39:25 +01:00
philipp lang 52eae62ce9 Pass initial value to editor function call 2025-01-26 16:39:25 +01:00
philipp lang fe4c414f1b Merge class attributes to form text field 2025-01-26 16:39:25 +01:00
philipp lang bd86f523b2 Add Lazy Loading prevention to app when testing 2025-01-26 16:39:25 +01:00
philipp lang 90dac2b377 Fix form tests 2025-01-26 16:39:25 +01:00
philipp lang 7dfa269ff7 Add contribution index page 2025-01-26 16:39:25 +01:00
philipp lang 89c8ce3ac1 Fix database migrations for mailgateways 2025-01-26 16:39:25 +01:00
philipp lang 0b1ec69bf1 Exit successfully after running copydb 2025-01-26 16:39:25 +01:00
philipp lang 0e0a7a9a1b Remove conditions from editor 2025-01-26 16:39:25 +01:00
philipp lang a33b67eae3 Add prevention settings 2025-01-26 16:39:25 +01:00
philipp lang 24490afbc0 Add fileshare settings 2025-01-26 16:39:25 +01:00
philipp lang c01e5fa39c Add form settings 2025-01-26 16:39:25 +01:00
philipp lang e57e908d39 Lint 2025-01-26 16:39:25 +01:00
philipp lang 6d9d3dc793 Add password reset confirm 2025-01-26 16:39:25 +01:00
philipp lang 86b77945bf Cleanup 2025-01-26 16:39:25 +01:00
philipp lang faaefb90a4 Add Password Reset email page 2025-01-26 16:39:25 +01:00
philipp lang 6aeab2fd93 Add Login module 2025-01-26 16:39:25 +01:00
philipp lang ae4a9616b4 Move DPSG Icon 2025-01-26 16:39:25 +01:00
philipp lang cb86306557 Add title and menu to Page component 2025-01-26 16:39:25 +01:00
philipp lang 84b0509d31 Add head component for html page head tag 2025-01-26 16:39:25 +01:00
philipp lang d004cdcd7d Fix error handling for mailgateway forms 2025-01-26 16:39:25 +01:00
philipp lang 397ff52e35 Add error handling via tooltip 2025-01-26 16:39:25 +01:00
philipp lang 6cd896d7c9 Fix: Add id to select field 2025-01-26 16:39:25 +01:00
philipp lang 835ced7291 Add Vite facade 2025-01-26 16:39:25 +01:00
philipp lang fba9bfe5d5 Add Danger theme for tippy 2025-01-26 16:39:25 +01:00
philipp lang 662a4dc720 Add vite spritemap 2025-01-26 16:39:25 +01:00
philipp lang 1ac0cae405 Update sprite component 2025-01-26 16:39:25 +01:00
philipp lang ed207df4cf Add validation attributes to nami field 2025-01-26 16:39:25 +01:00
philipp lang 513b832813 Add SettingIntro component 2025-01-26 16:39:25 +01:00
philipp lang f42ebdfcbb Add Nami Settings 2025-01-26 16:39:25 +01:00
philipp lang 6b49aa4f81 Move test files 2025-01-26 16:39:25 +01:00
philipp lang 3664739c86 Add page title 2025-01-26 16:39:25 +01:00
philipp lang 371a70c007 Add active menu entry for setting 2025-01-26 16:39:25 +01:00
philipp lang a18fb18074 Set active title for setting 2025-01-26 16:39:25 +01:00
philipp lang 5f7d1402bb Update arch tests 2025-01-26 16:39:25 +01:00
philipp lang b64d9994ed Lint 2025-01-26 16:39:25 +01:00
philipp lang f99f97b5ae Move types 2025-01-26 16:39:25 +01:00
philipp lang a3f70fa10a Move modules 2025-01-26 16:39:25 +01:00
philipp lang f46ffced8d Add Badge component 2025-01-26 16:39:25 +01:00
philipp lang 92156616da Add data type cast for Mailgateway 2025-01-26 16:39:25 +01:00
philipp lang 318300ca88 Allow MailmanTypeRequest to cast to data 2025-01-26 16:39:25 +01:00
philipp lang 71d7189826 Mod setArray macro for nested values 2025-01-26 16:39:25 +01:00
philipp lang 98572ffdea Simplify form 2025-01-26 16:39:25 +01:00
philipp lang 791e22eaa5 Update update hook 2025-01-26 16:39:25 +01:00
philipp lang 660b012e1c Add test for events 2025-01-26 16:39:25 +01:00
philipp lang ac0b86cb08 Lint 2025-01-26 16:39:25 +01:00
philipp lang fb2292f8e4 Update refresh page 2025-01-26 16:39:25 +01:00
philipp lang f9240270af Add mailgateway 2025-01-26 16:39:25 +01:00
philipp lang ae4b2aef31 Fix layout 2025-01-26 16:39:25 +01:00
philipp lang 46b82922f7 Remove old MailgatewayRequestFactory 2025-01-26 16:39:25 +01:00
philipp lang 4b618be3b6 Lint 2025-01-26 16:39:25 +01:00
philipp lang 71725c5045 Add HTTP Check to MailmanTypeRequest 2025-01-26 16:39:25 +01:00
philipp lang 283c627a9c Delete old mailgateway tests 2025-01-26 16:39:25 +01:00
philipp lang 32a53b4e77 Mark MailgatewayResource as deprecated 2025-01-26 16:39:25 +01:00
philipp lang 5f2e78fce7 Mark old mailgateway settings as deprecated 2025-01-26 16:39:25 +01:00
philipp lang 51741cd6b3 Add BooleanDisplay component 2025-01-26 16:39:25 +01:00
philipp lang 603ec6bddc Make right slot optional in SettingLayout 2025-01-26 16:39:25 +01:00
philipp lang 429f7f70f5 fixup! Add modal component 2025-01-26 16:39:25 +01:00
philipp lang 8132a771f4 Add testable macro for setArray 2025-01-26 16:39:25 +01:00
philipp lang aa87d507ab Add modal component 2025-01-26 16:39:25 +01:00
philipp lang 79784b70de Throw exception when variant not found 2025-01-26 16:39:25 +01:00
philipp lang 141dbdfd9b Lint 2025-01-26 16:39:25 +01:00
philipp lang 4cbb390211 Merge attributes for hint 2025-01-26 16:39:25 +01:00
philipp lang bc21447f30 Lint 2025-01-26 16:39:25 +01:00
philipp lang c0451786d5 Fix Tooltip attribute of Table action 2025-01-26 16:39:25 +01:00
philipp lang 6590ca14e5 Register MailgatewayServiceProvider 2025-01-26 16:39:25 +01:00
philipp lang fe134d86ca Add select field 2025-01-26 16:39:25 +01:00
philipp lang ceec787f2e Disable autocomplete for password fields 2025-01-26 16:39:25 +01:00
philipp lang 077e79192a Fix label required 2025-01-26 16:39:25 +01:00
philipp lang 21f769b0de Add Table action button 2025-01-26 16:39:25 +01:00
philipp lang 4216d37bff Add table component 2025-01-26 16:39:25 +01:00
philipp lang 7d167e212a Mod Component Test 2025-01-26 16:39:25 +01:00
philipp lang 0948a8172d Move Dashboard registrations 2025-01-26 16:39:25 +01:00
philipp lang 4496529ef0 Add Settings for Bill 2025-01-26 16:39:25 +01:00
philipp lang 28469c7b40 Add Toast and tooltip 2025-01-26 16:39:25 +01:00
philipp lang 82c56f25fb Remove Base tests from Composer 2025-01-26 16:39:25 +01:00
philipp lang 9450faac7d Add Component Resolver for Modules 2025-01-26 16:39:25 +01:00
philipp lang ded5da6eb3 Add Dashboard 2025-01-26 16:39:25 +01:00
philipp lang 09d49edad2 Add Service Provider 2025-01-26 16:39:25 +01:00
philipp lang 212d83e6dc Add livewire components 2025-01-26 16:39:25 +01:00
philipp lang 270a4674fa Add config file 2025-01-26 16:39:25 +01:00
philipp lang d1d6564805 Install Livewire Package 2025-01-26 16:39:25 +01:00
philipp lang 17e4fe5f82 Update CHANGELOG
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2025-01-18 21:19:49 +01:00
philipp lang b2f3e4f1fd Fix: Split subscriptions between members
continuous-integration/drone/push Build is passing Details
2025-01-18 21:17:04 +01:00
philipp lang 376c1ceb9b Update CHANGELOG
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone/push Build is passing Details
2025-01-18 13:05:43 +01:00
philipp lang 056b8f9ed6 Fixed: Dont update subscription from nami if fee id already matches
continuous-integration/drone/push Build is passing Details
2025-01-18 12:03:36 +01:00
philipp lang 011e414848 Update CHANGELOG
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is failing Details
2025-01-06 22:16:14 +01:00
philipp lang 5e64797277 Fixed tests
continuous-integration/drone/push Build is passing Details
2025-01-05 00:13:10 +01:00
philipp lang 2e8dc96665 Add frontend for svk and vk filter
continuous-integration/drone/push Build is failing Details
2025-01-04 23:35:00 +01:00
philipp lang b1dcdeb579 Add filter for member prevention
continuous-integration/drone/push Build is failing Details
2025-01-04 23:27:23 +01:00
13 changed files with 127 additions and 8 deletions

View File

@ -1,5 +1,21 @@
# Letzte Änderungen # Letzte Änderungen
### 1.12.6
- Fix: Beiträge von Familienmitgliedern splitten
### 1.12.5
- Fix: Synchronisieren von bestehenden Beiträgen aus NaMi
### 1.12.4
- Filter Mitglieder nach Verhaltenskodex
### 1.12.3
- Volltextsuche für Rechnungen
### 1.12.2 ### 1.12.2
- Zuschussliste Gallier - Zuschussliste Gallier

View File

@ -22,7 +22,8 @@ class InsertMemberAction
{ {
$region = Region::firstWhere('nami_id', $member->regionId ?: -1); $region = Region::firstWhere('nami_id', $member->regionId ?: -1);
return Member::updateOrCreate(['nami_id' => $member->id], [
$payload = [
'firstname' => $member->firstname, 'firstname' => $member->firstname,
'lastname' => $member->lastname, 'lastname' => $member->lastname,
'joined_at' => $member->joinedAt, 'joined_at' => $member->joinedAt,
@ -51,7 +52,16 @@ class InsertMemberAction
'mitgliedsnr' => $member->memberId, 'mitgliedsnr' => $member->memberId,
'version' => $member->version, 'version' => $member->version,
'keepdata' => $member->keepdata, 'keepdata' => $member->keepdata,
]); ];
// Dont update subscription if fee id of existing member's subscription is already the same
if ($existing = Member::nami($member->id)) {
if ($existing->subscription && $existing->subscription->fee->nami_id === $member->feeId) {
$payload['subscription_id'] = $existing->subscription->id;
}
}
return Member::updateOrCreate(['nami_id' => $member->id], $payload);
} }
public function getSubscription(NamiMember $member): ?Subscription public function getSubscription(NamiMember $member): ?Subscription

View File

@ -49,7 +49,6 @@ class Invoice extends Model
*/ */
public static function createForMember(Member $member, Collection $members, int $year, Subscription $subscription = null): self public static function createForMember(Member $member, Collection $members, int $year, Subscription $subscription = null): self
{ {
$subscription = $subscription ?: $member->subscription;
$invoice = new self([ $invoice = new self([
'to' => [ 'to' => [
'name' => 'Familie ' . $member->lastname, 'name' => 'Familie ' . $member->lastname,
@ -66,7 +65,8 @@ class Invoice extends Model
$positions = collect([]); $positions = collect([]);
foreach ($members as $member) { foreach ($members as $member) {
foreach ($subscription->children as $child) { $memberSubscription = $subscription ?: $member->subscription;
foreach ($memberSubscription->children as $child) {
$positions->push([ $positions->push([
'description' => str($child->name)->replace('{name}', $member->firstname . ' ' . $member->lastname)->replace('{year}', (string) $year), 'description' => str($child->name)->replace('{name}', $member->firstname . ' ' . $member->lastname)->replace('{year}', (string) $year),
'price' => $child->amount, 'price' => $child->amount,

View File

@ -46,6 +46,8 @@ class FilterScope extends ScoutFilter
public array $exclude = [], public array $exclude = [],
public ?bool $hasFullAddress = null, public ?bool $hasFullAddress = null,
public ?bool $hasBirthday = null, public ?bool $hasBirthday = null,
public ?bool $hasSvk = null,
public ?bool $hasVk = null,
) { ) {
} }
@ -85,6 +87,12 @@ class FilterScope extends ScoutFilter
if ($this->hasBirthday === true) { if ($this->hasBirthday === true) {
$filter->push('birthday IS NOT NULL'); $filter->push('birthday IS NOT NULL');
} }
if ($this->hasSvk !== null) {
$filter->push('has_svk = ' . ($this->hasSvk ? 'true' : 'false'));
}
if ($this->hasVk !== null) {
$filter->push('has_vk = ' . ($this->hasVk ? 'true' : 'false'));
}
if ($this->ausstand === true) { if ($this->ausstand === true) {
$filter->push('ausstand > 0'); $filter->push('ausstand > 0');
} }

View File

@ -544,6 +544,8 @@ class Member extends Model implements Geolocatable
'bill_kind' => $this->bill_kind?->value, 'bill_kind' => $this->bill_kind?->value,
'group_id' => $this->group->id, 'group_id' => $this->group->id,
'group_name' => $this->group->inner_name ?: $this->group->name, 'group_name' => $this->group->inner_name ?: $this->group->name,
'has_vk' => $this->has_vk,
'has_svk' => $this->has_svk,
'links' => [ 'links' => [
'show' => route('member.show', ['member' => $this], false), 'show' => route('member.show', ['member' => $this], false),
'edit' => route('member.edit', ['member' => $this], false), 'edit' => route('member.edit', ['member' => $this], false),

View File

@ -163,6 +163,10 @@ class MemberResource extends JsonResource
'activity_ids' => [], 'activity_ids' => [],
'subactivity_ids' => [] 'subactivity_ids' => []
], ],
'boolean_filter' => [
['id' => true, 'name' => 'Ja'],
['id' => false, 'name' => 'Nein'],
],
'default' => [ 'default' => [
'gender_id' => null, 'gender_id' => null,
'salutation' => '', 'salutation' => '',

View File

@ -138,7 +138,7 @@ return [
'key' => env('MEILI_MASTER_KEY', null), 'key' => env('MEILI_MASTER_KEY', null),
'index-settings' => [ 'index-settings' => [
Member::class => [ Member::class => [
'filterableAttributes' => ['address', 'birthday', 'ausstand', 'bill_kind', 'group_id', 'memberships', 'id'], 'filterableAttributes' => ['address', 'birthday', 'ausstand', 'bill_kind', 'group_id', 'memberships', 'has_vk', 'has_svk', 'id'],
'searchableAttributes' => ['fullname', 'address'], 'searchableAttributes' => ['fullname', 'address'],
'sortableAttributes' => ['lastname', 'firstname'], 'sortableAttributes' => ['lastname', 'firstname'],
'displayedAttributes' => ['age_group_icon', 'group_name', 'links', 'is_leader', 'lastname', 'firstname', 'fullname', 'address', 'ausstand', 'birthday', 'id', 'memberships', 'bill_kind', 'group_id'], 'displayedAttributes' => ['age_group_icon', 'group_name', 'links', 'is_leader', 'lastname', 'firstname', 'fullname', 'address', 'ausstand', 'birthday', 'id', 'memberships', 'bill_kind', 'group_id'],

View File

@ -37,6 +37,8 @@ class MemberFactory extends Factory
'email' => $this->faker->safeEmail(), 'email' => $this->faker->safeEmail(),
'recertified_at' => null, 'recertified_at' => null,
'keepdata' => false, 'keepdata' => false,
'has_svk' => $this->faker->boolean(),
'has_vk' => $this->faker->boolean(),
]; ];
} }

View File

@ -48,6 +48,24 @@
size="sm" size="sm"
@update:model-value="setFilter('ausstand', $event)" @update:model-value="setFilter('ausstand', $event)"
></f-switch> ></f-switch>
<f-select
id="has_vk"
name="has_vk"
:model-value="getFilter('has_vk')"
label="Verhaltenskodex unterschrieben"
size="sm"
:options="meta.boolean_filter"
@update:model-value="setFilter('has_vk', $event)"
></f-select>
<f-select
id="has_svk"
name="has_svk"
:model-value="getFilter('has_svk')"
label="SVK unterschrieben"
size="sm"
:options="meta.boolean_filter"
@update:model-value="setFilter('has_svk', $event)"
></f-select>
<f-multipleselect <f-multipleselect
id="group_ids" id="group_ids"
:options="meta.groups" :options="meta.groups"

View File

@ -202,6 +202,30 @@ class MemberIndexTest extends EndToEndTestCase
]])->assertInertiaCount('data.data', 1); ]])->assertInertiaCount('data.data', 1);
} }
public function testItFiltersForSvkPrevention(): void
{
Member::factory()->defaults()->create(['has_svk' => true]);
Member::factory()->defaults()->create(['has_svk' => false]);
Member::factory()->defaults()->create(['has_svk' => false]);
sleep(1);
$this->callFilter('member.index', ['has_svk' => true])->assertInertiaCount('data.data', 1);
$this->callFilter('member.index', ['has_svk' => false])->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['has_svk' => null])->assertInertiaCount('data.data', 3);
}
public function testItFiltersForVkPrevention(): void
{
Member::factory()->defaults()->create(['has_vk' => true]);
Member::factory()->defaults()->create(['has_vk' => false]);
Member::factory()->defaults()->create(['has_vk' => false]);
sleep(1);
$this->callFilter('member.index', ['has_vk' => true])->assertInertiaCount('data.data', 1);
$this->callFilter('member.index', ['has_vk' => false])->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['has_vk' => null])->assertInertiaCount('data.data', 3);
}
public function testGroupOfMembershipsFilterCanBeEmpty(): void public function testGroupOfMembershipsFilterCanBeEmpty(): void
{ {
$mitglied = Activity::factory()->create(); $mitglied = Activity::factory()->create();

View File

@ -90,8 +90,8 @@ class MassStoreActionTest extends TestCase
$this->assertDatabaseCount('invoices', 1); $this->assertDatabaseCount('invoices', 1);
$this->assertDatabaseCount('invoice_positions', 2); $this->assertDatabaseCount('invoice_positions', 2);
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Max Muster']); $this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Max Muster', 'price' => 4466]);
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Jane Muster']); $this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Jane Muster', 'price' => 4466]);
} }
public function testItSeparatesBillKinds(): void public function testItSeparatesBillKinds(): void
@ -105,4 +105,21 @@ class MassStoreActionTest extends TestCase
$this->assertDatabaseCount('invoices', 2); $this->assertDatabaseCount('invoices', 2);
$this->assertDatabaseCount('invoice_positions', 2); $this->assertDatabaseCount('invoice_positions', 2);
} }
public function testItSeparatesSubscriptions(): void
{
$member1 = Member::factory()->defaults()->emailBillKind()
->for(Subscription::factory()->forFee()->children([new Child('beitrag1 {name}', 4466)]))
->create(['firstname' => 'Member1', 'lastname' => 'ln']);
$member2 = Member::factory()->defaults()->sameFamilyAs($member1)->emailBillKind()
->for(Subscription::factory()->forFee()->children([new Child('beitrag2 {name}', 4467)]))
->create(['firstname' => 'Member2']);
$this->postJson(route('invoice.mass-store'), ['year' => now()->addYear()->year])->assertOk();
$invoice = Invoice::first();
$this->assertDatabaseCount('invoice_positions', 2);
$this->assertDatabaseHas('invoice_positions', ['invoice_id' => $invoice->id, 'member_id' => $member1->id, 'description' => 'beitrag1 Member1 ln', 'price' => 4466]);
$this->assertDatabaseHas('invoice_positions', ['invoice_id' => $invoice->id, 'member_id' => $member2->id, 'description' => 'beitrag2 Member2 ln', 'price' => 4467]);
}
} }

View File

@ -7,6 +7,7 @@ use App\Country;
use App\Fee; use App\Fee;
use App\Gender; use App\Gender;
use App\Group; use App\Group;
use App\Member\Member;
use App\Nationality; use App\Nationality;
use App\Payment\Subscription; use App\Payment\Subscription;
use App\Region; use App\Region;
@ -142,3 +143,20 @@ it('testItPullsMemberWithNoSubscription', function () {
'subscription_id' => null, 'subscription_id' => null,
]); ]);
}); });
it('doesnt set first subscription if fee matches', function () {
$this->loginNami();
Subscription::factory()->forFee(55)->create();
$otherSubscription = Subscription::factory()->forFee(55)->create();
$member = Member::factory()->defaults()->inNami(1001)->create(['subscription_id' => $otherSubscription->id]);
app(MemberFake::class)->shows(1000, 1001, [
'beitragsartId' => 55,
]);
app(PullMemberAction::class)->handle(1000, 1001);
$this->assertDatabaseHas('members', [
'subscription_id' => $otherSubscription->id,
'id' => $member->id,
]);
});

View File

@ -149,7 +149,7 @@ class ShowTest extends TestCase
->for(Group::factory()) ->for(Group::factory())
->for(Nationality::factory()->name('deutsch')) ->for(Nationality::factory()->name('deutsch'))
->for(Subscription::factory()->forFee()) ->for(Subscription::factory()->forFee())
->create(['firstname' => 'Max', 'lastname' => 'Muster']); ->create(['firstname' => 'Max', 'lastname' => 'Muster', 'has_vk' => false, 'has_svk' => false]);
$response = $this->get("/member/{$member->id}"); $response = $this->get("/member/{$member->id}");