Basic signup for events
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CertificateOfConductionCheckCommand {
|
||||
function __construct(public CertificateOfConductionCheckRequest $request)
|
||||
{}
|
||||
|
||||
public function execute() : CertificateOfConductionCheckResponse {
|
||||
$response = new CertificateOfConductionCheckResponse();
|
||||
|
||||
|
||||
|
||||
$localGroup = str_replace('Stamm ', '', $this->request->participant->localGroup()->first()->name);
|
||||
|
||||
$apiResponse = Http::acceptJson()
|
||||
->asJson()
|
||||
->withoutVerifying()
|
||||
->post(env('COC_CHECK_URL'), [
|
||||
'firstName' => trim($this->request->participant->firstname),
|
||||
'lastName' => $this->request->participant->lastname,
|
||||
'nickname' => $this->request->participant->nickname,
|
||||
'address' => trim($this->request->participant->address_1 . ' ' . $this->request->participant->address_2),
|
||||
'zip' => $this->request->participant->zip,
|
||||
'city' => $this->request->participant->city,
|
||||
'birthday' => $this->request->participant->birthday->format('Y-m-d'),
|
||||
'email' => $this->request->participant->email_1,
|
||||
'localGroup' => $localGroup,
|
||||
'checkForDate' => $this->request->participant->departure_date->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
if (! $apiResponse->successful()) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$responseParsed = $apiResponse->json();
|
||||
|
||||
if (($responseParsed['status'] ?? null) !== '200/ok') {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->status = match ($responseParsed['efzStatus']) {
|
||||
'NOT_REQUIRED' => EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
||||
'CHECKED_VALID' => EfzStatus::EFZ_STATUS_CHECKED_VALID,
|
||||
'CHECKED_INVALID' => EfzStatus::EFZ_STATUS_CHECKED_INVALID,
|
||||
default => EfzStatus::EFZ_STATUS_NOT_CHECKED
|
||||
};
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class CertificateOfConductionCheckRequest {
|
||||
function __construct(public EventParticipant $participant)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
|
||||
class CertificateOfConductionCheckResponse {
|
||||
public string $status;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->status = EfzStatus::EFZ_STATUS_NOT_CHECKED;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ class SetParticipationFeesCommand {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function excetute() : SetParticipationFeesResponse {
|
||||
public function execute() : SetParticipationFeesResponse {
|
||||
$response = new SetParticipationFeesResponse();
|
||||
$this->cleanBefore();
|
||||
|
||||
@@ -19,7 +19,9 @@ class SetParticipationFeesCommand {
|
||||
'type' => $this->request->participationFeeFirst['type'],
|
||||
'name' => $this->request->participationFeeFirst['name'],
|
||||
'description' => $this->request->participationFeeFirst['description'],
|
||||
'amount' => $this->request->participationFeeFirst['amount']->getAmount()
|
||||
'amount_standard' => $this->request->participationFeeFirst['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeFirst['amount_reduced'] ? $this->request->participationFeeFirst['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeFirst['amount_solidarity'] ? $this->request->participationFeeFirst['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
|
||||
if ($this->request->participationFeeSecond !== null) {
|
||||
@@ -28,7 +30,9 @@ class SetParticipationFeesCommand {
|
||||
'type' => $this->request->participationFeeSecond['type'],
|
||||
'name' => $this->request->participationFeeSecond['name'],
|
||||
'description' => $this->request->participationFeeSecond['description'],
|
||||
'amount' => $this->request->participationFeeSecond['amount']->getAmount()
|
||||
'amount_standard' => $this->request->participationFeeSecond['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeSecond['amount_reduced'] ? $this->request->participationFeeSecond['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeSecond['amount_solidarity'] ? $this->request->participationFeeSecond['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
@@ -38,7 +42,9 @@ class SetParticipationFeesCommand {
|
||||
'type' => $this->request->participationFeeThird['type'],
|
||||
'name' => $this->request->participationFeeThird['name'],
|
||||
'description' => $this->request->participationFeeThird['description'],
|
||||
'amount' => $this->request->participationFeeThird['amount']->getAmount()
|
||||
'amount_standard' => $this->request->participationFeeThird['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeThird['amount_reduced'] ? $this->request->participationFeeThird['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeThird['amount_solidarity'] ? $this->request->participationFeeThird['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
@@ -48,7 +54,9 @@ class SetParticipationFeesCommand {
|
||||
'type' => $this->request->participationFeeFourth['type'],
|
||||
'name' => $this->request->participationFeeFourth['name'],
|
||||
'description' => $this->request->participationFeeFourth['description'],
|
||||
'amount' => $this->request->participationFeeFourth['amount']->getAmount()
|
||||
'amount_standard' => $this->request->participationFeeFourth['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeFourth['amount_reduced'] ? $this->request->participationFeeFourth['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeFourth['amount_solidarity'] ? $this->request->participationFeeFourth['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
|
||||
65
app/Domains/Event/Actions/SignUp/SignUpCommand.php
Normal file
65
app/Domains/Event/Actions/SignUp/SignUpCommand.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\ValueObjects\Age;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SignUpCommand {
|
||||
public function __construct(public SignUpRequest $request) {
|
||||
}
|
||||
|
||||
public function execute() : SignUpResponse {
|
||||
$response = new SignUpResponse();
|
||||
|
||||
$participantAge = new Age($this->request->birthday);
|
||||
|
||||
$response->participant = $this->request->event->participants()->create(
|
||||
[
|
||||
'tenant' => $this->request->event->tenant,
|
||||
'user_id' => $this->request->user_id,
|
||||
'identifier' => Str::random(10),
|
||||
'firstname' => $this->request->firstname,
|
||||
'lastname' => $this->request->lastname,
|
||||
'nickname' => $this->request->nickname,
|
||||
'participation_type' => $this->request->participationType,
|
||||
'local_group' => $this->request->localGroup->slug,
|
||||
'birthday' => $this->request->birthday,
|
||||
'address_1' => $this->request->address_1,
|
||||
'address_2' => $this->request->address_2,
|
||||
'postcode' => $this->request->postcode,
|
||||
'city' => $this->request->city,
|
||||
'email_1' => $this->request->email_1,
|
||||
'email_2' => $this->request->email_2,
|
||||
'phone_1' => $this->request->phone_1,
|
||||
'phone_2' => $this->request->phone_2,
|
||||
'contact_person' => $this->request->contact_person,
|
||||
'allergies' => $this->request->allergies,
|
||||
'intolerances' => $this->request->intolerances,
|
||||
'medications' => $this->request->medications,
|
||||
'tetanus_vaccination' => $this->request->tetanus_vaccination,
|
||||
'eating_habit' => $this->request->eating_habit,
|
||||
'swimming_permission' => $participantAge->isfullAged() ? 'SWIMMING_PERMISSION_ALLOWED' : $this->request->swimming_permission,
|
||||
'first_aid_permission' => $participantAge->isfullAged() ? 'FIRST_AID_PERMISSION_ALLOWED' : $this->request->first_aid_permission,
|
||||
'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_date' => $this->request->arrival,
|
||||
'departure_date' => $this->request->departure,
|
||||
'arrival_eating' => $this->request->arrival_eating,
|
||||
'departure_eating' => $this->request->departure_eating,
|
||||
'notes' => $this->request->notes,
|
||||
'amount' => $this->request->amount,
|
||||
'payment_purpose' => $this->request->event->name . ' - Beitrag ' . $this->request->firstname . ' ' . $this->request->lastname,
|
||||
'efz_status' => $participantAge->isfullAged() ? EfzStatus::EFZ_STATUS_NOT_CHECKED : EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
||||
]
|
||||
);
|
||||
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
50
app/Domains/Event/Actions/SignUp/SignUpRequest.php
Normal file
50
app/Domains/Event/Actions/SignUp/SignUpRequest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Models\Event;
|
||||
use App\Models\Tenant;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class SignUpRequest {
|
||||
function __construct(
|
||||
public Event $event,
|
||||
public ?int $user_id,
|
||||
public string $firstname,
|
||||
public string $lastname,
|
||||
public ?string $nickname,
|
||||
public string $participationType,
|
||||
public Tenant $localGroup,
|
||||
public DateTime $birthday,
|
||||
public string $address_1,
|
||||
public string $address_2,
|
||||
public string $postcode,
|
||||
public string $city,
|
||||
public string $email_1,
|
||||
public ?string $phone_1,
|
||||
public ?string $email_2,
|
||||
public ?string $phone_2,
|
||||
public ?string $contact_person,
|
||||
public ?string $allergies,
|
||||
public ?string $intolerances,
|
||||
public ?string $medications,
|
||||
public ?DateTime $tetanus_vaccination,
|
||||
public string $eating_habit,
|
||||
public ?string $swimming_permission,
|
||||
public ?string $first_aid_permission,
|
||||
public bool $foto_socialmedia,
|
||||
public bool $foto_print,
|
||||
public bool $foto_webseite,
|
||||
public bool $foto_partner,
|
||||
public bool $foto_intern,
|
||||
public DateTime $arrival,
|
||||
public DateTime $departure,
|
||||
public int $arrival_eating,
|
||||
public int $departure_eating,
|
||||
public ?string $notes,
|
||||
public Amount $amount
|
||||
) {
|
||||
}
|
||||
}
|
||||
16
app/Domains/Event/Actions/SignUp/SignUpResponse.php
Normal file
16
app/Domains/Event/Actions/SignUp/SignUpResponse.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class SignUpResponse {
|
||||
public bool $success;
|
||||
public ?EventParticipant $participant;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->participant = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,8 @@ class UpdateEventCommand {
|
||||
$this->request->event->alcoholics_age = $this->request->alcoholicsAge;
|
||||
$this->request->event->support_per_person = $this->request->supportPerPerson;
|
||||
$this->request->event->support_flat = $this->request->flatSupport;
|
||||
$this->request->event->send_weekly_report = $this->request->sendWeeklyReports;
|
||||
$this->request->event->registration_allowed = $this->request->registrationAllowed;
|
||||
$this->request->event->save();
|
||||
|
||||
$this->request->event->resetAllowedEatingHabits();
|
||||
|
||||
22
app/Domains/Event/Controllers/AvailableEventsController.php
Normal file
22
app/Domains/Event/Controllers/AvailableEventsController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class AvailableEventsController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request) : Response {
|
||||
$events = [];
|
||||
foreach ($this->events->getAvailable(false) as $event) {
|
||||
$events[] = $event->toResource()->toArray($request);
|
||||
};
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Event/ListAvailable', ['events' => $events]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class CreateController extends CommonController {
|
||||
if ($wasSuccessful) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'event' => new EventResource($costUnitUpdateRequest->event)->toArray()
|
||||
'event' => new EventResource($costUnitUpdateRequest->event)->toArray($request)
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
|
||||
@@ -23,9 +23,9 @@ class DetailsController extends CommonController {
|
||||
return new InertiaProvider('Event/Details', ['event' => $event])->render();
|
||||
}
|
||||
|
||||
public function summary(int $eventId) : JsonResponse {
|
||||
public function summary(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
return response()->json(['event' => new EventResource($event)->toArray()]);
|
||||
return response()->json(['event' => $event->toResource()->toArray($request)]);
|
||||
}
|
||||
|
||||
public function updateCommonSettings(int $eventId, Request $request) : JsonResponse {
|
||||
@@ -78,7 +78,9 @@ class DetailsController extends CommonController {
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||||
'name' => 'Teilnehmer',
|
||||
'description' => $request->input('pft_1_description'),
|
||||
'amount' => Amount::fromString($request->input('pft_1_amount'))
|
||||
'amount_standard' => Amount::fromString($request->input('pft_1_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_1_amount_reduced') ? Amount::fromString($request->input('pft_1_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_1_amount_solidarity') ? Amount::fromString($request->input('pft_1_amount_solidarity')) : null
|
||||
];
|
||||
|
||||
$participationFeeRequest = new SetParticipationFeesRequest($event, $participationFeeFirst);
|
||||
@@ -88,7 +90,9 @@ class DetailsController extends CommonController {
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_TEAM,
|
||||
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Kernteam' : 'Solidaritätsbeitrag',
|
||||
'description' => $request->input('pft_2_description'),
|
||||
'amount' => Amount::fromString($request->input('pft_2_amount'))
|
||||
'amount_standard' => Amount::fromString($request->input('pft_2_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_2_amount_reduced') ? Amount::fromString($request->input('pft_2_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_2_amount_solidarity') ? Amount::fromString($request->input('pft_2_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
@@ -97,7 +101,9 @@ class DetailsController extends CommonController {
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_VOLUNTEER,
|
||||
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Unterstützende' : 'Reduzierter Beitrag',
|
||||
'description' => $event->participation_fee_type !== ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY ? $request->input('pft_3_description') : 'Nach Verfügbarkeit',
|
||||
'amount' => Amount::fromString($request->input('pft_3_amount'))
|
||||
'amount_standard' => Amount::fromString($request->input('pft_3_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_3_amount_reduced') ? Amount::fromString($request->input('pft_3_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_3_amount_solidarity') ? Amount::fromString($request->input('pft_3_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
@@ -106,12 +112,14 @@ class DetailsController extends CommonController {
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_OTHER,
|
||||
'name' => 'Sonstige',
|
||||
'description' => $request->input('pft_4_description'),
|
||||
'amount' => Amount::fromString($request->input('pft_4_amount'))
|
||||
'amount_standard' => Amount::fromString($request->input('pft_4_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_4_amount_reduced') ? Amount::fromString($request->input('pft_4_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_4_amount_solidarity') ? Amount::fromString($request->input('pft_4_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
$participationFeeCommand = new SetParticipationFeesCommand($participationFeeRequest);
|
||||
$response = $participationFeeCommand->excetute();
|
||||
$response = $participationFeeCommand->execute();
|
||||
|
||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||
}
|
||||
|
||||
171
app/Domains/Event/Controllers/SignupController.php
Normal file
171
app/Domains/Event/Controllers/SignupController.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?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\SignUp\SignUpCommand;
|
||||
use App\Domains\Event\Actions\SignUp\SignUpRequest;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Resources\EventResource;
|
||||
use App\Resources\UserResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use http\Env\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class SignupController extends CommonController {
|
||||
public function __invoke(int $eventId, Request $request) {
|
||||
$availableEvents = [];
|
||||
foreach ($this->events->getAvailable(false) as $event) {
|
||||
$availableEvents[] = $event->toResource()->toArray($request);
|
||||
};
|
||||
|
||||
$event = $this->events->getById($eventId, false)?->toResource()->toArray($request);
|
||||
|
||||
$participantData = [
|
||||
'firstname' => '',
|
||||
'lastname' => '',
|
||||
];
|
||||
|
||||
if (auth()->check()) {
|
||||
$user = new UserResource(auth()->user())->toArray($request);
|
||||
|
||||
$participantData = [
|
||||
'id' => $user['id'],
|
||||
'firstname' => $user['firstname'],
|
||||
'lastname' => $user['lastname'],
|
||||
'nickname' => $user['nickname'],
|
||||
'email' => $user['email'],
|
||||
'phone' => $user['phone'],
|
||||
'postcode' => $user['postcode'],
|
||||
'city' => $user['city'],
|
||||
'address_1' => $user['address_1'],
|
||||
'address_2' => $user['address_2'],
|
||||
'birthday' => $user['birthday'],
|
||||
'localGroup' => $user['localGroup'],
|
||||
'allergies' => $user['allergies'],
|
||||
'intolerances' => $user['intolerances'],
|
||||
'eating_habit' => $user['eating_habits'],
|
||||
'medications' => $user['medications'],
|
||||
'tetanusVaccination' => $user['tetanus_vaccination'],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Event/Signup', [
|
||||
'event' => $event,
|
||||
'availableEvents' => $availableEvents,
|
||||
'localGroups' => $event['contributingLocalGroups'],
|
||||
'participantData' => $participantData,
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function signUp(int $eventId, Request $request) {
|
||||
$event = $this->events->getById($eventId, false);
|
||||
$eventResource = $event->toResource();
|
||||
$registrationData = $request->input('registration_data');
|
||||
|
||||
$arrival = \DateTime::createFromFormat('Y-m-d', $registrationData['arrival']);
|
||||
$departure = \DateTime::createFromFormat('Y-m-d', $registrationData['departure']);
|
||||
$tetanusVaccination = $registrationData['tetanusVaccination'] ? \DateTime::createFromFormat('Y-m-d', $registrationData['tetanusVaccination']) : null;
|
||||
|
||||
// Steps:
|
||||
// 1. Check, if bereits angemeldet
|
||||
|
||||
|
||||
//
|
||||
$amount = $eventResource->calculateAmount(
|
||||
$registrationData['participationType'],
|
||||
$registrationData['beitrag'],
|
||||
$arrival,
|
||||
$departure
|
||||
);
|
||||
|
||||
$signupRequest = new SignUpRequest(
|
||||
$event,$registrationData['userId'],
|
||||
$registrationData['vorname'],
|
||||
$registrationData['nachname'],
|
||||
$registrationData['pfadiname'],
|
||||
$registrationData['participationType'],
|
||||
Tenant::findOrFail($registrationData['localGroup']),
|
||||
\DateTime::createFromFormat('Y-m-d', $registrationData['geburtsdatum']),
|
||||
$registrationData['address1'],
|
||||
$registrationData['address2'],
|
||||
$registrationData['plz'],
|
||||
$registrationData['ort'],
|
||||
$registrationData['email_1'],
|
||||
$registrationData['telefon_1'],
|
||||
$registrationData['email_2'],
|
||||
$registrationData['telefon_2'],
|
||||
$registrationData['ansprechpartner'],
|
||||
$registrationData['allergien'],
|
||||
$registrationData['intolerances'],
|
||||
$registrationData['medikamente'],
|
||||
$tetanusVaccination,
|
||||
$registrationData['essgewohnheit'],
|
||||
$registrationData['badeerlaubnis'],
|
||||
$registrationData['first_aid'],
|
||||
$registrationData['foto']['socialmedia'],
|
||||
$registrationData['foto']['print'],
|
||||
$registrationData['foto']['webseite'],
|
||||
$registrationData['foto']['partner'],
|
||||
$registrationData['foto']['intern'],
|
||||
$arrival,
|
||||
$departure,
|
||||
$registrationData['anreise_essen'],
|
||||
$registrationData['abreise_essen'],
|
||||
$registrationData['anmerkungen'],
|
||||
$amount
|
||||
);
|
||||
|
||||
$signupCommand = new SignUpCommand($signupRequest);
|
||||
$signupResponse = $signupCommand->execute();
|
||||
|
||||
// 4. Addons registrieren
|
||||
|
||||
|
||||
$certificateOfConductionCheckRequest = new CertificateOfConductionCheckRequest($signupResponse->participant);
|
||||
$certificateOfConductionCheckCommand = new CertificateOfConductionCheckCommand($certificateOfConductionCheckRequest);
|
||||
$certificateOfConductionCheckResponse = $certificateOfConductionCheckCommand->execute();
|
||||
|
||||
$signupResponse->participant->efz_status = $certificateOfConductionCheckResponse->status;
|
||||
$signupResponse->participant->save();
|
||||
|
||||
// 6. E-Mail senden & Bestätigung senden
|
||||
|
||||
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'participant' => $signupResponse->participant->toResource()->toArray($request),
|
||||
'status' => 'success',
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
dd($eventId, $registrationData, $amount);
|
||||
}
|
||||
|
||||
public function calculateAmount(int $eventId, Request $request, bool $forDisplay = true) : JsonResponse | float {
|
||||
$event = $this->events->getById($eventId, false)->toResource();
|
||||
|
||||
return response()->json(['amount' =>
|
||||
$event->calculateAmount(
|
||||
$request->input('participationType'),
|
||||
$request->input('beitrag'),
|
||||
\DateTime::createFromFormat('Y-m-d', $request->input('arrival')),
|
||||
\DateTime::createFromFormat('Y-m-d', $request->input('departure'))
|
||||
)->toString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Domains\Event\Controllers\CreateController;
|
||||
use App\Domains\Event\Controllers\DetailsController;
|
||||
use App\Domains\Event\Controllers\SignupController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@@ -9,6 +10,9 @@ Route::prefix('api/v1')
|
||||
->group(function () {
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('event')->group(function () {
|
||||
Route::post('{eventId}/calculate-amount', [SignupController::class, 'calculateAmount']);
|
||||
Route::post('{eventId}/signup', [SignupController::class, 'signUp']);
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::post('/create', [CreateController::class, 'doCreate']);
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Event\Controllers\AvailableEventsController;
|
||||
use App\Domains\Event\Controllers\CreateController;
|
||||
use App\Domains\Event\Controllers\DetailsController;
|
||||
use App\Domains\Event\Controllers\SignupController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('event')->group(function () {
|
||||
Route::get('/available-events', AvailableEventsController::class);
|
||||
|
||||
|
||||
Route::get('/{eventId}/signup', SignupController::class);
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/new', CreateController::class);
|
||||
Route::get('/details/{eventId}', DetailsController::class);
|
||||
|
||||
102
app/Domains/Event/Views/Partials/AvailableEvents.vue
Normal file
102
app/Domains/Event/Views/Partials/AvailableEvents.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script setup>
|
||||
|
||||
import ShadowedBox from "../../../../Views/Components/ShadowedBox.vue";
|
||||
|
||||
const props = defineProps({
|
||||
events: Array,
|
||||
})
|
||||
|
||||
console.log(props.events)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 95%; margin: 20px auto;">
|
||||
|
||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
||||
Aktuell sind keine Veranstaltungen verfügbar.
|
||||
</div>
|
||||
|
||||
<shadowed-box
|
||||
v-for="event in props.events"
|
||||
:key="event.id"
|
||||
style="padding: 24px; margin-bottom: 20px;"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px;">
|
||||
<div>
|
||||
<h2 style="margin: 0 0 4px 0; font-size: 1.25rem;">{{ event.name }}</h2>
|
||||
<span style="color: #6b7280; font-size: 0.9rem;">{{ event.postalCode }} {{ event.location }}</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="event.registrationAllowed"
|
||||
style="background: #d1fae5; color: #065f46; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
||||
>
|
||||
Anmeldung offen
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
style="background: #fee2e2; color: #991b1b; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
||||
>
|
||||
Anmeldung geschlossen
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr style="margin: 16px 0; border: none; border-top: 1px solid #e5e7eb;" />
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Zeitraum</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.eventBegin }} – {{ event.eventEnd }} ({{ event.duration }} Tage)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Veranstaltungsort</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.postalCode }} {{ event.location }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Frühbuchen bis</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.earlyBirdEnd.formatted }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Anmeldeschluss</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.registrationFinalEnd.formatted }}</td>
|
||||
</tr>
|
||||
<tr v-if="event.email">
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Kontakt</th>
|
||||
<td style="padding: 6px 0;">
|
||||
<a :href="'mailto:' + event.email" style="color: #2563eb;">{{ event.email }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
||||
<a
|
||||
:href="'/event/' + event.id + '/signup'"
|
||||
style="
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
opacity: 1;
|
||||
transition: background-color 0.2s;
|
||||
"
|
||||
:style="{ opacity: event.registrationAllowed ? '1' : '0.5', pointerEvents: event.registrationAllowed ? 'auto' : 'none' }"
|
||||
>
|
||||
Zur Anmeldung →
|
||||
</a>
|
||||
</div>
|
||||
</shadowed-box>
|
||||
|
||||
</div><div style="width: 95%; margin: 20px auto;">
|
||||
|
||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
||||
Aktuell sind keine Veranstaltungen verfügbar.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -16,19 +16,27 @@
|
||||
const errors = reactive({})
|
||||
const formData = reactive({
|
||||
"pft_1_active": true,
|
||||
"pft_1_amount": props.event.participationFee_1.amount,
|
||||
"pft_1_amount_standard": props.event.participationFee_1.amount_standard_edit,
|
||||
"pft_1_amount_reduced": props.event.participationFee_1.amount_reduced_edit,
|
||||
"pft_1_amount_solidarity": props.event.participationFee_1.amount_solidarity_edit,
|
||||
"pft_1_description": props.event.participationFee_1.description,
|
||||
|
||||
"pft_2_active": props.event.participationFee_2.active,
|
||||
"pft_2_amount": props.event.participationFee_2.amount,
|
||||
"pft_2_amount_standard": props.event.participationFee_2.amount_standard_edit,
|
||||
"pft_2_amount_reduced": props.event.participationFee_2.amount_reduced_edit,
|
||||
"pft_2_amount_solidarity": props.event.participationFee_2.amount_solidarity_edit,
|
||||
"pft_2_description": props.event.participationFee_2.description,
|
||||
|
||||
"pft_3_active": props.event.participationFee_3.active,
|
||||
"pft_3_amount": props.event.participationFee_3.amount,
|
||||
"pft_3_amount_standard": props.event.participationFee_3.amount_standard_edit,
|
||||
"pft_3_amount_reduced": props.event.participationFee_3.amount_reduced_edit,
|
||||
"pft_3_amount_solidarity": props.event.participationFee_3.amount_solidarity_edit,
|
||||
"pft_3_description": props.event.participationFee_3.description,
|
||||
|
||||
"pft_4_active": props.event.participationFee_4.active,
|
||||
"pft_4_amount": props.event.participationFee_4.amount,
|
||||
"pft_4_amount_standard": props.event.participationFee_4.amount_standard_edit,
|
||||
"pft_4_amount_reduced": props.event.participationFee_4.amount_reduced_edit,
|
||||
"pft_4_amount_solidarity": props.event.participationFee_4.amount_solidarity_edit,
|
||||
"pft_4_description": props.event.participationFee_4.description,
|
||||
|
||||
'maxAmount': props.event.maxAmount,
|
||||
@@ -38,17 +46,17 @@
|
||||
function validateInput() {
|
||||
var noErrors = true;
|
||||
|
||||
if (formData.pft_1_description === '' && !props.event.solidarityPayment) {
|
||||
if (formData.pft_1_description === '') {
|
||||
errors.pft_1_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
if (formData.pft_2_description === '' && formData.pft_2_active && !props.event.solidarityPayment) {
|
||||
if (formData.pft_2_description === '' && formData.pft_2_active) {
|
||||
errors.pft_2_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
if (formData.pft_3_description === '' && formData.pft_3_active && !props.event.solidarityPayment) {
|
||||
if (formData.pft_3_description === '' && formData.pft_3_active) {
|
||||
errors.pft_3_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||
noErrors = false;
|
||||
}
|
||||
@@ -73,19 +81,27 @@
|
||||
body: {
|
||||
event_id: props.event.id,
|
||||
pft_1_active: formData.pft_1_active,
|
||||
pft_1_amount: formData.pft_1_amount,
|
||||
pft_1_amount_standard: formData.pft_1_amount_standard,
|
||||
pft_1_amount_reduced: formData.pft_1_amount_reduced,
|
||||
pft_1_amount_solidarity: formData.pft_1_amount_solidarity,
|
||||
pft_1_description: formData.pft_1_description,
|
||||
|
||||
pft_2_active: formData.pft_2_active,
|
||||
pft_2_amount: formData.pft_2_amount,
|
||||
pft_2_amount_standard: formData.pft_2_amount_standard,
|
||||
pft_2_amount_reduced: formData.pft_2_amount_reduced,
|
||||
pft_2_amount_solidarity: formData.pft_2_amount_solidarity,
|
||||
pft_2_description: formData.pft_2_description,
|
||||
|
||||
pft_3_active: formData.pft_3_active,
|
||||
pft_3_amount: formData.pft_3_amount,
|
||||
pft_3_amount_standard: formData.pft_3_amount_standard,
|
||||
pft_3_amount_reduced: formData.pft_3_amount_reduced,
|
||||
pft_3_amount_solidarity: formData.pft_3_amount_solidarity,
|
||||
pft_3_description: formData.pft_3_description,
|
||||
|
||||
pft_4_active: formData.pft_4_active,
|
||||
pft_4_amount: formData.pft_4_amount,
|
||||
pft_4_amount_standard: formData.pft_4_amount_standard,
|
||||
pft_4_amount_reduced: formData.pft_4_amount_reduced,
|
||||
pft_4_amount_solidarity: formData.pft_4_amount_solidarity,
|
||||
pft_4_description: formData.pft_4_description,
|
||||
|
||||
maxAmount: formData.maxAmount,
|
||||
@@ -114,29 +130,41 @@
|
||||
<template>
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td>Aktiv</td>
|
||||
<td>Preisgruppe</td>
|
||||
<td>Betrag</td>
|
||||
<td>Beschreibung</td>
|
||||
<td><h4>Aktiv</h4></td>
|
||||
<td><h4>Preisgruppe</h4></td>
|
||||
<td v-if="!props.event.solidarityPayment"><h4>Betrag</h4></td>
|
||||
<td v-else><h4>Regulärer Beitrag</h4></td>
|
||||
<td v-if="props.event.solidarityPayment"><h4>Reduzierter Beitrag</h4></td>
|
||||
<td v-if="props.event.solidarityPayment"><h4>Solidaritätsbeitrag</h4></td>
|
||||
<td><h4>Beschreibung</h4></td>
|
||||
</tr>
|
||||
<tr style="height: 65px; vertical-align: top">
|
||||
<td>
|
||||
<input type="checkbox" v-model="formData.participationFeeType_1" checked disabled/>
|
||||
</td>
|
||||
<td v-if="props.event.solidarityPayment">
|
||||
Regulärer Beitrag
|
||||
</td>
|
||||
<td v-else>
|
||||
<td>
|
||||
Teilnehmende
|
||||
</td>
|
||||
<td>
|
||||
<AmountInput v-model="formData.pft_1_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount)" />
|
||||
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label v-else> Euro Gesamt</label>
|
||||
<AmountInput v-model="formData.pft_1_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment">
|
||||
<AmountInput v-model="formData.pft_1_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment">
|
||||
<AmountInput v-model="formData.pft_1_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input v-if="!props.event.solidarityPayment" type="text" v-model="formData.pft_1_description" style="width: 300px;" />
|
||||
<label v-else></label>
|
||||
<input type="text" v-model="formData.pft_1_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_1_description" />
|
||||
</td>
|
||||
</tr>
|
||||
@@ -145,24 +173,32 @@
|
||||
<td>
|
||||
<input id="use_pft_2" type="checkbox" v-model="formData.pft_2_active" :checked="formData.pft_2_active" />
|
||||
</td>
|
||||
<td v-if="props.event.solidarityPayment">
|
||||
<label for="use_pft_2" style="cursor: default">
|
||||
Solidaritätsbeitrag
|
||||
</label>
|
||||
</td>
|
||||
<td v-else>
|
||||
<td>
|
||||
<label for="use_pft_2" style="cursor: default">
|
||||
Kernteam
|
||||
</label>
|
||||
</td>
|
||||
<td v-if="formData.pft_2_active">
|
||||
<AmountInput v-model="formData.pft_2_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount)" />
|
||||
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label v-else> Euro Gesamt</label>
|
||||
<AmountInput v-model="formData.pft_2_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_2_active">
|
||||
<AmountInput v-model="formData.pft_2_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_2_active">
|
||||
<AmountInput v-model="formData.pft_2_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
|
||||
<td v-if="formData.pft_2_active">
|
||||
<input v-if="!props.event.solidarityPayment" type="text" v-model="formData.pft_2_description" style="width: 300px;" />
|
||||
<label v-else></label>
|
||||
<input type="text" v-model="formData.pft_2_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_2_description" />
|
||||
</td>
|
||||
</tr>
|
||||
@@ -171,29 +207,36 @@
|
||||
<td>
|
||||
<input id="use_pft_3" type="checkbox" v-model="formData.pft_3_active" :checked="formData.pft_3_active" />
|
||||
</td>
|
||||
<td v-if="props.event.solidarityPayment">
|
||||
<label for="use_pft_3" style="cursor: default">
|
||||
Reduzierter Beitrag
|
||||
</label>
|
||||
</td>
|
||||
<td v-else>
|
||||
<td>
|
||||
<label for="use_pft_3" style="cursor: default">
|
||||
Unterstützende
|
||||
</label>
|
||||
</td>
|
||||
<td v-if="formData.pft_3_active">
|
||||
<AmountInput v-model="formData.pft_3_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount)" />
|
||||
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label v-else> Euro Gesamt</label>
|
||||
<AmountInput v-model="formData.pft_3_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_3_active">
|
||||
<AmountInput v-model="formData.pft_3_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_3_active">
|
||||
<AmountInput v-model="formData.pft_3_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="formData.pft_3_active">
|
||||
<input v-if="!props.event.solidarityPayment" type="text" v-model="formData.pft_3_description" style="width: 300px;" />
|
||||
<label v-else>Nach Verfügbarkeit</label>
|
||||
<input type="text" v-model="formData.pft_3_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_3_description" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="height: 65px; vertical-align: top;" v-if="!props.event.solidarityPayment">
|
||||
<tr style="height: 65px; vertical-align: top;">
|
||||
<td>
|
||||
<input id="use_pft_4" type="checkbox" v-model="formData.pft_4_active" :checked="formData.pft_4_active" />
|
||||
</td>
|
||||
@@ -203,10 +246,23 @@
|
||||
</label>
|
||||
</td>
|
||||
<td v-if="formData.pft_4_active">
|
||||
<AmountInput v-model="formData.pft_4_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount)" />
|
||||
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label v-else> Euro Gesamt</label>
|
||||
<AmountInput v-model="formData.pft_4_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_4_active">
|
||||
<AmountInput v-model="formData.pft_4_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_4_active">
|
||||
<AmountInput v-model="formData.pft_4_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="formData.pft_4_active">
|
||||
<input type="text" v-model="formData.pft_4_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_4_description" />
|
||||
|
||||
@@ -43,7 +43,11 @@ const steps = [
|
||||
<template>
|
||||
<div>
|
||||
<!-- Nach Submit -->
|
||||
<SubmitSuccess v-if="submitResult?.type === 'success'" :data="submitResult.data" />
|
||||
<SubmitSuccess
|
||||
v-if="submitResult?.status === 'success'"
|
||||
:participant="submitResult?.participant"
|
||||
:event="event"
|
||||
/>
|
||||
<SubmitAlreadyExists v-else-if="submitResult?.type === 'exists'" :data="submitResult.data" />
|
||||
|
||||
<template v-else>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup>
|
||||
defineProps({ data: Object })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="padding: 20px 0;">
|
||||
<h3>{{ data.nicename }}</h3>
|
||||
<p>{{ data.text_1 }}</p>
|
||||
<p>{{ data.text_2 }}</p>
|
||||
<a :href="data.email_link" style="color: #2563eb;">{{ data.email_text }}</a>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
participant: Object,
|
||||
event: Object,
|
||||
})
|
||||
|
||||
console.log(props.event)
|
||||
console.log(props.participant)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="padding: 20px 0;">
|
||||
<h3>Hallo {{ props.participant.nicename }},</h3>
|
||||
<p>Vielen Dank für dein Interesse an der Veranstaltung {{event.name}}<br />Wir haben folgende Daten erhalten:</p>
|
||||
|
||||
<table class="form-table" style="margin-bottom: 20px;">
|
||||
<tr><td>Anreise:</td><td>{{ props.participant.arrival }}</td></tr>
|
||||
<tr><td>Abreise:</td><td>{{ props.participant.departure }}</td></tr>
|
||||
<tr><td>Teilnahmegruppe:</td><td>{{ props.participant.participation_group }}</td></tr>
|
||||
</table>
|
||||
|
||||
<div v-if="props.participant.efz_status === 'NOT_CHECKED'" style="font-weight: bold; color: #b45309; margin-bottom: 20px;">
|
||||
Dein erweitertes Führungszeugnis konnte nicht automatisch geprüft werden. Bitte kontaktiere die Aktionsleitung.
|
||||
</div>
|
||||
<div v-else-if="props.participant.efz_status === 'CHECKED_INVALID'" style="font-weight: bold; color: #dc2626; margin-bottom: 20px;">
|
||||
Du hast noch kein erweitertes Führungszeugnis hinterlegt. Bitte reiche es umgehend ein.
|
||||
</div>
|
||||
|
||||
<template v-if="props.participant.needs_payment">
|
||||
<table class="form-table" style="margin-bottom: 16px;">
|
||||
<tr>
|
||||
<td>Kontoinhaber:</td><td>{{ props.event.accountOwner }}</td>
|
||||
<td rowspan="4" style="vertical-align: top; padding-left: 20px;" v-if="props.participant.identifier !== ''">
|
||||
<img :src="'/print-girocode/' + props.participant.identifier" alt="GiroCode" style="max-width: 180px;" />
|
||||
<span style="width: 180px; text-align: center; display: block; font-size: 0.8rem; color: #6b7280; margin-top: 4px;">Giro-Code</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>IBAN:</td><td>{{ props.event.accountIban }}</td></tr>
|
||||
<tr><td>Verwendungszweck:</td><td>{{ props.participant.payment_purpose }}</td></tr>
|
||||
<tr><td>Betrag:</td><td><strong>{{ props.participant.amount_left_string }}</strong></td></tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Bitte beachte, dass deine Anmeldung erst nach Zahlungseiongang vollständig ist.<br />
|
||||
Wenn dieser nicht bis zum {{ props.event.registrationFinalEnd.formatted }} erfolgt, kann deine Anmeldung storniert werden.<br /><br />
|
||||
Solltest du den Beitrag bis zu diesem Datum nicht oder nur teilweise überweisen können, kontaktiere bitte die Aktionsleitung, damit wir eine gemeinsame Lösiung finden können.
|
||||
</p>
|
||||
|
||||
|
||||
</template>
|
||||
<p v-else>
|
||||
Du musst keinen Beitrag überweisen. Deine Anmeldung ist bestätigt.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Du erhältst innerhalb von 2 Stunden eine E-Mail mit weiteren Informationen.<br />
|
||||
Kontakt: <a :href="'mailto:' + props.event.email" style="color: #2563eb;">{{ props.event.email }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,98 @@
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
export function useSignupForm(event, participantData) {
|
||||
const currentStep = ref(1)
|
||||
const submitting = ref(false)
|
||||
const summaryLoading = ref(false)
|
||||
const submitResult = ref(null) // null | { type: 'success'|'exists', data: {} }
|
||||
|
||||
const selectedAddons = reactive({})
|
||||
console.log(participantData)
|
||||
|
||||
const formData = reactive({
|
||||
eatingHabit: 'EATING_HABIT_VEGAN',
|
||||
userId: participantData.id,
|
||||
eventId: event.id,
|
||||
vorname: participantData.firstname ?? '',
|
||||
nachname: participantData.lastname ?? '',
|
||||
pfadiname: participantData.nickname ?? '',
|
||||
localGroup: participantData.localGroup ?? '-1',
|
||||
geburtsdatum: participantData.birthday ?? '',
|
||||
address1: participantData.address_1 ?? '',
|
||||
address2: participantData.address_2 ?? '',
|
||||
plz: participantData.postcode ?? '',
|
||||
ort: participantData.city ?? '',
|
||||
telefon_1: participantData.phone ?? '',
|
||||
email_1: participantData.email ?? '',
|
||||
participationType: '',
|
||||
ansprechpartner: '',
|
||||
telefon_2: '',
|
||||
email_2: '',
|
||||
badeerlaubnis: '-1',
|
||||
first_aid: '-1',
|
||||
participant_group: '',
|
||||
beitrag: 'regular',
|
||||
arrival: event.eventBeginInternal?.split('T')[0] ?? '',
|
||||
departure: event.eventEndInternal?.split('T')[0] ?? '',
|
||||
anreise_essen: '1',
|
||||
abreise_essen: '2',
|
||||
foto: { socialmedia: false, print: false, webseite: false, partner: false, intern: false },
|
||||
allergien: participantData.allergies ?? '',
|
||||
intolerances: participantData.intolerances ?? '',
|
||||
medikamente: participantData.medications ?? '',
|
||||
tetanusVaccination: participantData.tetanusVaccination ?? '',
|
||||
essgewohnheit: 'vegetarian',
|
||||
anmerkungen: '',
|
||||
summary_information_correct: false,
|
||||
summary_accept_terms: false,
|
||||
legal_accepted: false,
|
||||
payment: false,
|
||||
})
|
||||
|
||||
const summaryAmount = ref('')
|
||||
|
||||
const goToStep = async (step) => {
|
||||
if (step === 9) {
|
||||
summaryLoading.value = true
|
||||
summaryAmount.value = ''
|
||||
try {
|
||||
const res = await axios.post('/api/v1/event/' + event.id + '/calculate-amount', {
|
||||
arrival: formData.arrival,
|
||||
departure: formData.departure,
|
||||
event_id: event.id,
|
||||
participation_group: formData.participant_group,
|
||||
selected_amount: formData.beitrag,
|
||||
addons: selectedAddons,
|
||||
participationType: formData.participationType,
|
||||
beitrag: formData.beitrag,
|
||||
})
|
||||
summaryAmount.value = res.data.amount
|
||||
} finally {
|
||||
summaryLoading.value = false
|
||||
}
|
||||
}
|
||||
currentStep.value = step
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!formData.summary_information_correct || !formData.summary_accept_terms || !formData.legal_accepted || !formData.payment) {
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
const res = await axios.post('/api/v1/event/'+ event.id + '/signup', {
|
||||
addons: selectedAddons,
|
||||
registration_data: { ...formData },
|
||||
})
|
||||
submitResult.value = {
|
||||
status: res.data.status,
|
||||
participant: res.data.participant,
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return { currentStep, goToStep, formData, selectedAddons, submit, submitting, submitResult, summaryLoading, summaryAmount }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
const props = defineProps({ formData: Object, event: Object, selectedAddons: Object })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Solidarbeitrag-Auswahl -->
|
||||
<div v-if="event.solidarityPayment" style="margin-bottom: 20px;">
|
||||
<h3>Beitrag</h3>
|
||||
<label v-if="event.participationFee_1?.active" style="display: block; margin-bottom: 8px;">
|
||||
<input type="radio" v-model="formData.beitrag" value="reduced" />
|
||||
{{ event.participationFee_1.name }} ({{ event.participationFee_1.amount }} €)
|
||||
</label>
|
||||
<label style="display: block; margin-bottom: 8px;">
|
||||
<input type="radio" v-model="formData.beitrag" value="regular" />
|
||||
{{ event.participationFee_2?.name ?? 'Regulärer Beitrag' }} ({{ event.participationFee_2?.amount }} €)
|
||||
</label>
|
||||
<label v-if="event.participationFee_3?.active" style="display: block; margin-bottom: 8px;">
|
||||
<input type="radio" v-model="formData.beitrag" value="social" />
|
||||
{{ event.participationFee_3.name }} ({{ event.participationFee_3.amount }} €)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Addons -->
|
||||
<div v-if="event.addons?.length > 0">
|
||||
<h3>Zusatzoptionen</h3>
|
||||
<div v-for="addon in event.addons" :key="addon.id" style="margin-bottom: 16px; padding: 12px; background: #f8fafc; border-radius: 8px;">
|
||||
<label style="display: flex; gap: 12px; cursor: pointer;">
|
||||
<input type="checkbox" v-model="selectedAddons[addon.id]" style="margin-top: 4px;" />
|
||||
<span>
|
||||
<strong>{{ addon.name }}</strong>
|
||||
<span style="display: block; color: #6b7280; font-size: 0.875rem;">Betrag: {{ addon.amount }}</span>
|
||||
<span style="display: block; color: #374151; font-size: 0.875rem; margin-top: 4px;">{{ addon.description }}</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button type="button" class="btn-secondary" @click="emit('back', 5)">← Zurück</button>
|
||||
<button type="button" class="btn-primary" @click="emit('next', 7)">Weiter →</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
103
app/Domains/Event/Views/Partials/SignUpForm/steps/StepAge.vue
Normal file
103
app/Domains/Event/Views/Partials/SignUpForm/steps/StepAge.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
|
||||
<script setup>
|
||||
defineProps({ event: Object })
|
||||
const emit = defineEmits(['next'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3 style="margin: 0 0 6px 0; color: #111827;">Wer nimmt teil?</h3>
|
||||
<p style="margin: 0 0 24px 0; color: #6b7280; font-size: 0.95rem;">Bitte wähle deine Altersgruppe aus.</p>
|
||||
|
||||
<div style="display: flex; gap: 20px; flex-wrap: wrap;">
|
||||
|
||||
<!-- Kind / Jugendliche:r -->
|
||||
<div class="age-card" @click="emit('next', 2)">
|
||||
<div class="age-card__badge">
|
||||
<img :src="'/images/children.png'" alt="Abzeichen Kind" class="age-card__img" onerror="this.style.display='none'" />
|
||||
<div class="age-card__badge-fallback">👦</div>
|
||||
</div>
|
||||
<div class="age-card__body">
|
||||
<h4 class="age-card__title">Mein Kind anmelden:</h4>
|
||||
<p class="age-card__desc">Mein Kind ist <strong>jünger als {{ event.alcoholicsAge }} Jahre.</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Erwachsene:r -->
|
||||
<div class="age-card" @click="emit('next', 3)">
|
||||
<div class="age-card__badge">
|
||||
<img :src="'/images/adults.png'" alt="Abzeichen Erwachsene" class="age-card__img" onerror="this.style.display='none'" />
|
||||
<div class="age-card__badge-fallback">🧑</div>
|
||||
</div>
|
||||
<div class="age-card__body">
|
||||
<h4 class="age-card__title">Mich selbst anmelden</h4>
|
||||
<p class="age-card__desc">Ich bin <strong>{{ event.alcoholicsAge }} Jahre oder älter</strong>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.age-card {
|
||||
flex: 1;
|
||||
min-width: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: #f8fafc;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 28px 20px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, box-shadow 0.15s, transform 0.1s;
|
||||
text-align: center;
|
||||
}
|
||||
.age-card:hover {
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 4px 16px rgba(37, 99, 235, 0.12);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.age-card__badge {
|
||||
position: relative;
|
||||
width: 350px;
|
||||
height: 200px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.age-card__img {
|
||||
width: 350px;
|
||||
height: 200px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.age-card__badge-fallback {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 3rem;
|
||||
background: #e0f2fe;
|
||||
border-radius: 50%;
|
||||
}
|
||||
/* Fallback ausblenden wenn Bild geladen ist */
|
||||
.age-card__img:not([style*="display:none"]) + .age-card__badge-fallback {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.age-card__body { display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
||||
.age-card__title { margin: 0; font-size: 1.1rem; font-weight: 700; color: #111827; }
|
||||
.age-card__desc { margin: 0; font-size: 0.9rem; color: #374151; }
|
||||
.age-card__hint { margin: 0; font-size: 0.8rem; color: #6b7280; }
|
||||
.age-card__cta {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
padding: 6px 18px;
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
border-radius: 999px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
const props = defineProps({ formData: Object, event: Object })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>Allergien & Ernährung</h3>
|
||||
<table class="form-table">
|
||||
<tr><td>Allergien:</td><td><input type="text" v-model="props.formData.allergien" /></td></tr>
|
||||
<tr>
|
||||
<td>
|
||||
Letzte Teranus-Impfung:
|
||||
<span style="display: block; font-size: 0.8rem; color: #6b7280; margin-top: 4px;">Lass das Feld frei, wenn die Information nicht vorliegt oder du diese nicht mitteilen willst</span>
|
||||
</td><td><input type="date" v-model="props.formData.tetanusVaccination" /></td></tr>
|
||||
|
||||
|
||||
|
||||
|
||||
<tr><td>Unverträglichkeiten:</td><td><input type="text" v-model="props.formData.intolerances" /></td></tr>
|
||||
<tr>
|
||||
<td>
|
||||
Medikamente:<br />
|
||||
<span style="display: block; font-size: 0.8rem; color: #6b7280; margin-top: 4px;">Bitte in ausreichender Menge mitbringen</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" v-model="props.formData.medikamente" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ernährungsweise:</td>
|
||||
<td>
|
||||
<select v-model="props.formData.eatingHabit">
|
||||
<option
|
||||
v-for="eatingHabit in props.event.eatingHabits"
|
||||
:value="eatingHabit.data.slug">{{eatingHabit.data.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Anmerkungen:</td>
|
||||
<td><textarea rows="5" v-model="props.formData.anmerkungen" style="width: 100%;"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="btn-row">
|
||||
<button type="button" class="btn-secondary" @click="emit('back', 7)">← Zurück</button>
|
||||
<button type="button" class="btn-primary" @click="emit('next', 9)">Weiter →</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,81 @@
|
||||
<script setup>
|
||||
const props = defineProps({ formData: Object, event: Object })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
|
||||
const next = () => {
|
||||
const arrival = new Date(props.formData.arrival)
|
||||
arrival.setHours(0,0,0,0);
|
||||
const departure = new Date(props.formData.departure)
|
||||
const eventStart = new Date(props.event.eventBeginInternal)
|
||||
const eventEnd = new Date(props.event.eventEndInternal)
|
||||
|
||||
arrival.setHours(0,0,0,0);
|
||||
departure.setHours(0,0,0,0);
|
||||
eventStart.setHours(0,0,0,0);
|
||||
eventEnd.setHours(0,0,0,0);
|
||||
|
||||
|
||||
if (arrival < eventStart) {
|
||||
alert('Bitte gültige Anreise angeben innerhalb des Veranstaltungszeitraums wählen.')
|
||||
return
|
||||
}
|
||||
|
||||
if (arrival > eventEnd) {
|
||||
alert('Bitte gültige Abreise angeben innerhalb des Veranstaltungszeitraums wählen.')
|
||||
return
|
||||
}
|
||||
|
||||
if (departure < arrival) {
|
||||
alert('Abreise kann niht vor der Anreise liegen. Bitte korrigieren.')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
const hasAddons = (props.event.addons?.length > 0) || props.event.solidarityPayment
|
||||
emit('next', 5)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
const back = () => emit('back', 3)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>An- und Abreise</h3>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td>Anreise:</td>
|
||||
<td>
|
||||
<input type="date" v-model="formData.arrival" /><br />
|
||||
<select v-model="formData.anreise_essen" style="margin-top: 6px;">
|
||||
<option value="1">Vor dem Abendessen</option>
|
||||
<option value="2">Vor dem Mittagessen</option>
|
||||
<option value="3">Vor dem Frühstück</option>
|
||||
<option value="4">Keine Mahlzeit</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Abreise:</td>
|
||||
<td>
|
||||
<input type="date" v-model="formData.departure" /><br />
|
||||
<select v-model="formData.abreise_essen" style="margin-top: 6px;">
|
||||
<option value="1">Nach dem Frühstück</option>
|
||||
<option value="2">Nach dem Mittagessen</option>
|
||||
<option value="3">Nach dem Abendessen</option>
|
||||
<option value="4">Keine Mahlzeit</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="btn-row">
|
||||
<button type="button" class="btn-secondary" @click="back">← Zurück</button>
|
||||
<button type="button" class="btn-primary" @click="next">Weiter →</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,125 @@
|
||||
<script setup>
|
||||
import ErrorText from "../../../../../../Views/Components/ErrorText.vue";
|
||||
import {reactive} from "vue";
|
||||
|
||||
const props = defineProps({ formData: Object, event: Object })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
|
||||
const errors = reactive({
|
||||
ansprechpartner: '',
|
||||
telefon_2: '',
|
||||
email_2: '',
|
||||
badeerlaubnis: '',
|
||||
first_aid: '',
|
||||
})
|
||||
|
||||
const next = () => {
|
||||
errors.ansprechpartner = ''
|
||||
errors.telefon_2 = ''
|
||||
errors.email_2 = ''
|
||||
errors.badeerlaubnis = ''
|
||||
errors.first_aid = ''
|
||||
|
||||
let hasError = false
|
||||
|
||||
if (!props.formData.ansprechpartner) {
|
||||
errors.ansprechpartner = 'Bitte eine Kontaktperson angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.telefon_2) {
|
||||
errors.telefon_2 = 'Bitte eine Telefonnummer angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.email_2) {
|
||||
errors.email_2 = 'Bitte eine E-Mail-Adresse angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (props.formData.badeerlaubnis === '-1') {
|
||||
errors.badeerlaubnis = 'Bitte triff eine Entscheidung. Bist du dir unsicher, kontaktiere bitte die Aktionsleitung'
|
||||
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 (hasError) {
|
||||
return
|
||||
}
|
||||
|
||||
emit('next', 3)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>Kontaktperson</h3>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td>Name (Nachname, Vorname):</td>
|
||||
<td>
|
||||
<input type="text" v-model="formData.ansprechpartner" />
|
||||
<ErrorText :message="errors.ansprechpartner" />
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telefon:</td>
|
||||
<td>
|
||||
<input type="text" v-model="formData.telefon_2" />
|
||||
<ErrorText :message="errors.telefon_2" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E-Mail:</td>
|
||||
<td>
|
||||
<input type="text" v-model="formData.email_2" />
|
||||
<ErrorText :message="errors.email_2" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Badeerlaubnis:</td>
|
||||
<td>
|
||||
<select v-model="formData.badeerlaubnis">
|
||||
<option value="-1">Bitte wählen</option>
|
||||
|
||||
<option
|
||||
v-for="swimmingPermission in props.event.swimmingPermissions"
|
||||
:value="swimmingPermission.slug">{{swimmingPermission.name}}</option>
|
||||
</select>
|
||||
<ErrorText :message="errors.badeerlaubnis" />
|
||||
</td>
|
||||
</tr>
|
||||
<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 props.event.firstAidPermissions"
|
||||
: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>
|
||||
<td></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<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,124 @@
|
||||
<script setup>
|
||||
import {reactive} from "vue";
|
||||
|
||||
const props = defineProps({ formData: Object, localGroups: Array })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
|
||||
const errors = reactive({
|
||||
vorname: '',
|
||||
nachname: '',
|
||||
geburtsdatum: '',
|
||||
localGroup: '',
|
||||
address1: '',
|
||||
plz: '',
|
||||
ort: '',
|
||||
telefon_1: '',
|
||||
email_1: '',
|
||||
})
|
||||
|
||||
const next = () => {
|
||||
errors.vorname = ''
|
||||
errors.nachname = ''
|
||||
errors.geburtsdatum = ''
|
||||
errors.localGroup = ''
|
||||
errors.address1 = ''
|
||||
errors.plz = ''
|
||||
errors.ort = ''
|
||||
errors.telefon_1 = ''
|
||||
errors.email_1 = ''
|
||||
|
||||
let hasError = false
|
||||
|
||||
if (!props.formData.vorname) {
|
||||
errors.vorname = 'Bitte den Vornamen angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.nachname) {
|
||||
errors.nachname = 'Bitte den Nachnamen angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.geburtsdatum) {
|
||||
errors.geburtsdatum = 'Bitte das Geburtsdatum angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (props.formData.localGroup === '-1') {
|
||||
errors.localGroup = 'Bitte den Stamm auswählen.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.address1) {
|
||||
errors.address1 = 'Bitte die Adresse angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.plz) {
|
||||
errors.plz = 'Bitte die Postleitzahl angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.ort) {
|
||||
errors.ort = 'Bitte den Ort angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (!props.formData.email_1) {
|
||||
errors.email_1 = 'Bitte eine E-Mail-Adresse angeben.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
return
|
||||
}
|
||||
emit('next', 4)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>Persönliche Daten</h3>
|
||||
<table class="form-table">
|
||||
<tr><td>Vorname:</td><td><input type="text" v-model="props.formData.vorname" /></td></tr>
|
||||
<tr><td>Nachname:</td><td><input type="text" v-model="props.formData.nachname" /></td></tr>
|
||||
<tr><td>Pfadiname:</td><td><input type="text" v-model="props.formData.pfadiname" /></td></tr>
|
||||
<tr>
|
||||
<td>Stamm:</td>
|
||||
<td>
|
||||
<select v-model="props.formData.localGroup">
|
||||
<option value="-1">Bitte wählen</option>
|
||||
<option v-for="lg in localGroups" :key="lg.id" :value="lg.id">{{ lg.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>Geburtsdatum:</td><td><input type="date" v-model="props.formData.geburtsdatum" /></td></tr>
|
||||
<tr>
|
||||
<td>Adresse:</td>
|
||||
<td>
|
||||
<input type="text" v-model="props.formData.address1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<input type="text" v-model="props.formData.address2" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PLZ, Ort:</td>
|
||||
<td>
|
||||
<input maxlength="5" type="text" v-model="props.formData.plz" style="width: 100px; margin-right: 8px;" />
|
||||
<input type="text" v-model="props.formData.ort" style="width: calc(100% - 110px);" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>Telefon:</td><td><input type="text" v-model="props.formData.telefon_1" /></td></tr>
|
||||
<tr><td>E-Mail:</td><td><input type="text" v-model="props.formData.email_1" /></td></tr>
|
||||
<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,33 @@
|
||||
<script setup>
|
||||
const props = defineProps({ formData: Object, event: Object })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
|
||||
const acceptAll = () => {
|
||||
Object.keys(props.formData.foto).forEach(k => props.formData.foto[k] = true)
|
||||
emit('next', 8)
|
||||
}
|
||||
|
||||
const back = () => {
|
||||
const hasAddons = (props.event.addons?.length > 0) || props.event.solidarityPayment
|
||||
emit('back', hasAddons ? 6 : 5)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>Fotoerlaubnis</h3>
|
||||
<div v-for="[key, label] in [['socialmedia','Social Media'],['print','Printmedien'],['webseite','Webseite'],['partner','Partnerorganisationen'],['intern','Interne Zwecke']]"
|
||||
:key="key"
|
||||
style="margin-bottom: 10px;">
|
||||
<label style="display: flex; gap: 10px; cursor: pointer;">
|
||||
<input type="checkbox" v-model="formData.foto[key]" />
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button type="button" class="btn-secondary" @click="back">← Zurück</button>
|
||||
<button type="button" class="btn-primary" style="background: #059669;" @click="acceptAll">Alle akzeptieren & weiter</button>
|
||||
<button type="button" class="btn-primary" @click="emit('next', 8)">Weiter →</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,130 @@
|
||||
<script setup>
|
||||
import { watch } from "vue";
|
||||
|
||||
const props = defineProps({ formData: Object, event: Object })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
|
||||
watch(
|
||||
() => props.formData.participationType,
|
||||
(value) => {
|
||||
if (!value) {
|
||||
props.formData.beitrag = 'standard'
|
||||
return
|
||||
}
|
||||
|
||||
props.formData.beitrag = 'standard'
|
||||
}
|
||||
)
|
||||
|
||||
const nextStep = () => {
|
||||
const hasAddons = (props.event.addons?.length ?? 0) > 0
|
||||
emit('next', hasAddons ? 6 : 7)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3 v-if="event.solidarityPayment">Solidarbeitrag – Teilnahmegruppe</h3>
|
||||
<h3 v-else>Ich nehme teil als ...</h3>
|
||||
|
||||
<table style="width: 100%;">
|
||||
<tr
|
||||
v-for="participationType in props.event.participationTypes"
|
||||
:key="participationType.type.slug"
|
||||
style="vertical-align: top;"
|
||||
>
|
||||
<td style="width: 50px; padding-top: 6px;">
|
||||
<input
|
||||
:id="participationType.type.slug"
|
||||
v-model="props.formData.participationType"
|
||||
type="radio"
|
||||
:value="participationType.type.slug"
|
||||
/>
|
||||
</td>
|
||||
<td style="padding-bottom: 16px;">
|
||||
<label :for="participationType.type.slug" style="line-height: 1.5; font-weight: 600; cursor: pointer;">
|
||||
{{ participationType.type.name }}
|
||||
</label><br />
|
||||
|
||||
<label
|
||||
:for="participationType.type.slug"
|
||||
style="line-height: 1.5; padding-left: 15px; font-style: italic; color: #606060; cursor: pointer;"
|
||||
>
|
||||
{{ participationType.description }}
|
||||
</label>
|
||||
|
||||
<div
|
||||
v-if="props.formData.participationType === participationType.type.slug"
|
||||
style="margin-top: 10px; margin-left: 15px; padding: 12px 14px; background: #f8fafc; border-left: 3px solid #2563eb; border-radius: 6px;"
|
||||
>
|
||||
<template
|
||||
v-if="participationType.amount_reduced !== null || participationType.amount_solidarity !== null"
|
||||
>
|
||||
<div style="margin-bottom: 8px; font-size: 0.95rem; font-weight: 600; color: #374151;">
|
||||
Beitrag auswählen
|
||||
</div>
|
||||
|
||||
<label style="display: block; margin-bottom: 8px; cursor: pointer;">
|
||||
<input type="radio" v-model="props.formData.beitrag" value="standard" />
|
||||
Standardbeitrag
|
||||
<span style="color: #606060;">
|
||||
({{ participationType.amount_standard.readable }}
|
||||
<template v-if="props.event.payPerDay">/ Tag</template>
|
||||
<template v-else>Gesamt</template>)
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label
|
||||
v-if="participationType.amount_reduced !== null"
|
||||
style="display: block; margin-bottom: 8px; cursor: pointer;"
|
||||
>
|
||||
<input type="radio" v-model="props.formData.beitrag" value="reduced" />
|
||||
Reduzierter Beitrag
|
||||
<span style="color: #606060;">
|
||||
({{ participationType.amount_reduced.readable }}
|
||||
<template v-if="props.event.payPerDay">/ Tag</template>
|
||||
<template v-else>Gesamt</template>)
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label
|
||||
v-if="participationType.amount_solidarity !== null"
|
||||
style="display: block; margin-bottom: 0; cursor: pointer;"
|
||||
>
|
||||
<input type="radio" v-model="props.formData.beitrag" value="solidarity" />
|
||||
Solidaritätsbeitrag
|
||||
<span style="color: #606060;">
|
||||
({{ participationType.amount_solidarity.readable }}
|
||||
<template v-if="props.event.payPerDay">/ Tag</template>
|
||||
<template v-else>Gesamt</template>)
|
||||
</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div style="font-size: 0.9rem; color: #606060;">
|
||||
Standardbeitrag:
|
||||
<strong>{{ participationType.amount_standard.readable }}</strong>
|
||||
<template v-if="props.event.payPerDay">/ Tag</template>
|
||||
<template v-else>Gesamt</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="btn-row">
|
||||
<button type="button" class="btn-secondary" @click="emit('back', 3)">← Zurück</button>
|
||||
<button
|
||||
type="button"
|
||||
v-if="props.formData.participationType"
|
||||
class="btn-primary"
|
||||
@click="nextStep"
|
||||
>
|
||||
Weiter →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
import {format, parseISO} from "date-fns";
|
||||
|
||||
const props = defineProps({
|
||||
formData: Object,
|
||||
event: Object,
|
||||
summaryAmount: String,
|
||||
summaryLoading: Boolean,
|
||||
submitting: Boolean,
|
||||
})
|
||||
const emit = defineEmits(['back', 'submit'])
|
||||
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return ''
|
||||
return format(parseISO(dateString), 'dd.MM.yyyy')
|
||||
}
|
||||
</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>Veranstaltung:</td><td><strong>{{ event.name }}</strong></td></tr>
|
||||
<tr><td>Anreise:</td><td>{{ formatDate(formData.arrival) }}</td></tr>
|
||||
<tr><td>Abreise:</td><td>{{ formatDate(formData.departure) }}</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 style="display: flex; gap: 10px; cursor: pointer;">
|
||||
<input type="checkbox" v-model="formData.payment" />
|
||||
Ich bestätige, den Betrag von <strong>{{ summaryAmount }}</strong> zu überweisen.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button type="button" class="btn-secondary" @click="emit('back', 8)">← Zurück</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
:disabled="!formData.summary_information_correct || !formData.summary_accept_terms || !formData.legal_accepted || !formData.payment || submitting"
|
||||
style="background: #059669;"
|
||||
>
|
||||
{{ submitting ? 'Wird gesendet…' : 'Jetzt anmelden ✓' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
31
app/Http/Controllers/GiroCodeGetController.php
Normal file
31
app/Http/Controllers/GiroCodeGetController.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
use App\Providers\GiroCodeProvider;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GiroCodeGetController {
|
||||
public function __invoke(string $participantToken, Request $request)
|
||||
{
|
||||
$participant = EventParticipant::where(['identifier' => $participantToken])->first();
|
||||
if (null === $participant) {
|
||||
return response()->json(['message' => 'Participant not found'], 404);
|
||||
}
|
||||
|
||||
$amount = $participant->amount;
|
||||
$amount->subtractAmount($participant->amount_paid);
|
||||
|
||||
$girocodeProvider = new GiroCodeProvider(
|
||||
$participant->event()->first()->account_owner,
|
||||
$participant->event()->first()->account_iban,
|
||||
$participant->amount->getAmount(),
|
||||
$participant->payment_purpose
|
||||
);
|
||||
|
||||
return response($girocodeProvider->create(), 200,
|
||||
['Content-Type' => 'image/png']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,14 @@ use App\Casts\AmountCast;
|
||||
use App\Enumerations\EatingHabit;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\RelationModels\EventParticipationFee;
|
||||
use App\Resources\EventResource;
|
||||
use App\Scopes\CommonModel;
|
||||
use App\Scopes\InstancedModel;
|
||||
use DateTime;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @property string $tenant
|
||||
@@ -53,6 +55,7 @@ class Event extends InstancedModel
|
||||
'tenant',
|
||||
'cost_unit_id',
|
||||
'name',
|
||||
'identifier',
|
||||
'location',
|
||||
'postal_code',
|
||||
'email',
|
||||
@@ -169,4 +172,5 @@ class Event extends InstancedModel
|
||||
public function participants() : hasMany {
|
||||
return $this->hasMany(EventParticipant::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class EventParticipant extends InstancedModel
|
||||
'tenant',
|
||||
'event_id',
|
||||
'user_id',
|
||||
'identifier',
|
||||
|
||||
'firstname',
|
||||
'lastname',
|
||||
@@ -42,6 +43,8 @@ class EventParticipant extends InstancedModel
|
||||
|
||||
'allergies',
|
||||
'intolerances',
|
||||
'medications',
|
||||
'tetanus_vaccination',
|
||||
'eating_habits',
|
||||
'swimming_permission',
|
||||
'first_aid_permission',
|
||||
@@ -60,12 +63,14 @@ class EventParticipant extends InstancedModel
|
||||
'notes',
|
||||
'amount',
|
||||
'amount_paid',
|
||||
'payment_purpose',
|
||||
'efz_status',
|
||||
'unregistered_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'birthday' => 'datetime',
|
||||
'tetanus_vaccination' => 'datetime',
|
||||
'arrival_date' => 'datetime',
|
||||
'departure_date' => 'datetime',
|
||||
'unregistered_at' => 'datetime',
|
||||
@@ -131,5 +136,21 @@ class EventParticipant extends InstancedModel
|
||||
return $this->belongsTo(EfzStatus::class, 'efz_status', 'slug');
|
||||
}
|
||||
|
||||
public function getOfficialName() : string {
|
||||
return sprintf('%1$s %2$s', $this->firstname, $this->lastname);
|
||||
}
|
||||
|
||||
public function getFullname() : string {
|
||||
return sprintf('%1$1s %2$s %3$s',
|
||||
$this->firstname,
|
||||
$this->lastname,
|
||||
$this->nickname !== null ? '(' . $this->nickname . ')' : '',
|
||||
)
|
||||
|>trim(...);
|
||||
}
|
||||
|
||||
public function getNicename() : string {
|
||||
return $this->nickname ?? $this->firstname;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ class User extends Authenticatable
|
||||
'medications',
|
||||
'allergies',
|
||||
'intolerances',
|
||||
'tetanus_vaccination',
|
||||
'eating_habits',
|
||||
'swimming_permission',
|
||||
'first_aid_permission',
|
||||
|
||||
27
app/Providers/GiroCodeProvider.php
Normal file
27
app/Providers/GiroCodeProvider.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
|
||||
class GiroCodeProvider {
|
||||
function __construct(private string $recipient, private string $iban, private float $amount, private string $paymentPurpose) {}
|
||||
|
||||
public function create() {
|
||||
$data = "BCD\n".
|
||||
"001\n".
|
||||
"1\n".
|
||||
"SCT\n".
|
||||
"\n".
|
||||
"$this->recipient\n".
|
||||
"$this->iban\n".
|
||||
"EUR$this->amount\n".
|
||||
"\n".
|
||||
"$this->paymentPurpose";
|
||||
|
||||
return
|
||||
QrCode::format('png')->size(300)->generate($data)
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ class GlobalDataProvider {
|
||||
}
|
||||
|
||||
$navigation['common'][] = ['url' => '/invoice/new', 'display' => 'Neue Abrechnung'];
|
||||
$navigation['common'][] = ['url' => '/available-events', 'display' => 'Verfügbare Veranstaltungen'];
|
||||
$navigation['common'][] = ['url' => '/event/available-events', 'display' => 'Verfügbare Veranstaltungen'];
|
||||
|
||||
return $navigation;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\RelationModels;
|
||||
|
||||
use App\Casts\AmountCast;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Models\Tenant;
|
||||
use App\Scopes\CommonModel;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -12,14 +13,19 @@ class EventParticipationFee extends CommonModel
|
||||
{
|
||||
protected $table = 'event_participation_fees';
|
||||
|
||||
protected $fillable = ['tenant', 'type', 'name', 'description', 'amount'];
|
||||
protected $casts = ['amount' => AmountCast::class];
|
||||
protected $fillable = ['tenant', 'type', 'name', 'description', 'amount_standard', 'amount_reduced', 'amount_solidarity', 'active'];
|
||||
protected $casts = [
|
||||
'amount_standard' => AmountCast::class,
|
||||
'amount_reduced' => AmountCast::class,
|
||||
'amount_solidarity' => AmountCast::class,
|
||||
|
||||
];
|
||||
|
||||
public function tenant() : BelongsTo {
|
||||
return $this->belongsTo(Tenant::class, 'tenant', 'slug');
|
||||
}
|
||||
|
||||
public function type() : BelongsTo {
|
||||
return $this->belongsTo(ParticipationFeeType::class, 'type', 'slug');
|
||||
return $this->belongsTo(ParticipationType::class, 'type', 'slug');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\CostUnit;
|
||||
use App\Models\Event;
|
||||
use App\Resources\CostUnitResource;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Psy\Readline\Hoa\EventBucket;
|
||||
|
||||
class EventRepository {
|
||||
public function listAll() : array {
|
||||
@@ -16,6 +17,17 @@ class EventRepository {
|
||||
|
||||
}
|
||||
|
||||
public function getAvailable(bool $accessCheck = true) : array {
|
||||
return $this->getEventsByCriteria([
|
||||
'archived' => false,
|
||||
'registration_allowed' => true
|
||||
],$accessCheck);
|
||||
}
|
||||
|
||||
public function getForRegistration(int $id) : ?Event {
|
||||
$events = self::getEventsByCriteria(['id' => $id, 'registration_allowed' => true]);
|
||||
}
|
||||
|
||||
public function getById(int $id, bool $accessCheck = true) : ?Event {
|
||||
$events = self::getEventsByCriteria(['id' => $id], $accessCheck);
|
||||
return $events[0] ?? null;
|
||||
@@ -50,7 +62,7 @@ class EventRepository {
|
||||
/** @var Event $event */
|
||||
foreach (Event::where($criteria)->get() as $event) {
|
||||
|
||||
if ($event->eventManagers()->where('user_id', $user->id)->exists() || $canSeeAll || !$accessCheck) {
|
||||
if ($canSeeAll || !$accessCheck || $event->eventManagers()->where('user_id', $user->id)->exists()) {
|
||||
$visibleEvents[] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
33
app/Resources/EventParticipantResource.php
Normal file
33
app/Resources/EventParticipantResource.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Resources;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EventParticipantResource extends JsonResource
|
||||
{
|
||||
function __construct(EventParticipant $participant)
|
||||
{
|
||||
parent::__construct($participant);
|
||||
}
|
||||
|
||||
public function toArray($request) : array
|
||||
{
|
||||
$event = $this->resource->event;
|
||||
|
||||
|
||||
|
||||
return array_merge(
|
||||
$this->resource->toArray(),
|
||||
[
|
||||
'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct,
|
||||
'nicename' => $this->resource->getNicename(),
|
||||
'arrival' => $this->resource->arrival_date->format('d.m.Y'),
|
||||
'departure' => $this->resource->departure_date->format('d.m.Y'),
|
||||
'amount_left_string' => $this->resource->amount->toString(),
|
||||
]
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,20 @@ namespace App\Resources;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Models\Event;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EventResource {
|
||||
class EventResource extends JsonResource{
|
||||
private Event $event;
|
||||
|
||||
public function __construct(Event $event) {
|
||||
$this->event = $event;
|
||||
}
|
||||
|
||||
public function toArray() : array {
|
||||
$duration = $this->event->end_date->diff($this->event->start_date)->d + 1;
|
||||
|
||||
public function toArray(Request $request) : array
|
||||
{
|
||||
$duration = $this->event->end_date->diff($this->event->start_date)->days + 1;
|
||||
|
||||
$returnArray = [
|
||||
'id' => $this->event->id,
|
||||
@@ -25,13 +28,31 @@ class EventResource {
|
||||
'email' => $this->event->email,
|
||||
'accountOwner' => $this->event->account_owner,
|
||||
'accountIban' => $this->event->account_iban,
|
||||
'accountOwner' => $this->event->account_owner,
|
||||
'accountIban' => $this->event->account_iban,
|
||||
'alcoholicsAge' => $this->event->alcoholics_age,
|
||||
'sendWeeklyReports' => $this->event->send_weekly_report,
|
||||
'registrationAllowed' => $this->event->registration_allowed,
|
||||
'earlyBirdEnd' => ['internal' => $this->event->early_bird_end->format('Y-m-d'), 'formatted' => $this->event->early_bird_end->format('d.m.Y')],
|
||||
'registrationFinalEnd' => ['internal' => $this->event->registration_final_end->format('Y-m-d'), 'formatted' => $this->event->registration_final_end->format('d.m.Y')],
|
||||
'refundAfterEarlyBirdEnd' => 100 - $this->event->early_bird_end_amount_increase,
|
||||
];
|
||||
|
||||
$returnArray['swimmingPermissions'] = \App\Enumerations\SwimmingPermission::query()
|
||||
->get()
|
||||
->map(fn ($permission) => [
|
||||
'slug' => $permission->slug,
|
||||
'name' => $permission->name,
|
||||
])
|
||||
->toArray();
|
||||
|
||||
$returnArray['firstAidPermissions'] = \App\Enumerations\FirstAidPermission::query()
|
||||
->get()
|
||||
->map(fn ($permission) => [
|
||||
'slug' => $permission->slug,
|
||||
'name' => $permission->name,
|
||||
])
|
||||
->toArray();
|
||||
|
||||
|
||||
$returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true);
|
||||
@@ -58,52 +79,122 @@ class EventResource {
|
||||
$returnArray['managers'] = $this->event->eventManagers()->get()->map(fn($user) => new UserResource($user))->toArray();
|
||||
|
||||
$returnArray['supportPersonCalced'] = $this->event->support_per_person->toString();
|
||||
$returnArray['contributingLocalGroups'] = $this->event->localGroups()->get()->map(fn($localGroup) => new LocalGroupResource($localGroup))->toArray();
|
||||
$returnArray['contributingLocalGroups'] = $this->event->localGroups
|
||||
->map(fn ($localGroup) => new LocalGroupResource($localGroup)->toArray($request))
|
||||
->toArray();
|
||||
|
||||
$returnArray['eatingHabits'] = $this->event->eatingHabits()->get()->map(
|
||||
fn($eatingHabit) => new EatingHabitResource($eatingHabit))->toArray();
|
||||
|
||||
|
||||
$returnArray['participationTypes'] = [];
|
||||
|
||||
$multiplier = $this->getMultiplier();
|
||||
|
||||
for ($i = 1; $i <= 4; $i++) {
|
||||
$returnArray['participationFee_' . $i] = [
|
||||
'active' => false,
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'amount' => '0,00',
|
||||
'type' => null
|
||||
'amount_standard' => null,
|
||||
'amount_reduced' => null,
|
||||
'amount_solidarity' => null,
|
||||
'type' => null,
|
||||
];
|
||||
|
||||
if ($this->event->{'participation_fee_' . $i} === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$participationFee = $this->event->{'participationFee' . $i}()->first();
|
||||
|
||||
$returnArray['participationFee_' . $i] = [
|
||||
$feeType = [
|
||||
'active' => true,
|
||||
'amount' => $this->event->{'participationFee' . $i}()->first()->amount->getFormattedAmount(),
|
||||
'name' => $this->event->{'participationFee' . $i}->first()->name,
|
||||
'description' => $this->event->{'participationFee' . $i}()->first()->description,
|
||||
'type' => $this->event->{'participationFee' . $i}->first()->type
|
||||
'amount_standard_edit' => $participationFee->amount_standard->getFormattedAmount(),
|
||||
'amount_standard' => [
|
||||
'internal' => [
|
||||
'amount' => $participationFee->amount_standard->getAmount(),
|
||||
'currency' => $participationFee->amount_standard->getCurrency(),
|
||||
],
|
||||
'readable' => $participationFee->amount_standard->multiply($multiplier)->toString(),
|
||||
],
|
||||
'amount_reduced_edit' => $participationFee->amount_reduced === null ? null : $participationFee->amount_reduced->getFormattedAmount(),
|
||||
'amount_reduced' => $participationFee->amount_reduced === null
|
||||
? null
|
||||
: [
|
||||
'internal' => [
|
||||
'amount' => $participationFee->amount_reduced->getAmount(),
|
||||
'currency' => $participationFee->amount_reduced->getCurrency(),
|
||||
],
|
||||
'readable' => $participationFee->amount_reduced->multiply($multiplier)->toString(),
|
||||
],
|
||||
'amount_solidarity_edit' => $participationFee->amount_solidarity === null ? null : $participationFee->amount_solidarity->getFormattedAmount(),
|
||||
'amount_solidarity' => $participationFee->amount_solidarity === null
|
||||
? null
|
||||
: [
|
||||
'internal' => [
|
||||
'amount' => $participationFee->amount_solidarity->getAmount(),
|
||||
'currency' => $participationFee->amount_solidarity->getCurrency(),
|
||||
],
|
||||
'readable' => $participationFee->amount_solidarity->multiply($multiplier)->toString(),
|
||||
],
|
||||
'name' => $participationFee->name,
|
||||
'description' => $participationFee->description,
|
||||
'type' => (new ParticipationTypeResource($participationFee->type()->first()))->toArray($request),
|
||||
];
|
||||
|
||||
|
||||
if ($this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY) {
|
||||
$returnArray['participationFee_1' . $i]['description'] = '';
|
||||
$returnArray['participationFee_2' . $i]['description'] = '';
|
||||
$returnArray['participationFee_3' . $i]['description'] = 'Nach Verfügbarkeit';
|
||||
}
|
||||
|
||||
|
||||
$returnArray['participationFee_' . $i] = $feeType;
|
||||
$returnArray['participationTypes'][] = $feeType;
|
||||
|
||||
}
|
||||
|
||||
return $returnArray;
|
||||
}
|
||||
|
||||
public function getMultiplier() : float {
|
||||
$earlyBirdEnd = $this->event->early_bird_end;
|
||||
if ($earlyBirdEnd > now()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1 + $this->event->early_bird_end_amount_increase / 100;
|
||||
}
|
||||
|
||||
public function calculateIncomes() : Amount {
|
||||
$amount = new Amount(0, 'Euro');
|
||||
$amount->addAmount($this->event->support_flat);
|
||||
return $amount;
|
||||
}
|
||||
|
||||
public function calculateAmount(
|
||||
string $participationType,
|
||||
string $feeType,
|
||||
DateTime $arrival,
|
||||
DateTime $departure
|
||||
) : Amount {
|
||||
$fee = collect([
|
||||
$this->event->participationFee1,
|
||||
$this->event->participationFee2,
|
||||
$this->event->participationFee3,
|
||||
$this->event->participationFee4,
|
||||
])->filter(fn ($participationFee) => $participationFee !== null)
|
||||
->first(fn ($participationFee) => $participationFee->type === $participationType);
|
||||
|
||||
if ($fee === null) {
|
||||
return new Amount(0, 'Euro');
|
||||
}
|
||||
|
||||
/** @var Amount $basicFee */
|
||||
$basicFee = $fee['amount_' . $feeType];
|
||||
$basicFee = $basicFee->multiply($this->getMultiplier());
|
||||
|
||||
if ($this->event->pay_per_day) {
|
||||
$days = $arrival->diff($departure)->days;
|
||||
$basicFee = $basicFee->multiply($days);
|
||||
}
|
||||
|
||||
return $basicFee;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,19 +6,17 @@ use App\Models\Tenant;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class LocalGroupResource extends JsonResource {
|
||||
private Tenant $tenant;
|
||||
|
||||
public function __construct(Tenant $tenant) {
|
||||
$this->tenant = $tenant;
|
||||
parent::__construct($tenant);
|
||||
}
|
||||
|
||||
public function toArray($request) : array {
|
||||
return [
|
||||
'id' => $this->tenant->id,
|
||||
'name' => $this->tenant->name,
|
||||
'email' => $this->tenant->email,
|
||||
'city' => $this->tenant->city,
|
||||
'postalcode'=> $this->tenant->postcode
|
||||
'id' => $this->resource->id,
|
||||
'name' => $this->resource->name,
|
||||
'email' => $this->resource->email,
|
||||
'city' => $this->resource->city,
|
||||
'postalcode' => $this->resource->postcode,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
34
app/Resources/ParticipationFeeTypeResource.php
Normal file
34
app/Resources/ParticipationFeeTypeResource.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Resources;
|
||||
|
||||
use App\RelationModels\EventParticipationFee;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ParticipationFeeTypeResource extends JsonResource
|
||||
{
|
||||
public function __construct(private EventParticipationFee $participationFee) {}
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$return = [
|
||||
'type' => new ParticipationTypeResource($this->participationFee->type()->first())->toArray($request),
|
||||
'name' => $this->participationFee->name,
|
||||
'description' => $this->participationFee->description,
|
||||
'amount_standard' => ['internal' => $this->participationFee->amount_standard, 'readable' => $this->participationFee->amount_standard->toString()],
|
||||
'amount_reduced' => ['internal' => null, 'readable' => null],
|
||||
'amount_solidarity' => ['internal' => null, 'readable' => null],
|
||||
];
|
||||
|
||||
if ($this->participationFee->amount_reduced !== null) {
|
||||
$return['amount_reduced'] = ['internal' => $this->participationFee->amount_reduced, 'readable' => $this->participationFee->amount_reduced->toString()];
|
||||
}
|
||||
|
||||
if ($this->participationFee->amount_solidarity !== null) {
|
||||
$return['amount_solidarity'] = ['internal' => $this->participationFee->amount_solidarity, 'readable' => $this->participationFee->amount_solidarity->toString()];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
21
app/Resources/ParticipationTypeResource.php
Normal file
21
app/Resources/ParticipationTypeResource.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Resources;
|
||||
|
||||
use App\Enumerations\ParticipationType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ParticipationTypeResource extends JsonResource
|
||||
{
|
||||
public function __construct(private ParticipationType $participationType) {}
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return ['slug' => $this->participationType->slug, 'name' => $this->participationType->name];
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,9 @@ class UserResource extends JsonResource {
|
||||
$this->user->toArray(),
|
||||
[
|
||||
'nicename' => $this->user->getNicename(),
|
||||
'fullname' => $this->user->getFullName()
|
||||
]);
|
||||
'fullname' => $this->user->getFullName(),
|
||||
'localGroup' => $this->user->localGroup()->id,
|
||||
]);
|
||||
|
||||
unset($data['password']);
|
||||
unset($data['remember_token']);
|
||||
@@ -39,6 +40,7 @@ class UserResource extends JsonResource {
|
||||
'firstname' => $this->user->firstname,
|
||||
'lastname' => $this->user->lastname,
|
||||
'localGroup' => $this->user->localGroup()->name,
|
||||
'localGroupId' => $this->user->localGroup()->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,18 @@
|
||||
namespace App\Scopes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
abstract class CommonModel extends Model
|
||||
{
|
||||
public function toResource(?string $resourceClass = null) : JsonResource {
|
||||
$modelClass = class_basename($this); // z.B. "Event"
|
||||
$resourceClass = "App\\Resources\\{$modelClass}Resource";
|
||||
|
||||
if (!class_exists($resourceClass)) {
|
||||
throw new \RuntimeException("Resource {$resourceClass} not found.");
|
||||
}
|
||||
|
||||
return new $resourceClass($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Scopes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
abstract class InstancedModel extends Model
|
||||
{
|
||||
@@ -10,4 +11,15 @@ abstract class InstancedModel extends Model
|
||||
{
|
||||
static::addGlobalScope(new SiteScope());
|
||||
}
|
||||
|
||||
public function toResource(?string $resourceClass = null) : JsonResource {
|
||||
$modelClass = class_basename($this); // z.B. "Event"
|
||||
$resourceClass = "App\\Resources\\{$modelClass}Resource";
|
||||
|
||||
if (!class_exists($resourceClass)) {
|
||||
throw new \RuntimeException("Resource {$resourceClass} not found.");
|
||||
}
|
||||
|
||||
return new $resourceClass($this);
|
||||
}
|
||||
}
|
||||
|
||||
16
app/ValueObjects/Age.php
Normal file
16
app/ValueObjects/Age.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\ValueObjects;
|
||||
|
||||
class Age {
|
||||
function __construct(private \DateTime $birthday) {
|
||||
}
|
||||
|
||||
public function getAge() : int {
|
||||
return (new \DateTime())->diff($this->birthday)->y;
|
||||
}
|
||||
|
||||
public function isfullAged() : bool {
|
||||
return $this->getAge() >= 18;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,11 @@ class Amount {
|
||||
$this->amount -= $amount->getAmount();
|
||||
}
|
||||
|
||||
public function multiply(float $factor) : Amount {
|
||||
$this->amount *= $factor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormattedAmount() : string {
|
||||
$value = number_format( round( $this->amount, 2 ), 2, ',', '.' );
|
||||
return $value
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"ext-dom": "*",
|
||||
"contelli/webdav-sync": "^1.0",
|
||||
"ext-zip": "*"
|
||||
"ext-zip": "*",
|
||||
"simplesoftwareio/simple-qrcode": "*",
|
||||
"dompdf/dompdf": "^3.0",
|
||||
"setasign/fpdf": "^1.8",
|
||||
"setasign/fpdi": "^2.6",
|
||||
"maennchen/zipstream-php": "^3.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
@@ -21,11 +26,7 @@
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"phpunit/phpunit": "^11.5.3",
|
||||
"dompdf/dompdf": "^3.0",
|
||||
"setasign/fpdf": "^1.8",
|
||||
"setasign/fpdi": "^2.6",
|
||||
"maennchen/zipstream-php": "^3.1"
|
||||
"phpunit/phpunit": "^11.5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
1860
composer.lock
generated
1860
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -58,6 +58,7 @@ return new class extends Migration
|
||||
$table->string('medications')->nullable();
|
||||
$table->string('allergies')->nullable();
|
||||
$table->string('intolerances')->nullable();
|
||||
$table->date('tetanus_vaccination')->nullable();
|
||||
$table->string('eating_habits')->nullable();
|
||||
$table->string('swimming_permission')->nullable();
|
||||
$table->string('first_aid_permission')->nullable();
|
||||
|
||||
@@ -26,8 +26,10 @@ return new class extends Migration {
|
||||
$table->string('tenant');
|
||||
$table->string('type');
|
||||
$table->string('name');
|
||||
$table->string('description')->default('');
|
||||
$table->float('amount',2);
|
||||
$table->string('description')->nullable()->default('');
|
||||
$table->float('amount_standard',2);
|
||||
$table->float('amount_reduced',2)->nullable();
|
||||
$table->float('amount_solidarity', 2)->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('type')->references('slug')->on('participation_types')->restrictOnDelete()->cascadeOnUpdate();
|
||||
|
||||
@@ -22,8 +22,7 @@ return new class extends Migration {
|
||||
|
||||
$table->foreignId('event_id')->constrained('events', 'id')->cascadeOnDelete()->cascadeOnUpdate();
|
||||
$table->foreignId('user_id')->nullable()->constrained('users', 'id')->cascadeOnDelete()->cascadeOnUpdate();
|
||||
|
||||
|
||||
$table->string('identifier');
|
||||
$table->string('firstname');
|
||||
$table->string('lastname');
|
||||
$table->string('nickname')->nullable();
|
||||
@@ -41,7 +40,9 @@ return new class extends Migration {
|
||||
$table->string('contact_person')->nullable();
|
||||
$table->string('allergies')->nullable();
|
||||
$table->string('intolerances')->nullable();
|
||||
$table->string('eating_habits')->nullable();
|
||||
$table->string('medications')->nullable();
|
||||
$table->date('tetanus_vaccination')->nullable();
|
||||
$table->string('eating_habit')->nullable();
|
||||
$table->string('swimming_permission')->nullable();
|
||||
$table->string('first_aid_permission')->nullable();
|
||||
$table->boolean('foto_socialmedia')->default(false);
|
||||
@@ -56,6 +57,7 @@ return new class extends Migration {
|
||||
$table->longText('notes')->nullable();
|
||||
$table->float('amount', 2);
|
||||
$table->float('amount_paid',0)->default(0);
|
||||
$table->string('payment_purpose');
|
||||
$table->string('efz_status');
|
||||
$table->timestamps();
|
||||
$table->dateTime('unregistered_at')->nullable();
|
||||
|
||||
@@ -19,12 +19,16 @@ RUN apt-get install -y nginx \
|
||||
libzip-dev \
|
||||
libpng-dev \
|
||||
libonig-dev \
|
||||
libxml2-dev
|
||||
libxml2-dev \
|
||||
libmagickwand-dev \
|
||||
imagemagick \
|
||||
&& pecl install imagick \
|
||||
&& docker-php-ext-enable imagick
|
||||
|
||||
#&& rm -rf /var/lib/apt/lists/* \
|
||||
RUN mkdir -p /run/nginx
|
||||
|
||||
RUN docker-php-ext-install mysqli pdo pdo_mysql mbstring zip exif pcntl
|
||||
RUN docker-php-ext-install mysqli pdo pdo_mysql mbstring zip exif pcntl gd
|
||||
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
5
justfile
Normal file
5
justfile
Normal file
@@ -0,0 +1,5 @@
|
||||
artisan *args:
|
||||
docker exec -it mareike-mareike-app-1 php artisan {{args}}
|
||||
|
||||
composer *args:
|
||||
docker exec -it mareike-mareike-app-1 composer {{args}}
|
||||
14
legacy/after-submit/alreadyexist.jsx
Normal file
14
legacy/after-submit/alreadyexist.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
function AlreadyExistsContainer({ labels, participant_data }) {
|
||||
return (
|
||||
<p>
|
||||
<h3>{participant_data.nicename}</h3>
|
||||
{participant_data.text_1}<br />
|
||||
{participant_data.text_2}<br />
|
||||
<a href={participant_data.email_link}>{participant_data.email_text}</a> ( {participant_data.email_text} )
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export default AlreadyExistsContainer;
|
||||
87
legacy/after-submit/success.jsx
Normal file
87
legacy/after-submit/success.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import {__} from '../../../../assets/javascripts/library.js';
|
||||
|
||||
function EfzStatusMessage({ efzStatus }) {
|
||||
if (efzStatus === 'NOT_CHECKED') {
|
||||
return <p style={{fontWeight: 'bold'}}>
|
||||
Dein erweitertes Führungszeugnis konnte nicht automatisch überprüft werden. Bitte kontaktiere die Aktionsleitung, da deine Teilnahme nur mit gültigem eFZ möglich ist.
|
||||
</p>;
|
||||
}
|
||||
|
||||
if (efzStatus === 'CHECKED_INVALID') {
|
||||
return <p style={{fontWeight: 'bold'}}>
|
||||
Du hast noch kein erweitertes Führungszeugnis bereitgestellt, sodass deine Teilnahme nicht möglich ist. Bitte reiche dein erweitertes Führungszeugnis umgehend ein,
|
||||
da deine Teilnahme andernfalls storniert werden kann. Bitte setze dich mit der Aktionsleitung in Verbindung.
|
||||
</p>;
|
||||
}
|
||||
|
||||
return null; // default: nix anzeigen
|
||||
}
|
||||
|
||||
function SuccessContainer({ participant_data }) {
|
||||
return (
|
||||
<p>
|
||||
<h3>{__('Hello', 'solea')} {participant_data.nicename}</h3>
|
||||
<p>
|
||||
{participant_data.introduction}<br />
|
||||
{__('We have received the following information:', 'solea')}
|
||||
</p>
|
||||
<table className="solea-payment-table">
|
||||
<tr><td>{__('Arrival', 'solea')}:</td><td>{participant_data.arrival}</td></tr>
|
||||
<tr><td>{__('Departure', 'solea')}:</td><td>{participant_data.departure}</td></tr>
|
||||
<tr><td>{__('Participation group', 'solea')}:</td><td>{participant_data.participation_group}</td></tr>
|
||||
</table>
|
||||
{participant_data.needs_payment ? (
|
||||
<p>
|
||||
<table className="solea-payment-table">
|
||||
<tr>
|
||||
<td>{__('Account owner', 'solea')}:</td>
|
||||
<td>{participant_data.account_owner}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{__('IBAN', 'solea')}:</td>
|
||||
<td>{participant_data.account_iban}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{__('Purpose', 'solea')}:</td>
|
||||
<td>{participant_data.payment_purpose}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{ __('Total amount', 'solea')}: </td>
|
||||
<td>{participant_data.amount}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
{__('If your bank supports QR-Code based paying, use this code', 'solea')}<br />
|
||||
<img className="girocode" src={participant_data.girocode_url} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
{__( 'If payment is not possible or only partially possible within this period, please contact the event management.', 'solea' )}
|
||||
</p>
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{__('You do not have to pay the registration fee. This is the case if participation is supported, billing is done through your local group, or there are other decisions.', 'solea')}<br/>
|
||||
{__('Your registration is confirmed now.', 'solea' ) }
|
||||
</p>
|
||||
|
||||
|
||||
)}
|
||||
|
||||
<EfzStatusMessage efzStatus={participant_data.efz_status} />
|
||||
|
||||
<p>
|
||||
{__('You will receive an email with further information within 2 hours. If you do not receive this mail or have any questions about your registration, please contact the event management.', 'solea')}<br />
|
||||
{__('You can contact us at', 'solea')}: {participant_data.event_email}
|
||||
</p>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export default SuccessContainer;
|
||||
45
legacy/components/amount-selector.jsx
Normal file
45
legacy/components/amount-selector.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { ArrivalDepratureValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function AmountSelectorContainer({ labels, event_data }) {
|
||||
return (
|
||||
<table>
|
||||
{event_data.amount_reduced !== '' && (
|
||||
<tr>
|
||||
<td>
|
||||
<input type="radio"
|
||||
name="beitrag" value="reduced" id="amount_reduced" />
|
||||
<label htmlFor="amount_reduced">
|
||||
{labels.addons.amount.reduced_amount} (
|
||||
{event_data.amount_reduced})
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>
|
||||
<input defaultChecked={"checked"} type="radio" name="beitrag" value="regular" id="amount_regular"/>
|
||||
<label htmlFor="amount_regular">
|
||||
{labels.addons.amount.regular_amount} (
|
||||
{event_data.amount_participant})
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{event_data.amount_social !== '' && (
|
||||
<tr>
|
||||
<td>
|
||||
<input name="beitrag" value="social" id="amount_social" type="radio"/>
|
||||
<label htmlFor="amount_social">
|
||||
{labels.addons.amount.social_amount} (
|
||||
{event_data.amount_social})
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default AmountSelectorContainer;
|
||||
48
legacy/partials/addons.jsx
Normal file
48
legacy/partials/addons.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import AmountSelectorContainer from "../components/amount-selector.jsx";
|
||||
|
||||
function AddonsContainer({ onStepClick, labels, event_data }) {
|
||||
const handle_next_step = () => {
|
||||
onStepClick(7);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<p>
|
||||
{event_data.registration_mode === 'solidarity' && (
|
||||
<AmountSelectorContainer event_data={event_data} labels={labels} />
|
||||
)}
|
||||
|
||||
{event_data.addons.length > 0 && (
|
||||
<p>
|
||||
<h3>{labels.addons.addons.available_addons}</h3>
|
||||
<table>
|
||||
{event_data.addons.map((addon, index) => (
|
||||
<tr>
|
||||
<td class="addon_checkbox">
|
||||
<input type="checkbox"
|
||||
name={"addons["+addon.id+"]"}
|
||||
id={"addons_"+addon.id} />
|
||||
</td>
|
||||
<td class="addon_description">
|
||||
<label for={"addons_"+addon.id}>
|
||||
<span class="bold">{addon.name}</span><br />
|
||||
<span class="small">{labels.common.amount}: {addon.amount}</span><br /><br />
|
||||
{addon.description}<br /><br />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</table>
|
||||
</p>
|
||||
)}
|
||||
<input type="button" value={labels.common.go_back} onClick={() => onStepClick(5)} />
|
||||
<input type="button" value={labels.common.next} onClick= {() => handle_next_step()} />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default AddonsContainer;
|
||||
36
legacy/partials/age.jsx
Normal file
36
legacy/partials/age.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
|
||||
function AgeContainer({ onStepClick, labels, configuration }) {
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() => onStepClick(2)} className="solea_age_selector">
|
||||
|
||||
<div>
|
||||
<h3>{labels.age.headline_children}</h3>
|
||||
{labels.age.text_children}
|
||||
</div>
|
||||
<p className="solea_emblems_selection">
|
||||
<img src={configuration.icon_children} class="solea_participant_icon" />
|
||||
|
||||
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div onClick={() => onStepClick(3)} className="solea_age_selector">
|
||||
<div>
|
||||
<h3>{labels.age.headline_adults}</h3>
|
||||
{labels.age.text_adults}
|
||||
</div>
|
||||
<p className="solea_emblems_selection">
|
||||
<img src={configuration.icon_adults} className="solea_participant_icon"/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default AgeContainer;
|
||||
60
legacy/partials/allergies.jsx
Normal file
60
legacy/partials/allergies.jsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { ContactDataValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function AllergiesContainer({ onStepClick, labels, event_data, participant_data }) {
|
||||
const handle_next_step = () => {
|
||||
onStepClick(9);
|
||||
}
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<h3>{labels.headlines.allergies}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.allergies.allergies}:</td>
|
||||
<td><input defaultValue={participant_data.allergies} type="text" name="allergien" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.allergies.intolerances}:</td>
|
||||
<td><input defaultValue={participant_data.intolerances} type="text" name="intolerances" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.allergies.medications}:</td>
|
||||
<td>
|
||||
<input defaultValue={participant_data.medications} type="text" name="medikamente" />*<br />
|
||||
{labels.allergies.medications_hint}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.allergies.eating_habits.headline}:</td>
|
||||
<td>
|
||||
<select name="essgewohnheit">
|
||||
<option value="vegetarian">{labels.allergies.eating_habits.vegetarian}</option>
|
||||
<option value="vegan">{labels.allergies.eating_habits.vegan}</option>
|
||||
{event_data.enable_all_eating && (
|
||||
<option value="all">{labels.allergies.eating_habits.meat}</option>
|
||||
)}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.allergies.notices}:</td>
|
||||
<td><textarea rows="15" name="anmerkungen"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="button" value={labels.common.go_back} onClick={() => onStepClick(7)} />
|
||||
<input type="button" value={labels.common.next} onClick= {() => handle_next_step()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default AllergiesContainer;
|
||||
65
legacy/partials/arrival.jsx
Normal file
65
legacy/partials/arrival.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { ArrivalDepratureValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function ArrivalContainer({ onStepClick, labels, event_data }) {
|
||||
const handle_next_step = () => {
|
||||
if (ArrivalDepratureValidator(
|
||||
|
||||
new Date(event_data.date_begin),
|
||||
new Date (event_data.date_end)
|
||||
) ) {
|
||||
if (event_data.addons.length > 0 || event_data.registration_mode === 'solidarity') {
|
||||
onStepClick(6);
|
||||
} else {
|
||||
onStepClick(7);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<h3>{labels.headlines.arrival}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.arrival_data.arrival.headline}:</td>
|
||||
<td>
|
||||
<input type="date" name="anreise" id="anreise" defaultValue={event_data.date_begin} /><br />
|
||||
<select name="anreise_essen">
|
||||
<option Value="1" selected>{labels.arrival_data.arrival.before_dinner}</option>
|
||||
<option value="2">{labels.arrival_data.arrival.before_lunch}</option>
|
||||
<option value="3">{labels.arrival_data.arrival.before_breakfast}</option>
|
||||
<option value="4">{labels.arrival_data.arrival.no_meal}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.arrival_data.departure.headline}:</td>
|
||||
<td>
|
||||
<input type="date" name="abreise" id="abreise" defaultValue={event_data.date_end} /><br />
|
||||
<select name="abreise_essen">
|
||||
<option value="1">{labels.arrival_data.departure.after_breakfast}</option>
|
||||
<option selected value="2">{labels.arrival_data.departure.after_lunch}</option>
|
||||
<option value="3">{labels.arrival_data.departure.after_dinner}</option>
|
||||
<option value="4">{labels.arrival_data.departure.no_meal}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="button" value={labels.common.go_back} onClick={() => onStepClick(4)} />
|
||||
<input type="button" value={labels.common.next} onClick= {() => handle_next_step()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArrivalContainer;
|
||||
68
legacy/partials/contactperson.jsx
Normal file
68
legacy/partials/contactperson.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { ContactPersonValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function ContactPersonContainer({ onStepClick, labels, participant_data }) {
|
||||
const handle_next_step = () => {
|
||||
|
||||
if (ContactPersonValidator() ) {
|
||||
onStepClick(3);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<h3>{labels.headlines.contactperson}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.common.lastname}, {labels.common.firstname}:</td>
|
||||
<td><input type="text" name="ansprechpartner" id="ansprechpartner" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.telephone}:</td>
|
||||
<td><input type="text" name="telefon_2" id="telefon_2" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.common.email}:</td>
|
||||
<td><input type="text" name="email_2" id="email_2"/></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.swimming_permission.label}:</td>
|
||||
<td>
|
||||
<select name="badeerlaubnis" id={"swimming_permission"}>
|
||||
<option value="-1">{labels.common.please_select}</option>
|
||||
<option value="none">{labels.swimming_permission.none}</option>
|
||||
<option value="partial">{labels.swimming_permission.partial}</option>
|
||||
<option value="complete">{labels.swimming_permission.complete}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.swimming_permission.first_aid_headline}:
|
||||
</td>
|
||||
<td>
|
||||
<select id="first_aid" name="first_aid" required>
|
||||
<option value="-1">{labels.common.please_select}</option>
|
||||
<option value="1">{labels.swimming_permission.first_aid_yes}</option>
|
||||
<option value="0">{labels.swimming_permission.first_aid_no}</option>
|
||||
</select><br />
|
||||
<label className="description">{labels.swimming_permission.first_aid_description}</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="button" value={labels.common.next} onClick= {() => handle_next_step()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContactPersonContainer;
|
||||
91
legacy/partials/personaldata.jsx
Normal file
91
legacy/partials/personaldata.jsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import { ContactDataValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function PersonalDataContainer({ onStepClick, labels, participant_data, local_groups }) {
|
||||
const handle_next_step = () => {
|
||||
if (ContactDataValidator()) {
|
||||
onStepClick(4);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<h3>{labels.headlines.personaldata}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.common.firstname};</td>
|
||||
<td><input defaultValue={participant_data.firstname} type="text" name="vorname" id="vorname" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.lastname}:</td>
|
||||
<td><input defaultValue={participant_data.lastname} type="text" name="nachname" id="nachname" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.nickname}</td>
|
||||
<td><input defaultValue={participant_data.nickname} type="text" name="pfadiname" id="pfadiname" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.localgroup}</td>
|
||||
<td>
|
||||
<select id="localgroup" name="localgroup" required placeholder="<?php echo esc_html__( 'Please select', 'solea' ); ?>">
|
||||
<option value="-1">{labels.common.please_select}</option>
|
||||
{local_groups.map((local_group, index) => (
|
||||
<option
|
||||
value={local_group.id}
|
||||
selected={participant_data.localgroup == local_group.id}
|
||||
>{local_group.name}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.birthday}:</td>
|
||||
<td><input defaultValue={participant_data.birthday} type="date" name="geburtsdatum" id="geburtsdatum" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.street}, {labels.common.housenumber}:</td>
|
||||
<td>
|
||||
<input type="text" defaultValue={participant_data.street} name="strasse" id="strasse" />
|
||||
<input type="text" defaultValue={participant_data.number} name="hausnummer" id="hausnummer" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{labels.common.postal_code}, {labels.common.city}:</td>
|
||||
<td>
|
||||
<input type="text" defaultValue={participant_data.zip} name="plz" id="plz" />
|
||||
<input type="text" defaultValue={participant_data.city} name="ort" id="ort" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.telephone}:</td>
|
||||
<td><input defaultValue={participant_data.phone} type="text" name="telefon_1" id="telefon_1" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.common.email}:</td>
|
||||
<td><input defaultValue={participant_data.email} type="text" name="email_1" id="email_1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="button" value={labels.common.next} onClick= {() => handle_next_step()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonalDataContainer;
|
||||
77
legacy/partials/photopermissions.jsx
Normal file
77
legacy/partials/photopermissions.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
|
||||
function PhotopermissionsContainer({ onStepClick, labels, event_data }) {
|
||||
const accept_all_permissions = () => {
|
||||
document.getElementById( 'foto_socialmedia' ).checked = true;
|
||||
document.getElementById( 'foto_print' ).checked = true;
|
||||
document.getElementById( 'foto_webseite' ).checked = true;
|
||||
document.getElementById( 'foto_partner' ).checked = true;
|
||||
document.getElementById( 'foto_intern' ).checked = true;
|
||||
handle_next_step();
|
||||
}
|
||||
|
||||
const handle_next_step = () => {
|
||||
onStepClick(8);
|
||||
|
||||
}
|
||||
|
||||
const handle_previous_step = () => {
|
||||
if (event_data.addons.length > 0 || event_data.registration_mode === 'solidarity') {
|
||||
onStepClick(6);
|
||||
} else {
|
||||
onStepClick(5);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p class="phptopermission_container">
|
||||
<h3>{labels.photopermissions.headline}</h3>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" name="foto[socialmedia]" value="active" id="foto_socialmedia"/>
|
||||
<label
|
||||
htmlFor="foto_socialmedia">{labels.photopermissions.socialmedia}
|
||||
</label><br/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" name="foto[print]" value="active" id="foto_print"/>
|
||||
<label
|
||||
htmlFor="foto_print">{labels.photopermissions.printmedia}
|
||||
</label><br/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" name="foto[webseite]" value="active" id="foto_webseite"/>
|
||||
<label
|
||||
htmlFor="foto_webseite">{labels.photopermissions.websites}
|
||||
</label><br/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" name="foto[partner]" value="active" id="foto_partner"/>
|
||||
<label
|
||||
htmlFor="foto_partner">{labels.photopermissions.partners}
|
||||
</label><br/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" name="foto[intern]" value="active" id="foto_intern"/>
|
||||
<label
|
||||
htmlFor="foto_intern">{labels.photopermissions.internalpurpose}
|
||||
</label><br/>
|
||||
</p>
|
||||
|
||||
<input class="acceptallbutton" type="button"
|
||||
value={labels.photopermissions.acceptall}
|
||||
onClick={() => accept_all_permissions()}
|
||||
/>
|
||||
<input type="button" value={labels.common.go_back} onClick={() => handle_previous_step()} />
|
||||
<input type="button" value={labels.common.next} onClick= {() => handle_next_step()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PhotopermissionsContainer;
|
||||
51
legacy/partials/registration-groupbased.jsx
Normal file
51
legacy/partials/registration-groupbased.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { ContactDataValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function GroupBasedRegistrationContainer({ onStepClick, labels, participant_data, local_groups, event_data }) {
|
||||
const handle_next_step = () => {
|
||||
onStepClick(5);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<table>
|
||||
<input defaultChecked={"checked"} type="radio" name="beitrag" value="regular" id="amount_regular" hidden />
|
||||
|
||||
<tr>
|
||||
<td colSpan="2"><h3>{labels.registration_mode.group_based.headline}:</h3></td>
|
||||
</tr>
|
||||
|
||||
|
||||
{event_data.possible_participation_groups.map((participation_group, index) => (
|
||||
<tr>
|
||||
<td colSpan="2" >
|
||||
<input type="radio"
|
||||
defaultChecked={'participant' === participation_group.name}
|
||||
name="participant_group"
|
||||
value={participation_group.name}
|
||||
id={participation_group.name} />
|
||||
|
||||
<label for={participation_group.name}>
|
||||
{participation_group.name_readable}
|
||||
{participation_group.description !== '' && (
|
||||
<span><br /><span class="solea-group-info-text">{participation_group.description}</span><br /><br /></span>
|
||||
)}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="button" value={labels.common.go_back} onClick={() => onStepClick(3)} />
|
||||
<input type="button" value={labels.common.next} onClick= {() => handle_next_step()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default GroupBasedRegistrationContainer;
|
||||
47
legacy/partials/registration-solidarity.jsx
Normal file
47
legacy/partials/registration-solidarity.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { ContactDataValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function SolidarityRegistrationContainer({ onStepClick, labels, participant_data, local_groups, event_data }) {
|
||||
const handle_next_step = () => {
|
||||
onStepClick(5);
|
||||
}
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<h3>{labels.headlines.solidarity_headline}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.registration_mode.solidarity.headline}...</td>
|
||||
<td>
|
||||
{event_data.possible_participation_groups.map((participation_group, index) => (
|
||||
<span>
|
||||
<input
|
||||
class="registratration_mode_solidatory_checkbox"
|
||||
type="radio"
|
||||
name="participant_group"
|
||||
value="team"
|
||||
defaultChecked={'participant' === participation_group.name}
|
||||
id={participation_group.name}
|
||||
/>
|
||||
|
||||
<label for={participation_group.name}>...{participation_group.description}</label><br/>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="button" value={labels.common.go_back} onClick={() => onStepClick(3)} />
|
||||
<input type="button" value={labels.common.next} onClick={() => handle_next_step()}/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default SolidarityRegistrationContainer;
|
||||
75
legacy/partials/summary.jsx
Normal file
75
legacy/partials/summary.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { ContactDataValidator } from '../../../../assets/javascripts/registration-validator.js'
|
||||
|
||||
|
||||
function SummaryContainer({ onStepClick, labels, participant_data, local_groups }) {
|
||||
const handle_next_step = () => {
|
||||
onStepClick(4);
|
||||
|
||||
if (ContactDataValidator()) {
|
||||
onStepClick(4);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<h3>{labels.headlines.summary}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{labels.summary.event_name}:</td>
|
||||
<td><span id="summary_eventname" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.summary.arrival}</td>
|
||||
<td><span id="summary_arrival" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{labels.summary.departure}:</td>
|
||||
<td><span id="summary_departure" /></td>
|
||||
</tr>
|
||||
<tr><td colSpan="2"><br /><br /></td></tr>
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="checkbox" id="summary_information_correct"/>
|
||||
<label htmlFor="summary_information_correct" id="summary_information_correct_label">
|
||||
{labels.summary.information_correct}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="checkbox" id="summary_accept_terms"/>
|
||||
<label htmlFor="summary_accept_terms" id="summary_accept_terms_label">
|
||||
{labels.summary.accept_terms}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="checkbox" id="legal_accepted"/>
|
||||
<label htmlFor="legal_accepted" id="legal_accepted_label">
|
||||
{labels.summary.legal_acceptance}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
<input type="checkbox" id="payment"/>
|
||||
<label htmlFor="payment" id="payment_label">
|
||||
{labels.summary.amount_text_1} <span class="bold" id="payment_information_label"></span> {labels.summary.amount_text_2}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default SummaryContainer;
|
||||
267
legacy/signup-main.jsx
Normal file
267
legacy/signup-main.jsx
Normal file
@@ -0,0 +1,267 @@
|
||||
import React from 'react';
|
||||
import AgeContainer from './partials/age.jsx';
|
||||
import ContactPersonContainer from './partials/contactperson.jsx';
|
||||
import PersonalDataContainer from './partials/personaldata.jsx';
|
||||
import SolidarityRegistrationContainer from './partials/registration-solidarity.jsx';
|
||||
import GroupSelectionContainer from './partials/registration-groupbased.jsx';
|
||||
import ArrivalContainer from './partials/arrival.jsx';
|
||||
import AddonsContainer from "./partials/addons.jsx";
|
||||
import PhotopermissionsContainer from "./partials/photopermissions.jsx";
|
||||
import AllergiesContainer from "./partials/allergies.jsx";
|
||||
import SummaryContainer from "./partials/summary.jsx";
|
||||
import { FinalValidator } from '../../../assets/javascripts/registration-validator.js'
|
||||
import { SelectedAddons } from '../../../assets/javascripts/registration-validator.js'
|
||||
import SuccessContainer from "./after-submit/success.jsx";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import AlreadyExistsContainer from "./after-submit/alreadyexist.jsx";
|
||||
import { __ } from '../../../assets/javascripts/library.js';
|
||||
|
||||
const { configuration, labels, participant_data, event_data, local_groups } = window.solea_data;
|
||||
|
||||
function showStep(step) {
|
||||
var steps = [
|
||||
'step_1', // Age-Selector
|
||||
'step_2', // Children only: Contact data
|
||||
'step_3', // Personal data
|
||||
'step_4', // Participation group
|
||||
'step_5', // Arival & Departure
|
||||
'step_6', // Addon information
|
||||
'step_7', // Photo-Permissions
|
||||
'step_8', // Allergies & Intolerances
|
||||
'step_9' // Summary
|
||||
];
|
||||
|
||||
for (var idx = 0; idx < steps.length; idx++) {
|
||||
var currentElement = steps[idx];
|
||||
document.getElementById(currentElement).classList.add('container_hidden');
|
||||
document.getElementById(currentElement).classList.remove('container_visible');
|
||||
}
|
||||
|
||||
document.getElementById('step_' + step).classList.remove('container_hidden');
|
||||
document.getElementById('step_' + step).classList.add('container_visible');
|
||||
|
||||
document.getElementById('summary_loading').classList.remove('container_hidden');
|
||||
document.getElementById('summary_loading').classList.add('container_visible');
|
||||
document.getElementById('summary_content').classList.add('container_hidden');
|
||||
document.getElementById('summary_content').classList.remove('container_visible');
|
||||
|
||||
if (step === 9) {
|
||||
var [ayear, amonth, aday] = document.getElementById('anreise').value.split('-');
|
||||
var [dyear, dmonth, dday] = document.getElementById('abreise').value.split('-');
|
||||
|
||||
document.getElementById('summary_eventname').innerHTML= event_data.event_name;
|
||||
document.getElementById('summary_arrival').innerHTML=`${aday}.${amonth}.${ayear}`;
|
||||
document.getElementById('summary_departure').innerHTML=`${dday}.${dmonth}.${dyear}`;
|
||||
|
||||
const response = fetch("/wp-json/solea/signup/preview_amount", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
anreise: document.getElementById('anreise').value,
|
||||
abreise: document.getElementById('abreise').value,
|
||||
event_id: event_data.id,
|
||||
participation_group: document.querySelector('input[name="participant_group"]:checked').value,
|
||||
selected_amount: document.querySelector('input[name="beitrag"]:checked').value,
|
||||
addons: SelectedAddons(),
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
document.getElementById('payment_information_label').innerHTML = data.amount
|
||||
document.getElementById('summary_loading').classList.add('container_hidden');
|
||||
document.getElementById('summary_loading').classList.remove('container_visible');
|
||||
|
||||
document.getElementById('summary_content').classList.add('container_visible');
|
||||
document.getElementById('summary_content').classList.remove('container_hidden');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function ParticipantSignup() {
|
||||
let RegistrationMode;
|
||||
|
||||
switch (event_data.registration_mode) {
|
||||
case 'solidarity':
|
||||
RegistrationMode = SolidarityRegistrationContainer;
|
||||
break;
|
||||
|
||||
case 'groupbased':
|
||||
RegistrationMode = GroupSelectionContainer;
|
||||
break;
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const data = new FormData(form);
|
||||
|
||||
if (!FinalValidator()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = Object.fromEntries(data.entries());
|
||||
const response = fetch("/wp-json/solea/signup/do-register", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
addons: SelectedAddons(),
|
||||
registration_data: formData
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const container = document.getElementById('solea_registration_form_frame');
|
||||
const root = ReactDOM.createRoot(container);
|
||||
|
||||
if (data.already_exists === true) {
|
||||
root.render(<AlreadyExistsContainer participant_data={data.result} labels={labels} />);
|
||||
} else {
|
||||
root.render(<SuccessContainer participant_data={data.result} />);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="solea-event-registration-container">
|
||||
<table class="solea-event-meta-data-table">
|
||||
<tr>
|
||||
<td>{__('Event location', 'solea')}:</td>
|
||||
<td>{event_data.postalcode} {event_data.location}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{__('Event duration', 'solea')}:</td>
|
||||
<td>{event_data.date_begin_formatted} - {event_data.date_end_formatted}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>{__('Regular registration until', 'solea')}:</td>
|
||||
<td>{event_data.last_minute_begin_formatted}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{__('Extended registration until', 'solea')}:
|
||||
</td>
|
||||
<td>
|
||||
{event_data.registration_end_formatted}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{__('Cancellation', 'solea')}:
|
||||
</td>
|
||||
<td>
|
||||
100% {__('until', 'solea')} {event_data.last_minute_begin_formatted} {__('available', 'solea')}<br />
|
||||
{event_data.reduced_return}% {__('until', 'solea')} {event_data.registration_end_formatted} {__('available', 'solea')}<br />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan="2">
|
||||
{ __('Do you have questions for registration? Please contact us by email:', 'solea') }
|
||||
<a href={"mailto:" + event_data.event_email}>{event_data.event_email}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id="solea_registration_form_frame">
|
||||
<form id="registration_form" onSubmit={handleSubmit}>
|
||||
<input type="hidden" name="solea-user-id" value={participant_data.id} />
|
||||
<input type="hidden" name="event-id" value={event_data.id} />
|
||||
<div id="step_1" className="container_visible">
|
||||
<AgeContainer
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
configuration={configuration}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_2" className="container_hidden">
|
||||
<ContactPersonContainer
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
participant_data={participant_data}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_3" className="container_hidden">
|
||||
<PersonalDataContainer
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
participant_data={participant_data}
|
||||
local_groups={local_groups}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_4" className="container_hidden">
|
||||
<RegistrationMode
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
participant_data={participant_data}
|
||||
local_groups={local_groups}
|
||||
event_data={event_data}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_5" className="container_hidden">
|
||||
<ArrivalContainer
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
event_data={event_data}
|
||||
local_groups={local_groups}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_6" className="container_hidden">
|
||||
<AddonsContainer
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
event_data={event_data}
|
||||
local_groups={local_groups}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_7" className="container_hidden">
|
||||
<PhotopermissionsContainer
|
||||
event_data={event_data}
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_8" className="container_hidden">
|
||||
<AllergiesContainer
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
participant_data={participant_data}
|
||||
event_data={event_data}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="step_9" className="container_hidden">
|
||||
<p id="summary_loading">Wird geladen</p>
|
||||
<p id="summary_content" className="container_hidden">
|
||||
<SummaryContainer
|
||||
onStepClick={showStep}
|
||||
labels={labels}
|
||||
participant_data={participant_data}
|
||||
event_data={event_data}
|
||||
/>
|
||||
<input type="button" value={labels.common.go_back} onClick={() => showStep(8)} />
|
||||
<input className="acceptallbutton" type="submit" value={labels.common.confirm}/>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default ParticipantSignup;
|
||||
@@ -3,8 +3,24 @@
|
||||
@source '../**/*.blade.php';
|
||||
@source '../**/*.js';
|
||||
|
||||
@font-face {
|
||||
font-family: Aleo;
|
||||
src: url('../fonts/aleo-regular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Immenhausen;
|
||||
src: url('../fonts/immenhausen.ttf');
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #FAFAFB !important;
|
||||
font-family: Aleo;
|
||||
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: Immenhausen;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
BIN
public/fonts/aleo-regular.ttf
Normal file
BIN
public/fonts/aleo-regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/immenhausen.ttf
Normal file
BIN
public/fonts/immenhausen.ttf
Normal file
Binary file not shown.
BIN
public/images/adults.png
Normal file
BIN
public/images/adults.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 268 KiB |
BIN
public/images/children.png
Normal file
BIN
public/images/children.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 KiB |
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||
use App\Http\Controllers\GiroCodeGetController;
|
||||
use App\Http\Controllers\TestRenderInertiaProvider;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use App\Providers\CronController;
|
||||
@@ -22,6 +23,7 @@ require_once __DIR__ . '/../app/Domains/Event/Routes/api.php';
|
||||
|
||||
|
||||
Route::get('/execute-crons', [CronTaskHandleProvider::class, 'run']);
|
||||
Route::get('/print-girocode/{participantToken}', GiroCodeGetController::class);
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::get('/', DashboardController::class);
|
||||
|
||||
Reference in New Issue
Block a user