From 3b654b0446c9daf7f805e8158e24533e964e292e Mon Sep 17 00:00:00 2001 From: philipp lang Date: Sat, 5 Jul 2025 17:01:32 +0200 Subject: [PATCH] --wip-- [skip ci] --- .../Actions/GenerateContributionAction.php | 45 +++++ app/Form/Data/FieldCollection.php | 7 +- app/Form/Enums/SpecialType.php | 6 + app/Form/Requests/FormCompileRequest.php | 101 +++++++++++ app/Form/Resources/FormResource.php | 4 + .../factories/Form/Models/FormFactory.php | 2 + docker-compose.yml | 4 + tests/Datasets/contribution.php | 10 ++ .../Feature/Form/GenerateContributionTest.php | 170 ++++++++++++++++++ 9 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 app/Form/Actions/GenerateContributionAction.php create mode 100644 app/Form/Requests/FormCompileRequest.php create mode 100644 tests/Feature/Form/GenerateContributionTest.php diff --git a/app/Form/Actions/GenerateContributionAction.php b/app/Form/Actions/GenerateContributionAction.php new file mode 100644 index 00000000..2f23c7d6 --- /dev/null +++ b/app/Form/Actions/GenerateContributionAction.php @@ -0,0 +1,45 @@ +type()::fromPayload($request)); + } + + public function asController(ActionRequest $request, Form $form): BaseCompiler|JsonResponse + { + $r = FormCompileRequest::from(['form' => $form]); + app(ContributionFactory::class)->validateType($r); + $r->validateContribution(); + + return $request->input('validate') + ? response()->json([]) + : $this->handle($r); + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'payload' => [new JsonBase64Rule()], + ]; + } +} diff --git a/app/Form/Data/FieldCollection.php b/app/Form/Data/FieldCollection.php index f1d0348b..0f989074 100644 --- a/app/Form/Data/FieldCollection.php +++ b/app/Form/Data/FieldCollection.php @@ -114,7 +114,12 @@ class FieldCollection extends Collection return $this->map(fn ($field) => $field->presentRaw())->toArray(); } - private function findBySpecialType(SpecialType $specialType): ?Field + public function hasSpecialType(SpecialType $specialType): bool + { + return $this->findBySpecialType($specialType) !== null; + } + + public function findBySpecialType(SpecialType $specialType): ?Field { return $this->first(fn ($field) => $field->specialType === $specialType); } diff --git a/app/Form/Enums/SpecialType.php b/app/Form/Enums/SpecialType.php index f136432e..88d54659 100644 --- a/app/Form/Enums/SpecialType.php +++ b/app/Form/Enums/SpecialType.php @@ -7,6 +7,12 @@ enum SpecialType: string case FIRSTNAME = 'Vorname'; case LASTNAME = 'Nachname'; case EMAIL = 'E-Mail-Adresse'; + case BIRTHDAY = 'Geburtsdatum'; + case ZIP = 'PLZ'; + case LOCATION = 'Ort'; + case ADDRESS = 'Adresse'; + case GENDER = 'Geschlecht'; + case LEADER = 'LeiterIn'; /** * @return array diff --git a/app/Form/Requests/FormCompileRequest.php b/app/Form/Requests/FormCompileRequest.php new file mode 100644 index 00000000..24c13aa5 --- /dev/null +++ b/app/Form/Requests/FormCompileRequest.php @@ -0,0 +1,101 @@ + + */ + public function type(): string + { + return request()->input('type'); + } + + public function dateFrom(): Carbon + { + return $this->form->from; + } + + public function dateUntil(): Carbon + { + return $this->form->to; + } + + public function zipLocation(): string + { + return $this->form->zip.' '.$this->form->location; + } + + public function eventName(): string + { + return $this->form->name; + } + + public function members(): Collection + { + $members = []; + $fields = [ + [SpecialType::FIRSTNAME, 'firstname'], + [SpecialType::LASTNAME, 'lastname'], + [SpecialType::BIRTHDAY, 'birthday'], + [SpecialType::ADDRESS, 'address'], + [SpecialType::ZIP, 'zip'], + [SpecialType::LOCATION, 'location'] + ]; + + foreach ($this->form->participants as $participant) { + $member = []; + foreach ($fields as [$type, $name]) { + $f = $this->form->getFields()->findBySpecialType($type); + $member[$name] = $participant->getFields()->find($f)->value; + } + + $members[] = [ + 'is_leader' => false, + 'gender' => 'weiblich', + ...$member, + ]; + } + + return MemberData::fromApi($members); + } + + public function country(): ?Country + { + return Country::first(); + } + + public function validateContribution(): void + { + Validator::make($this->form->toArray(), [ + 'zip' => 'required', + 'location' => 'required' + ]) + ->after(function($validator) { + foreach ($this->type()::requiredFormSpecialTypes() as $type) { + if (!$this->form->getFields()->hasSpecialType($type)) { + $validator->errors()->add($type->name, 'Kein Feld für ' . $type->value . ' vorhanden.'); + } + } + if ($this->form->participants->count() === 0) { + $validator->errors()->add('participants', 'Veranstaltung besitzt noch keine Teilnehmer*innen.'); + } + }) + ->validate(); + } +} diff --git a/app/Form/Resources/FormResource.php b/app/Form/Resources/FormResource.php index 2718f6ca..ea7e8448 100644 --- a/app/Form/Resources/FormResource.php +++ b/app/Form/Resources/FormResource.php @@ -53,6 +53,8 @@ class FormResource extends JsonResource 'needs_prevention' => $this->needs_prevention, 'prevention_text' => $this->prevention_text, 'prevention_conditions' => $this->prevention_conditions, + 'zip' => $this->zip, + 'location' => $this->location, 'links' => [ 'participant_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => null]), 'participant_root_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => -1]), @@ -102,6 +104,8 @@ class FormResource extends JsonResource 'id' => null, 'export' => ExportData::from([]), 'prevention_conditions' => ['mode' => 'all', 'ifs' => []], + 'zip' => '', + 'location' => '', ], 'section_default' => [ 'name' => '', diff --git a/database/factories/Form/Models/FormFactory.php b/database/factories/Form/Models/FormFactory.php index 339f3f14..7feac3ff 100644 --- a/database/factories/Form/Models/FormFactory.php +++ b/database/factories/Form/Models/FormFactory.php @@ -57,6 +57,8 @@ class FormFactory extends Factory 'is_private' => false, 'export' => ExportData::from([]), 'prevention_conditions' => Condition::defaults(), + 'zip' => $this->faker->numberBetween(1100, 99999), + 'location' => $this->faker->city(), ]; } diff --git a/docker-compose.yml b/docker-compose.yml index aa27c717..48522bec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,6 +92,8 @@ services: socketi: image: quay.io/soketi/soketi:89604f268623cf799573178a7ba56b7491416bde-16-debian + ports: + - "6001:6001" environment: SOKETI_DEFAULT_APP_ID: adremaid SOKETI_DEFAULT_APP_KEY: adremakey @@ -104,6 +106,8 @@ services: meilisearch: image: getmeili/meilisearch:v1.6 + ports: + - "7700:7700" volumes: - ./data/meilisearch:/meili_data env_file: diff --git a/tests/Datasets/contribution.php b/tests/Datasets/contribution.php index 6d3f5043..f0af8774 100644 --- a/tests/Datasets/contribution.php +++ b/tests/Datasets/contribution.php @@ -100,3 +100,13 @@ dataset('contribution-assertions', fn () => [ [BdkjHesse::class, ["Max", "Muster", "Jane"]], [WuppertalDocument::class, ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]], ]); + +dataset('contribution-documents', fn () => [ + CitySolingenDocument::class, + RdpNrwDocument::class, + CityRemscheidDocument::class, + CityFrankfurtMainDocument::class, + BdkjHesse::class, + WuppertalDocument::class, +]); + diff --git a/tests/Feature/Form/GenerateContributionTest.php b/tests/Feature/Form/GenerateContributionTest.php new file mode 100644 index 00000000..65fb715b --- /dev/null +++ b/tests/Feature/Form/GenerateContributionTest.php @@ -0,0 +1,170 @@ +create(); + Gender::factory()->male()->create(); + Gender::factory()->female()->create(); +}); + +/** + * @param array $fields + */ +it('doesnt create document when no special fields given', function (array $fields, string $field, string $message, string $type) { + $this->login()->loginNami(); + + $form = Form::factory() + ->fields($fields) + ->has(Participant::factory()) + ->create(); + + $this->json('GET', route('form.contribution', [ + 'type' => $type, + 'form' => $form, + 'validate' => '1', + ]))->assertJsonValidationErrors([$field => $message]); +}) + ->with([ + [fn() => [], 'FIRSTNAME', 'Kein Feld für Vorname vorhanden.'], + [fn() => [test()->textField('f')->specialType(SpecialType::FIRSTNAME)], 'LASTNAME', 'Kein Feld für Nachname vorhanden.'], + [fn() => [test()->textField('f')->specialType(SpecialType::FIRSTNAME), test()->textField('l')->specialType(SpecialType::LASTNAME)], 'BIRTHDAY', 'Kein Feld für Geburtsdatum vorhanden.'], + [fn() => [test()->textField('f')->specialType(SpecialType::FIRSTNAME), test()->textField('l')->specialType(SpecialType::LASTNAME), test()->dateField('b')->specialType(SpecialType::BIRTHDAY)], 'ZIP', 'Kein Feld für PLZ vorhanden.'], + [fn() => [test()->textField('f')->specialType(SpecialType::FIRSTNAME), test()->textField('l')->specialType(SpecialType::LASTNAME), test()->dateField('b')->specialType(SpecialType::BIRTHDAY), test()->dateField('p')->specialType(SpecialType::ZIP)], 'LOCATION', 'Kein Feld für Ort vorhanden.'], + ])->with('contribution-documents'); + +/** + * @param array $fields + */ +it('validates special types of each document', function (string $type, array $fields, string $field, string $message) { + $this->login()->loginNami(); + + $form = Form::factory()->fields([ + test()->textField('f')->specialType(SpecialType::FIRSTNAME), + test()->textField('l')->specialType(SpecialType::LASTNAME), + test()->dateField('b')->specialType(SpecialType::BIRTHDAY), + test()->dateField('p')->specialType(SpecialType::ZIP), + test()->dateField('l')->specialType(SpecialType::LOCATION), + ...$fields, + ]) + ->has(Participant::factory()) + ->create(); + + $this->json('GET', route('form.contribution', [ + 'type' => $type, + 'form' => $form, + 'validate' => '1', + ]))->assertJsonValidationErrors([$field => $message]); +}) + ->with([ + [CitySolingenDocument::class, [], 'ADDRESS', 'Kein Feld für Adresse vorhanden.'], + [RdpNrwDocument::class, [], 'GENDER', 'Kein Feld für Geschlecht vorhanden.'], + ]); + +it('throws error when not validating but fields are not present', function () { + $this->login()->loginNami(); + + $form = Form::factory()->fields([]) + ->has(Participant::factory()) + ->create(); + + $this->json('GET', route('form.contribution', [ + 'type' => CitySolingenDocument::class, + 'form' => $form, + ]))->assertStatus(422); +}); + +it('throws error when form doesnt have meta', function () { + $this->login()->loginNami(); + + $form = Form::factory()->fields([]) + ->has(Participant::factory()) + ->zip('') + ->location('') + ->create(); + + $this->json('GET', route('form.contribution', [ + 'type' => CitySolingenDocument::class, + 'form' => $form, + ]))->assertStatus(422)->assertJsonValidationErrors([ + 'zip' => 'PLZ ist erforderlich.', + 'location' => 'Ort ist erforderlich.' + ]); +}); + +it('throws error when form doesnt have participants', function () { + $this->login()->loginNami(); + + $form = Form::factory()->fields([])->create(); + + $this->json('GET', route('form.contribution', [ + 'type' => CitySolingenDocument::class, + 'form' => $form, + 'validate' => '1', + ]))->assertJsonValidationErrors(['participants' => 'Veranstaltung besitzt noch keine Teilnehmer*innen.']); +}); + +it('creates document when fields are present', function () { + Tex::spy(); + $this->login()->loginNami(); + + $form = Form::factory()->fields([ + test()->textField('fn')->specialType(SpecialType::FIRSTNAME), + test()->textField('ln')->specialType(SpecialType::LASTNAME), + test()->dateField('bd')->specialType(SpecialType::BIRTHDAY), + test()->dateField('zip')->specialType(SpecialType::ZIP), + test()->dateField('loc')->specialType(SpecialType::LOCATION), + test()->dateField('add')->specialType(SpecialType::ADDRESS), + ]) + ->has(Participant::factory()->data(['fn' => 'Baum', 'ln' => 'Muster', 'bd' => '1991-05-06', 'zip' => '33333', 'loc' => 'Musterstadt', 'add' => 'Laastr 4'])) + ->create(); + + $this->json('GET', route('form.contribution', [ + 'type' => CitySolingenDocument::class, + 'form' => $form, + ]))->assertOk(); + Tex::assertCompiled(CitySolingenDocument::class, fn($document) => $document->hasAllContent(['Baum', 'Muster', '1991', 'Musterstadt', 'Laastr 4', '33333'])); +}); + + +it('creates document with form meta', function () { + Tex::spy(); + $this->login()->loginNami(); + + $form = Form::factory()->fields([ + test()->textField('fn')->specialType(SpecialType::FIRSTNAME), + test()->textField('ln')->specialType(SpecialType::LASTNAME), + test()->dateField('bd')->specialType(SpecialType::BIRTHDAY), + test()->dateField('zip')->specialType(SpecialType::ZIP), + test()->dateField('loc')->specialType(SpecialType::LOCATION), + test()->dateField('add')->specialType(SpecialType::ADDRESS), + test()->dateField('gen')->specialType(SpecialType::GENDER), + ]) + ->has(Participant::factory()->data(['fn' => 'Baum', 'ln' => 'Muster', 'bd' => '1991-05-06', 'zip' => '33333', 'loc' => 'Musterstadt', 'add' => 'Laastr 4', 'gen' => 'weiblich'])) + ->name('Sommerlager') + ->from('2008-06-20') + ->to('2008-06-22') + ->zip('12345') + ->location('Frankfurt') + ->create(); + + $this->json('GET', route('form.contribution', [ + 'type' => RdpNrwDocument::class, + 'form' => $form, + ]))->assertOk(); + Tex::assertCompiled(RdpNrwDocument::class, fn($document) => $document->hasAllContent(['20.06.2008', '22.06.2008', '12345 Frankfurt'])); +});