Kurzanmeldungen
This commit is contained in:
+2
-1
@@ -12,7 +12,8 @@ class CertificateOfConductionCheckCommand {
|
|||||||
public function execute() : CertificateOfConductionCheckResponse {
|
public function execute() : CertificateOfConductionCheckResponse {
|
||||||
$response = new 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()
|
$apiResponse = Http::acceptJson()
|
||||||
->asJson()
|
->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\EatingHabit;
|
||||||
use App\Enumerations\EfzStatus;
|
use App\Enumerations\EfzStatus;
|
||||||
|
use App\Enumerations\SwimmingPermission;
|
||||||
use App\EventPaymentModules\EventPaymentModuleRegistry;
|
use App\EventPaymentModules\EventPaymentModuleRegistry;
|
||||||
use App\ValueObjects\Age;
|
use App\ValueObjects\Age;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -59,7 +60,7 @@ class SignUpCommand {
|
|||||||
'participation_type' => $this->request->participationType,
|
'participation_type' => $this->request->participationType,
|
||||||
'fee_type' => $this->request->feeType,
|
'fee_type' => $this->request->feeType,
|
||||||
'sibling_reduction' => $this->request->siblingReduction,
|
'sibling_reduction' => $this->request->siblingReduction,
|
||||||
'local_group' => $this->request->localGroup->slug,
|
'local_group' => $this->request->localGroup?->slug,
|
||||||
'birthday' => $this->request->birthday,
|
'birthday' => $this->request->birthday,
|
||||||
'address_1' => $this->request->address_1,
|
'address_1' => $this->request->address_1,
|
||||||
'address_2' => $this->request->address_2,
|
'address_2' => $this->request->address_2,
|
||||||
@@ -75,9 +76,7 @@ class SignUpCommand {
|
|||||||
'medications' => $this->request->medications,
|
'medications' => $this->request->medications,
|
||||||
'tetanus_vaccination' => $this->request->tetanus_vaccination,
|
'tetanus_vaccination' => $this->request->tetanus_vaccination,
|
||||||
'eating_habit' => $eatingHabit,
|
'eating_habit' => $eatingHabit,
|
||||||
'swimming_permission' => ($participantAge->isfullAged() || $this->request->swimming_permission === '-1')
|
'swimming_permission' => $this->resolveSwimmingPermission($participantAge),
|
||||||
? 'SWIMMING_PERMISSION_ALLOWED'
|
|
||||||
: $this->request->swimming_permission,
|
|
||||||
'first_aid_permission' => ($participantAge->isfullAged() || $this->request->first_aid_permission === '-1')
|
'first_aid_permission' => ($participantAge->isfullAged() || $this->request->first_aid_permission === '-1')
|
||||||
? 'FIRST_AID_PERMISSION_ALLOWED'
|
? 'FIRST_AID_PERMISSION_ALLOWED'
|
||||||
: $this->request->first_aid_permission,
|
: $this->request->first_aid_permission,
|
||||||
@@ -109,6 +108,26 @@ class SignUpCommand {
|
|||||||
return $response;
|
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
|
* Nächste laufende Nummer des Teilis innerhalb der Veranstaltung -- der letzte Block der
|
||||||
* Rechnungsnummer. Die Event-Zeile wird gesperrt, damit gleichzeitige Anmeldungen derselben
|
* Rechnungsnummer. Die Event-Zeile wird gesperrt, damit gleichzeitige Anmeldungen derselben
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class SignUpRequest {
|
|||||||
public string $lastname,
|
public string $lastname,
|
||||||
public ?string $nickname,
|
public ?string $nickname,
|
||||||
public string $participationType,
|
public string $participationType,
|
||||||
public Tenant $localGroup,
|
/** Stamm des Teilnehmers; in der Kurzanmeldung nicht erhoben und deshalb optional. */
|
||||||
|
public ?Tenant $localGroup,
|
||||||
public DateTime $birthday,
|
public DateTime $birthday,
|
||||||
public string $address_1,
|
public string $address_1,
|
||||||
public ?string $address_2,
|
public ?string $address_2,
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class UpdateEventCommand {
|
|||||||
$this->request->event->support_flat = $this->request->flatSupport;
|
$this->request->event->support_flat = $this->request->flatSupport;
|
||||||
$this->request->event->send_weekly_report = $this->request->sendWeeklyReports;
|
$this->request->event->send_weekly_report = $this->request->sendWeeklyReports;
|
||||||
$this->request->event->registration_allowed = $this->request->registrationAllowed;
|
$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->save();
|
||||||
|
|
||||||
$this->request->event->resetAllowedEatingHabits();
|
$this->request->event->resetAllowedEatingHabits();
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ class UpdateEventRequest {
|
|||||||
public int $alcoholicsAge;
|
public int $alcoholicsAge;
|
||||||
public bool $sendWeeklyReports;
|
public bool $sendWeeklyReports;
|
||||||
public bool $registrationAllowed;
|
public bool $registrationAllowed;
|
||||||
|
public bool $shortRegistration;
|
||||||
|
public bool $swimmingPermissionRequired;
|
||||||
public Amount $flatSupport;
|
public Amount $flatSupport;
|
||||||
public Amount $supportPerPerson;
|
public Amount $supportPerPerson;
|
||||||
public array $contributingLocalGroups;
|
public array $contributingLocalGroups;
|
||||||
public array $eatingHabits;
|
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->event = $event;
|
||||||
$this->eventName = $eventName;
|
$this->eventName = $eventName;
|
||||||
@@ -34,6 +36,8 @@ class UpdateEventRequest {
|
|||||||
$this->alcoholicsAge = $alcoholicsAge;
|
$this->alcoholicsAge = $alcoholicsAge;
|
||||||
$this->sendWeeklyReports = $sendWeeklyReports;
|
$this->sendWeeklyReports = $sendWeeklyReports;
|
||||||
$this->registrationAllowed = $registrationAllowed;
|
$this->registrationAllowed = $registrationAllowed;
|
||||||
|
$this->shortRegistration = $shortRegistration;
|
||||||
|
$this->swimmingPermissionRequired = $swimmingPermissionRequired;
|
||||||
$this->flatSupport = $flatSupport;
|
$this->flatSupport = $flatSupport;
|
||||||
$this->supportPerPerson = $supportPerPerson;
|
$this->supportPerPerson = $supportPerPerson;
|
||||||
$this->contributingLocalGroups = $contributingLocalGroups;
|
$this->contributingLocalGroups = $contributingLocalGroups;
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ class UpdateParticipantCommand {
|
|||||||
$p->address_2 = $this->request->address_2;
|
$p->address_2 = $this->request->address_2;
|
||||||
$p->postcode = $this->request->postcode;
|
$p->postcode = $this->request->postcode;
|
||||||
$p->city = $this->request->city;
|
$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->birthday = DateTime::createFromFormat('Y-m-d', $this->request->birthday);
|
||||||
$p->email_1 = $this->request->email_1;
|
$p->email_1 = $this->request->email_1;
|
||||||
$p->phone_1 = $this->request->phone_1;
|
$p->phone_1 = $this->request->phone_1;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class UpdateParticipantRequest {
|
|||||||
public ?string $address_2,
|
public ?string $address_2,
|
||||||
public string $postcode,
|
public string $postcode,
|
||||||
public string $city,
|
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 $birthday,
|
||||||
public string $email_1,
|
public string $email_1,
|
||||||
public string $phone_1,
|
public string $phone_1,
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ class DetailsController extends CommonController {
|
|||||||
$flatSupport,
|
$flatSupport,
|
||||||
$supportPerPerson,
|
$supportPerPerson,
|
||||||
$contributinLocalGroups,
|
$contributinLocalGroups,
|
||||||
$eatingHabits
|
$eatingHabits,
|
||||||
|
(bool)$request->input('shortRegistration', false),
|
||||||
|
(bool)$request->input('swimmingPermissionRequired', true),
|
||||||
);
|
);
|
||||||
|
|
||||||
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beitrag einer Kurzanmeldung. Teilnahmegruppe, Beitragsstufe und Zeitraum stehen fest, deshalb braucht der
|
||||||
|
* Endpunkt keine Eingaben -- und der Betrag lässt sich vom Client nicht beeinflussen.
|
||||||
|
*
|
||||||
|
* `amountValue` steuert im Frontend, ob der Schritt "Zahlungsart" gezeigt wird.
|
||||||
|
*/
|
||||||
|
class ShortCalculateAmountController extends CommonController {
|
||||||
|
public function __invoke(int $eventId) : JsonResponse {
|
||||||
|
$event = $this->events->getById($eventId, false);
|
||||||
|
|
||||||
|
$participationType = $event->firstParticipationTypeSlug();
|
||||||
|
|
||||||
|
$amount = $participationType === null
|
||||||
|
? new Amount(0, 'Euro')
|
||||||
|
: $event->toResource()->calculateAmount(
|
||||||
|
$participationType,
|
||||||
|
'standard',
|
||||||
|
$event->start_date,
|
||||||
|
$event->end_date,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'amount' => $amount->toString(),
|
||||||
|
'amountValue' => $amount->getAmount(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckCommand;
|
||||||
|
use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckRequest;
|
||||||
|
use App\Domains\Event\Actions\ShortSignUp\ShortSignUpCommand;
|
||||||
|
use App\Domains\Event\Actions\ShortSignUp\ShortSignUpRequest;
|
||||||
|
use App\Enumerations\FirstAidPermission;
|
||||||
|
use App\Enumerations\SwimmingPermission;
|
||||||
|
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
||||||
|
use App\Providers\DoubleCheckEventRegistrationProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use DateTime;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nimmt die verkürzte Anmeldung entgegen. Öffentlich erreichbar, deshalb werden die Eingaben hier -- anders als
|
||||||
|
* im langen Prozess -- validiert, bevor sie die Action erreichen.
|
||||||
|
*/
|
||||||
|
class ShortSignupController extends CommonController {
|
||||||
|
public function __invoke(int $eventId, Request $request) : JsonResponse {
|
||||||
|
$event = $this->events->getById($eventId, false);
|
||||||
|
|
||||||
|
if (!$event->registration_allowed || new DateTime() > $event->start_date) {
|
||||||
|
return response()->json(['status' => 'closed'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die Kurzanmeldung darf nicht als Abkürzung um den langen Prozess herum benutzt werden.
|
||||||
|
if (!$event->short_registration) {
|
||||||
|
return response()->json(['status' => 'closed'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'firstname' => 'required|string|max:255',
|
||||||
|
'lastname' => 'required|string|max:255',
|
||||||
|
'nickname' => 'nullable|string|max:255',
|
||||||
|
'birthday' => 'required|date_format:Y-m-d|before:today',
|
||||||
|
'email' => 'required|email|max:255',
|
||||||
|
'phone' => 'required|string|max:255',
|
||||||
|
// Gegen die teilnehmenden Stämme prüft der Command -- hier reicht die Typprüfung.
|
||||||
|
'localGroup' => 'nullable|integer',
|
||||||
|
'contactPerson' => 'nullable|string|max:255',
|
||||||
|
'contactEmail' => 'nullable|email|max:255',
|
||||||
|
'allergies' => 'nullable|string|max:255',
|
||||||
|
'intolerances' => 'nullable|string|max:255',
|
||||||
|
'firstAidPermission' => ['nullable', 'string', 'in:' . implode(',', FirstAidPermission::query()->pluck('slug')->toArray())],
|
||||||
|
'swimmingPermission' => ['nullable', 'string', 'in:' . implode(',', SwimmingPermission::query()->pluck('slug')->toArray())],
|
||||||
|
'foto' => 'array',
|
||||||
|
'participationOptions' => 'array',
|
||||||
|
'paymentMethod' => 'nullable|string',
|
||||||
|
'paymentOptions' => 'array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json(['status' => 'error', 'message' => $validator->errors()->first()], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$birthday = DateTime::createFromFormat('Y-m-d', $request->input('birthday'));
|
||||||
|
|
||||||
|
$doubleCheck = new DoubleCheckEventRegistrationProvider(
|
||||||
|
$event,
|
||||||
|
$request->input('firstname'),
|
||||||
|
$request->input('lastname'),
|
||||||
|
$request->input('email'),
|
||||||
|
$birthday
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($doubleCheck->isRegistered()) {
|
||||||
|
return response()->json(['status' => 'exists']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = (array)$request->input('foto', []);
|
||||||
|
|
||||||
|
$shortSignUpRequest = new ShortSignUpRequest(
|
||||||
|
event: $event,
|
||||||
|
user_id: currentUser()?->id,
|
||||||
|
firstname: $request->input('firstname'),
|
||||||
|
lastname: $request->input('lastname'),
|
||||||
|
nickname: $request->input('nickname'),
|
||||||
|
birthday: $birthday,
|
||||||
|
email: $request->input('email'),
|
||||||
|
phone: $request->input('phone'),
|
||||||
|
localGroupId: $request->input('localGroup') !== null ? (int)$request->input('localGroup') : null,
|
||||||
|
contactPerson: $request->input('contactPerson') ?: null,
|
||||||
|
contactEmail: $request->input('contactEmail') ?: null,
|
||||||
|
allergies: $request->input('allergies') ?: null,
|
||||||
|
intolerances: $request->input('intolerances') ?: null,
|
||||||
|
firstAidPermission: $request->input('firstAidPermission') ?: null,
|
||||||
|
swimmingPermission: $request->input('swimmingPermission') ?: null,
|
||||||
|
foto_socialmedia: (bool)($foto['socialmedia'] ?? false),
|
||||||
|
foto_print: (bool)($foto['print'] ?? false),
|
||||||
|
foto_webseite: (bool)($foto['webseite'] ?? false),
|
||||||
|
foto_partner: (bool)($foto['partner'] ?? false),
|
||||||
|
foto_intern: (bool)($foto['intern'] ?? false),
|
||||||
|
paymentMethod: $request->input('paymentMethod') ?: null,
|
||||||
|
paymentOptions: (array)$request->input('paymentOptions', []),
|
||||||
|
participationOptions: (array)$request->input('participationOptions', []),
|
||||||
|
);
|
||||||
|
|
||||||
|
$shortSignUpResponse = new ShortSignUpCommand($shortSignUpRequest)->execute();
|
||||||
|
|
||||||
|
if (!$shortSignUpResponse->success) {
|
||||||
|
return response()->json(['status' => 'error', 'message' => $shortSignUpResponse->message], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$participant = $shortSignUpResponse->participant;
|
||||||
|
|
||||||
|
$cocResponse = new CertificateOfConductionCheckCommand(
|
||||||
|
new CertificateOfConductionCheckRequest($participant)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
$participant->efz_status = $cocResponse->status;
|
||||||
|
$participant->save();
|
||||||
|
|
||||||
|
Mail::to($participant->email_1)->send(new EventSignUpSuccessfullMail(participant: $participant));
|
||||||
|
|
||||||
|
if ($participant->email_2 !== null) {
|
||||||
|
Mail::to($participant->email_2)->send(new EventSignUpSuccessfullMail(participant: $participant));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'participant' => $participant->toResource()->toArray($request),
|
||||||
|
'status' => 'success',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ use App\Domains\Event\Controllers\ParticipantReSignOnController;
|
|||||||
use App\Domains\Event\Controllers\ParticipantSignOffController;
|
use App\Domains\Event\Controllers\ParticipantSignOffController;
|
||||||
use App\Domains\Event\Controllers\ParticipantUpdateController;
|
use App\Domains\Event\Controllers\ParticipantUpdateController;
|
||||||
use App\Domains\Event\Controllers\PaymentReminderController;
|
use App\Domains\Event\Controllers\PaymentReminderController;
|
||||||
|
use App\Domains\Event\Controllers\ShortCalculateAmountController;
|
||||||
|
use App\Domains\Event\Controllers\ShortSignupController;
|
||||||
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;
|
||||||
@@ -27,6 +29,9 @@ Route::prefix('api/v1')
|
|||||||
Route::post('{eventId}/calculate-amount', [SignupController::class, 'calculateAmount']);
|
Route::post('{eventId}/calculate-amount', [SignupController::class, 'calculateAmount']);
|
||||||
Route::post('{eventId}/signup', [SignupController::class, 'signUp']);
|
Route::post('{eventId}/signup', [SignupController::class, 'signUp']);
|
||||||
|
|
||||||
|
Route::post('{eventId}/short-calculate-amount', ShortCalculateAmountController::class);
|
||||||
|
Route::post('{eventId}/short-signup', ShortSignupController::class);
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
Route::post('/create', [CreateController::class, 'doCreate']);
|
Route::post('/create', [CreateController::class, 'doCreate']);
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ const emit = defineEmits(['close'])
|
|||||||
eatingHabits: eatingHabits.value,
|
eatingHabits: eatingHabits.value,
|
||||||
sendWeeklyReports: props.event.sendWeeklyReports,
|
sendWeeklyReports: props.event.sendWeeklyReports,
|
||||||
registrationAllowed: props.event.registrationAllowed,
|
registrationAllowed: props.event.registrationAllowed,
|
||||||
|
shortRegistration: props.event.shortRegistration,
|
||||||
|
swimmingPermissionRequired: props.event.swimmingPermissionRequired,
|
||||||
flatSupport: props.event.flatSupportEdit,
|
flatSupport: props.event.flatSupportEdit,
|
||||||
supportPerson: props.event.supportPersonIndex,
|
supportPerson: props.event.supportPersonIndex,
|
||||||
})
|
})
|
||||||
@@ -64,6 +66,8 @@ const emit = defineEmits(['close'])
|
|||||||
alcoholicsAge: formData.alcoholicsAge,
|
alcoholicsAge: formData.alcoholicsAge,
|
||||||
sendWeeklyReports: formData.sendWeeklyReports,
|
sendWeeklyReports: formData.sendWeeklyReports,
|
||||||
registrationAllowed: formData.registrationAllowed,
|
registrationAllowed: formData.registrationAllowed,
|
||||||
|
shortRegistration: formData.shortRegistration,
|
||||||
|
swimmingPermissionRequired: formData.swimmingPermissionRequired,
|
||||||
flatSupport: formData.flatSupport,
|
flatSupport: formData.flatSupport,
|
||||||
supportPerson: formData.supportPerson,
|
supportPerson: formData.supportPerson,
|
||||||
contributingLocalGroups: contributingLocalGroups.value,
|
contributingLocalGroups: contributingLocalGroups.value,
|
||||||
@@ -172,6 +176,27 @@ const emit = defineEmits(['close'])
|
|||||||
<label for="registrationAllowed">Veranstaltung ist für Anmeldungen geöffnet</label>
|
<label for="registrationAllowed">Veranstaltung ist für Anmeldungen geöffnet</label>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="checkbox" v-model="formData.shortRegistration" id="shortRegistration" />
|
||||||
|
<label for="shortRegistration">Verkürzte Anmeldung verwenden</label><br />
|
||||||
|
<span style="font-size: 0.8rem; color: #6b7280;">
|
||||||
|
Fragt nur die nötigsten Daten ab (Name, Kontakt, Allergien, Foto-Erlaubnis).
|
||||||
|
Nicht erhobene Angaben wie Anschrift und An-/Abreise werden automatisch gefüllt.
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="checkbox" v-model="formData.swimmingPermissionRequired" id="swimmingPermissionRequired" />
|
||||||
|
<label for="swimmingPermissionRequired">Badeerlaubnis abfragen</label><br />
|
||||||
|
<span style="font-size: 0.8rem; color: #6b7280;">
|
||||||
|
Ist die Abfrage deaktiviert, wird für Minderjährige „Keine Badeerlaubnis" hinterlegt.
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ function saveParticipant() {
|
|||||||
<td>
|
<td>
|
||||||
<span v-if="!staticProps.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 value="">Kein Stamm</option>
|
||||||
<option v-for="group in staticProps.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>
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {useShortSignupForm} from './composables/useShortSignupForm.js'
|
||||||
|
import {
|
||||||
|
SHORT_STEP_PERSON,
|
||||||
|
SHORT_STEP_PARTICIPATION_OPTIONS,
|
||||||
|
SHORT_STEP_PHOTO,
|
||||||
|
SHORT_STEP_PAYMENT,
|
||||||
|
SHORT_STEP_SUMMARY,
|
||||||
|
stepVisible,
|
||||||
|
} from './composables/shortStepFlow.js'
|
||||||
|
import ShortStepPerson from './steps/ShortStepPerson.vue'
|
||||||
|
import ShortStepSummary from './steps/ShortStepSummary.vue'
|
||||||
|
import StepParticipationOptions from '../SignUpForm/steps/StepParticipationOptions.vue'
|
||||||
|
import StepPhotoPermissions from '../SignUpForm/steps/StepPhotoPermissions.vue'
|
||||||
|
import StepPaymentMethod from '../SignUpForm/steps/StepPaymentMethod.vue'
|
||||||
|
import SubmitSuccess from '../SignUpForm/after-submit/SubmitSuccess.vue'
|
||||||
|
import SubmitAlreadyExists from '../SignUpForm/after-submit/SubmitAlreadyExists.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
event: Object,
|
||||||
|
participantData: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentStep, goToStep, goNext, goBack, formData, isMinor, localGroups, needsLocalGroup,
|
||||||
|
submit, submitting, submitResult, submitError,
|
||||||
|
summaryLoading, summaryAmount, summaryAmountValue,
|
||||||
|
} = useShortSignupForm(props.event, props.participantData)
|
||||||
|
|
||||||
|
// Die aus dem langen Prozess übernommenen Schritte emittieren ihre Zielnummer aus jenem Flow mit. Hier zählt
|
||||||
|
// nur, dass "weiter" bzw. "zurück" gedrückt wurde -- die Reihenfolge kennt dieser Host.
|
||||||
|
const onNext = () => goNext()
|
||||||
|
const onBack = () => goBack()
|
||||||
|
|
||||||
|
const allSteps = [
|
||||||
|
{step: SHORT_STEP_PERSON, label: 'Person'},
|
||||||
|
{step: SHORT_STEP_PARTICIPATION_OPTIONS, label: 'Teilnahmeoptionen'},
|
||||||
|
{step: SHORT_STEP_PHOTO, label: 'Fotoerlaubnis'},
|
||||||
|
{step: SHORT_STEP_PAYMENT, label: 'Zahlungsart'},
|
||||||
|
{step: SHORT_STEP_SUMMARY, label: 'Zusammenfassung'},
|
||||||
|
]
|
||||||
|
|
||||||
|
// Nur die tatsächlich vorkommenden Schritte anzeigen, sonst zeigt die Leiste Schritte, die nie erscheinen.
|
||||||
|
const steps = computed(() =>
|
||||||
|
allSteps.filter(s => stepVisible(props.event, s.step, summaryAmountValue.value))
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentIndex = computed(() => steps.value.findIndex(s => s.step === currentStep.value))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- Nach Submit -->
|
||||||
|
<SubmitSuccess
|
||||||
|
v-if="submitResult?.status === 'success'"
|
||||||
|
:participant="submitResult?.participant"
|
||||||
|
:event="event"
|
||||||
|
/>
|
||||||
|
<SubmitAlreadyExists v-else-if="submitResult?.status === 'exists'" :event="event" />
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<!-- Fortschrittsleiste -->
|
||||||
|
<div class="signup-progress">
|
||||||
|
<div class="signup-progress-pills">
|
||||||
|
<template v-for="(s, index) in steps" :key="s.step">
|
||||||
|
<div v-if="index > 0" class="signup-progress-separator"></div>
|
||||||
|
<div
|
||||||
|
class="signup-pill"
|
||||||
|
:class="{
|
||||||
|
'signup-pill--active': currentStep === s.step,
|
||||||
|
'signup-pill--done': currentStep > s.step,
|
||||||
|
'signup-pill--upcoming': currentStep < s.step,
|
||||||
|
}"
|
||||||
|
@click="currentStep > s.step ? goToStep(s.step) : null"
|
||||||
|
>
|
||||||
|
<span v-if="currentStep > s.step" class="signup-pill__check">✓</span>
|
||||||
|
{{ s.label }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="signup-progress-bar">
|
||||||
|
<div
|
||||||
|
class="signup-progress-bar__fill"
|
||||||
|
:style="{ width: (steps.length > 1 ? (currentIndex / (steps.length - 1)) * 100 : 100) + '%' }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="signup-progress-mobile">
|
||||||
|
Schritt {{ currentIndex + 1 }} von {{ steps.length }}:
|
||||||
|
<strong>{{ steps.find(s => s.step === currentStep)?.label }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Steps -->
|
||||||
|
<form @submit.prevent="submit">
|
||||||
|
<ShortStepPerson
|
||||||
|
v-if="currentStep === SHORT_STEP_PERSON"
|
||||||
|
:formData="formData" :event="event" :isMinor="isMinor"
|
||||||
|
:localGroups="localGroups" :needsLocalGroup="needsLocalGroup"
|
||||||
|
@next="onNext"
|
||||||
|
/>
|
||||||
|
<StepParticipationOptions
|
||||||
|
v-if="currentStep === SHORT_STEP_PARTICIPATION_OPTIONS"
|
||||||
|
:formData="formData" :event="event"
|
||||||
|
@next="onNext" @back="onBack"
|
||||||
|
/>
|
||||||
|
<StepPhotoPermissions
|
||||||
|
v-if="currentStep === SHORT_STEP_PHOTO"
|
||||||
|
:formData="formData" :event="event"
|
||||||
|
@next="onNext" @back="onBack"
|
||||||
|
/>
|
||||||
|
<StepPaymentMethod
|
||||||
|
v-if="currentStep === SHORT_STEP_PAYMENT"
|
||||||
|
:formData="formData" :event="event"
|
||||||
|
@next="onNext" @back="onBack"
|
||||||
|
/>
|
||||||
|
<ShortStepSummary
|
||||||
|
v-if="currentStep === SHORT_STEP_SUMMARY"
|
||||||
|
:formData="formData"
|
||||||
|
:event="event"
|
||||||
|
:isMinor="isMinor"
|
||||||
|
:summaryAmount="summaryAmount"
|
||||||
|
:summaryAmountValue="summaryAmountValue"
|
||||||
|
:summaryLoading="summaryLoading"
|
||||||
|
:submitting="submitting"
|
||||||
|
:submitError="submitError"
|
||||||
|
@back="onBack"
|
||||||
|
@submit="submit"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Unscoped und mit dem langen Anmeldeprozess geteilt. -->
|
||||||
|
<style src="../SignUpForm/signupForm.css"></style>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Schrittreihenfolge der Kurzanmeldung. Zwei Schritte sind optional: die Teilnahmeoptionen entfallen, wenn für
|
||||||
|
// die Veranstaltung keine definiert sind, die Zahlungsart entfällt bei Betrag 0 oder wenn es ohnehin nur eine
|
||||||
|
// Zahlungsart gibt (die wird dann vorausgewählt).
|
||||||
|
export const SHORT_STEP_PERSON = 1
|
||||||
|
export const SHORT_STEP_PARTICIPATION_OPTIONS = 2
|
||||||
|
export const SHORT_STEP_PHOTO = 3
|
||||||
|
export const SHORT_STEP_PAYMENT = 4
|
||||||
|
export const SHORT_STEP_SUMMARY = 5
|
||||||
|
|
||||||
|
export function stepVisible(event, step, amountValue) {
|
||||||
|
if (step === SHORT_STEP_PARTICIPATION_OPTIONS) {
|
||||||
|
return (event.participationOptions?.length ?? 0) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === SHORT_STEP_PAYMENT) {
|
||||||
|
const activeMethods = (event.paymentMethods ?? []).filter(m => m.active)
|
||||||
|
return amountValue > 0 && activeMethods.length > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextVisibleFrom(event, step, amountValue) {
|
||||||
|
let candidate = step + 1
|
||||||
|
while (candidate < SHORT_STEP_SUMMARY && !stepVisible(event, candidate, amountValue)) {
|
||||||
|
candidate++
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prevVisibleFrom(event, step, amountValue) {
|
||||||
|
let candidate = step - 1
|
||||||
|
while (candidate > SHORT_STEP_PERSON && !stepVisible(event, candidate, amountValue)) {
|
||||||
|
candidate--
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import {computed, reactive, ref} from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import {differenceInYears, parseISO} from 'date-fns'
|
||||||
|
import {SHORT_STEP_PERSON, nextVisibleFrom, prevVisibleFrom} from './shortStepFlow.js'
|
||||||
|
|
||||||
|
export function useShortSignupForm(event, participantData) {
|
||||||
|
const currentStep = ref(SHORT_STEP_PERSON)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const summaryLoading = ref(false)
|
||||||
|
const submitResult = ref(null) // null | { status: 'success'|'exists', participant: {} }
|
||||||
|
const submitError = ref('')
|
||||||
|
|
||||||
|
// Gibt es nur eine Zahlungsart, wird sie vorausgewählt und der Zahlungsschritt entfällt.
|
||||||
|
const activeMethods = (event.paymentMethods ?? []).filter(m => m.active)
|
||||||
|
|
||||||
|
// Bei genau einem teilnehmenden Stamm wird dieser gesetzt, ohne danach zu fragen; erst ab zwei
|
||||||
|
// Stämmen erscheint das Auswahlfeld.
|
||||||
|
const localGroups = event.contributingLocalGroups ?? []
|
||||||
|
const needsLocalGroup = localGroups.length > 1
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
userId: participantData.id,
|
||||||
|
vorname: participantData.firstname ?? '',
|
||||||
|
nachname: participantData.lastname ?? '',
|
||||||
|
pfadiname: participantData.nickname ?? '',
|
||||||
|
geburtsdatum: participantData.birthday ?? '',
|
||||||
|
email_1: participantData.email ?? '',
|
||||||
|
telefon_1: participantData.phone ?? '',
|
||||||
|
localGroup: localGroups.length === 1 ? localGroups[0].id : (participantData.localGroup ?? '-1'),
|
||||||
|
ansprechpartner: '',
|
||||||
|
email_2: '',
|
||||||
|
allergien: participantData.allergies ?? '',
|
||||||
|
intolerances: participantData.intolerances ?? '',
|
||||||
|
first_aid: '-1',
|
||||||
|
badeerlaubnis: '-1',
|
||||||
|
// Antworten auf die event-spezifischen Teilnahmeoptionen: { [question.key]: option.value }
|
||||||
|
participationOptions: {},
|
||||||
|
foto: {socialmedia: false, print: false, webseite: false, partner: false, intern: false},
|
||||||
|
paymentMethod: activeMethods.length === 1 ? activeMethods[0].slug : null,
|
||||||
|
paymentOptions: {},
|
||||||
|
summary_information_correct: false,
|
||||||
|
summary_accept_terms: false,
|
||||||
|
legal_accepted: false,
|
||||||
|
payment: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Minderjährigkeit steuert Kontaktperson, Erweiterte Erste Hilfe und Badeerlaubnis. Die Grenze von 18 Jahren
|
||||||
|
// entspricht Age::isfullAged() im Backend.
|
||||||
|
const isMinor = computed(() => {
|
||||||
|
if (!formData.geburtsdatum) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return differenceInYears(new Date(), parseISO(formData.geburtsdatum)) < 18
|
||||||
|
})
|
||||||
|
|
||||||
|
const summaryAmount = ref('')
|
||||||
|
const summaryAmountValue = ref(0)
|
||||||
|
const amountLoaded = ref(false)
|
||||||
|
|
||||||
|
// Der Betrag steht für die ganze Anmeldung fest (feste Teilnahmegruppe, fester Zeitraum) und wird deshalb
|
||||||
|
// genau einmal geholt. Er entscheidet, ob der Zahlungsschritt überhaupt vorkommt.
|
||||||
|
const ensureAmount = async () => {
|
||||||
|
if (amountLoaded.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await axios.post('/api/v1/event/' + event.id + '/short-calculate-amount')
|
||||||
|
summaryAmount.value = res.data.amount
|
||||||
|
summaryAmountValue.value = Number(res.data.amountValue ?? 0)
|
||||||
|
amountLoaded.value = true
|
||||||
|
|
||||||
|
if (summaryAmountValue.value <= 0) {
|
||||||
|
formData.paymentMethod = null
|
||||||
|
formData.paymentOptions = {}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
summaryLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToStep = (step) => {
|
||||||
|
currentStep.value = step
|
||||||
|
}
|
||||||
|
|
||||||
|
const goNext = async () => {
|
||||||
|
await ensureAmount()
|
||||||
|
goToStep(nextVisibleFrom(event, currentStep.value, summaryAmountValue.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const goBack = async () => {
|
||||||
|
await ensureAmount()
|
||||||
|
goToStep(prevVisibleFrom(event, currentStep.value, summaryAmountValue.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (!formData.summary_information_correct || !formData.summary_accept_terms || !formData.legal_accepted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
submitError.value = ''
|
||||||
|
try {
|
||||||
|
const res = await axios.post('/api/v1/event/' + event.id + '/short-signup', {
|
||||||
|
firstname: formData.vorname,
|
||||||
|
lastname: formData.nachname,
|
||||||
|
nickname: formData.pfadiname || null,
|
||||||
|
birthday: formData.geburtsdatum,
|
||||||
|
email: formData.email_1,
|
||||||
|
phone: formData.telefon_1,
|
||||||
|
localGroup: formData.localGroup !== '-1' ? formData.localGroup : null,
|
||||||
|
contactPerson: isMinor.value ? formData.ansprechpartner : null,
|
||||||
|
contactEmail: isMinor.value ? formData.email_2 : null,
|
||||||
|
allergies: formData.allergien || null,
|
||||||
|
intolerances: formData.intolerances || null,
|
||||||
|
firstAidPermission: isMinor.value && formData.first_aid !== '-1' ? formData.first_aid : null,
|
||||||
|
swimmingPermission: isMinor.value && formData.badeerlaubnis !== '-1' ? formData.badeerlaubnis : null,
|
||||||
|
foto: formData.foto,
|
||||||
|
participationOptions: formData.participationOptions,
|
||||||
|
paymentMethod: formData.paymentMethod,
|
||||||
|
paymentOptions: formData.paymentOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
submitResult.value = {
|
||||||
|
status: res.data.status,
|
||||||
|
participant: res.data.participant,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
submitError.value = error.response?.data?.message
|
||||||
|
?? 'Die Anmeldung konnte nicht gespeichert werden. Bitte versuche es später erneut.'
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentStep,
|
||||||
|
goToStep,
|
||||||
|
goNext,
|
||||||
|
goBack,
|
||||||
|
formData,
|
||||||
|
isMinor,
|
||||||
|
localGroups,
|
||||||
|
needsLocalGroup,
|
||||||
|
submit,
|
||||||
|
submitting,
|
||||||
|
submitResult,
|
||||||
|
submitError,
|
||||||
|
summaryLoading,
|
||||||
|
summaryAmount,
|
||||||
|
summaryAmountValue,
|
||||||
|
ensureAmount,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
<script setup>
|
||||||
|
import ErrorText from "../../../../../../Views/Components/ErrorText.vue";
|
||||||
|
import {reactive} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
formData: Object,
|
||||||
|
event: Object,
|
||||||
|
isMinor: Boolean,
|
||||||
|
localGroups: {type: Array, default: () => []},
|
||||||
|
needsLocalGroup: Boolean,
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['next'])
|
||||||
|
|
||||||
|
const errors = reactive({
|
||||||
|
vorname: '',
|
||||||
|
nachname: '',
|
||||||
|
geburtsdatum: '',
|
||||||
|
localGroup: '',
|
||||||
|
email_1: '',
|
||||||
|
telefon_1: '',
|
||||||
|
ansprechpartner: '',
|
||||||
|
email_2: '',
|
||||||
|
first_aid: '',
|
||||||
|
badeerlaubnis: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Nur sichtbare Felder werden geprüft -- Kontaktperson und Erlaubnisse gibt es bei Volljährigen nicht.
|
||||||
|
const next = () => {
|
||||||
|
Object.keys(errors).forEach(key => errors[key] = '')
|
||||||
|
|
||||||
|
let hasError = false
|
||||||
|
|
||||||
|
if (!props.formData.vorname) {
|
||||||
|
errors.vorname = 'Bitte einen Vornamen angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.formData.nachname) {
|
||||||
|
errors.nachname = 'Bitte einen Nachnamen angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.formData.geburtsdatum) {
|
||||||
|
errors.geburtsdatum = 'Bitte ein Geburtsdatum angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.needsLocalGroup && (!props.formData.localGroup || props.formData.localGroup === '-1')) {
|
||||||
|
errors.localGroup = 'Bitte einen Stamm auswählen.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.formData.email_1) {
|
||||||
|
errors.email_1 = 'Bitte eine E-Mail-Adresse angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.formData.telefon_1) {
|
||||||
|
errors.telefon_1 = 'Bitte eine Telefonnummer angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.isMinor) {
|
||||||
|
if (!props.formData.ansprechpartner) {
|
||||||
|
errors.ansprechpartner = 'Bitte eine Kontaktperson angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.formData.email_2) {
|
||||||
|
errors.email_2 = 'Bitte eine E-Mail-Adresse der Kontaktperson angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.formData.first_aid === '-1') {
|
||||||
|
errors.first_aid = 'Bitte triff eine Entscheidung. Bist du dir unsicher, kontaktiere bitte die Aktionsleitung.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.event.swimmingPermissionRequired && props.formData.badeerlaubnis === '-1') {
|
||||||
|
errors.badeerlaubnis = 'Bitte triff eine Entscheidung. Bist du dir unsicher, kontaktiere bitte die Aktionsleitung.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('next')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3>Angaben zur Person</h3>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<td>Vorname:</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.vorname" />
|
||||||
|
<ErrorText :message="errors.vorname" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Pfadiname:</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.pfadiname" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Nachname:</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.nachname" />
|
||||||
|
<ErrorText :message="errors.nachname" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Geburtsdatum:</td>
|
||||||
|
<td>
|
||||||
|
<input type="date" v-model="formData.geburtsdatum" />
|
||||||
|
<ErrorText :message="errors.geburtsdatum" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="needsLocalGroup">
|
||||||
|
<td>Stamm:</td>
|
||||||
|
<td>
|
||||||
|
<select v-model="formData.localGroup">
|
||||||
|
<option value="-1">Bitte wählen</option>
|
||||||
|
<option v-for="group in localGroups" :key="group.id" :value="group.id">{{ group.name }}</option>
|
||||||
|
</select>
|
||||||
|
<ErrorText :message="errors.localGroup" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>E-Mail:</td>
|
||||||
|
<td>
|
||||||
|
<input type="email" v-model="formData.email_1" />
|
||||||
|
<ErrorText :message="errors.email_1" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Telefon:</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.telefon_1" />
|
||||||
|
<ErrorText :message="errors.telefon_1" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<template v-if="isMinor">
|
||||||
|
<tr>
|
||||||
|
<td>Kontaktperson (Nachname, Vorname):</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.ansprechpartner" />
|
||||||
|
<ErrorText :message="errors.ansprechpartner" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>E-Mail der Kontaktperson:</td>
|
||||||
|
<td>
|
||||||
|
<input type="email" v-model="formData.email_2" />
|
||||||
|
<ErrorText :message="errors.email_2" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Allergien:</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.allergien" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Unverträglichkeiten:</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.intolerances" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<template v-if="isMinor">
|
||||||
|
<tr>
|
||||||
|
<td>Erweiterte Erste Hilfe erlaubt:</td>
|
||||||
|
<td>
|
||||||
|
<select v-model="formData.first_aid">
|
||||||
|
<option value="-1">Bitte wählen</option>
|
||||||
|
<option
|
||||||
|
v-for="firstAidPermission in event.firstAidPermissions"
|
||||||
|
:key="firstAidPermission.slug"
|
||||||
|
:value="firstAidPermission.slug">{{ firstAidPermission.name }}</option>
|
||||||
|
</select><br />
|
||||||
|
<span style="font-size: 0.8rem; color: #6b7280;">
|
||||||
|
Nicht dringend-notwendige Erste-Hilfe-Maßnahmen, beinhaltet das Entfernen von Zecken und Splittern sowie das Kleben von Pflastern.
|
||||||
|
</span>
|
||||||
|
<ErrorText :message="errors.first_aid" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="event.swimmingPermissionRequired">
|
||||||
|
<td>Badeerlaubnis:</td>
|
||||||
|
<td>
|
||||||
|
<select v-model="formData.badeerlaubnis">
|
||||||
|
<option value="-1">Bitte wählen</option>
|
||||||
|
<option
|
||||||
|
v-for="swimmingPermission in event.swimmingPermissions"
|
||||||
|
:key="swimmingPermission.slug"
|
||||||
|
:value="swimmingPermission.slug">{{ swimmingPermission.name }}</option>
|
||||||
|
</select>
|
||||||
|
<ErrorText :message="errors.badeerlaubnis" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="btn-row">
|
||||||
|
<button type="button" class="btn-primary" @click="next">Weiter →</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
const PAYMENT_ACCOUNT_TRANSACTION = 'PAYMENT_ACCOUNT_TRANSACTION'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
formData: Object,
|
||||||
|
event: Object,
|
||||||
|
isMinor: Boolean,
|
||||||
|
summaryAmount: String,
|
||||||
|
summaryAmountValue: {type: Number, default: 0},
|
||||||
|
summaryLoading: Boolean,
|
||||||
|
submitting: Boolean,
|
||||||
|
submitError: {type: String, default: ''},
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['back', 'submit'])
|
||||||
|
|
||||||
|
const selectedMethod = computed(() =>
|
||||||
|
(props.event.paymentMethods ?? []).find(m => m.slug === props.formData.paymentMethod) ?? null
|
||||||
|
)
|
||||||
|
|
||||||
|
// Die Überweisungs-Bestätigung wird nur bei Banküberweisung verlangt.
|
||||||
|
const requiresPaymentConfirmation = computed(() =>
|
||||||
|
props.formData.paymentMethod === PAYMENT_ACCOUNT_TRANSACTION
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bestätigungstext ist ein admin-konfigurierbarer Freitext ({amount}-Platzhalter), sonst Default.
|
||||||
|
const paymentConfirmationText = computed(() => {
|
||||||
|
const configured = selectedMethod.value?.configuration?.summary_confirmation_text
|
||||||
|
const template = (configured && String(configured).trim() !== '')
|
||||||
|
? configured
|
||||||
|
: 'Ich bestätige, den Betrag von {amount} zu überweisen.'
|
||||||
|
return template.replaceAll('{amount}', props.summaryAmount ?? '')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gewählte Teilnahmeoptionen für die Zusammenfassung (Label statt Wert anzeigen).
|
||||||
|
const selectedParticipationOptions = computed(() =>
|
||||||
|
(props.event.participationOptions ?? []).map(question => {
|
||||||
|
const value = props.formData.participationOptions[question.key]
|
||||||
|
const option = (question.options ?? []).find(o => o.value === value)
|
||||||
|
return {label: question.title || question.label, value: option?.label ?? ''}
|
||||||
|
}).filter(entry => entry.value !== '')
|
||||||
|
)
|
||||||
|
|
||||||
|
// Der Stamm steht entweder fest (genau ein teilnehmender Stamm) oder wurde auf Seite 1 gewählt.
|
||||||
|
const localGroupName = computed(() =>
|
||||||
|
(props.event.contributingLocalGroups ?? []).find(g => g.id === props.formData.localGroup)?.name ?? null
|
||||||
|
)
|
||||||
|
|
||||||
|
const permissionName = (list, slug) =>
|
||||||
|
(list ?? []).find(entry => entry.slug === slug)?.name ?? '—'
|
||||||
|
|
||||||
|
const canSubmit = computed(() =>
|
||||||
|
props.formData.summary_information_correct
|
||||||
|
&& props.formData.summary_accept_terms
|
||||||
|
&& props.formData.legal_accepted
|
||||||
|
&& (!requiresPaymentConfirmation.value || props.formData.payment)
|
||||||
|
&& !props.submitting
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3>Zusammenfassung</h3>
|
||||||
|
|
||||||
|
<div v-if="summaryLoading" style="color: #6b7280; padding: 20px 0;">Wird geladen…</div>
|
||||||
|
<div v-else>
|
||||||
|
<table class="form-table" style="margin-bottom: 20px;">
|
||||||
|
<tr>
|
||||||
|
<td>Dein Name:</td>
|
||||||
|
<td>{{ formData.vorname }} {{ formData.nachname }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="formData.pfadiname">
|
||||||
|
<td>Pfadiname:</td>
|
||||||
|
<td>{{ formData.pfadiname }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Deine E-Mail:</td>
|
||||||
|
<td>{{ formData.email_1 }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Deine Telefonnummer:</td>
|
||||||
|
<td>{{ formData.telefon_1 }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="localGroupName">
|
||||||
|
<td>Stamm:</td>
|
||||||
|
<td>{{ localGroupName }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<template v-if="isMinor">
|
||||||
|
<tr>
|
||||||
|
<td>Kontaktperson:</td>
|
||||||
|
<td>{{ formData.ansprechpartner }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>E-Mail der Kontaktperson:</td>
|
||||||
|
<td>{{ formData.email_2 }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Erweiterte Erste Hilfe:</td>
|
||||||
|
<td>{{ permissionName(event.firstAidPermissions, formData.first_aid) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="event.swimmingPermissionRequired">
|
||||||
|
<td>Badeerlaubnis:</td>
|
||||||
|
<td>{{ permissionName(event.swimmingPermissions, formData.badeerlaubnis) }}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Allergien:</td>
|
||||||
|
<td>{{ formData.allergien || 'Keine Angabe' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Unverträglichkeiten:</td>
|
||||||
|
<td>{{ formData.intolerances || 'Keine Angabe' }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-for="option in selectedParticipationOptions" :key="option.label">
|
||||||
|
<td>{{ option.label }}:</td>
|
||||||
|
<td>{{ option.value }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Foto-Erlaubnis:</td>
|
||||||
|
<td>
|
||||||
|
<strong>Social Media:</strong> {{ formData.foto.socialmedia ? 'Ja' : 'Nein' }},
|
||||||
|
<strong>Printmedien:</strong> {{ formData.foto.print ? 'Ja' : 'Nein' }},
|
||||||
|
<strong>Webseite:</strong> {{ formData.foto.webseite ? 'Ja' : 'Nein' }},
|
||||||
|
<strong>Partnerorganisationen:</strong> {{ formData.foto.partner ? 'Ja' : 'Nein' }},
|
||||||
|
<strong>Interne Zwecke:</strong> {{ formData.foto.intern ? 'Ja' : 'Nein' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td>Veranstaltung:</td><td><strong>{{ event.name }}</strong></td></tr>
|
||||||
|
<tr><td>Zeitraum:</td><td>{{ event.eventBegin }} – {{ event.eventEnd }}</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4 style="margin: 0 0 8px 0;">Kostenübersicht</h4>
|
||||||
|
<table class="form-table cost-table" style="margin-bottom: 20px;">
|
||||||
|
<tr class="cost-total">
|
||||||
|
<td><strong>Teilnahmebeitrag:</strong></td>
|
||||||
|
<td><strong>{{ summaryAmount }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px;">
|
||||||
|
<label style="display: flex; gap: 10px; cursor: pointer;">
|
||||||
|
<input type="checkbox" v-model="formData.summary_information_correct" />
|
||||||
|
Ich bestätige, dass alle Angaben korrekt sind.
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; gap: 10px; cursor: pointer;">
|
||||||
|
<input type="checkbox" v-model="formData.summary_accept_terms" />
|
||||||
|
Ich akzeptiere die Teilnahmebedingungen.
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; gap: 10px; cursor: pointer;">
|
||||||
|
<input type="checkbox" v-model="formData.legal_accepted" />
|
||||||
|
Ich stimme der Datenschutzerklärung zu.
|
||||||
|
</label>
|
||||||
|
<label v-if="requiresPaymentConfirmation" style="display: flex; gap: 10px; cursor: pointer;">
|
||||||
|
<input type="checkbox" v-model="formData.payment" />
|
||||||
|
{{ paymentConfirmationText }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="submitError" style="color: #991b1b; font-weight: 600;">{{ submitError }}</p>
|
||||||
|
|
||||||
|
<div class="btn-row">
|
||||||
|
<button type="button" class="btn-secondary" @click="emit('back')">← Zurück</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn-primary"
|
||||||
|
:disabled="!canSubmit"
|
||||||
|
style="background: #059669;"
|
||||||
|
>
|
||||||
|
{{ submitting ? 'Wird gesendet…' : 'Jetzt anmelden ✓' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.cost-table .cost-total td {
|
||||||
|
border-top: 1px solid #d1d5db;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -122,170 +122,5 @@ const steps = [
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<!-- Unscoped und mit der Kurzanmeldung geteilt (ShortSignupForm.vue bindet dieselbe Datei ein). -->
|
||||||
/* ─── Progress (Step-Pills) ─── */
|
<style src="./signupForm.css"></style>
|
||||||
.signup-progress {
|
|
||||||
margin-bottom: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-progress-pills {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-progress-separator {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 16px;
|
|
||||||
height: 2px;
|
|
||||||
background: #e5e7eb;
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-pill {
|
|
||||||
padding: 5px 14px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 2px solid;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-pill__check { margin-right: 4px; }
|
|
||||||
|
|
||||||
.signup-pill--active {
|
|
||||||
border-color: #2563eb;
|
|
||||||
background: #2563eb;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-pill--done {
|
|
||||||
border-color: #bbf7d0;
|
|
||||||
background: #f0fdf4;
|
|
||||||
color: #15803d;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-pill--upcoming {
|
|
||||||
border-color: #e5e7eb;
|
|
||||||
background: #f9fafb;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-progress-bar {
|
|
||||||
margin-top: 10px;
|
|
||||||
height: 3px;
|
|
||||||
background: #e5e7eb;
|
|
||||||
border-radius: 2px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-progress-bar__fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, #2563eb, #3b82f6);
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-progress-mobile {
|
|
||||||
display: none;
|
|
||||||
margin-top: 8px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── Form-Table ─── */
|
|
||||||
.form-table { width: 100%; border-collapse: collapse; }
|
|
||||||
.form-table td { padding: 8px 12px 8px 0; vertical-align: top; }
|
|
||||||
.form-table td:first-child { width: 220px; color: #374151; font-weight: 500; }
|
|
||||||
.form-table input[type="text"],
|
|
||||||
.form-table input[type="date"],
|
|
||||||
.form-table input[type="email"],
|
|
||||||
.form-table input[type="number"],
|
|
||||||
.form-table select,
|
|
||||||
.form-table textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 6px 10px;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-row { display: flex; gap: 10px; padding-top: 16px; flex-wrap: wrap; }
|
|
||||||
.btn-primary {
|
|
||||||
padding: 8px 20px;
|
|
||||||
background: #2563eb;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
||||||
.btn-secondary {
|
|
||||||
padding: 8px 20px;
|
|
||||||
background: #f3f4f6;
|
|
||||||
color: #374151;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── Tablet ─── */
|
|
||||||
@media (max-width: 1023px) {
|
|
||||||
.form-table td:first-child {
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── Smartphone ─── */
|
|
||||||
@media (max-width: 639px) {
|
|
||||||
/* Pills auf Mobile: kompakter, Trennstriche ausblenden */
|
|
||||||
.signup-progress-pills {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.signup-progress-mobile {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form-Table: Label oberhalb des Feldes */
|
|
||||||
.form-table,
|
|
||||||
.form-table tbody,
|
|
||||||
.form-table tr {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-table td {
|
|
||||||
display: block;
|
|
||||||
width: 100% !important;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-table td:first-child {
|
|
||||||
width: 100% !important;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #374151;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-table td[colspan] {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-row {
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary,
|
|
||||||
.btn-secondary {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/* Geteilte Styles der Anmelde-Wizards (langer Prozess und Kurzanmeldung). */
|
||||||
|
|
||||||
|
/* ─── Progress (Step-Pills) ─── */
|
||||||
|
.signup-progress {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-pills {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-separator {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill {
|
||||||
|
padding: 5px 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 2px solid;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill__check { margin-right: 4px; }
|
||||||
|
|
||||||
|
.signup-pill--active {
|
||||||
|
border-color: #2563eb;
|
||||||
|
background: #2563eb;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill--done {
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #15803d;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill--upcoming {
|
||||||
|
border-color: #e5e7eb;
|
||||||
|
background: #f9fafb;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-bar {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 3px;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-bar__fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #2563eb, #3b82f6);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-mobile {
|
||||||
|
display: none;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Form-Table ─── */
|
||||||
|
.form-table { width: 100%; border-collapse: collapse; }
|
||||||
|
.form-table td { padding: 8px 12px 8px 0; vertical-align: top; }
|
||||||
|
.form-table td:first-child { width: 220px; color: #374151; font-weight: 500; }
|
||||||
|
.form-table input[type="text"],
|
||||||
|
.form-table input[type="date"],
|
||||||
|
.form-table input[type="email"],
|
||||||
|
.form-table input[type="number"],
|
||||||
|
.form-table select,
|
||||||
|
.form-table textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-row { display: flex; gap: 10px; padding-top: 16px; flex-wrap: wrap; }
|
||||||
|
.btn-primary {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background: #2563eb;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
.btn-secondary {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #374151;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.form-table td:first-child {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
/* Pills auf Mobile: kompakter, Trennstriche ausblenden */
|
||||||
|
.signup-progress-pills {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.signup-progress-mobile {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form-Table: Label oberhalb des Feldes */
|
||||||
|
.form-table,
|
||||||
|
.form-table tbody,
|
||||||
|
.form-table tr {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td {
|
||||||
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td:first-child {
|
||||||
|
width: 100% !important;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td[colspan] {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-row {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary,
|
||||||
|
.btn-secondary {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ const next = () => {
|
|||||||
hasError = true
|
hasError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.formData.badeerlaubnis === '-1') {
|
if (props.event.swimmingPermissionRequired && props.formData.badeerlaubnis === '-1') {
|
||||||
errors.badeerlaubnis = 'Bitte triff eine Entscheidung. Bist du dir unsicher, kontaktiere bitte die Aktionsleitung'
|
errors.badeerlaubnis = 'Bitte triff eine Entscheidung. Bist du dir unsicher, kontaktiere bitte die Aktionsleitung'
|
||||||
hasError = true
|
hasError = true
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ const next = () => {
|
|||||||
<ErrorText :message="errors.email_2" />
|
<ErrorText :message="errors.email_2" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr v-if="event.swimmingPermissionRequired">
|
||||||
<td>Badeerlaubnis:</td>
|
<td>Badeerlaubnis:</td>
|
||||||
<td>
|
<td>
|
||||||
<select v-model="formData.badeerlaubnis">
|
<select v-model="formData.badeerlaubnis">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
import SignupForm from './Partials/SignUpForm/SignupForm.vue'
|
import SignupForm from './Partials/SignUpForm/SignupForm.vue'
|
||||||
|
import ShortSignupForm from './Partials/ShortSignUpForm/ShortSignupForm.vue'
|
||||||
import FullScreenModal from "../../../Views/Components/FullScreenModal.vue";
|
import FullScreenModal from "../../../Views/Components/FullScreenModal.vue";
|
||||||
import AvailableEvents from "./Partials/AvailableEvents.vue";
|
import AvailableEvents from "./Partials/AvailableEvents.vue";
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
@@ -98,8 +99,13 @@ function close() {
|
|||||||
<hr class="signup-divider" />
|
<hr class="signup-divider" />
|
||||||
|
|
||||||
<div class="signup-body">
|
<div class="signup-body">
|
||||||
|
<ShortSignupForm
|
||||||
|
v-if="props.event.registrationAllowed && props.event.shortRegistration"
|
||||||
|
:event="props.event"
|
||||||
|
:participantData="props.participantData ?? {}"
|
||||||
|
/>
|
||||||
<SignupForm
|
<SignupForm
|
||||||
v-if="props.event.registrationAllowed"
|
v-else-if="props.event.registrationAllowed"
|
||||||
:event="props.event"
|
:event="props.event"
|
||||||
:participantData="props.participantData ?? {}"
|
:participantData="props.participantData ?? {}"
|
||||||
:localGroups="props.localGroups ?? []"
|
:localGroups="props.localGroups ?? []"
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
* @property float $support_flat
|
* @property float $support_flat
|
||||||
* @property int $alcoholics_age
|
* @property int $alcoholics_age
|
||||||
* @property boolean $archived
|
* @property boolean $archived
|
||||||
|
* @property boolean $short_registration
|
||||||
|
* @property boolean $swimming_permission_required
|
||||||
* @property string|null $invoice_key
|
* @property string|null $invoice_key
|
||||||
*/
|
*/
|
||||||
class Event extends InstancedModel
|
class Event extends InstancedModel
|
||||||
@@ -91,6 +93,9 @@ class Event extends InstancedModel
|
|||||||
'participation_options',
|
'participation_options',
|
||||||
'addons',
|
'addons',
|
||||||
|
|
||||||
|
'short_registration',
|
||||||
|
'swimming_permission_required',
|
||||||
|
|
||||||
'invoice_key',
|
'invoice_key',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -121,6 +126,9 @@ class Event extends InstancedModel
|
|||||||
|
|
||||||
'participation_options' => 'array',
|
'participation_options' => 'array',
|
||||||
'addons' => 'array',
|
'addons' => 'array',
|
||||||
|
|
||||||
|
'short_registration' => 'boolean',
|
||||||
|
'swimming_permission_required' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function tenant(): BelongsTo
|
public function tenant(): BelongsTo
|
||||||
@@ -205,4 +213,18 @@ class Event extends InstancedModel
|
|||||||
return $this->hasMany(EventParticipant::class);
|
return $this->hasMany(EventParticipant::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slug der ersten konfigurierten Teilnahmegruppe. Die Kurzanmeldung fragt die Gruppe nicht ab und ordnet
|
||||||
|
* jede Anmeldung dieser Gruppe zu. Null, wenn für die Veranstaltung noch keine Gebühr hinterlegt ist --
|
||||||
|
* dann ist eine Kurzanmeldung nicht möglich, weil `participation_type` am Teilnehmer Pflicht ist.
|
||||||
|
*/
|
||||||
|
public function firstParticipationTypeSlug() : ?string {
|
||||||
|
return collect([
|
||||||
|
$this->participationFee1,
|
||||||
|
$this->participationFee2,
|
||||||
|
$this->participationFee3,
|
||||||
|
$this->participationFee4,
|
||||||
|
])->first(fn ($participationFee) => $participationFee !== null)?->type;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,12 +52,15 @@ class EventParticipantResource extends JsonResource
|
|||||||
'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 ?? 'Nicht im LV',
|
'localgroup' => $this->resource->localGroup()?->first()?->name ?? 'Nicht im LV',
|
||||||
'swimmingPermission' => $this->resource->swimmingPermission()->first()->short,
|
// Die Zuordnungen sind nullable: die Kurzanmeldung erhebt nicht alles, und in der Verwaltung
|
||||||
'extendedFirstAid' => $this->resource->firstAidPermission()->first()->name,
|
// lassen sich Werte leeren. Ohne Fallback fielen Teilnehmerliste und Detailansicht komplett aus.
|
||||||
|
'swimmingPermission' => $this->resource->swimmingPermission()->first()?->short ?? 'Unbekannt',
|
||||||
|
'extendedFirstAid' => $this->resource->firstAidPermission()->first()?->name ?? 'Unbekannt',
|
||||||
'tetanusVaccination' => $this->resource->tetanus_vaccination?->format('d.m.Y') ?? 'Unbekannt',
|
'tetanusVaccination' => $this->resource->tetanus_vaccination?->format('d.m.Y') ?? 'Unbekannt',
|
||||||
'tetanusVaccinationEdit' => $this->resource->tetanus_vaccination?->format('Y-m-d') ?? null,
|
'tetanusVaccinationEdit' => $this->resource->tetanus_vaccination?->format('Y-m-d') ?? null,
|
||||||
'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport],
|
'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport],
|
||||||
'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()->name,
|
'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()?->name
|
||||||
|
?? $this->resource->participation_type,
|
||||||
'needs_payment' => $this->resource->amount->getAmount() > 0
|
'needs_payment' => $this->resource->amount->getAmount() > 0
|
||||||
&& $this->resource->payment_method === PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION
|
&& $this->resource->payment_method === PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION
|
||||||
&& $this->resource->amount_paid?->getAmount() < $this->resource->amount->getAmount(),
|
&& $this->resource->amount_paid?->getAmount() < $this->resource->amount->getAmount(),
|
||||||
@@ -87,7 +90,7 @@ class EventParticipantResource extends JsonResource
|
|||||||
'state' => config('postCode.map.' . $this->resource->postcode),
|
'state' => config('postCode.map.' . $this->resource->postcode),
|
||||||
'localGroupState' => null !== $this->resource->localGroup()->first()?->postcode ? config('postCode.map.' . $this->resource->postcode) : '--',
|
'localGroupState' => null !== $this->resource->localGroup()->first()?->postcode ? config('postCode.map.' . $this->resource->postcode) : '--',
|
||||||
'birthday' => $this->resource->birthday->format('d.m.Y'),
|
'birthday' => $this->resource->birthday->format('d.m.Y'),
|
||||||
'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()->name,
|
'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()?->name ?? 'Unbekannt',
|
||||||
'paymentMethod' => $this->resource->payment_method !== null
|
'paymentMethod' => $this->resource->payment_method !== null
|
||||||
? AvailablePaymentMethod::withoutGlobalScopes()->where('tenant', $this->resource->tenant)->where('slug', $this->resource->payment_method)->first()?->name ?? $this->resource->payment_method
|
? AvailablePaymentMethod::withoutGlobalScopes()->where('tenant', $this->resource->tenant)->where('slug', $this->resource->payment_method)->first()?->name ?? $this->resource->payment_method
|
||||||
: null,
|
: null,
|
||||||
@@ -96,12 +99,14 @@ class EventParticipantResource extends JsonResource
|
|||||||
EfzStatus::EFZ_STATUS_NOT_REQUIRED => 'bg-green',
|
EfzStatus::EFZ_STATUS_NOT_REQUIRED => 'bg-green',
|
||||||
EfzStatus::EFZ_STATUS_NOT_CHECKED => 'bg-yellow',
|
EfzStatus::EFZ_STATUS_NOT_CHECKED => 'bg-yellow',
|
||||||
EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'bg-red',
|
EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'bg-red',
|
||||||
|
default => 'bg-yellow',
|
||||||
},
|
},
|
||||||
'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 => 'Nicht eingereicht',
|
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',
|
||||||
|
default => 'Nicht geprüft',
|
||||||
},
|
},
|
||||||
'eventName' => $this->resource->event()->first()->name,
|
'eventName' => $this->resource->event()->first()->name,
|
||||||
'arrivalDateReadable' => $this->resource->arrival_date->format('d.m.Y'),
|
'arrivalDateReadable' => $this->resource->arrival_date->format('d.m.Y'),
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ class EventResource extends JsonResource{
|
|||||||
$returnArray['nameShort'] = substr($returnArray['nameShort'], 8, 13) . '...';
|
$returnArray['nameShort'] = substr($returnArray['nameShort'], 8, 13) . '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Steuert im Frontend, welcher Anmelde-Wizard gerendert wird, und ob die Badeerlaubnis abgefragt wird.
|
||||||
|
$returnArray['shortRegistration'] = (bool)$this->event->short_registration;
|
||||||
|
$returnArray['swimmingPermissionRequired'] = (bool)$this->event->swimming_permission_required;
|
||||||
|
|
||||||
$returnArray['siblingReduction'] = $this->event->sibling_reduction ?? true;
|
$returnArray['siblingReduction'] = $this->event->sibling_reduction ?? true;
|
||||||
$returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true);
|
$returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true);
|
||||||
$returnArray['solidarityPayment'] = $this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY;
|
$returnArray['solidarityPayment'] = $this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY;
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('events', function (Blueprint $table) {
|
||||||
|
// Verkürzter Anmeldeprozess: fragt nur die nötigsten Daten ab, fehlende Pflichtangaben werden
|
||||||
|
// beim Speichern mit Platzhaltern gefüllt. Bestandsveranstaltungen bleiben beim langen Prozess.
|
||||||
|
$table->boolean('short_registration')->default(false);
|
||||||
|
|
||||||
|
// Ob die Badeerlaubnis überhaupt abgefragt wird -- wirkt in beiden Anmeldeprozessen. Default true,
|
||||||
|
// damit bestehende Veranstaltungen sich unverändert verhalten.
|
||||||
|
$table->boolean('swimming_permission_required')->default(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('events', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['short_registration', 'swimming_permission_required']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,6 +18,9 @@
|
|||||||
</include>
|
</include>
|
||||||
</source>
|
</source>
|
||||||
<php>
|
<php>
|
||||||
|
<!-- config/postCode.php ist sehr groß und wird je Test neu geladen; mit dem PHP-Default von 128M
|
||||||
|
reißt die Suite ab einer gewissen Testanzahl das Limit. -->
|
||||||
|
<ini name="memory_limit" value="512M"/>
|
||||||
<env name="APP_ENV" value="testing"/>
|
<env name="APP_ENV" value="testing"/>
|
||||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Domains\Event\Actions\ShortSignUp\ShortSignUpCommand;
|
||||||
|
use App\Domains\Event\Actions\ShortSignUp\ShortSignUpRequest;
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Enumerations\EfzStatus;
|
||||||
|
use App\Enumerations\FirstAidPermission;
|
||||||
|
use App\Enumerations\ParticipationType;
|
||||||
|
use App\Enumerations\SwimmingPermission;
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\Models\EventParticipant;
|
||||||
|
use App\Models\PaymentMethod;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\RelationModels\EventParticipationFee;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ShortSignUpTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private Tenant $tenant;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
Mail::fake();
|
||||||
|
|
||||||
|
$this->tenant = Tenant::create([
|
||||||
|
'slug' => 'tv', 'name' => 'Test', 'email' => 't@example.com', 'email_finance' => 'f@example.com',
|
||||||
|
'url' => 'test.local', 'account_name' => 'Test e.V.', 'account_iban' => 'DE00', 'account_bic' => 'XY',
|
||||||
|
'city' => 'Stadt', 'postcode' => '00000', 'is_active_local_group' => true, 'has_active_instance' => true,
|
||||||
|
]);
|
||||||
|
app()->instance('tenant', $this->tenant);
|
||||||
|
|
||||||
|
PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]);
|
||||||
|
DB::table('participation_fee_types')->insert(['slug' => 'FIXED', 'name' => 'Fixed', 'created_at' => now(), 'updated_at' => now()]);
|
||||||
|
ParticipationType::create(['slug' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, 'name' => 'Teilnehmende']);
|
||||||
|
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'name' => 'Nicht geprüft']);
|
||||||
|
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']);
|
||||||
|
SwimmingPermission::create(['slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, 'name' => 'Darf baden', 'short' => 'Erteilt']);
|
||||||
|
SwimmingPermission::create(['slug' => SwimmingPermission::SWIMMING_PERMISSION_DENIED, 'name' => 'Darf nicht baden', 'short' => 'Keine']);
|
||||||
|
FirstAidPermission::create(['slug' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, 'name' => 'Zugestimmt', 'description' => '']);
|
||||||
|
FirstAidPermission::create(['slug' => FirstAidPermission::FIRST_AID_PERMISSION_DENIED, 'name' => 'Abgelehnt', 'description' => '']);
|
||||||
|
EatingHabit::create(['slug' => EatingHabit::EATING_HABIT_OMNIVOR, 'name' => 'Omnivor']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createEvent(bool $withFee = true, bool $swimmingPermissionRequired = true): Event
|
||||||
|
{
|
||||||
|
$event = Event::create([
|
||||||
|
'tenant' => $this->tenant->slug, 'name' => 'Event', 'identifier' => 'evt-1', 'location' => 'Ort',
|
||||||
|
'postal_code' => '00000', 'email' => 'e@example.com', 'start_date' => '2026-08-01', 'end_date' => '2026-08-05',
|
||||||
|
'early_bird_end' => '2026-07-01', 'registration_final_end' => '2026-07-20', 'early_bird_end_amount_increase' => 0,
|
||||||
|
'account_owner' => 'Owner', 'account_iban' => 'DE00', 'participation_fee_type' => 'FIXED',
|
||||||
|
'pay_per_day' => false, 'pay_direct' => false,
|
||||||
|
'short_registration' => true, 'swimming_permission_required' => $swimmingPermissionRequired,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($withFee) {
|
||||||
|
$fee = EventParticipationFee::create([
|
||||||
|
'tenant' => $this->tenant->slug, 'type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||||||
|
'name' => 'Teilnahme', 'description' => '', 'amount_standard' => 50.0,
|
||||||
|
'amount_reduced' => 30.0, 'amount_solidarity' => 70.0,
|
||||||
|
]);
|
||||||
|
$event->participation_fee_1 = $fee->id;
|
||||||
|
$event->save();
|
||||||
|
$event->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shortRequest(Event $event, array $overrides = []): ShortSignUpRequest
|
||||||
|
{
|
||||||
|
return new ShortSignUpRequest(
|
||||||
|
event: $event,
|
||||||
|
user_id: null,
|
||||||
|
firstname: $overrides['firstname'] ?? 'Hans',
|
||||||
|
lastname: $overrides['lastname'] ?? 'Muster',
|
||||||
|
nickname: $overrides['nickname'] ?? null,
|
||||||
|
// Volljährig, sofern nicht anders angegeben.
|
||||||
|
birthday: $overrides['birthday'] ?? new \DateTime('2000-01-01'),
|
||||||
|
email: $overrides['email'] ?? 'h@example.com',
|
||||||
|
phone: $overrides['phone'] ?? '123',
|
||||||
|
localGroupId: $overrides['localGroupId'] ?? null,
|
||||||
|
contactPerson: $overrides['contactPerson'] ?? null,
|
||||||
|
contactEmail: $overrides['contactEmail'] ?? null,
|
||||||
|
allergies: $overrides['allergies'] ?? null,
|
||||||
|
intolerances: $overrides['intolerances'] ?? null,
|
||||||
|
firstAidPermission: $overrides['firstAidPermission'] ?? null,
|
||||||
|
swimmingPermission: $overrides['swimmingPermission'] ?? null,
|
||||||
|
foto_socialmedia: true,
|
||||||
|
foto_print: false,
|
||||||
|
foto_webseite: false,
|
||||||
|
foto_partner: false,
|
||||||
|
foto_intern: false,
|
||||||
|
paymentMethod: $overrides['paymentMethod'] ?? PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION,
|
||||||
|
paymentOptions: [],
|
||||||
|
participationOptions: $overrides['participationOptions'] ?? [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_kurzanmeldung_fuellt_nicht_erhobene_pflichtfelder(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event))->execute();
|
||||||
|
|
||||||
|
$this->assertTrue($response->success, $response->message ?? '');
|
||||||
|
$participant = $response->participant;
|
||||||
|
|
||||||
|
$this->assertSame('Nicht erforderlich', $participant->address_1);
|
||||||
|
$this->assertSame('Nicht erforderlich', $participant->city);
|
||||||
|
$this->assertSame('00000', $participant->postcode);
|
||||||
|
$this->assertNull($participant->local_group);
|
||||||
|
$this->assertNull($participant->address_2);
|
||||||
|
$this->assertNull($participant->medications);
|
||||||
|
|
||||||
|
// Zeitraum und Mahlzeiten kommen aus der Veranstaltung.
|
||||||
|
$this->assertSame('2026-08-01', $participant->arrival_date->format('Y-m-d'));
|
||||||
|
$this->assertSame('2026-08-05', $participant->departure_date->format('Y-m-d'));
|
||||||
|
$this->assertSame(1, $participant->arrival_eating);
|
||||||
|
$this->assertSame(2, $participant->departure_eating);
|
||||||
|
|
||||||
|
// Gruppe und Beitragsstufe stehen fest.
|
||||||
|
$this->assertSame(ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, $participant->participation_type);
|
||||||
|
$this->assertSame('standard', $participant->fee_type);
|
||||||
|
$this->assertSame(50.0, $participant->amount->getAmount());
|
||||||
|
$this->assertSame(EatingHabit::EATING_HABIT_OMNIVOR, $participant->eating_habit);
|
||||||
|
|
||||||
|
// Volljährige brauchen keine Erlaubnisse.
|
||||||
|
$this->assertSame(SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, $participant->swimming_permission);
|
||||||
|
$this->assertSame(FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, $participant->first_aid_permission);
|
||||||
|
|
||||||
|
$this->assertSame(1, $participant->invoice_sequence);
|
||||||
|
$this->assertTrue($participant->foto_socialmedia);
|
||||||
|
$this->assertFalse($participant->foto_print);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_u18_ohne_abgefragte_badeerlaubnis_bekommt_keine(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent(swimmingPermissionRequired: false);
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event, [
|
||||||
|
'birthday' => new \DateTime('2015-01-01'),
|
||||||
|
'contactPerson' => 'Muster, Maria',
|
||||||
|
'contactEmail' => 'maria@example.com',
|
||||||
|
'firstAidPermission' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED,
|
||||||
|
]))->execute();
|
||||||
|
|
||||||
|
$this->assertTrue($response->success, $response->message ?? '');
|
||||||
|
$this->assertSame(SwimmingPermission::SWIMMING_PERMISSION_DENIED, $response->participant->swimming_permission);
|
||||||
|
$this->assertSame(EfzStatus::EFZ_STATUS_NOT_REQUIRED, $response->participant->efz_status);
|
||||||
|
$this->assertSame('Muster, Maria', $response->participant->contact_person);
|
||||||
|
$this->assertSame('maria@example.com', $response->participant->email_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_u18_uebernimmt_gewaehlte_badeerlaubnis(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event, [
|
||||||
|
'birthday' => new \DateTime('2015-01-01'),
|
||||||
|
'contactPerson' => 'Muster, Maria',
|
||||||
|
'contactEmail' => 'maria@example.com',
|
||||||
|
'firstAidPermission' => FirstAidPermission::FIRST_AID_PERMISSION_DENIED,
|
||||||
|
'swimmingPermission' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED,
|
||||||
|
]))->execute();
|
||||||
|
|
||||||
|
$this->assertTrue($response->success, $response->message ?? '');
|
||||||
|
$this->assertSame(SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, $response->participant->swimming_permission);
|
||||||
|
$this->assertSame(FirstAidPermission::FIRST_AID_PERMISSION_DENIED, $response->participant->first_aid_permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_u18_ohne_kontaktperson_wird_abgelehnt(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event, [
|
||||||
|
'birthday' => new \DateTime('2015-01-01'),
|
||||||
|
]))->execute();
|
||||||
|
|
||||||
|
$this->assertFalse($response->success);
|
||||||
|
$this->assertSame(0, EventParticipant::count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_ohne_teilnahmegruppe_wird_abgelehnt(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent(withFee: false);
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event))->execute();
|
||||||
|
|
||||||
|
$this->assertFalse($response->success);
|
||||||
|
$this->assertNotNull($response->message);
|
||||||
|
$this->assertSame(0, EventParticipant::count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_teilnahmeoptionen_werden_gespeichert(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$event->participation_options = [[
|
||||||
|
'key' => 'shirt',
|
||||||
|
'title' => 'T-Shirt',
|
||||||
|
'label' => 'T-Shirt-Größe',
|
||||||
|
'options' => [['value' => 'm', 'label' => 'M'], ['value' => 'l', 'label' => 'L']],
|
||||||
|
]];
|
||||||
|
$event->save();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event, [
|
||||||
|
'participationOptions' => ['shirt' => 'l'],
|
||||||
|
]))->execute();
|
||||||
|
|
||||||
|
$this->assertTrue($response->success, $response->message ?? '');
|
||||||
|
$options = $response->participant->selectedOptions()->get();
|
||||||
|
$this->assertCount(1, $options);
|
||||||
|
$this->assertSame('shirt', $options->first()->question_key);
|
||||||
|
$this->assertSame('L', $options->first()->value_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_unbeantwortete_teilnahmeoption_wird_abgelehnt(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$event->participation_options = [[
|
||||||
|
'key' => 'shirt',
|
||||||
|
'title' => 'T-Shirt',
|
||||||
|
'label' => 'T-Shirt-Größe',
|
||||||
|
'options' => [['value' => 'm', 'label' => 'M']],
|
||||||
|
]];
|
||||||
|
$event->save();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event))->execute();
|
||||||
|
|
||||||
|
$this->assertFalse($response->success);
|
||||||
|
$this->assertSame(0, EventParticipant::count());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createLocalGroup(string $slug, string $name): Tenant
|
||||||
|
{
|
||||||
|
return Tenant::create([
|
||||||
|
'slug' => $slug, 'name' => $name, 'email' => $slug . '@example.com',
|
||||||
|
'email_finance' => $slug . '-f@example.com', 'url' => $slug . '.local',
|
||||||
|
'account_name' => $name, 'account_iban' => 'DE00', 'account_bic' => 'XY',
|
||||||
|
'city' => 'Stadt', 'postcode' => '00000', 'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_einziger_stamm_wird_ohne_auswahl_zugeordnet(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$group = $this->createLocalGroup('stamm-a', 'Stamm A');
|
||||||
|
$event->localGroups()->attach($group->id);
|
||||||
|
$event->refresh();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event))->execute();
|
||||||
|
|
||||||
|
$this->assertTrue($response->success, $response->message ?? '');
|
||||||
|
$this->assertSame('stamm-a', $response->participant->local_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_bei_mehreren_staemmen_wird_die_auswahl_uebernommen(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$first = $this->createLocalGroup('stamm-a', 'Stamm A');
|
||||||
|
$second = $this->createLocalGroup('stamm-b', 'Stamm B');
|
||||||
|
$event->localGroups()->attach([$first->id, $second->id]);
|
||||||
|
$event->refresh();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event, [
|
||||||
|
'localGroupId' => $second->id,
|
||||||
|
]))->execute();
|
||||||
|
|
||||||
|
$this->assertTrue($response->success, $response->message ?? '');
|
||||||
|
$this->assertSame('stamm-b', $response->participant->local_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_bei_mehreren_staemmen_ist_die_auswahl_pflicht(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$first = $this->createLocalGroup('stamm-a', 'Stamm A');
|
||||||
|
$second = $this->createLocalGroup('stamm-b', 'Stamm B');
|
||||||
|
$event->localGroups()->attach([$first->id, $second->id]);
|
||||||
|
$event->refresh();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event))->execute();
|
||||||
|
|
||||||
|
$this->assertFalse($response->success);
|
||||||
|
$this->assertSame(0, EventParticipant::count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_nicht_teilnehmender_stamm_wird_abgelehnt(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$first = $this->createLocalGroup('stamm-a', 'Stamm A');
|
||||||
|
$second = $this->createLocalGroup('stamm-b', 'Stamm B');
|
||||||
|
$event->localGroups()->attach([$first->id, $second->id]);
|
||||||
|
$event->refresh();
|
||||||
|
|
||||||
|
// Stamm existiert, nimmt aber nicht an der Veranstaltung teil.
|
||||||
|
$foreign = $this->createLocalGroup('stamm-c', 'Stamm C');
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event, [
|
||||||
|
'localGroupId' => $foreign->id,
|
||||||
|
]))->execute();
|
||||||
|
|
||||||
|
$this->assertFalse($response->success);
|
||||||
|
$this->assertSame(0, EventParticipant::count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_resource_ueberlebt_kurzanmeldung(): void
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
|
||||||
|
$response = new ShortSignUpCommand($this->shortRequest($event))->execute();
|
||||||
|
$this->assertTrue($response->success, $response->message ?? '');
|
||||||
|
|
||||||
|
// Regressionswächter für Teilnehmerliste, Detailansicht und CSV-Export.
|
||||||
|
$array = $response->participant->toResource()->toArray(request());
|
||||||
|
|
||||||
|
$this->assertSame('Nicht im LV', $array['localgroup']);
|
||||||
|
$this->assertSame('Erteilt', $array['swimmingPermission']);
|
||||||
|
$this->assertSame('Zugestimmt', $array['extendedFirstAid']);
|
||||||
|
$this->assertSame('Omnivor', $array['eatingHabit']);
|
||||||
|
$this->assertSame('Teilnehmende', $array['participationType']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user