From ed7f887e3ae3f30d21eaed8e59577e7d708a0769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sat, 11 Apr 2026 22:17:38 +0200 Subject: [PATCH] Participant mangement --- ...ualCertificateOfConductionCheckCommand.php | 8 + .../ParticipantPaymentCommand.php | 50 ++++ .../ParticipantPaymentResponse.php | 15 +- .../SetParticipationStateCommand.php | 53 ++++ .../SetParticipationStateReSignonRequest.php | 9 + .../SetParticipationStateResponse.php | 7 + .../SetParticipationStateSignoffRequest.php | 10 + .../UpdateParticipantCommand.php | 142 +++++++++++ .../UpdateParticipantRequest.php | 39 +++ .../UpdateParticipantResponse.php | 23 ++ .../Event/Controllers/DetailsController.php | 1 + .../Controllers/ParticipantController.php | 24 +- .../ParticipantPaymentController.php | 53 ++++ .../ParticipantReSignOnController.php | 28 +++ .../ParticipantSignOffController.php | 29 +++ .../ParticipantUpdateController.php | 85 +++++++ .../Event/Controllers/SignupController.php | 6 +- app/Domains/Event/Routes/api.php | 12 +- .../Event/Views/Partials/ParticipantData.vue | 86 ++++--- .../Event/Views/Partials/ParticipantsList.vue | 175 +++++++++---- .../Views/Partials/ParticipationFees.vue | 2 - .../Views/Partials/ParticipationSummary.vue | 4 - app/Installer/ProductionDataSeeder.php | 4 +- .../ParticipantCocCompleteMail.php | 63 +++++ .../ParticipantCocInvalidMail.php | 63 +++++ .../EventSignUpSuccessfullMail.php} | 7 +- .../ParticipantSignOffMail.php | 68 ++++++ .../ParticipantPaymentMissingPaymentMail.php | 88 +++++++ .../ParticipantPaymentOverpaidMail.php | 79 ++++++ .../ParticipantPaymentPaidMail.php | 73 ++++++ app/Models/EventParticipant.php | 10 +- app/Providers/MissingPaymentProvider.php | 15 ++ .../EventParticipantRepository.php | 21 +- app/Resources/EventParticipantResource.php | 6 +- app/Views/Components/Modal.vue | 4 +- ..._02_15_140010_create_event_particpants.php | 2 +- .../cocCheck/coc_check_complete.blade.php | 23 ++ .../cocCheck/coc_check_invalid.blade.php | 26 ++ .../events/participant_signed_off.blade.php | 24 ++ .../emails/events/signup_complete.blade.php | 229 +++++++----------- .../amount_overpaid.blade.php | 58 +++++ .../participantPayments/amount_paid.blade.php | 52 ++++ .../last_reminder_missing_payments.blade.php | 1 + .../missing_amount.blade.php | 61 +++++ .../{events => subparts}/disclaimer.blade.php | 0 .../emails/{events => subparts}/faq.blade.php | 0 .../views/emails/subparts/payment.blade.php | 72 ++++++ 47 files changed, 1641 insertions(+), 269 deletions(-) create mode 100644 app/Domains/Event/Actions/SetParticipationState/SetParticipationStateCommand.php create mode 100644 app/Domains/Event/Actions/SetParticipationState/SetParticipationStateReSignonRequest.php create mode 100644 app/Domains/Event/Actions/SetParticipationState/SetParticipationStateResponse.php create mode 100644 app/Domains/Event/Actions/SetParticipationState/SetParticipationStateSignoffRequest.php create mode 100644 app/Domains/Event/Actions/UpdateParticipant/UpdateParticipantCommand.php create mode 100644 app/Domains/Event/Actions/UpdateParticipant/UpdateParticipantRequest.php create mode 100644 app/Domains/Event/Actions/UpdateParticipant/UpdateParticipantResponse.php create mode 100644 app/Domains/Event/Controllers/ParticipantPaymentController.php create mode 100644 app/Domains/Event/Controllers/ParticipantReSignOnController.php create mode 100644 app/Domains/Event/Controllers/ParticipantSignOffController.php create mode 100644 app/Domains/Event/Controllers/ParticipantUpdateController.php create mode 100644 app/Mail/ParticipantCocMails/ParticipantCocCompleteMail.php create mode 100644 app/Mail/ParticipantCocMails/ParticipantCocInvalidMail.php rename app/Mail/{EventSignUpSuccessfull.php => ParticipantParticipationMails/EventSignUpSuccessfullMail.php} (93%) create mode 100644 app/Mail/ParticipantParticipationMails/ParticipantSignOffMail.php create mode 100644 app/Mail/ParticipantPaymentMails/ParticipantPaymentMissingPaymentMail.php create mode 100644 app/Mail/ParticipantPaymentMails/ParticipantPaymentOverpaidMail.php create mode 100644 app/Mail/ParticipantPaymentMails/ParticipantPaymentPaidMail.php create mode 100644 app/Providers/MissingPaymentProvider.php create mode 100644 resources/views/emails/cocCheck/coc_check_complete.blade.php create mode 100644 resources/views/emails/cocCheck/coc_check_invalid.blade.php create mode 100644 resources/views/emails/events/participant_signed_off.blade.php create mode 100644 resources/views/emails/participantPayments/amount_overpaid.blade.php create mode 100644 resources/views/emails/participantPayments/amount_paid.blade.php create mode 100644 resources/views/emails/participantPayments/last_reminder_missing_payments.blade.php create mode 100644 resources/views/emails/participantPayments/missing_amount.blade.php rename resources/views/emails/{events => subparts}/disclaimer.blade.php (100%) rename resources/views/emails/{events => subparts}/faq.blade.php (100%) create mode 100644 resources/views/emails/subparts/payment.blade.php diff --git a/app/Domains/Event/Actions/ManualCertificateOfConductionCheck/ManualCertificateOfConductionCheckCommand.php b/app/Domains/Event/Actions/ManualCertificateOfConductionCheck/ManualCertificateOfConductionCheckCommand.php index 09af199..f3bc963 100644 --- a/app/Domains/Event/Actions/ManualCertificateOfConductionCheck/ManualCertificateOfConductionCheckCommand.php +++ b/app/Domains/Event/Actions/ManualCertificateOfConductionCheck/ManualCertificateOfConductionCheckCommand.php @@ -3,7 +3,10 @@ namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck; use App\Enumerations\EfzStatus; +use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail; +use App\Mail\ParticipantCocMails\ParticipantCocInvalidMail; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Mail; class ManualCertificateOfConductionCheckCommand { function __construct(public ManualCertificateOfConductionCheckRequest $request) @@ -16,6 +19,11 @@ class ManualCertificateOfConductionCheckCommand { $this->request->participant->efz_status = EfzStatus::EFZ_STATUS_CHECKED_VALID; $this->request->participant->save(); $response->success = true; + + Mail::to($this->request->participant->email_1)->send(new ParticipantCocCompleteMail( + participant: $this->request->participant, + )); + return $response; } } diff --git a/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentCommand.php b/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentCommand.php index 71a0861..8494c70 100644 --- a/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentCommand.php +++ b/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentCommand.php @@ -2,6 +2,12 @@ namespace App\Domains\Event\Actions\ParticipantPayment; +use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail; +use App\Mail\ParticipantPaymentMails\ParticipantPaymentOverpaidMail; +use App\Mail\ParticipantPaymentMails\ParticipantPaymentPaidMail; +use App\Providers\MissingPaymentProvider; +use Illuminate\Support\Facades\Mail; + class ParticipantPaymentCommand { public function __construct(public ParticipantPaymentRequest $request) { } @@ -12,6 +18,50 @@ class ParticipantPaymentCommand { $this->request->participant->amount_paid = $this->request->amountPaid; $this->request->participant->save(); + $response->amountPaid = $this->request->participant->amount_paid; + $response->amountExpected = $this->request->participant->amount; + + $amountToPay = MissingPaymentProvider::calculateMissingPayment( + amountPaid: $this->request->participant->amount_paid, + amountToPay: $this->request->participant->amount, + ); + + switch (true) { + case $amountToPay->getAmount() > 0: + Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentMissingPaymentMail( + participant: $this->request->participant, + )); + + if ($this->request->participant->email_2 !== null) { + Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentMissingPaymentMail( + participant: $this->request->participant, + )); + } + break; + case $amountToPay->getAmount() < 0: + Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentOverpaidMail( + participant: $this->request->participant, + )); + + if ($this->request->participant->email_2 !== null) { + Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentOverpaidMail( + participant: $this->request->participant, + )); + } + break; + default: + Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentPaidMail( + participant: $this->request->participant, + )); + + if ($this->request->participant->email_2 !== null) { + Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentPaidMail( + participant: $this->request->participant, + )); + } + } + + $response->participant = $this->request->participant; $response->success = true; return $response; diff --git a/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentResponse.php b/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentResponse.php index 9edd641..94e04e7 100644 --- a/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentResponse.php +++ b/app/Domains/Event/Actions/ParticipantPayment/ParticipantPaymentResponse.php @@ -2,6 +2,19 @@ namespace App\Domains\Event\Actions\ParticipantPayment; +use App\Models\EventParticipant; +use App\ValueObjects\Amount; + class ParticipantPaymentResponse { - public bool $success = false; + public bool $success; + public ?Amount $amountPaid; + public ?Amount $amountExpected; + public ?EventParticipant $participant; + + public function __construct() { + $this->amountPaid = null; + $this->amountExpected = null; + $this->success = false; + $this->participant = null; + } } diff --git a/app/Domains/Event/Actions/SetParticipationState/SetParticipationStateCommand.php b/app/Domains/Event/Actions/SetParticipationState/SetParticipationStateCommand.php new file mode 100644 index 0000000..761dcdd --- /dev/null +++ b/app/Domains/Event/Actions/SetParticipationState/SetParticipationStateCommand.php @@ -0,0 +1,53 @@ +request instanceof SetParticipationStateSignoffRequest: + $this->request->participant->unregistered_at = $this->request->date; + $this->request->participant->save(); + $response->success = true; + Mail::to($this->request->participant->email_1)->send(new ParticipantSignOffMail( + participant: $this->request->participant, + )); + + if ($this->request->participant->email_2 !== null) { + Mail::to($this->request->participant->email_2)->send(new ParticipantSignOffMail( + participant: $this->request->participant, + )); + } + + break; + case $this->request instanceof SetParticipationStateReSignonRequest: + $this->request->participant->unregistered_at = null; + $this->request->participant->save(); + + Mail::to($this->request->participant->email_1)->send(new EventSignUpSuccessfullMail( + participant: $this->request->participant, + )); + + if ($this->request->participant->email_2 !== null) { + Mail::to($this->request->participant->email_2)->send(new EventSignUpSuccessfullMail( + participant: $this->request->participant, + )); + } + + break; + } + + + + return $response; + } +} diff --git a/app/Domains/Event/Actions/SetParticipationState/SetParticipationStateReSignonRequest.php b/app/Domains/Event/Actions/SetParticipationState/SetParticipationStateReSignonRequest.php new file mode 100644 index 0000000..f7c6b99 --- /dev/null +++ b/app/Domains/Event/Actions/SetParticipationState/SetParticipationStateReSignonRequest.php @@ -0,0 +1,9 @@ +response = new UpdateParticipantResponse(); + + $p = clone($this->request->participant); + + $p->firstname = $this->request->firstname; + $p->lastname = $this->request->lastname; + $p->nickname = $this->request->nickname; + $p->address_1 = $this->request->address_1; + $p->address_2 = $this->request->address_2; + $p->postcode = $this->request->postcode; + $p->city = $this->request->city; + $p->local_group = $this->request->localgroup; + $p->birthday = DateTime::createFromFormat('Y-m-d', $this->request->birthday); + $p->email_1 = $this->request->email_1; + $p->phone_1 = $this->request->phone_1; + $p->contact_person = $this->request->contact_person; + $p->email_2 = $this->request->email_2; + $p->phone_2 = $this->request->phone_2; + $p->arrival_date = DateTime::createFromFormat('Y-m-d', $this->request->arrival); + $p->departure_date = DateTime::createFromFormat('Y-m-d', $this->request->departure); + $p->participation_type = $this->request->participationType; + $p->eating_habit = $this->request->eatingHabit; + $p->allergies = $this->request->allergies; + $p->intolerances = $this->request->intolerances; + $p->medications = $this->request->medications; + $p->first_aid_permission = $this->request->extendedFirstAid; + $p->swimming_permission = $this->request->swimmingPermission; + $p->tetanus_vaccination = $this->request->tetanusVaccination !== null + ? DateTime::createFromFormat('Y-m-d', $this->request->tetanusVaccination) + : null; + $p->notes = $this->request->notes; + $p->amount_paid = Amount::fromString($this->request->amountPaid); + $p->amount = Amount::fromString($this->request->amountExpected); + $p->efz_status = $this->request->cocStatus; + + if ( + MissingPaymentProvider::calculateMissingPayment(amountPaid: $p->amount_paid, amountToPay: $p->amount)->getAmount() + !== + MissingPaymentProvider::calculateMissingPayment(amountPaid: $this->request->participant->amount_paid, amountToPay: $this->request->participant->amount)->getAmount() + ) { + $this->handleAmountChanges($p); + } + + if ( + $p->efz_status !== $this->request->participant->efz_status + ) { + $this->handleCocStatusChange($p); + } + + + $p->save(); + $this->response->success = true; + $this->response->participant = $p; + return $this->response; + } + + private function handleAmountChanges(EventParticipant $participant) { + $this->response->amountPaid = $participant->amount_paid; + $this->response->amountExpected = $participant->amount; + + $amountToPay = MissingPaymentProvider::calculateMissingPayment( + amountPaid: $participant->amount_paid, + amountToPay: $participant->amount, + ); + + switch (true) { + case $amountToPay->getAmount() > 0: + Mail::to($participant->email_1)->send(new ParticipantPaymentMissingPaymentMail( + participant: $participant, + )); + + if ($participant->email_2 !== null) { + Mail::to($participant->email_2)->send(new ParticipantPaymentMissingPaymentMail( + participant: $participant, + )); + } + break; + case $amountToPay->getAmount() < 0: + Mail::to($participant->email_1)->send(new ParticipantPaymentOverpaidMail( + participant: $participant, + )); + + if ($participant->email_2 !== null) { + Mail::to($participant->email_2)->send(new ParticipantPaymentOverpaidMail( + participant: $participant, + )); + } + break; + default: + Mail::to($participant->email_1)->send(new ParticipantPaymentPaidMail( + participant: $participant, + )); + + if ($participant->email_2 !== null) { + Mail::to($participant->email_2)->send(new ParticipantPaymentPaidMail( + participant: $participant, + )); + } + } + } + + private function handleCocStatusChange(EventParticipant $participant) { + $this->response->cocStatus = $participant->efzStatus()->first(); + + switch ($participant->efzStatus()->first()->slug) { + case EfzStatus::EFZ_STATUS_CHECKED_VALID: + case EfzStatus::EFZ_STATUS_NOT_REQUIRED: + Mail::to($participant->email_1)->send(new ParticipantCocCompleteMail( + participant: $participant, + )); + break; + + case EfzStatus::EFZ_STATUS_CHECKED_INVALID: + Mail::to($participant->email_1)->send(new ParticipantCocInvalidMail( + participant: $participant, + )); + break; + } + } +} diff --git a/app/Domains/Event/Actions/UpdateParticipant/UpdateParticipantRequest.php b/app/Domains/Event/Actions/UpdateParticipant/UpdateParticipantRequest.php new file mode 100644 index 0000000..4941794 --- /dev/null +++ b/app/Domains/Event/Actions/UpdateParticipant/UpdateParticipantRequest.php @@ -0,0 +1,39 @@ +success = false; + $this->cocStatus =null; + $this->amountPaid = null; + $this->amountExpected = null; + $this->participant = null; + } +} diff --git a/app/Domains/Event/Controllers/DetailsController.php b/app/Domains/Event/Controllers/DetailsController.php index 6ab13c8..f07ff35 100644 --- a/app/Domains/Event/Controllers/DetailsController.php +++ b/app/Domains/Event/Controllers/DetailsController.php @@ -182,6 +182,7 @@ class DetailsController extends CommonController { $participants = $this->eventParticipants->groupByParticipationType($event, $request); break; case 'signed-off': + $participants = $this->eventParticipants->getSignedOffParticipants($event, $request); break; default: $participants = ['Alle Teilnehmenden' => $this->eventParticipants->getForList($event, $request)]; diff --git a/app/Domains/Event/Controllers/ParticipantController.php b/app/Domains/Event/Controllers/ParticipantController.php index 147ee82..e1568f4 100644 --- a/app/Domains/Event/Controllers/ParticipantController.php +++ b/app/Domains/Event/Controllers/ParticipantController.php @@ -11,22 +11,8 @@ use App\Scopes\CommonController; use Illuminate\Support\Facades\Request; class ParticipantController extends CommonController { - public function paymentComplete(string $participantIdentifier, Request $request) { - $participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events, false); - - $paymentRequest = new ParticipantPaymentRequest($participant, $participant->amount); - - $paymentCommand = new ParticipantPaymentCommand($paymentRequest); - $paymentResponse = $paymentCommand->execute(); - - return response()->json([ - 'status' => $paymentResponse->success ? 'success' : 'error', - 'message' => $paymentResponse->success ? 'Die Zahlung wurde erfolgreich gebucht.' : 'Beim Buchen der Zahlung ist ein Fehler aufgetreten.' - ]); - } - public function markCocExisting(string $participantIdentifier, Request $request) { - $participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events, false); + $participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events); $cocRequest = new ManualCertificateOfConductionCheckRequest($participant); $cocCommand = new ManualCertificateOfConductionCheckCommand($cocRequest); @@ -37,4 +23,12 @@ class ParticipantController extends CommonController { 'message' => $cocResponse->success ? 'Das eFZ wurde als gültig hinterlegt' : 'Beim Aktualisieren des eFZ-Status ist ein Fehler aufgetreten.' ]); } + + public function __invoke(string $participantIdentifier, Request $request) { + $participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events)->toResource()->toArray($request); + + return response()->json([ + 'participant' => $participant, + ]); + } } diff --git a/app/Domains/Event/Controllers/ParticipantPaymentController.php b/app/Domains/Event/Controllers/ParticipantPaymentController.php new file mode 100644 index 0000000..2b42681 --- /dev/null +++ b/app/Domains/Event/Controllers/ParticipantPaymentController.php @@ -0,0 +1,53 @@ +eventParticipants->getByIdentifier($participantIdentifier, $this->events); + + $paymentRequest = new ParticipantPaymentRequest($participant, $participant->amount); + + $paymentCommand = new ParticipantPaymentCommand($paymentRequest); + $paymentResponse = $paymentCommand->execute(); + + return response()->json([ + 'status' => $paymentResponse->success ? 'success' : 'error', + 'message' => $paymentResponse->success ? 'Die Zahlung wurde erfolgreich gebucht.' : 'Beim Buchen der Zahlung ist ein Fehler aufgetreten.' + ]); + } + + public function partialPaymentComplete(string $participantIdentifier, Request $request) { + $participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events); + + $paymentRequest = new ParticipantPaymentRequest($participant, Amount::fromString($request->input('amount'))); + + $paymentCommand = new ParticipantPaymentCommand($paymentRequest); + $paymentResponse = $paymentCommand->execute(); + + + $amountLeft = clone($paymentResponse->amountExpected); + $amountLeft->subtractAmount($paymentResponse->amountPaid); + + return response()->json([ + 'status' => $paymentResponse->success ? 'success' : 'error', + 'message' => $paymentResponse->success ? 'Die Zahlung wurde erfolgreich gebucht.' : 'Beim Buchen der Zahlung ist ein Fehler aufgetreten.', + 'identifier' => $participant->identifier, + 'amount' => [ + 'paid' => $paymentResponse->amountPaid->toString(), + 'expected' => $paymentResponse->amountExpected->toString(), + 'actions' => $amountLeft->getAmount() != 0 ? 'inline' : 'none', + 'class' => $amountLeft->getAmount() != 0 ? 'not-paid' : 'paid', + ] + ]); + + dd($participant); + } +} diff --git a/app/Domains/Event/Controllers/ParticipantReSignOnController.php b/app/Domains/Event/Controllers/ParticipantReSignOnController.php new file mode 100644 index 0000000..8d65308 --- /dev/null +++ b/app/Domains/Event/Controllers/ParticipantReSignOnController.php @@ -0,0 +1,28 @@ +eventParticipants->getByIdentifier($participantIdentifier, $this->events); + + $request = new SetParticipationStateReSignonRequest($participant); + $command = new SetParticipationStateCommand($request); + $command->execute(); + + return response()->json([ + 'status' => 'success', + 'identifier' => $participant->identifier, + 'message' => 'Die Wiederanmeldung wurde erfolgreich durchgeführt.' + ]); + + } +} diff --git a/app/Domains/Event/Controllers/ParticipantSignOffController.php b/app/Domains/Event/Controllers/ParticipantSignOffController.php new file mode 100644 index 0000000..65ad2bd --- /dev/null +++ b/app/Domains/Event/Controllers/ParticipantSignOffController.php @@ -0,0 +1,29 @@ +eventParticipants->getByIdentifier($participantIdentifier, $this->events); + + $signOffDate = DateTime::createFromFormat('Y-m-d', $request->input('cancel_date')); + + $signOffRequest = new SetParticipationStateSignoffRequest($participant, $signOffDate); + $signOffCommand = new SetParticipationStateCommand($signOffRequest); + $signOffCommand->execute(); + + return response()->json([ + 'status' => 'success', + 'identifier' => $participant->identifier, + 'message' => 'Die Abmeldung wurde erfolgreich durchgeführt.' + ]); + + } +} diff --git a/app/Domains/Event/Controllers/ParticipantUpdateController.php b/app/Domains/Event/Controllers/ParticipantUpdateController.php new file mode 100644 index 0000000..869bc51 --- /dev/null +++ b/app/Domains/Event/Controllers/ParticipantUpdateController.php @@ -0,0 +1,85 @@ +eventParticipants->getByIdentifier($participantIdentifier, $this->events); + + $updateRequest = new UpdateParticipantRequest( + participant: $participant, + firstname: $request->input('firstname'), + lastname: $request->input('lastname'), + nickname: $request->input('nickname'), + address_1: $request->input('address_1'), + address_2: $request->input('address_2'), + postcode: $request->input('postcode'), + city: $request->input('city'), + localgroup: $request->input('localgroup'), + birthday: $request->input('birthday'), + email_1: $request->input('email_1'), + phone_1: $request->input('phone_1'), + contact_person: $request->input('contact_person'), + email_2: $request->input('email_2'), + phone_2: $request->input('phone_2'), + arrival: $request->input('arrival'), + departure: $request->input('departure'), + participationType: $request->input('participationType'), + eatingHabit: $request->input('eatingHabit'), + allergies: $request->input('allergies'), + intolerances: $request->input('intolerances'), + medications: $request->input('medications'), + extendedFirstAid: $request->input('extendedFirstAid'), + swimmingPermission: $request->input('swimmingPermission'), + tetanusVaccination: $request->input('tetanusVaccination'), + notes: $request->input('notes'), + amountPaid: $request->input('amountPaid'), + amountExpected: $request->input('amountExpected'), + cocStatus: $request->input('cocStatus'), + ); + + $command = new UpdateParticipantCommand($updateRequest); + $response = $command->execute(); + + $data = [ + 'status' => $response->success ? 'success' : 'error', + 'identifier' => $participant->identifier, + 'participant' => $response->participant->toResource()->toArray($request), + ]; + + if ($response->cocStatus !== null) { + $data['cocChanged'] = true; + $data['coc']['action'] = in_array($response->cocStatus->slug, [ + EfzStatus::EFZ_STATUS_CHECKED_INVALID, + EfzStatus::EFZ_STATUS_NOT_CHECKED]) ? 'inline' : 'none'; + $data['coc']['statusText'] = $response->cocStatus->name; + $data['coc']['class'] = match($response->cocStatus->slug) { + EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'efz-invalid', + EfzStatus::EFZ_STATUS_NOT_CHECKED => 'efz-not-checked', + default => 'efz-valid', + }; + } + + if ($response->amountPaid !== null) { + $amountLeft = clone($response->amountExpected); + $amountLeft->subtractAmount($response->amountPaid); + + $data['amountChanged'] = true; + $data['amount'] = [ + 'paid' => $response->amountPaid->toString(), + 'expected' => $response->amountExpected->toString(), + 'actions' => $amountLeft->getAmount() != 0 ? 'inline' : 'none', + 'class' => $amountLeft->getAmount() != 0 ? 'not-paid' : 'paid', + ]; + } + + return response()->json($data); + } +} diff --git a/app/Domains/Event/Controllers/SignupController.php b/app/Domains/Event/Controllers/SignupController.php index 568d454..497eae7 100644 --- a/app/Domains/Event/Controllers/SignupController.php +++ b/app/Domains/Event/Controllers/SignupController.php @@ -6,7 +6,7 @@ use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConducti use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckRequest; use App\Domains\Event\Actions\SignUp\SignUpCommand; use App\Domains\Event\Actions\SignUp\SignUpRequest; -use App\Mail\EventSignUpSuccessfull; +use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail; use App\Models\Tenant; use App\Providers\DoubleCheckEventRegistrationProvider; use App\Providers\InertiaProvider; @@ -143,12 +143,12 @@ class SignupController extends CommonController { $signupResponse->participant->efz_status = $certificateOfConductionCheckResponse->status; $signupResponse->participant->save(); - Mail::to($signupResponse->participant->email_1)->send(new EventSignUpSuccessfull( + Mail::to($signupResponse->participant->email_1)->send(new EventSignUpSuccessfullMail( participant: $signupResponse->participant, )); if ($signupResponse->participant->email_2 !== null) { - Mail::to($signupResponse->participant->email_2)->send(new EventSignUpSuccessfull( + Mail::to($signupResponse->participant->email_2)->send(new EventSignUpSuccessfullMail( participant: $signupResponse->participant, )); } diff --git a/app/Domains/Event/Routes/api.php b/app/Domains/Event/Routes/api.php index 6f7292c..ccdc5c1 100644 --- a/app/Domains/Event/Routes/api.php +++ b/app/Domains/Event/Routes/api.php @@ -3,6 +3,10 @@ use App\Domains\Event\Controllers\CreateController; use App\Domains\Event\Controllers\DetailsController; use App\Domains\Event\Controllers\ParticipantController; +use App\Domains\Event\Controllers\ParticipantPaymentController; +use App\Domains\Event\Controllers\ParticipantReSignOnController; +use App\Domains\Event\Controllers\ParticipantSignOffController; +use App\Domains\Event\Controllers\ParticipantUpdateController; use App\Domains\Event\Controllers\SignupController; use App\Middleware\IdentifyTenant; use Illuminate\Support\Facades\Route; @@ -30,8 +34,14 @@ Route::prefix('api/v1') Route::prefix('/participant/{participantIdentifier}')->group(function () { - Route::post('/payment-complete', [ParticipantController::class, 'paymentComplete']); + Route::get('/', ParticipantController::class); + Route::post('/payment-complete', [ParticipantPaymentController::class, 'paymentComplete']); + Route::post('/partial-payment', [ParticipantPaymentController::class, 'partialPaymentComplete']); Route::post('/mark-coc-existing', [ParticipantController::class, 'markCocExisting']); + Route::post('/signoff', ParticipantSignOffController::class); + Route::post('/re-signon', ParticipantReSignOnController::class); + Route::post('/update', ParticipantUpdateController::class); + }); }); diff --git a/app/Domains/Event/Views/Partials/ParticipantData.vue b/app/Domains/Event/Views/Partials/ParticipantData.vue index 2dc2c7d..dae7de7 100644 --- a/app/Domains/Event/Views/Partials/ParticipantData.vue +++ b/app/Domains/Event/Views/Partials/ParticipantData.vue @@ -1,14 +1,25 @@ @@ -124,7 +136,7 @@ function saveParticipant() { Name - + {{ props.participant.firstname }} {{ props.participant.lastname }} @@ -137,7 +149,7 @@ function saveParticipant() { Pfadiname - {{ props.participant.nickname }} + {{ props.participant.nickname }} @@ -145,7 +157,7 @@ function saveParticipant() { Anschrift - + {{ props.participant.address_1 }}
{{ props.participant.address_2 }}
{{ props.participant.postcode }} @@ -163,9 +175,9 @@ function saveParticipant() { Stamm - {{ props.participant.localgroup }} + {{ props.participant.localgroup }} @@ -173,7 +185,7 @@ function saveParticipant() { Geburtsdatum - {{ props.participant.birthday }} + {{ props.participant.birthday }} @@ -186,7 +198,7 @@ function saveParticipant() { E-Mail - {{ props.participant.email_1 }} + {{ props.participant.email_1 }} @@ -194,7 +206,7 @@ function saveParticipant() { Telefon - {{ props.participant.phone_1 }} + {{ props.participant.phone_1 }} @@ -202,7 +214,7 @@ function saveParticipant() { Ansprechperson - {{ props.participant.contact_person }} + {{ props.participant.contact_person }} @@ -210,7 +222,7 @@ function saveParticipant() { Ansprechperson E-Mail - {{ props.participant.email_2 }} + {{ props.participant.email_2 }} @@ -218,7 +230,7 @@ function saveParticipant() { Ansprechperson Telefon - {{ props.participant.phone_2 }} + {{ props.participant.phone_2 }} @@ -233,24 +245,24 @@ function saveParticipant() { Anreise - {{ props.participant.arrival }} - + {{ props.participant.arrival }} + Abreise - {{ props.participant.departure }} - + {{ props.participant.departure }} + Teilnahmegruppe - {{ props.participant.participationType }} + {{ props.participant.participationType }} @@ -346,14 +358,14 @@ function saveParticipant() { Letzte Tetanus-Impfung - {{ props.participant.tetanusVaccination }} + {{ props.participant.tetanusVaccination }} Anmerkungen - {{ props.participant.notes }} + {{ props.participant.notes }} @@ -362,12 +374,12 @@ function saveParticipant() { - + - - - + + + diff --git a/app/Domains/Event/Views/Partials/ParticipantsList.vue b/app/Domains/Event/Views/Partials/ParticipantsList.vue index bd07ec5..5eea518 100644 --- a/app/Domains/Event/Views/Partials/ParticipantsList.vue +++ b/app/Domains/Event/Views/Partials/ParticipantsList.vue @@ -5,6 +5,7 @@ import ParticipantData from "./ParticipantData.vue"; import {toast} from "vue3-toastify"; import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js"; import {format, getDay, getMonth, getYear} from "date-fns"; +import AmountInput from "../../../../Views/Components/AmountInput.vue"; const props = defineProps({ data: { @@ -41,18 +42,49 @@ function openParticipantDetails(input) { editMode.value = false; } +console.log(props.data.participants) + async function saveParticipant(formData) { if (!showParticipant.value?.identifier) { return; } - const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/update', { method: "POST", - body: JSON.stringify(formData), + body: formData, }); if (data.status === 'success') { - toast.success(data.message ?? 'Änderungen gespeichert'); + toast.success(data.message ?? 'Die Änderungen wurden gespichert. Die Liste wird beim nächsten Neuladen neu generiert.'); + + document.getElementById('participant-' + data.participant.identifier + '-fullname').innerHTML = data.participant.fullname; + document.getElementById('participant-' + data.participant.identifier + '-birthday').innerText = data.participant.birthday; + document.getElementById('participant-' + data.participant.identifier + '-age').innerText = data.participant.age; + document.getElementById('participant-' + data.participant.identifier + '-localgroup').innerText = data.participant.localgroup; + document.getElementById('participant-' + data.participant.identifier + '-arrival').innerText = data.participant.arrival; + document.getElementById('participant-' + data.participant.identifier + '-departure').innerText = data.participant.departure; + document.getElementById('participant-' + data.participant.identifier + '-email_1').innerText = data.participant.email_1; + document.getElementById('participant-' + data.participant.identifier + '-email_2').innerText = data.participant.email_2 ?? '--'; + document.getElementById('participant-' + data.participant.identifier + '-phone_1').innerText = data.participant.phone_1; + document.getElementById('participant-' + data.participant.identifier + '-phone_2').innerText = data.participant.phone_2 ?? '--'; + + if (data.cocChanged) { + document.getElementById('participant-' + data.identifier + '-coc-status').innerText = data.coc.statusText; + document.getElementById('participant-' + data.identifier + '-coc-action').style.display=data.coc.action; + document.getElementById('participant-' + data.identifier + '-name').className = data.coc.class; + } + + if (data.amountChanged) { + document.getElementById('participant-' + data.identifier + '-payment').removeAttribute('class'); + document.getElementById('participant-' + data.identifier + '-paid').innerText = data.amount.paid; + document.getElementById('participant-' + data.identifier + '-expected').innerText = data.amount.expected; + document.getElementById('participant-' + data.identifier + '-actions').style.display=data.amount.actions; + document.getElementById('participant-' + data.identifier + '-payment').className = data.amount.class; + } + + + + + editMode.value = false; } else { toast.error(data.message ?? 'Speichern fehlgeschlagen'); @@ -146,16 +178,6 @@ const getRowClass = (participant) => { return ""; }; -const ensureGroupState = (groupKey) => { - if (searchTerms[groupKey] === undefined) { - searchTerms[groupKey] = ""; - } - - if (selectedStatuses[groupKey] === undefined) { - selectedStatuses[groupKey] = "all"; - } -}; - async function paymentComplete(participant) { const data = await request('/api/v1/event/participant/' + participant.identifier + '/payment-complete', { method: "POST", @@ -189,26 +211,76 @@ async function markCocExisting(participant) { } function openCancelParticipationDialog(participant) { + showParticipant.value = participant; openCancelDialog.value = true; - showParticipant = participant; - } async function execCancelParticipation() { - openCancelDialog.value = false; - toast.success('Abmeldung erfolgreich') + const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/signoff', { + method: "POST", + body: { + cancel_date: document.getElementById('cancel_date').value, + }, + }); + if (data.status === 'success') { + toast.success(data.message); + + document.getElementById('participant-' + data.identifier + '-common').style.display = 'none'; + document.getElementById('participant-' + data.identifier + '-meta').style.display = 'none'; + + } else { + toast.error(data.message); + + } + openCancelDialog.value = false; +} + +async function execResignonParticipant(participant) { + const data = await request('/api/v1/event/participant/' + participant.identifier + '/re-signon', { + method: "POST", + }); + + if (data.status === 'success') { + toast.success(data.message); + + document.getElementById('participant-' + data.identifier + '-common').style.display = 'none'; + document.getElementById('participant-' + data.identifier + '-meta').style.display = 'none'; + + } else { + toast.error(data.message); + + } + openCancelDialog.value = false; } function openPartialPaymentDialog(participant) { + showParticipant.value = participant; openPartialPaymentDialogSwitch.value = true; - showParticipant = participant; - } async function execPartialPayment() { + const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/partial-payment', { + method: "POST", + body: { + amount: document.getElementById('partial_payment_amount').value, + }, + }); + + if (data.status === 'success') { + toast.success(data.message); + + document.getElementById('participant-' + data.identifier + '-payment').removeAttribute('class'); + document.getElementById('participant-' + data.identifier + '-paid').innerText = data.amount.paid; + document.getElementById('participant-' + data.identifier + '-expected').innerText = data.amount.expected; + document.getElementById('participant-' + data.identifier + '-actions').style.display=data.amount.actions; + document.getElementById('participant-' + data.identifier + '-payment').className = data.amount.class; + } else { + toast.error(data.message); + + } + openPartialPaymentDialogSwitch.value = false; - toast.success('Teilzahlung erfolgreich') } @@ -237,58 +309,66 @@ async function execPartialPayment() { v-for="participant in getFilteredParticipants(groupKey, participants)" :key="participant.id" > - + -

- Geburtsdatum: {{ participant.birthday }}
- Alter: {{ participant.age }} Jahre
+

+ Geburtsdatum:
+ Alter: Jahre
eFZ-Status:   Vorhanden? - + Gezahlt: /
- Gesamt: {{ participant?.amountExpected.readable }} + Gesamt:

- + Zahlung buchen   Teilzahlung buchen - {{ participant?.email_1 ?? "-" }} -
- {{ participant.email_2 }} -
- + + - {{ participant?.phone_1 ?? "-" }} -
- {{ participant.phone_2 }} -
+ + - + - {{ participant?.localgroup ?? "-" }} | - Anreise: {{ participant?.arrival ?? "-" }} | - Abreise: {{ participant?.departure ?? "-" }} | - Angemeldet am: {{ participant?.registerDate ?? "-" }} | + | + Anreise: + | + Abreise: + | + + | Details | E-Mail senden | - Abmelden - Wieder anmelden + Abmelden + Wieder anmelden @@ -331,6 +411,7 @@ async function execPartialPayment() { Datum der Abmeldung: @@ -342,10 +423,12 @@ async function execPartialPayment() { Gesamtbetrag der Zahlung: - Euro + Euro / + {{showParticipant.amountExpected.readable}} Euro

@@ -429,4 +512,8 @@ async function execPartialPayment() { .efz-not-checked { color: #8D914BFF; background-color: #F4E99EFF; } + + .block-label { + display: block; + } diff --git a/app/Domains/Event/Views/Partials/ParticipationFees.vue b/app/Domains/Event/Views/Partials/ParticipationFees.vue index 5cdb518..53e687c 100644 --- a/app/Domains/Event/Views/Partials/ParticipationFees.vue +++ b/app/Domains/Event/Views/Partials/ParticipationFees.vue @@ -1,6 +1,4 @@