Kurzanmeldungen

This commit is contained in:
2026-09-10 11:11:20 +02:00
parent 6301c342e4
commit 70a57d10f5
31 changed files with 1753 additions and 184 deletions
@@ -12,7 +12,8 @@ class CertificateOfConductionCheckCommand {
public function execute() : CertificateOfConductionCheckResponse {
$response = new CertificateOfConductionCheckResponse();
$localGroup = str_replace('Stamm ', '', $this->request->participant->localGroup()->first()->name);
// Der Stamm ist optional (die Kurzanmeldung erhebt ihn nicht, in der Verwaltung kann er entfernt werden).
$localGroup = str_replace('Stamm ', '', $this->request->participant->localGroup()->first()?->name ?? '');
$apiResponse = Http::acceptJson()
->asJson()
@@ -0,0 +1,142 @@
<?php
namespace App\Domains\Event\Actions\ShortSignUp;
use App\Domains\Event\Actions\SignUp\SignUpCommand;
use App\Domains\Event\Actions\SignUp\SignUpRequest;
use App\ValueObjects\Age;
/**
* Verkürzte Anmeldung: ergänzt die nicht erhobenen Pflichtangaben um Platzhalter und übergibt an die reguläre
* Anmelde-Action. Dadurch gelten für Kurzanmeldungen dieselben Regeln wie für jede andere Anmeldung
* (Transaktion, laufende Rechnungsnummer, eFZ-Vorbelegung, Snapshot der Teilnahmeoptionen).
*/
class ShortSignUpCommand {
/** Platzhalter für Angaben, die die Kurzanmeldung nicht erhebt. */
private const string PLACEHOLDER = 'Nicht erforderlich';
/**
* Die Postleitzahl bekommt bewusst keinen Text-Platzhalter: aus ihr wird in Teilnehmerliste und CSV-Export
* das Bundesland abgeleitet.
*/
private const string PLACEHOLDER_POSTCODE = '00000';
public function __construct(public ShortSignUpRequest $request) {
}
public function execute() : ShortSignUpResponse {
$response = new ShortSignUpResponse();
// Ohne konfigurierte Teilnahmegruppe gäbe es keinen gültigen `participation_type` -- sauber abbrechen,
// statt in einen Fremdschlüssel-Fehler zu laufen.
$participationType = $this->request->event->firstParticipationTypeSlug();
if ($participationType === null) {
$response->message = 'Für diese Veranstaltung ist noch keine Teilnahmegruppe hinterlegt.';
return $response;
}
$participantAge = new Age($this->request->birthday);
// Bei Minderjährigen ist die Kontaktperson Pflicht; der Client prüft das ebenfalls, verlassen wird sich
// darauf nicht.
if (!$participantAge->isfullAged()
&& ($this->request->contactPerson === null || $this->request->contactEmail === null)) {
$response->message = 'Für Minderjährige werden Name und E-Mail-Adresse einer Kontaktperson benötigt.';
return $response;
}
// Stamm: bei genau einem teilnehmenden Stamm wird dieser gesetzt, bei mehreren muss gewählt werden,
// ohne teilnehmende Stämme bleibt die Zuordnung leer.
$localGroups = $this->request->event->localGroups;
$localGroup = null;
if ($localGroups->count() === 1) {
$localGroup = $localGroups->first();
} elseif ($localGroups->count() > 1) {
$localGroup = $localGroups->firstWhere('id', $this->request->localGroupId);
if ($localGroup === null) {
$response->message = 'Bitte einen Stamm auswählen.';
return $response;
}
}
$eventResource = $this->request->event->toResource();
// Zeitraum, Gruppe und Beitragsstufe stehen bei der Kurzanmeldung fest -- es gibt nichts zu wählen.
$amount = $eventResource->calculateAmount(
$participationType,
'standard',
$this->request->event->start_date,
$this->request->event->end_date,
false
);
$signUpRequest = new SignUpRequest(
event: $this->request->event,
user_id: $this->request->user_id,
firstname: $this->request->firstname,
lastname: $this->request->lastname,
nickname: $this->request->nickname,
participationType: $participationType,
localGroup: $localGroup,
birthday: $this->request->birthday,
address_1: self::PLACEHOLDER,
address_2: null,
postcode: self::PLACEHOLDER_POSTCODE,
city: self::PLACEHOLDER,
email_1: $this->request->email,
phone_1: $this->request->phone,
email_2: $this->request->contactEmail,
phone_2: null,
contact_person: $this->request->contactPerson,
allergies: $this->request->allergies,
intolerances: $this->request->intolerances,
medications: null,
tetanus_vaccination: null,
// Kein Wert der Zuordnung -- der SignUpCommand fällt damit auf "Omnivor" zurück.
eating_habit: '',
swimming_permission: $this->resolveSwimmingPermission($participantAge),
first_aid_permission: $participantAge->isfullAged() ? '-1' : ($this->request->firstAidPermission ?? '-1'),
foto_socialmedia: $this->request->foto_socialmedia,
foto_print: $this->request->foto_print,
foto_webseite: $this->request->foto_webseite,
foto_partner: $this->request->foto_partner,
foto_intern: $this->request->foto_intern,
arrival: $this->request->event->start_date,
departure: $this->request->event->end_date,
arrival_eating: 1,
departure_eating: 2,
notes: null,
amount: $amount,
paymentMethod: $this->request->paymentMethod,
paymentOptions: $this->request->paymentOptions,
participationOptions: $this->request->participationOptions,
addonItems: [],
feeType: 'standard',
siblingReduction: false,
);
$signUpResponse = new SignUpCommand($signUpRequest)->execute();
$response->success = $signUpResponse->success;
$response->participant = $signUpResponse->participant;
$response->message = $signUpResponse->message;
return $response;
}
/**
* Bei Volljährigen und wenn die Veranstaltung die Badeerlaubnis nicht abfragt, entscheidet der SignUpCommand
* (volljährig => erteilt, nicht abgefragt => keine). Nur die getroffene Auswahl Minderjähriger wird
* durchgereicht.
*/
private function resolveSwimmingPermission(Age $participantAge) : ?string
{
if ($participantAge->isfullAged() || !$this->request->event->swimming_permission_required) {
return null;
}
return $this->request->swimmingPermission;
}
}
@@ -0,0 +1,47 @@
<?php
namespace App\Domains\Event\Actions\ShortSignUp;
use App\Models\Event;
use DateTime;
/**
* Eingabedaten der verkürzten Anmeldung -- ausschließlich das, was der Kurz-Wizard tatsächlich erhebt.
* Alle übrigen Pflichtangaben des Teilnehmers setzt der Command auf Platzhalter.
*/
class ShortSignUpRequest {
function __construct(
public Event $event,
public ?int $user_id,
public string $firstname,
public string $lastname,
public ?string $nickname,
public DateTime $birthday,
public string $email,
public string $phone,
/**
* Tenant-ID des Stamms. Nur gefüllt, wenn die Veranstaltung mehrere teilnehmende Stämme hat --
* bei genau einem wird dieser gesetzt, ohne zu fragen.
*/
public ?int $localGroupId,
/** Kontaktperson: nur bei Minderjährigen erhoben. */
public ?string $contactPerson,
public ?string $contactEmail,
public ?string $allergies,
public ?string $intolerances,
/** Slug aus `first_aid_permissions`; nur bei Minderjährigen erhoben. */
public ?string $firstAidPermission,
/** Slug aus `swimming_permissions`; nur bei Minderjährigen und nur wenn die Veranstaltung sie abfragt. */
public ?string $swimmingPermission,
public bool $foto_socialmedia,
public bool $foto_print,
public bool $foto_webseite,
public bool $foto_partner,
public bool $foto_intern,
public ?string $paymentMethod,
public array $paymentOptions = [],
/** Antworten auf die Teilnahmeoptionen: [ question_key => value ]. */
public array $participationOptions = [],
) {
}
}
@@ -0,0 +1,17 @@
<?php
namespace App\Domains\Event\Actions\ShortSignUp;
use App\Models\EventParticipant;
class ShortSignUpResponse {
public bool $success;
public ?EventParticipant $participant;
public ?string $message = null;
public function __construct() {
$this->success = false;
$this->participant = null;
}
}
@@ -4,6 +4,7 @@ namespace App\Domains\Event\Actions\SignUp;
use App\Enumerations\EatingHabit;
use App\Enumerations\EfzStatus;
use App\Enumerations\SwimmingPermission;
use App\EventPaymentModules\EventPaymentModuleRegistry;
use App\ValueObjects\Age;
use Illuminate\Support\Facades\DB;
@@ -59,7 +60,7 @@ class SignUpCommand {
'participation_type' => $this->request->participationType,
'fee_type' => $this->request->feeType,
'sibling_reduction' => $this->request->siblingReduction,
'local_group' => $this->request->localGroup->slug,
'local_group' => $this->request->localGroup?->slug,
'birthday' => $this->request->birthday,
'address_1' => $this->request->address_1,
'address_2' => $this->request->address_2,
@@ -75,9 +76,7 @@ class SignUpCommand {
'medications' => $this->request->medications,
'tetanus_vaccination' => $this->request->tetanus_vaccination,
'eating_habit' => $eatingHabit,
'swimming_permission' => ($participantAge->isfullAged() || $this->request->swimming_permission === '-1')
? 'SWIMMING_PERMISSION_ALLOWED'
: $this->request->swimming_permission,
'swimming_permission' => $this->resolveSwimmingPermission($participantAge),
'first_aid_permission' => ($participantAge->isfullAged() || $this->request->first_aid_permission === '-1')
? 'FIRST_AID_PERMISSION_ALLOWED'
: $this->request->first_aid_permission,
@@ -109,6 +108,26 @@ class SignUpCommand {
return $response;
}
/**
* Badeerlaubnis auflösen. Volljährige brauchen keine Erlaubnis. Für Minderjährige gilt die getroffene Auswahl;
* fehlt sie -- weil die Veranstaltung die Badeerlaubnis gar nicht abfragt oder keine Wahl getroffen wurde --,
* wird bewusst "Keine" hinterlegt und nicht etwa stillschweigend eine Erlaubnis angenommen.
*/
private function resolveSwimmingPermission(Age $participantAge): string
{
if ($participantAge->isfullAged()) {
return SwimmingPermission::SWIMMING_PERMISSION_ALLOWED;
}
if (!$this->request->event->swimming_permission_required
|| $this->request->swimming_permission === null
|| $this->request->swimming_permission === '-1') {
return SwimmingPermission::SWIMMING_PERMISSION_DENIED;
}
return $this->request->swimming_permission;
}
/**
* Nächste laufende Nummer des Teilis innerhalb der Veranstaltung -- der letzte Block der
* Rechnungsnummer. Die Event-Zeile wird gesperrt, damit gleichzeitige Anmeldungen derselben
@@ -15,7 +15,8 @@ class SignUpRequest {
public string $lastname,
public ?string $nickname,
public string $participationType,
public Tenant $localGroup,
/** Stamm des Teilnehmers; in der Kurzanmeldung nicht erhoben und deshalb optional. */
public ?Tenant $localGroup,
public DateTime $birthday,
public string $address_1,
public ?string $address_2,
@@ -22,6 +22,8 @@ class UpdateEventCommand {
$this->request->event->support_flat = $this->request->flatSupport;
$this->request->event->send_weekly_report = $this->request->sendWeeklyReports;
$this->request->event->registration_allowed = $this->request->registrationAllowed;
$this->request->event->short_registration = $this->request->shortRegistration;
$this->request->event->swimming_permission_required = $this->request->swimmingPermissionRequired;
$this->request->event->save();
$this->request->event->resetAllowedEatingHabits();
@@ -17,12 +17,14 @@ class UpdateEventRequest {
public int $alcoholicsAge;
public bool $sendWeeklyReports;
public bool $registrationAllowed;
public bool $shortRegistration;
public bool $swimmingPermissionRequired;
public Amount $flatSupport;
public Amount $supportPerPerson;
public array $contributingLocalGroups;
public array $eatingHabits;
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits)
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits, bool $shortRegistration = false, bool $swimmingPermissionRequired = true)
{
$this->event = $event;
$this->eventName = $eventName;
@@ -34,6 +36,8 @@ class UpdateEventRequest {
$this->alcoholicsAge = $alcoholicsAge;
$this->sendWeeklyReports = $sendWeeklyReports;
$this->registrationAllowed = $registrationAllowed;
$this->shortRegistration = $shortRegistration;
$this->swimmingPermissionRequired = $swimmingPermissionRequired;
$this->flatSupport = $flatSupport;
$this->supportPerPerson = $supportPerPerson;
$this->contributingLocalGroups = $contributingLocalGroups;
@@ -31,7 +31,8 @@ class UpdateParticipantCommand {
$p->address_2 = $this->request->address_2;
$p->postcode = $this->request->postcode;
$p->city = $this->request->city;
$p->local_group = $this->request->localgroup;
// Leerer String würde als Fremdschlüssel scheitern -- kein Stamm heißt null.
$p->local_group = $this->request->localgroup ?: null;
$p->birthday = DateTime::createFromFormat('Y-m-d', $this->request->birthday);
$p->email_1 = $this->request->email_1;
$p->phone_1 = $this->request->phone_1;
@@ -14,7 +14,8 @@ class UpdateParticipantRequest {
public ?string $address_2,
public string $postcode,
public string $city,
public string $localgroup,
/** Stamm-Slug; leer/null bei Anmeldungen, die keinen Stamm erfasst haben (Kurzanmeldung). */
public ?string $localgroup,
public string $birthday,
public string $email_1,
public string $phone_1,