Participant mangement

This commit is contained in:
2026-04-11 22:17:38 +02:00
parent e6bd8c684d
commit ed7f887e3a
47 changed files with 1641 additions and 269 deletions

View File

@@ -3,7 +3,10 @@
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck; namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
use App\Enumerations\EfzStatus; use App\Enumerations\EfzStatus;
use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail;
use App\Mail\ParticipantCocMails\ParticipantCocInvalidMail;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
class ManualCertificateOfConductionCheckCommand { class ManualCertificateOfConductionCheckCommand {
function __construct(public ManualCertificateOfConductionCheckRequest $request) function __construct(public ManualCertificateOfConductionCheckRequest $request)
@@ -16,6 +19,11 @@ class ManualCertificateOfConductionCheckCommand {
$this->request->participant->efz_status = EfzStatus::EFZ_STATUS_CHECKED_VALID; $this->request->participant->efz_status = EfzStatus::EFZ_STATUS_CHECKED_VALID;
$this->request->participant->save(); $this->request->participant->save();
$response->success = true; $response->success = true;
Mail::to($this->request->participant->email_1)->send(new ParticipantCocCompleteMail(
participant: $this->request->participant,
));
return $response; return $response;
} }
} }

View File

@@ -2,6 +2,12 @@
namespace App\Domains\Event\Actions\ParticipantPayment; 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 { class ParticipantPaymentCommand {
public function __construct(public ParticipantPaymentRequest $request) { public function __construct(public ParticipantPaymentRequest $request) {
} }
@@ -12,6 +18,50 @@ class ParticipantPaymentCommand {
$this->request->participant->amount_paid = $this->request->amountPaid; $this->request->participant->amount_paid = $this->request->amountPaid;
$this->request->participant->save(); $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; $response->success = true;
return $response; return $response;

View File

@@ -2,6 +2,19 @@
namespace App\Domains\Event\Actions\ParticipantPayment; namespace App\Domains\Event\Actions\ParticipantPayment;
use App\Models\EventParticipant;
use App\ValueObjects\Amount;
class ParticipantPaymentResponse { 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;
}
} }

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Domains\Event\Actions\SetParticipationState;
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
use App\Mail\ParticipantParticipationMails\ParticipantSignOffMail;
use Illuminate\Support\Facades\Mail;
class SetParticipationStateCommand {
function __construct(private SetParticipationStateSignoffRequest|SetParticipationStateReSignonRequest $request) {}
public function execute() : SetParticipationStateResponse {
$response = new SetParticipationStateResponse();
switch (true) {
case $this->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;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Domains\Event\Actions\SetParticipationState;
use App\Models\EventParticipant;
class SetParticipationStateReSignonRequest {
function __construct(public EventParticipant $participant) {}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace App\Domains\Event\Actions\SetParticipationState;
class SetParticipationStateResponse {
function __construct(public bool $success = false) {}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Domains\Event\Actions\SetParticipationState;
use App\Models\EventParticipant;
class SetParticipationStateSignoffRequest {
function __construct(public EventParticipant $participant, public \DateTime $date) {
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace App\Domains\Event\Actions\UpdateParticipant;
use App\Enumerations\EfzStatus;
use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail;
use App\Mail\ParticipantCocMails\ParticipantCocInvalidMail;
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
use App\Mail\ParticipantPaymentMails\ParticipantPaymentOverpaidMail;
use App\Mail\ParticipantPaymentMails\ParticipantPaymentPaidMail;
use App\Models\EventParticipant;
use App\Providers\MissingPaymentProvider;
use App\ValueObjects\Amount;
use DateTime;
use Illuminate\Support\Facades\Mail;
class UpdateParticipantCommand {
private UpdateParticipantResponse $response;
function __construct(public UpdateParticipantRequest $request) {
}
public function execute() {
$this->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;
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Domains\Event\Actions\UpdateParticipant;
use App\Models\EventParticipant;
class UpdateParticipantRequest {
function __construct(
public EventParticipant $participant,
public string $firstname,
public string $lastname,
public ?string $nickname,
public string $address_1,
public ?string $address_2,
public string $postcode,
public string $city,
public string $localgroup,
public string $birthday,
public string $email_1,
public string $phone_1,
public string $contact_person,
public ?string $email_2,
public ?string $phone_2,
public string $arrival,
public string $departure,
public string $participationType,
public string $eatingHabit,
public ?string $allergies,
public ?string $intolerances,
public ?string $medications,
public string $extendedFirstAid,
public string $swimmingPermission,
public ?string $tetanusVaccination,
public ?string $notes,
public string $amountPaid,
public string $amountExpected,
public string $cocStatus,
) {}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Domains\Event\Actions\UpdateParticipant;
use App\Enumerations\EfzStatus;
use App\Models\EventParticipant;
use App\ValueObjects\Amount;
class UpdateParticipantResponse {
public bool $success;
public ?EfzStatus $cocStatus;
public ?Amount $amountPaid;
public ?Amount $amountExpected;
public ?EventParticipant $participant;
public function __construct() {
$this->success = false;
$this->cocStatus =null;
$this->amountPaid = null;
$this->amountExpected = null;
$this->participant = null;
}
}

View File

@@ -182,6 +182,7 @@ class DetailsController extends CommonController {
$participants = $this->eventParticipants->groupByParticipationType($event, $request); $participants = $this->eventParticipants->groupByParticipationType($event, $request);
break; break;
case 'signed-off': case 'signed-off':
$participants = $this->eventParticipants->getSignedOffParticipants($event, $request);
break; break;
default: default:
$participants = ['Alle Teilnehmenden' => $this->eventParticipants->getForList($event, $request)]; $participants = ['Alle Teilnehmenden' => $this->eventParticipants->getForList($event, $request)];

View File

@@ -11,22 +11,8 @@ use App\Scopes\CommonController;
use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Request;
class ParticipantController extends CommonController { 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) { 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); $cocRequest = new ManualCertificateOfConductionCheckRequest($participant);
$cocCommand = new ManualCertificateOfConductionCheckCommand($cocRequest); $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.' '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,
]);
}
} }

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Domains\Event\Controllers;
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentCommand;
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentRequest;
use App\Scopes\CommonController;
use App\ValueObjects\Amount;
use Illuminate\Http\Request;
class ParticipantPaymentController extends CommonController
{
public function paymentComplete(string $participantIdentifier, Request $request) {
$participant = $this->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);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Domains\Event\Controllers;
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateCommand;
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateReSignonRequest;
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateSignoffRequest;
use App\Scopes\CommonController;
use DateTime;
use Illuminate\Http\Request;
class ParticipantReSignOnController extends CommonController
{
public function __invoke(string $participantIdentifier, Request $request) {
$participant = $this->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.'
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Domains\Event\Controllers;
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateCommand;
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateSignoffRequest;
use App\Scopes\CommonController;
use DateTime;
use Illuminate\Http\Request;
class ParticipantSignOffController extends CommonController
{
public function __invoke(string $participantIdentifier, Request $request) {
$participant = $this->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.'
]);
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Domains\Event\Controllers;
use App\Domains\Event\Actions\UpdateParticipant\UpdateParticipantCommand;
use App\Domains\Event\Actions\UpdateParticipant\UpdateParticipantRequest;
use App\Enumerations\EfzStatus;
use App\Scopes\CommonController;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ParticipantUpdateController extends CommonController {
public function __invoke(string $participantIdentifier, Request $request): JsonResponse {
$participant = $this->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);
}
}

View File

@@ -6,7 +6,7 @@ use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConducti
use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckRequest; use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckRequest;
use App\Domains\Event\Actions\SignUp\SignUpCommand; use App\Domains\Event\Actions\SignUp\SignUpCommand;
use App\Domains\Event\Actions\SignUp\SignUpRequest; use App\Domains\Event\Actions\SignUp\SignUpRequest;
use App\Mail\EventSignUpSuccessfull; use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
use App\Models\Tenant; use App\Models\Tenant;
use App\Providers\DoubleCheckEventRegistrationProvider; use App\Providers\DoubleCheckEventRegistrationProvider;
use App\Providers\InertiaProvider; use App\Providers\InertiaProvider;
@@ -143,12 +143,12 @@ class SignupController extends CommonController {
$signupResponse->participant->efz_status = $certificateOfConductionCheckResponse->status; $signupResponse->participant->efz_status = $certificateOfConductionCheckResponse->status;
$signupResponse->participant->save(); $signupResponse->participant->save();
Mail::to($signupResponse->participant->email_1)->send(new EventSignUpSuccessfull( Mail::to($signupResponse->participant->email_1)->send(new EventSignUpSuccessfullMail(
participant: $signupResponse->participant, participant: $signupResponse->participant,
)); ));
if ($signupResponse->participant->email_2 !== null) { 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, participant: $signupResponse->participant,
)); ));
} }

View File

@@ -3,6 +3,10 @@
use App\Domains\Event\Controllers\CreateController; use App\Domains\Event\Controllers\CreateController;
use App\Domains\Event\Controllers\DetailsController; use App\Domains\Event\Controllers\DetailsController;
use App\Domains\Event\Controllers\ParticipantController; 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\Domains\Event\Controllers\SignupController;
use App\Middleware\IdentifyTenant; use App\Middleware\IdentifyTenant;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@@ -30,8 +34,14 @@ Route::prefix('api/v1')
Route::prefix('/participant/{participantIdentifier}')->group(function () { 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('/mark-coc-existing', [ParticipantController::class, 'markCocExisting']);
Route::post('/signoff', ParticipantSignOffController::class);
Route::post('/re-signon', ParticipantReSignOnController::class);
Route::post('/update', ParticipantUpdateController::class);
}); });
}); });

View File

@@ -1,14 +1,25 @@
<script setup> <script setup>
import { reactive, watch } from "vue"; import {onMounted, reactive, watch} from "vue";
import AmountInput from "../../../../Views/Components/AmountInput.vue"; import AmountInput from "../../../../Views/Components/AmountInput.vue";
const props = defineProps({ const staticProps = defineProps({
editMode: Boolean, editMode: Boolean,
participant: Object, participant: Object,
event: Object, event: Object,
}); });
console.log(props.participant) const props = reactive({
participant: staticProps.participant,
});
onMounted(async () => {
const response = await fetch('/api/v1/event/participant/' + staticProps.participant.identifier + '/');
const data = await response.json();
Object.assign(props, data);
console.log(props);
});
const emit = defineEmits([ const emit = defineEmits([
'closeParticipantDetails', 'closeParticipantDetails',
@@ -67,8 +78,8 @@ watch(
form.contact_person = participant?.contact_person ?? ''; form.contact_person = participant?.contact_person ?? '';
form.email_2 = participant?.email_2 ?? ''; form.email_2 = participant?.email_2 ?? '';
form.phone_2 = participant?.phone_2 ?? ''; form.phone_2 = participant?.phone_2 ?? '';
form.arrival = participant?.arrival ?? ''; form.arrival = participant?.arrivalDate ?? '';
form.departure = participant?.departure ?? ''; form.departure = participant?.departureDate ?? '';
form.participationType = participant?.participation_type ?? ''; form.participationType = participant?.participation_type ?? '';
form.eatingHabit = participant?.eating_habit ?? ''; form.eatingHabit = participant?.eating_habit ?? '';
form.allergies = participant?.allergies ?? ''; form.allergies = participant?.allergies ?? '';
@@ -110,6 +121,7 @@ function enableEditMode() {
function saveParticipant() { function saveParticipant() {
emit('saveParticipant', { ...form }); emit('saveParticipant', { ...form });
close();
} }
</script> </script>
@@ -124,7 +136,7 @@ function saveParticipant() {
<tr> <tr>
<th>Name</th> <th>Name</th>
<td> <td>
<span v-if="!props.editMode"> <span v-if="!staticProps.editMode">
{{ props.participant.firstname }} {{ props.participant.lastname }} {{ props.participant.firstname }} {{ props.participant.lastname }}
</span> </span>
<span v-else> <span v-else>
@@ -137,7 +149,7 @@ function saveParticipant() {
<tr> <tr>
<th>Pfadiname</th> <th>Pfadiname</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.nickname }}</span> <span v-if="!staticProps.editMode">{{ props.participant.nickname }}</span>
<input v-else v-model="form.nickname" type="text" /> <input v-else v-model="form.nickname" type="text" />
</td> </td>
</tr> </tr>
@@ -145,7 +157,7 @@ function saveParticipant() {
<tr> <tr>
<th>Anschrift</th> <th>Anschrift</th>
<td> <td>
<span v-if="!props.editMode"> <span v-if="!staticProps.editMode">
{{ props.participant.address_1 }}<br /> {{ props.participant.address_1 }}<br />
{{ props.participant.address_2 }}<br /> {{ props.participant.address_2 }}<br />
{{ props.participant.postcode }} {{ props.participant.postcode }}
@@ -163,9 +175,9 @@ function saveParticipant() {
<tr> <tr>
<th>Stamm</th> <th>Stamm</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.localgroup }}</span> <span v-if="!staticProps.editMode">{{ props.participant.localgroup }}</span>
<select v-else v-model="form.localgroup"> <select v-else v-model="form.localgroup">
<option v-for="group in props.event.contributingLocalGroups" :key="group.id" :value="group.slug">{{ group.name }}</option> <option v-for="group in staticProps.event.contributingLocalGroups" :key="group.id" :value="group.slug">{{ group.name }}</option>
</select> </select>
</td> </td>
</tr> </tr>
@@ -173,7 +185,7 @@ function saveParticipant() {
<tr> <tr>
<th>Geburtsdatum</th> <th>Geburtsdatum</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.birthday }}</span> <span v-if="!staticProps.editMode">{{ props.participant.birthday }}</span>
<input v-else v-model="form.birthday" type="date" /> <input v-else v-model="form.birthday" type="date" />
</td> </td>
</tr> </tr>
@@ -186,7 +198,7 @@ function saveParticipant() {
<tr> <tr>
<th>E-Mail</th> <th>E-Mail</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.email_1 }}</span> <span v-if="!staticProps.editMode">{{ props.participant.email_1 }}</span>
<input v-else v-model="form.email_1" type="email" /> <input v-else v-model="form.email_1" type="email" />
</td> </td>
</tr> </tr>
@@ -194,7 +206,7 @@ function saveParticipant() {
<tr> <tr>
<th>Telefon</th> <th>Telefon</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.phone_1 }}</span> <span v-if="!staticProps.editMode">{{ props.participant.phone_1 }}</span>
<input v-else v-model="form.phone_1" type="text" /> <input v-else v-model="form.phone_1" type="text" />
</td> </td>
</tr> </tr>
@@ -202,7 +214,7 @@ function saveParticipant() {
<tr> <tr>
<th>Ansprechperson</th> <th>Ansprechperson</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.contact_person }}</span> <span v-if="!staticProps.editMode">{{ props.participant.contact_person }}</span>
<input v-else v-model="form.contact_person" type="text" /> <input v-else v-model="form.contact_person" type="text" />
</td> </td>
</tr> </tr>
@@ -210,7 +222,7 @@ function saveParticipant() {
<tr> <tr>
<th>Ansprechperson E-Mail</th> <th>Ansprechperson E-Mail</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.email_2 }}</span> <span v-if="!staticProps.editMode">{{ props.participant.email_2 }}</span>
<input v-else v-model="form.email_2" type="email" /> <input v-else v-model="form.email_2" type="email" />
</td> </td>
</tr> </tr>
@@ -218,7 +230,7 @@ function saveParticipant() {
<tr> <tr>
<th>Ansprechperson Telefon</th> <th>Ansprechperson Telefon</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.phone_2 }}</span> <span v-if="!staticProps.editMode">{{ props.participant.phone_2 }}</span>
<input v-else v-model="form.phone_2" type="text" /> <input v-else v-model="form.phone_2" type="text" />
</td> </td>
</tr> </tr>
@@ -233,24 +245,24 @@ function saveParticipant() {
<tr> <tr>
<th>Anreise</th> <th>Anreise</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.arrival }}</span> <span v-if="!staticProps.editMode">{{ props.participant.arrival }}</span>
<input v-else v-model="form.arrival" type="text" /> <input v-else v-model="form.arrival" type="date" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Abreise</th> <th>Abreise</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.departure }}</span> <span v-if="!staticProps.editMode">{{ props.participant.departure }}</span>
<input v-else v-model="form.departure" type="text" /> <input v-else v-model="form.departure" type="date" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Teilnahmegruppe</th> <th>Teilnahmegruppe</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.participationType }}</span> <span v-if="!staticProps.editMode">{{ props.participant.participationType }}</span>
<select v-else v-model="form.participationType"> <select v-else v-model="form.participationType">
<option <option
v-for="participationType in event.participationTypes" v-for="participationType in staticProps.event.participationTypes"
:value="participationType.type.slug" :value="participationType.type.slug"
> >
{{ participationType.type.name }} {{ participationType.type.name }}
@@ -261,10 +273,10 @@ function saveParticipant() {
<tr> <tr>
<th>Ernährung</th> <th>Ernährung</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.eatingHabit }}</span> <span v-if="!staticProps.editMode">{{ props.participant.eatingHabit }}</span>
<select v-else v-model="form.eatingHabit"> <select v-else v-model="form.eatingHabit">
<option <option
v-for="eatingHabit in event.eatingHabits" v-for="eatingHabit in staticProps.event.eatingHabits"
:value="eatingHabit.slug" :value="eatingHabit.slug"
> >
{{ eatingHabit.name }} {{ eatingHabit.name }}
@@ -275,7 +287,7 @@ function saveParticipant() {
<tr> <tr>
<th>eFZ-Status</th> <th>eFZ-Status</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.efzStatusReadable }}</span> <span v-if="!staticProps.editMode">{{ props.participant.efzStatusReadable }}</span>
<select v-else v-model="form.cocStatus"> <select v-else v-model="form.cocStatus">
<option value="not_checked">Nicht geprüft</option> <option value="not_checked">Nicht geprüft</option>
<option value="not_required">Nicht erforderlich</option> <option value="not_required">Nicht erforderlich</option>
@@ -287,7 +299,7 @@ function saveParticipant() {
<tr> <tr>
<th>Beitrag</th> <th>Beitrag</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.amountPaid.readable }} / {{ props.participant.amountExpected.readable }}</span> <span v-if="!staticProps.editMode">{{ props.participant.amountPaid.readable }} / {{ props.participant.amountExpected.readable }}</span>
<span v-else> <span v-else>
<AmountInput v-model="form.amountPaid" style="width:74px" /> Euro <AmountInput v-model="form.amountPaid" style="width:74px" /> Euro
/ /
@@ -304,28 +316,28 @@ function saveParticipant() {
<tr> <tr>
<th>Allergien</th> <th>Allergien</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.allergies }}</span> <span v-if="!staticProps.editMode">{{ props.participant.allergies }}</span>
<input type="text" v-else v-model="form.allergies" /> <input type="text" v-else v-model="form.allergies" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Unverträglichkeiten</th> <th>Unverträglichkeiten</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.intolerances }}</span> <span v-if="!staticProps.editMode">{{ props.participant.intolerances }}</span>
<input type="text" v-else v-model="form.intolerances" /> <input type="text" v-else v-model="form.intolerances" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Medikamente</th> <th>Medikamente</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.medications }}</span> <span v-if="!staticProps.editMode">{{ props.participant.medications }}</span>
<input type="text" v-else v-model="form.medications" /> <input type="text" v-else v-model="form.medications" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Erweiterte Erste Hilfe</th> <th>Erweiterte Erste Hilfe</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.extendedFirstAid }}</span> <span v-if="!staticProps.editMode">{{ props.participant.extendedFirstAid }}</span>
<select v-else v-model="form.extendedFirstAid"> <select v-else v-model="form.extendedFirstAid">
<option value="FIRST_AID_PERMISSION_ALLOWED">Erlaubt</option> <option value="FIRST_AID_PERMISSION_ALLOWED">Erlaubt</option>
<option value="FIRST_AID_PERMISSION_DENIED">Verweigert</option> <option value="FIRST_AID_PERMISSION_DENIED">Verweigert</option>
@@ -335,7 +347,7 @@ function saveParticipant() {
<tr> <tr>
<th>Badeerlaubnis</th> <th>Badeerlaubnis</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.swimmingPermission }}</span> <span v-if="!staticProps.editMode">{{ props.participant.swimmingPermission }}</span>
<select v-else v-model="form.swimmingPermission"> <select v-else v-model="form.swimmingPermission">
<option value="SWIMMING_PERMISSION_ALLOWED">Vorhanden, kann schwimmen</option> <option value="SWIMMING_PERMISSION_ALLOWED">Vorhanden, kann schwimmen</option>
<option value="SWIMMING_PERMISSION_LIMITED">Vorhanden, kann nicht schwimmen</option> <option value="SWIMMING_PERMISSION_LIMITED">Vorhanden, kann nicht schwimmen</option>
@@ -346,14 +358,14 @@ function saveParticipant() {
<tr> <tr>
<th>Letzte Tetanus-Impfung</th> <th>Letzte Tetanus-Impfung</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.tetanusVaccination }}</span> <span v-if="!staticProps.editMode">{{ props.participant.tetanusVaccination }}</span>
<input v-else v-model="form.tetanusVaccination" type="date" /> <input v-else v-model="form.tetanusVaccination" type="date" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Anmerkungen</th> <th>Anmerkungen</th>
<td> <td>
<span v-if="!props.editMode">{{ props.participant.notes }}</span> <span v-if="!staticProps.editMode">{{ props.participant.notes }}</span>
<textarea v-else v-model="form.notes"></textarea> <textarea v-else v-model="form.notes"></textarea>
</td> </td>
</tr> </tr>
@@ -362,12 +374,12 @@ function saveParticipant() {
</div> </div>
</div> </div>
<button v-if="!props.editMode" class="button" @click="enableEditMode">Bearbeiten</button> <button v-if="!staticProps.editMode" class="button" @click="enableEditMode">Bearbeiten</button>
<button v-else class="button" @click="saveParticipant">Speichern</button> <button v-else class="button" @click="saveParticipant">Speichern</button>
<button class="button" @click="paymentComplete(props.participant)">Zahlung vollständig</button> <button v-if="!props.participant.unregistered" class="button" @click="paymentComplete(props.participant)">Zahlung vollständig</button>
<button class="button" @click="markCocExisting(props.participant)">eFZ liegt vor</button> <button v-if="!props.participant.unregistered" class="button" @click="markCocExisting(props.participant)">eFZ liegt vor</button>
<button class="button" @click="cancelParticipation(props.participant)">Abmelden</button> <button v-if="!props.participant.unregistered" class="button" @click="cancelParticipation(props.participant)">Abmelden</button>
<button class="button" @click="close">Schließen</button> <button class="button" @click="close">Schließen</button>
</template> </template>

View File

@@ -5,6 +5,7 @@ import ParticipantData from "./ParticipantData.vue";
import {toast} from "vue3-toastify"; import {toast} from "vue3-toastify";
import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js"; import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js";
import {format, getDay, getMonth, getYear} from "date-fns"; import {format, getDay, getMonth, getYear} from "date-fns";
import AmountInput from "../../../../Views/Components/AmountInput.vue";
const props = defineProps({ const props = defineProps({
data: { data: {
@@ -41,18 +42,49 @@ function openParticipantDetails(input) {
editMode.value = false; editMode.value = false;
} }
console.log(props.data.participants)
async function saveParticipant(formData) { async function saveParticipant(formData) {
if (!showParticipant.value?.identifier) { if (!showParticipant.value?.identifier) {
return; return;
} }
const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/update', { const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/update', {
method: "POST", method: "POST",
body: JSON.stringify(formData), body: formData,
}); });
if (data.status === 'success') { 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; editMode.value = false;
} else { } else {
toast.error(data.message ?? 'Speichern fehlgeschlagen'); toast.error(data.message ?? 'Speichern fehlgeschlagen');
@@ -146,16 +178,6 @@ const getRowClass = (participant) => {
return ""; return "";
}; };
const ensureGroupState = (groupKey) => {
if (searchTerms[groupKey] === undefined) {
searchTerms[groupKey] = "";
}
if (selectedStatuses[groupKey] === undefined) {
selectedStatuses[groupKey] = "all";
}
};
async function paymentComplete(participant) { async function paymentComplete(participant) {
const data = await request('/api/v1/event/participant/' + participant.identifier + '/payment-complete', { const data = await request('/api/v1/event/participant/' + participant.identifier + '/payment-complete', {
method: "POST", method: "POST",
@@ -189,26 +211,76 @@ async function markCocExisting(participant) {
} }
function openCancelParticipationDialog(participant) { function openCancelParticipationDialog(participant) {
showParticipant.value = participant;
openCancelDialog.value = true; openCancelDialog.value = true;
showParticipant = participant;
} }
async function execCancelParticipation() { async function execCancelParticipation() {
openCancelDialog.value = false; const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/signoff', {
toast.success('Abmeldung erfolgreich') 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) { function openPartialPaymentDialog(participant) {
showParticipant.value = participant;
openPartialPaymentDialogSwitch.value = true; openPartialPaymentDialogSwitch.value = true;
showParticipant = participant;
} }
async function execPartialPayment() { 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; openPartialPaymentDialogSwitch.value = false;
toast.success('Teilzahlung erfolgreich')
} }
</script> </script>
@@ -237,58 +309,66 @@ async function execPartialPayment() {
v-for="participant in getFilteredParticipants(groupKey, participants)" v-for="participant in getFilteredParticipants(groupKey, participants)"
:key="participant.id" :key="participant.id"
> >
<tr :class="getRowClass(participant)" :id="'participant-' + participant.identifier"> <tr :class="getRowClass(participant)" :id="'participant-' + participant.identifier + '-common'">
<td :id="'participant-' + participant.identifier +'-name'" <td :id="'participant-' + participant.identifier +'-name'"
style="width: 300px;" style="width: 300px;"
:class="participant.efz_status === 'checked_invalid' ? 'efz-invalid' : :class="participant.efz_status === 'checked_invalid' ? 'efz-invalid' :
participant.efz_status === 'not_checked' ? 'efz-not-checked' : ''"> participant.efz_status === 'not_checked' ? 'efz-not-checked' : ''">
<div v-html="participant.fullname" /><br /> <div :id="'participant-' + participant.identifier +'-fullname'" v-html="participant.fullname" /><br />
Geburtsdatum: {{ participant.birthday }}<br /> Geburtsdatum: <label :id="'participant-' + participant.identifier +'-birthday'">{{ participant.birthday }}</label><br />
Alter: {{ participant.age }} Jahre<br /> Alter: <label :id="'participant-' + participant.identifier +'-age'">{{ participant.age }}</label> Jahre<br />
<span>eFZ-Status: <label :id="'participant-' + participant.identifier +'-coc-status'">{{ participant.efzStatusReadable }}</label></span> &nbsp; <span>eFZ-Status: <label :id="'participant-' + participant.identifier +'-coc-status'">{{ participant.efzStatusReadable }}</label></span> &nbsp;
<span :id="'participant-' + participant.identifier +'-coc-action'" v-if="participant.efz_status !== 'checked_valid' && participant.efz_status !== 'not_required'" class="link" style="color: #3cb62e; font-size: 11pt;" @click="markCocExisting(participant)">Vorhanden?</span> <span :id="'participant-' + participant.identifier +'-coc-action'" v-if="participant.efz_status !== 'checked_valid' && participant.efz_status !== 'not_required'" class="link" style="color: #3cb62e; font-size: 11pt;" @click="markCocExisting(participant)">Vorhanden?</span>
</td> </td>
<td :id="'participant-' + participant.identifier +'-payment'" :class="participant.amount_left_value != 0 && !unregistered_at ? 'not-paid' : ''" style="width: 275px; '"> <td :id="'participant-' + participant.identifier +'-payment'" :class="participant.amount_left_value != 0 && !participant.unregistered ? 'not-paid' : ''" style="width: 275px; '">
Gezahlt: <label :id="'participant-' + participant.identifier + '-paid'">{{ participant?.amountPaid.readable }}</label> /<br /> Gezahlt: <label :id="'participant-' + participant.identifier + '-paid'">{{ participant?.amountPaid.readable }}</label> /<br />
Gesamt: {{ participant?.amountExpected.readable }} Gesamt: <label :id="'participant-' + participant.identifier + '-expected'">{{ participant?.amountExpected.readable }}</label>
<br /><br /> <br /><br />
<span v-if="participant.amount_left_value != 0 && !unregistered_at" :id="'participant-' + participant.identifier + '-actions'"> <span v-if="participant.amount_left_value != 0 && !participant.unregistered" :id="'participant-' + participant.identifier + '-actions'">
<span class="link" style="font-size:10pt;" @click="paymentComplete(participant)">Zahlung buchen</span> &nbsp; <span class="link" style="font-size:10pt;" @click="paymentComplete(participant)">Zahlung buchen</span> &nbsp;
<span class="link" style="font-size:10pt;" @click="openPartialPaymentDialog(participant)">Teilzahlung buchen</span> <span class="link" style="font-size:10pt;" @click="openPartialPaymentDialog(participant)">Teilzahlung buchen</span>
</span> </span>
</td> </td>
<td> <td>
{{ participant?.email_1 ?? "-" }} <label :id="'participant-' + participant.identifier +'-email_1'" class="block-label">{{ participant?.email_1 ?? "-" }}</label>
<div v-if="participant?.email_2 && participant.email_2 !== participant.email_1" class="text-xs text-gray-500"> <label :id="'participant-' + participant.identifier +'-email_2'" class="block-label">{{ participant.email_2 }}</label>
{{ participant.email_2 }}
</div>
</td> </td>
<td> <td>
{{ participant?.phone_1 ?? "-" }} <label :id="'participant-' + participant.identifier +'-phone_1'" class="block-label">{{ participant?.phone_1 }}</label>
<div v-if="participant?.phone_2 && participant.phone_2 !== participant.phone_1" class="text-xs text-gray-500"> <label :id="'participant-' + participant.identifier +'-phone_2'" class="block-label">{{ participant?.phone_2 }}</label>
{{ participant.phone_2 }}
</div>
</td> </td>
</tr> </tr>
<tr class="participant-meta-row"> <tr class="participant-meta-row" :id="'participant-' + participant.identifier + '-meta'">
<td colspan="5" style="height: 15px !important; font-size: 9pt; background-color: #ffffff; border-top-style: none;"> <td colspan="5" style="height: 15px !important; font-size: 9pt; background-color: #ffffff; border-top-style: none;">
{{ participant?.localgroup ?? "-" }} | <label :id="'participant-' + participant.identifier +'-localgroup'">
<strong> Anreise: </strong>{{ participant?.arrival ?? "-" }} | {{ participant?.localgroup ?? "-" }}
<strong> Abreise: </strong>{{ participant?.departure ?? "-" }} | </label> |
<strong> Angemeldet am: </strong>{{ participant?.registerDate ?? "-" }} | <strong> Anreise: </strong>
<label :id="'participant-' + participant.identifier +'-arrival'">
{{ participant?.arrival ?? "-" }}
</label>|
<strong> Abreise: </strong>
<label :id="'participant-' + participant.identifier +'-departure'">
{{ participant?.departure ?? "-" }}
</label> |
<label v-if="!participant.unregistered">
<strong> Angemeldet am: </strong>{{ participant?.registerDate ?? "-" }}
</label>
<label v-else>
<strong> Abgemeldet am: </strong>{{ participant?.unregisteredAt ?? "-" }}
</label> |
<span class="link" @click="openParticipantDetails(participant)">Details</span> | <span class="link" @click="openParticipantDetails(participant)">Details</span> |
<span class="link">E-Mail senden</span> | <span class="link">E-Mail senden</span> |
<span @click="openCancelParticipationDialog(participant)" v-if="!unregistered_at" class="link" style="color: #da7070;">Abmelden</span> <span @click="openCancelParticipationDialog(participant)" v-if="!participant.unregistered" class="link" style="color: #da7070;">Abmelden</span>
<span v-else class="link" style="color: #3cb62e;">Wieder anmelden</span> <span v-else class="link" @click="execResignonParticipant(participant)" style="color: #3cb62e;">Wieder anmelden</span>
</td> </td>
</tr> </tr>
</template> </template>
@@ -331,6 +411,7 @@ async function execPartialPayment() {
<Modal <Modal
:show="openCancelDialog" :show="openCancelDialog"
title="Anmeldung stornieren" title="Anmeldung stornieren"
width="350px"
@close="openCancelDialog = false" @close="openCancelDialog = false"
> >
Datum der Abmeldung: Datum der Abmeldung:
@@ -342,10 +423,12 @@ async function execPartialPayment() {
<Modal <Modal
:show="openPartialPaymentDialogSwitch" :show="openPartialPaymentDialogSwitch"
title="Teilbetragszahlung" title="Teilbetragszahlung"
width="350px"
@close="openPartialPaymentDialogSwitch = false" @close="openPartialPaymentDialogSwitch = false"
> >
Gesamtbetrag der Zahlung: Gesamtbetrag der Zahlung:
<input type="text" style="margin-top: 10px; width: 150px !important;" id="partial_payment_amount" /> Euro <AmountInput type="text" v-model="showParticipant.amountExpected.short" style="margin-top: 10px; width: 100px !important;" id="partial_payment_amount" /> Euro /
{{showParticipant.amountExpected.readable}} Euro
<br /><br /> <br /><br />
<button class="button" @click="execPartialPayment()">Teilbetrag buchen</button> <button class="button" @click="execPartialPayment()">Teilbetrag buchen</button>
</Modal> </Modal>
@@ -429,4 +512,8 @@ async function execPartialPayment() {
.efz-not-checked { .efz-not-checked {
color: #8D914BFF; background-color: #F4E99EFF; color: #8D914BFF; background-color: #F4E99EFF;
} }
.block-label {
display: block;
}
</style> </style>

View File

@@ -1,6 +1,4 @@
<script setup> <script setup>
import AppLayout from "../../../../../resources/js/layouts/AppLayout.vue";
import ShadowedBox from "../../../../Views/Components/ShadowedBox.vue";
import {reactive, watch} from "vue"; import {reactive, watch} from "vue";
import AmountInput from "../../../../Views/Components/AmountInput.vue"; import AmountInput from "../../../../Views/Components/AmountInput.vue";
import ErrorText from "../../../../Views/Components/ErrorText.vue"; import ErrorText from "../../../../Views/Components/ErrorText.vue";

View File

@@ -2,10 +2,6 @@
const props = defineProps({ const props = defineProps({
event: Object event: Object
}) })
console.log(props.event)
</script> </script>
<template> <template>

View File

@@ -35,8 +35,8 @@ class ProductionDataSeeder {
private function installEfzStatus() { private function installEfzStatus() {
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'name' => 'Nicht geprüft']); EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'name' => 'Nicht geprüft']);
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']); EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']);
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_CHECKED_VALID, 'name' => 'Geprüft und gültig']); EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_CHECKED_VALID, 'name' => 'Gültig']);
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_CHECKED_INVALID, 'name' => 'Geprüft und ungültig']); EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_CHECKED_INVALID, 'name' => 'Nicht eingereicht']);
} }
private function installParticipationTypes() { private function installParticipationTypes() {

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Mail\ParticipantCocMails;
use App\Models\EventParticipant;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class ParticipantCocCompleteMail extends Mailable {
public function __construct(
private EventParticipant $participant,
)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$participant = $this->participant->toResource()->toArray(new Request());
$subject = 'Dein erweitertes Führungszeugnis wurde bestätigt';
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
$event = $this->participant->event()->first()->toResource()->toArray(new Request());
$participant = $this->participant->toResource()->toArray(new Request());
return new Content(
view: 'emails.cocCheck.coc_check_complete',
with: [
'name' => $participant['nicename'],
'eventTitle' => $event['name'],
'eventEmail' => $event['email'],
'cocFinalDate' => $event['registrationFinalEnd']['formatted'],
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Mail\ParticipantCocMails;
use App\Models\EventParticipant;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class ParticipantCocInvalidMail extends Mailable {
public function __construct(
private EventParticipant $participant,
)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$participant = $this->participant->toResource()->toArray(new Request());
$subject = 'Bitte reiche ein erweiteres polizeiliches Führungszeugnis ein';
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
$event = $this->participant->event()->first()->toResource()->toArray(new Request());
$participant = $this->participant->toResource()->toArray(new Request());
return new Content(
view: 'emails.cocCheck.coc_check_invalid',
with: [
'name' => $participant['nicename'],
'eventTitle' => $event['name'],
'eventEmail' => $event['email'],
'cocFinalDate' => $event['registrationFinalEnd']['formatted'],
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -1,12 +1,9 @@
<?php <?php
namespace App\Mail; namespace App\Mail\ParticipantParticipationMails;
use App\Enumerations\EfzStatus;
use App\Models\EventParticipant; use App\Models\EventParticipant;
use App\Resources\EventParticipantResource;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment; use Illuminate\Mail\Mailables\Attachment;
@@ -14,7 +11,7 @@ use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope; use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class EventSignUpSuccessfull extends Mailable class EventSignUpSuccessfullMail extends Mailable
{ {
use Queueable, SerializesModels; use Queueable, SerializesModels;

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Mail\ParticipantParticipationMails;
use App\Models\EventParticipant;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class ParticipantSignOffMail extends Mailable
{
public function __construct(
private EventParticipant $participant,
)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$participant = $this->participant->toResource()->toArray(new Request());
$subject = sprintf(
'Abmeldebestätigung %1$s %2$s',
'für die Veranstaltung',
$this->participant->event()->first()->name
);
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
$event = $this->participant->event()->first()->toResource()->toArray(new Request());
$participant = $this->participant->toResource()->toArray(new Request());
return new Content(
view: 'emails.events.participant_signed_off',
with: [
'name' => $participant['nicename'],
'eventTitle' => $event['name'],
'eventEmail' => $event['email'],
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Mail\ParticipantPaymentMails;
use App\Models\EventParticipant;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class ParticipantPaymentMissingPaymentMail extends Mailable
{
public function __construct(
private EventParticipant $participant,
)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$participant = $this->participant->toResource()->toArray(new Request());
$subject = sprintf(
$participant['needs_payment'] ? 'Teilnahme- & Zahlungsinformationen %1$s %2$s' : 'Anmeldebestätigung %1$s %2$s',
'für die Veranstaltung',
$this->participant->event()->first()->name
);
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
$event = $this->participant->event()->first()->toResource()->toArray(new Request());
$participant = $this->participant->toResource()->toArray(new Request());
$girocodeProvider = new \App\Providers\GiroCodeProvider(
$event['accountOwner'],
$event['accountIban'],
(float) $participant['amount_left_value'],
$participant['payment_purpose']
);
$girocodeBinary = (string)$girocodeProvider->create();
return new Content(
view: 'emails.participantPayments.missing_amount',
with: [
'participationType' => $participant['participationType'],
'name' => $participant['nicename'],
'eventTitle' => $event['name'],
'eventEmail' => $event['email'],
'arrival' => $participant['arrival'],
'departure' => $participant['departure'],
'amount' => $participant['amount_left_string'],
'paymentFinalDate' => $event['registrationFinalEnd']['formatted'],
'paymentRequired' => $participant['needs_payment'],
'accountOwner' => $event['accountOwner'],
'accountIban' => $event['accountIban'],
'paymentPurpose' => $participant['payment_purpose'],
'girocodeBinary' => $girocodeBinary,
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Mail\ParticipantPaymentMails;
use App\Models\EventParticipant;
use App\ValueObjects\Amount;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class ParticipantPaymentOverpaidMail extends Mailable
{
public function __construct(
private EventParticipant $participant,
)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$participant = $this->participant->toResource()->toArray(new Request());
$subject = sprintf(
'Überzahlung des Beitrags %1$s%2$s',
'für die Veranstaltung',
$this->participant->event()->first()->name
);
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
$event = $this->participant->event()->first()->toResource()->toArray(new Request());
$participant = $this->participant->toResource()->toArray(new Request());
$overpaidAmount = Amount::fromString($participant['amount_left_value'] * -1)->toString();
return new Content(
view: 'emails.participantPayments.amount_overpaid',
with: [
'participationType' => $participant['participationType'],
'name' => $participant['nicename'],
'eventTitle' => $event['name'],
'eventEmail' => $event['email'],
'arrival' => $participant['arrival'],
'departure' => $participant['departure'],
'amount' => $participant['amountExpected']['readable'],
'amount_paid' => $participant['amountPaid']['readable'],
'overpaidAmount' => $overpaidAmount,
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Mail\ParticipantPaymentMails;
use App\Models\EventParticipant;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class ParticipantPaymentPaidMail extends Mailable
{
public function __construct(
private EventParticipant $participant,
)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$participant = $this->participant->toResource()->toArray(new Request());
$subject = sprintf(
'Anmeldebestätigung %1$s %2$s',
'für die Veranstaltung',
$this->participant->event()->first()->name
);
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
$event = $this->participant->event()->first()->toResource()->toArray(new Request());
$participant = $this->participant->toResource()->toArray(new Request());
return new Content(
view: 'emails.participantPayments.amount_paid',
with: [
'participationType' => $participant['participationType'],
'name' => $participant['nicename'],
'eventTitle' => $event['name'],
'eventEmail' => $event['email'],
'arrival' => $participant['arrival'],
'departure' => $participant['departure'],
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -69,11 +69,11 @@ class EventParticipant extends InstancedModel
]; ];
protected $casts = [ protected $casts = [
'birthday' => 'datetime', 'birthday' => 'date',
'tetanus_vaccination' => 'datetime', 'tetanus_vaccination' => 'date',
'arrival_date' => 'datetime', 'arrival_date' => 'date',
'departure_date' => 'datetime', 'departure_date' => 'date',
'unregistered_at' => 'datetime', 'unregistered_at' => 'date',
'foto_socialmedia' => 'boolean', 'foto_socialmedia' => 'boolean',
'foto_print' => 'boolean', 'foto_print' => 'boolean',

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Providers;
use App\ValueObjects\Amount;
class MissingPaymentProvider {
public static function calculateMissingPayment(Amount $amountPaid, Amount $amountToPay) : Amount {
$workingAmount = clone($amountToPay);
$workingAmount->subtractAmount($amountPaid);
return $workingAmount;
}
}

View File

@@ -8,9 +8,16 @@ use App\Models\EventParticipant;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class EventParticipantRepository { class EventParticipantRepository {
public function getForList(Event $event, Request $request) : array { public function getForList(Event $event, Request $request, bool $signedOffParticipants = false) : array {
$participants = []; $participants = [];
foreach ($event->participants()->orderBy('lastname')->orderBy('firstname')->get() as $participant) {
if (!$signedOffParticipants) {
$allParticipants = $event->participants()->whereNull('unregistered_at');
} else {
$allParticipants = $event->participants()->whereNotNull('unregistered_at');
}
foreach ($allParticipants->orderBy('lastname')->orderBy('firstname')->get() as $participant) {
$participants[] = $participant->toResource()->toArray($request); $participants[] = $participant->toResource()->toArray($request);
}; };
@@ -52,6 +59,16 @@ class EventParticipantRepository {
return $participants; return $participants;
} }
public function getSignedOffParticipants(Event $event, Request $request) : array {
$allParticipants = $this->getForList($event, $request, true);
$participants = [];
foreach ($allParticipants as $participant) {
$participants[$participant['participationType']][] = $participant;
}
return $participants;
}
public function getParticipantsWithIntolerances(Event $event, Request $request) : array { public function getParticipantsWithIntolerances(Event $event, Request $request) : array {
$participants = []; $participants = [];
foreach ($event->participants()->whereNotNull('intolerances')->whereNot('intolerances' , '=', '')->get() as $participant) { foreach ($event->participants()->whereNotNull('intolerances')->whereNot('intolerances' , '=', '')->get() as $participant) {

View File

@@ -41,7 +41,11 @@ class EventParticipantResource extends JsonResource
$this->resource->toArray(), $this->resource->toArray(),
[ [
'birthdayDate' => $this->resource->birthday->format('Y-m-d'), 'birthdayDate' => $this->resource->birthday->format('Y-m-d'),
'arrivalDate' => $this->resource->arrival_date->format('Y-m-d'),
'departureDate' => $this->resource->departure_date->format('Y-m-d'),
'registerDate' => $this->resource->created_at->format('d.m.Y'), 'registerDate' => $this->resource->created_at->format('d.m.Y'),
'unregistered' => $this->resource->unregistered_at !== null,
'unregisteredAt' => $this->resource->unregistered_at?->format('d.m.Y'),
'fullname' => $this->resource->getFullName(), 'fullname' => $this->resource->getFullName(),
'age' => new Age($this->resource->birthday)->getAge(), 'age' => new Age($this->resource->birthday)->getAge(),
'localgroup' => $this->resource->localGroup()->first()->name, 'localgroup' => $this->resource->localGroup()->first()->name,
@@ -68,7 +72,7 @@ class EventParticipantResource extends JsonResource
'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()->name, 'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()->name,
'efzStatusReadable' => match($this->resource->efz_status) { 'efzStatusReadable' => match($this->resource->efz_status) {
EfzStatus::EFZ_STATUS_CHECKED_VALID => 'Gültig', EfzStatus::EFZ_STATUS_CHECKED_VALID => 'Gültig',
EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'Ungültig', EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'Nicht eingereicht',
EfzStatus::EFZ_STATUS_NOT_CHECKED => 'Nicht geprüft', EfzStatus::EFZ_STATUS_NOT_CHECKED => 'Nicht geprüft',
EfzStatus::EFZ_STATUS_NOT_REQUIRED => 'Nicht erforderlich', EfzStatus::EFZ_STATUS_NOT_REQUIRED => 'Nicht erforderlich',
}, },

View File

@@ -12,6 +12,7 @@
ref="modalRef" ref="modalRef"
class="modal-content" class="modal-content"
tabindex="-1" tabindex="-1"
:style="{ width: width }"
> >
<div class="modal-header"> <div class="modal-header">
@@ -35,7 +36,8 @@ import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
const props = defineProps({ const props = defineProps({
show: Boolean, show: Boolean,
title: { type: String, default: 'Modal' } title: { type: String, default: 'Modal' },
width: { type: String, default: '1200px' },
}) })
const emit = defineEmits(['close']) const emit = defineEmits(['close'])

View File

@@ -30,7 +30,7 @@ return new class extends Migration {
$table->string('local_group'); $table->string('local_group');
$table->date('birthday'); $table->date('birthday');
$table->string('address_1'); $table->string('address_1');
$table->string('address_2'); $table->string('address_2')->nullable();
$table->string('postcode'); $table->string('postcode');
$table->string('city'); $table->string('city');
$table->string('email_1'); $table->string('email_1');

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hallo {{$name}}!</h1>
<p>
Vielen Dank für deine Anmeldung zur Veranstaltung "{{$eventTitle}}".
</p>
<p>
Wir konnten dein erweitertes polizeiliches Führungszeugnis nun zuordnen oder haben festgestellt, dass du kein eFZ einreichen musst.<br />
Somit ist deine Teilnahme an der Veranstaltung "{{$eventTitle}}" problemlos möglich.
</p>
<p>
@include('emails.subparts.faq')
</p><br /><br />
<p>
@include('emails.subparts.disclaimer')
</p>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hallo {{$name}}!</h1>
<p>
Vielen Dank für deine Anmeldung zur Veranstaltung "{{$eventTitle}}".
</p>
<p>
Bei einer Prüfung der Teilnehmenden hat sich gezeigt, dass du noch kein erweitertes polizeiliches Führungszeugnis einreicht hast.
</p>
<p style="border-style: solid; border-width: 2px; border-color: #ff0000; padding: 10px; font-weight: bold; color: #501e1e; background-color: #da7070">
Bitte reiche dein erweitertes Führungszeugnis bis zum {{$cocFinalDate}} ein, da deine Teilnahme andernfalls storniert wird.<br /><br />
Solltest du diese Frist nicht einhalten können, setze dich bitte mit der Aktionsleitung in Verbindung.
</p>
<p>
@include('emails.subparts.faq')
</p><br /><br />
<p>
@include('emails.subparts.disclaimer')
</p>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hallo {{$name}}!</h1>
<p>
Deine Abmeldung von der Veranstaltung "{{$eventTitle}}" wurde soeben ausgeführt.<br/>
</p>
<p>
Insofern du bereits Beiträge für die Teilnahme entrichtet hast, wird sich die Veranstaltungsleitung schnellstmöglich bei dir melden, um die Rückerstattung abzuklären.
</p>
<p>
Hast du keine Abmeldung gewünscht, und möchtest an der Veranstaltung teilnehmen, kontaktiere bitte umgehend die Veranstaltungsleitung.
</p>
<p>
@include('emails.subparts.faq')
</p><br /><br />
<p>
@include('emails.subparts.disclaimer')
</p>
</body>
</html>

View File

@@ -1,151 +1,90 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<body> <body>
<h1>Hallo {{$name}}!</h1> <h1>Hallo {{$name}}!</h1>
<p> <p>
Vielen Dank für deine Anmeldung zur Veranstaltung "{{$eventTitle}}".<br /> Vielen Dank für deine Anmeldung zur Veranstaltung "{{$eventTitle}}".<br/>
Wir haben folgende Daten zu deiner Teilnahme erfasst: Wir haben folgende Daten zu deiner Teilnahme erfasst:
</p>
<table cellpadding="0" cellspacing="0" border="0"
style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
<tr>
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Ankunft
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $arrival }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Abreise
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $departure }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563;">
Teilnahmegruppe
</td>
<td style="padding: 8px 12px;">
{{ $participationType }}
</td>
</tr>
</table>
@if ($paymentRequired)
<p>
Deine Teilnahme ist <strong>noch nicht bestätigt,</strong> da dsies erst nach vollständigem Zahlungseingang der
Fall ist.
Bitte zahle den Betrag in Höhe von <strong>{{$amount}}</strong> bis zum <strong>{{$paymentFinalDate}},</strong>
da andernfalls die Stornierung deiner Anmeldung erfolgen kann.
Um deine Anmeldung zu vervollständigen, überweise den Betrag bitte zugunsten folgender Bankverbindung:
</p>
<p>
@include('emails.subparts.payment')
</p>
@else
<p>
Deine Teilnahme wird entweder gefördert, sodass du nichts überweisen musst, oder die Beitragszahlung wird durch
deinen Stamm abgewickelt.<br/>
In diesem Fall wird sich dein Stamm bei dir melden, ob und in welcher Höhe ein Eigenanteil zu zahlen ist.
</p>
@endif
@switch ($efzStatus)
@case ('not_checked')
<p style="border-style: solid; border-width: 2px; border-color: #bdb053; padding: 10px; font-weight: bold; color: #501e1e; background-color: #f4e99e">
Dein erweitertes Führungszeugnis konnte nicht automatisch überprüft werden. Bitte kontaktiere die
Aktionsleitung, da deine Teilnahme nur mit gültigem eFZ möglich ist.
</p> </p>
@break
@case ('checked_invalid')
<table cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;"> <p style="border-style: solid; border-width: 2px; border-color: #ff0000; padding: 10px; font-weight: bold; color: #501e1e; background-color: #da7070">
<tr> Du hast noch kein erweitertes Führungszeugnis bereitgestellt, sodass deine Teilnahme nicht möglich ist.
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;"> Bitte reiche dein erweitertes Führungszeugnis bis zum {{$paymentFinalDate}} ein, da deine Teilnahme
Ankunft andernfalls storniert wird.<br/><br/>
</td> Solltest du diese Frist nicht einhalten können, setze dich bitte mit der Aktionsleitung in Verbindung.
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $arrival }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Abreise
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $departure }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563;">
Teilnahmegruppe
</td>
<td style="padding: 8px 12px;">
{{ $participationType }}
</td>
</tr>
</table>
@if ($paymentRequired)
<p>
Deine Teilnahme ist <strong>noch nicht bestätigt,</strong> da dsies erst nach vollständigem Zahlungseingang der Fall ist.
Bitte zahle den Betrag in Höhe von <strong>{{$amount}}</strong> bis zum <strong>{{$paymentFinalDate}},</strong> da andernfalls die Stornierung deiner Anmeldung erfolgen kann.
Um deine Anmeldung zu vervollständigen, überweise den Betrag bitte zugunsten folgender Bankverbindung:
</p>
<table cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
<tr>
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Kontoinhaber*in
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $accountOwner }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
IBAN
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; font-family: monospace;">
{{ $accountIban }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Verwendungszweck
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $paymentPurpose }}
</td>
</tr>
<tr>
<td style="padding: 10px 12px; font-weight: 700; color: #111827; border-top: 2px solid #d1d5db;">
Betrag
</td>
<td style="padding: 10px 12px; font-weight: 700; color: #111827; border-top: 2px solid #d1d5db;">
{{ $amount }}
</td>
</tr>
</table>
@php
$girocodeSrc = $message->embedData($girocodeBinary, 'girocode.png', 'image/png');
@endphp
<table style="margin: 20px;">
<tr style="vertical-align: top; background-color: #eaeaea; border-spacing: 0">
<td style="padding: 20px; text-align: center; font-weight: bold;">
<strong>
Bequem überweisen mit QR-Code
</strong><br /><br />
<img
style="padding-left: 20px;width: 150px; height: 150px;"
src="{{ $girocodeSrc }}"
alt="Giro-Code">
</td>
<td>
<p style="padding-left:10px; padding-right: 10px; text-align: left;">
<strong>Und so funktioniert es</strong><br />
<ul style="text-align: left; list-style: decimal; font-weight: normal">
<li style="padding-right: 10px;">Mache einen Screenshot des QR-Codes</li>
<li style="padding-right: 10px;">Öffne deine Banking-App und wähle den Menüpunkt (Foto-)Überweisung“.</li>
<li style="padding-right: 10px;">Wähle das gespeicherte Bild aus</li>
</ul>
</p>
</td>
</tr>
</table>
<p>
Bitte zahle immer mit dem vorgegeben Betreff, damit deine Zahlung möglichst einfach zugeordnet werden kann.<br />
Sollte die Zahlung innerhalb dieser Frist nicht oder nur teilweise möglich sein, kontaktiere bitte die Aktionsleitung, sodass wir eine gemeinsame Lösung finden können.
</p>
@else
<p>
Deine Teilnahme wird entweder gefördert, sodass du nichts überweisen musst, oder die Beitragszahlung wird durch deinen Stamm abgewickelt.<br />
In diesem Fall wird sich dein Stamm bei dir melden, ob und in welcher Höhe ein Eigenanteil zu zahlen ist.
</p>
@endif
@switch ($efzStatus)
@case ('not_checked')
<p style="border-style: solid; border-width: 2px; border-color: #bdb053; padding: 10px; font-weight: bold; color: #501e1e; background-color: #f4e99e">
Dein erweitertes Führungszeugnis konnte nicht automatisch überprüft werden. Bitte kontaktiere die Aktionsleitung, da deine Teilnahme nur mit gültigem eFZ möglich ist.
</p>
@break
@case ('checked_invalid')
<p style="border-style: solid; border-width: 2px; border-color: #ff0000; padding: 10px; font-weight: bold; color: #501e1e; background-color: #da7070">
Du hast noch kein erweitertes Führungszeugnis bereitgestellt, sodass deine Teilnahme nicht möglich ist.
Bitte reiche dein erweitertes Führungszeugnis bis zum {{$paymentFinalDate}} ein, da deine Teilnahme andernfalls storniert wird.<br /><br />
Solltest du diese Frist nicht einhalten können, setze dich bitte mit der Aktionsleitung in Verbindung.',
</p>
@break
@default
@endswitch
<p>
@include('emails.events.faq')
</p><br /><br />
<p>
@include('emails.events.disclaimer')
</p> </p>
</body> @break
@default
@endswitch
<p>
@include('emails.subparts.faq')
</p><br /><br />
<p>
@include('emails.subparts.disclaimer')
</p>
</body>
</html> </html>

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hallo {{$name}}!</h1>
<p>
Vielen Dank für deine Anmeldung zur Veranstaltung "{{$eventTitle}}".<br/>
Wir haben folgende Daten zu deiner Teilnahme erfasst:
</p>
<table cellpadding="0" cellspacing="0" border="0"
style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
<tr>
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Ankunft
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $arrival }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Abreise
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $departure }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563;">
Teilnahmegruppe
</td>
<td style="padding: 8px 12px;">
{{ $participationType }}
</td>
</tr>
</table>
<p>
Wir haben festgestellt, dass du für die Anmeldung einen zu hohen Betrag entrichtet hast.<br />
Der Anmeldebetrag für deine Teilnahme beträgt {{ $amount }} Euro, die geleistete Zahlung beläuft sich auf {{$amount_paid}}.<br />
Somit ergibt sich eine Überzahlung zu deinen Gunsten in Höhe von <strong>{{ $overpaidAmount }}.</strong>
</p>
<p>
Bitte teile uns mit, ob du den Betrag spenden möchtest, oder ob du eine Rückzahlung wünscht.<br />
Insofern du eine Rückzahlung wünschst, teile uns bitte auch deine Bankverbindung mit.
</p>
<p>
@include('emails.subparts.faq')
</p><br /><br />
<p>
@include('emails.subparts.disclaimer')
</p>
</body>
</html>

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hallo {{$name}}!</h1>
<p>
Vielen Dank für deine Anmeldung zur Veranstaltung "{{$eventTitle}}".<br/>
Wir haben folgende Daten zu deiner Teilnahme erfasst:
</p>
<table cellpadding="0" cellspacing="0" border="0"
style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
<tr>
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Ankunft
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $arrival }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Abreise
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $departure }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563;">
Teilnahmegruppe
</td>
<td style="padding: 8px 12px;">
{{ $participationType }}
</td>
</tr>
</table>
<p>
Wir haben den vollständigen Betrag für deine Teilnahme erhalten und deine Anmeldung ist nun bestätigt.
</p>
<p>
@include('emails.subparts.faq')
</p><br /><br />
<p>
@include('emails.subparts.disclaimer')
</p>
</body>
</html>

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hallo {{$name}}!</h1>
<p>
Vielen Dank für deine Anmeldung zur Veranstaltung "{{$eventTitle}}".<br/>
Wir haben folgende Daten zu deiner Teilnahme erfasst:
</p>
<table cellpadding="0" cellspacing="0" border="0"
style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
<tr>
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Ankunft
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $arrival }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Abreise
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $departure }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563;">
Teilnahmegruppe
</td>
<td style="padding: 8px 12px;">
{{ $participationType }}
</td>
</tr>
</table>
@if ($paymentRequired)
<p>
Wahrscheinlich hast du vergessen, deinen Teilnahmebeitrag zu überweisen, sodass deine Anmeldung leider <strong>noch nicht bestätigt,</strong> werden konnte.
Um deine Anmeldung zu bestätigen, zahle bitte den Betrag in Höhe von <strong>{{$amount}}</strong> bis zum <strong>{{$paymentFinalDate}},</strong>
da andernfalls die Stornierung deiner Anmeldung erfolgen kann.
Um deine Anmeldung zu vervollständigen, überweise den Betrag bitte zugunsten folgender Bankverbindung:
</p>
<p>
@include('emails.subparts.payment')
</p>
@endif
<p>
@include('emails.subparts.faq')
</p><br /><br />
<p>
@include('emails.subparts.disclaimer')
</p>
</body>
</html>

View File

@@ -0,0 +1,72 @@
<table cellpadding="0" cellspacing="0" border="0"
style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
<tr>
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Kontoinhaber*in
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $accountOwner }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
IBAN
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; font-family: monospace;">
{{ $accountIban }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Verwendungszweck
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{ $paymentPurpose }}
</td>
</tr>
<tr>
<td style="padding: 10px 12px; font-weight: 700; color: #111827; border-top: 2px solid #d1d5db;">
Betrag
</td>
<td style="padding: 10px 12px; font-weight: 700; color: #111827; border-top: 2px solid #d1d5db;">
{{ $amount }}
</td>
</tr>
</table>
@php
$girocodeSrc = $message->embedData($girocodeBinary, 'girocode.png', 'image/png');
@endphp
<table style="margin: 20px;">
<tr style="vertical-align: top; background-color: #eaeaea; border-spacing: 0">
<td style="padding: 20px; text-align: center; font-weight: bold;">
<strong>
Bequem überweisen mit QR-Code
</strong><br/><br/>
<img
style="padding-left: 20px;width: 150px; height: 150px;"
src="{{ $girocodeSrc }}"
alt="Giro-Code">
</td>
<td>
<p style="padding-left:10px; padding-right: 10px; text-align: left;">
<strong>Und so funktioniert es</strong><br/>
<ul style="text-align: left; list-style: decimal; font-weight: normal">
<li style="padding-right: 10px;">Mache einen Screenshot des QR-Codes</li>
<li style="padding-right: 10px;">Öffne deine Banking-App und wähle den Menüpunkt
(Foto-)Überweisung“.
</li>
<li style="padding-right: 10px;">Wähle das gespeicherte Bild aus</li>
</ul>
</p>
</td>
</tr>
</table>
<p>
Bitte zahle immer mit dem vorgegeben Betreff, damit deine Zahlung möglichst einfach zugeordnet werden kann.<br/>
Sollte die Zahlung innerhalb dieser Frist nicht oder nur teilweise möglich sein, kontaktiere bitte die
Aktionsleitung, sodass wir eine gemeinsame Lösung finden können.
</p>