First Aid list, amount list and kitchen list PDF download implemented

This commit is contained in:
2026-03-28 22:22:06 +01:00
parent df7c14442e
commit 7bea223ded
20 changed files with 92 additions and 36 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Domains\Event\Actions\SignUp; namespace App\Domains\Event\Actions\SignUp;
use App\Enumerations\EatingHabit;
use App\Enumerations\EfzStatus; use App\Enumerations\EfzStatus;
use App\ValueObjects\Age; use App\ValueObjects\Age;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -13,8 +14,13 @@ class SignUpCommand {
public function execute() : SignUpResponse { public function execute() : SignUpResponse {
$response = new SignUpResponse(); $response = new SignUpResponse();
$participantAge = new Age($this->request->birthday); $eatingHabit = match ($this->request->eating_habit) {
'vegan' => EatingHabit::EATING_HABIT_VEGAN,
'vegetarian' => EatingHabit::EATING_HABIT_VEGETARIAN,
default => EatingHabit::EATING_HABIT_OMNIVOR,
};
$participantAge = new Age($this->request->birthday);
$response->participant = $this->request->event->participants()->create( $response->participant = $this->request->event->participants()->create(
[ [
'tenant' => $this->request->event->tenant, 'tenant' => $this->request->event->tenant,
@@ -39,7 +45,7 @@ class SignUpCommand {
'intolerances' => $this->request->intolerances, 'intolerances' => $this->request->intolerances,
'medications' => $this->request->medications, 'medications' => $this->request->medications,
'tetanus_vaccination' => $this->request->tetanus_vaccination, 'tetanus_vaccination' => $this->request->tetanus_vaccination,
'eating_habit' => $this->request->eating_habit, 'eating_habit' => $eatingHabit,
'swimming_permission' => $participantAge->isfullAged() ? 'SWIMMING_PERMISSION_ALLOWED' : $this->request->swimming_permission, '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, 'first_aid_permission' => $participantAge->isfullAged() ? 'FIRST_AID_PERMISSION_ALLOWED' : $this->request->first_aid_permission,
'foto_socialmedia' => $this->request->foto_socialmedia, 'foto_socialmedia' => $this->request->foto_socialmedia,

View File

@@ -10,12 +10,15 @@ use App\Domains\Event\Actions\UpdateManagers\UpdateManagersCommand;
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest; use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest;
use App\Enumerations\ParticipationFeeType; use App\Enumerations\ParticipationFeeType;
use App\Enumerations\ParticipationType; use App\Enumerations\ParticipationType;
use App\Models\EventParticipant;
use App\Providers\InertiaProvider; use App\Providers\InertiaProvider;
use App\Providers\PdfGenerateAndDownloadProvider;
use App\Resources\EventResource; use App\Resources\EventResource;
use App\Scopes\CommonController; use App\Scopes\CommonController;
use App\ValueObjects\Amount; use App\ValueObjects\Amount;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response;
class DetailsController extends CommonController { class DetailsController extends CommonController {
public function __invoke(int $eventId) { public function __invoke(int $eventId) {
@@ -123,4 +126,31 @@ class DetailsController extends CommonController {
return response()->json(['status' => $response->success ? 'success' : 'error']); return response()->json(['status' => $response->success ? 'success' : 'error']);
} }
public function downloadPdfList(string $eventId, string $listType, Request $request): Response
{
$event = $this->events->getByIdentifier($eventId);
$participants = $this->eventParticipants->getForList($event, $request);
$kitchenOverview = $this->eventParticipants->getKitchenOverview($event);
$html = view('pdfs.' . $listType, [
'event' => $event->name,
'eventStart' => $event->start_date,
'eventEnd' => $event->end_date,
'rows' => $participants,
'kitchenRequirements' => $kitchenOverview,
'participantsForKitchenList' => $this->eventParticipants->getParticipantsWithIntolerances($event, $request),
])->render();
$pdf = PdfGenerateAndDownloadProvider::fromHtml(
$html,
'landscape'
);
return response($pdf, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $listType .'.pdf"',
]);
}
} }

View File

@@ -81,10 +81,10 @@ class SignupController extends CommonController {
$registrationData['nachname'], $registrationData['nachname'],
$registrationData['email_1'], $registrationData['email_1'],
DateTime::createFromFormat('Y-m-d', $registrationData['geburtsdatum'])); DateTime::createFromFormat('Y-m-d', $registrationData['geburtsdatum']));
/*
if ($doubleCheckEventRegistrationProvider->isRegistered()) { if ($doubleCheckEventRegistrationProvider->isRegistered()) {
return response()->json(['status' => 'exists']); return response()->json(['status' => 'exists']);
} }*/
$amount = $eventResource->calculateAmount( $amount = $eventResource->calculateAmount(
$registrationData['participationType'], $registrationData['participationType'],

View File

@@ -18,6 +18,7 @@ Route::middleware(IdentifyTenant::class)->group(function () {
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::get('/new', CreateController::class); Route::get('/new', CreateController::class);
Route::get('/details/{eventId}', DetailsController::class); Route::get('/details/{eventId}', DetailsController::class);
Route::get('/details/{eventId}/pdf/{listType}', [DetailsController::class, 'downloadPdfList']);
}); });
}); });
}); });

View File

@@ -36,7 +36,7 @@
sendWeeklyReports: props.event.sendWeeklyReports, sendWeeklyReports: props.event.sendWeeklyReports,
registrationAllowed: props.event.registrationAllowed, registrationAllowed: props.event.registrationAllowed,
flatSupport: props.event.flatSupportEdit, flatSupport: props.event.flatSupportEdit,
supportPerson: props.event.supportPersonEdit, supportPerson: props.event.supportPersonIndex,
}) })
onMounted(async () => { onMounted(async () => {

View File

@@ -60,11 +60,20 @@ import {onMounted, reactive, ref} from "vue";
<div class="event-flexbox-row top"> <div class="event-flexbox-row top">
<div class="left"><ParticipationSummary v-if="dynamicProps.event" :event="dynamicProps.event" /></div> <div class="left"><ParticipationSummary v-if="dynamicProps.event" :event="dynamicProps.event" /></div>
<div class="right"> <div class="right">
<input type="button" value="Erste-Hilfe-Liste (PDF)" /><br/> <a :href="'/event/details/' + props.data.event.identifier + '/pdf/first-aid-list'">
<input type="button" value="Erste-Hilfe-Liste (PDF)" />
</a><br/>
<input type="button" value="Teili-Liste (CSV)" /><br/> <input type="button" value="Teili-Liste (CSV)" /><br/>
<input type="button" value="KSV-Daten (CSV)" /><br/> <input type="button" value="KSV-Daten (CSV)" /><br/>
<input type="button" value="Küchenübersicht (PDF)" /><br/> <a :href="'/event/details/' + props.data.event.identifier + '/pdf/kitchen-list'">
<input type="button" value="Beitragsliste (PDF)" /><br/> <input type="button" value="Küchenübersicht (PDF)" />
</a><br/>
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/amount-list'">
<input type="button" value="Beitragsliste (PDF)" />
</a><br/>
<input type="button" value="Getränkeliste (PDF)" /><br/> <input type="button" value="Getränkeliste (PDF)" /><br/>
<input type="button" value="Foto-Erlaubnis (PDF)" /><br/> <input type="button" value="Foto-Erlaubnis (PDF)" /><br/>
<input type="button" class="fix-button" value="Zahlungserinnerung senden" /><br/> <input type="button" class="fix-button" value="Zahlungserinnerung senden" /><br/>

View File

@@ -17,7 +17,7 @@
<table class="participant-income-table" style="margin-bottom: 40px; font-size: 11pt;"> <table class="participant-income-table" style="margin-bottom: 40px; font-size: 11pt;">
<tr> <tr>
<th>Teili</th> <th>Teili</th>
<td>{{props.event.participants.participant.count}} Personen:</td> <td><strong>{{props.event.participants.participant.count}} Personen:</strong></td>
<td> <td>
{{props.event.participants.participant.amount.paid.readable}} / {{props.event.participants.participant.amount.paid.readable}} /
</td> </td>
@@ -28,7 +28,7 @@
<tr> <tr>
<th>Team</th> <th>Team</th>
<td>{{props.event.participants.team.count}} Personen:</td> <td><strong>{{props.event.participants.team.count}} Personen:</strong></td>
<td> <td>
{{props.event.participants.team.amount.paid.readable}} / {{props.event.participants.team.amount.paid.readable}} /
</td> </td>
@@ -39,7 +39,7 @@
<tr> <tr>
<th>Unterstützende</th> <th>Unterstützende</th>
<td>{{props.event.participants.volunteer.count}} Personen:</td> <td><strong>{{props.event.participants.volunteer.count}} Personen:</strong></td>
<td> <td>
{{props.event.participants.volunteer.amount.paid.readable}} / {{props.event.participants.volunteer.amount.paid.readable}} /
</td> </td>
@@ -50,7 +50,7 @@
<tr> <tr>
<th>Sonstige</th> <th>Sonstige</th>
<td>{{props.event.participants.other.count}} Personen:</td> <td><strong>{{props.event.participants.other.count}} Personen:</strong></td>
<td> <td>
{{props.event.participants.other.amount.paid.readable}} / {{props.event.participants.other.amount.paid.readable}} /
</td> </td>
@@ -91,7 +91,7 @@
{{ props.event.totalBalance.real.readable }} / {{ props.event.totalBalance.real.readable }} /
</td> </td>
<td v-else style="color: #f44336; font-weight: bold; padding-top: 20px; font-size: 12pt !important;"> <td v-else style="color: #f44336; font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
{{props.event.totalBalance.expected.readable}} {{props.event.totalBalance.real.readable}}
</td> </td>
<td v-if="props.event.totalBalance.expected.value >= 0" style="color: #4caf50; font-weight: bold; padding-top: 20px; font-size: 12pt !important;"> <td v-if="props.event.totalBalance.expected.value >= 0" style="color: #4caf50; font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
@@ -107,14 +107,14 @@
<div class="right"> <div class="right">
<h3>Ausgaben</h3> <h3>Ausgaben</h3>
<table class="event-payment-table" style="font-size: 11pt;"> <table class="event-payment-table" style="font-size: 10pt;">
<tr v-for="amount in props.event.costUnit.amounts"> <tr v-for="amount in props.event.costUnit.amounts">
<th>{{amount.name}}</th> <th>{{amount.name}}</th>
<td>{{amount.string}}</td> <td>{{amount.string}}</td>
</tr> </tr>
<tr> <tr>
<th style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 20px">Gesamt</th> <th style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 58px">Gesamt</th>
<td style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 20px; font-weight: bold">{{props.event.costUnit.overAllAmount.text}}</td> <td style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 58px; font-weight: bold">{{props.event.costUnit.overAllAmount.text}}</td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@@ -33,8 +33,8 @@ console.log(participantData)
first_aid: '-1', first_aid: '-1',
participant_group: '', participant_group: '',
beitrag: 'regular', beitrag: 'regular',
arrival: event.eventBeginInternal?.split('T')[0] ?? '', arrival: event.arrivalDefault ?? '',
departure: event.eventEndInternal?.split('T')[0] ?? '', departure: event.departureDefault ?? '',
anreise_essen: '1', anreise_essen: '1',
abreise_essen: '2', abreise_essen: '2',
foto: { socialmedia: false, print: false, webseite: false, partner: false, intern: false }, foto: { socialmedia: false, print: false, webseite: false, partner: false, intern: false },

View File

@@ -31,7 +31,7 @@ const emit = defineEmits(['next'])
</div> </div>
<div class="age-card__body"> <div class="age-card__body">
<h4 class="age-card__title">Mich selbst anmelden</h4> <h4 class="age-card__title">Mich selbst anmelden</h4>
<p class="age-card__desc">Ich bin <strong>{{ event.alcoholicsAge }} Jahre oder älter</strong>.</p> <p class="age-card__desc">Ich bin <strong>18 Jahre oder älter</strong>.</p>
</div> </div>
</div> </div>

View File

@@ -22,5 +22,6 @@ class SwimmingPermission extends CommonModel
protected $fillable = [ protected $fillable = [
'slug', 'slug',
'name', 'name',
'short'
]; ];
} }

View File

@@ -73,9 +73,9 @@ class ProductionDataSeeder {
} }
private function installSwimmingPermissions() { private function installSwimmingPermissions() {
SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED]); SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, 'short' => 'Erteilt']);
SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann NICHT schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_LIMITED]); SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann NICHT schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_LIMITED, 'short' => 'Nur Flachwasser']);
SwimmingPermission::create(['name' => 'Mein Kind darf nicht baden', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_DENIED]); SwimmingPermission::create(['name' => 'Mein Kind darf nicht baden', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_DENIED, 'short' => 'Verweiger']);
} }
private function installEatingHabits() { private function installEatingHabits() {

View File

@@ -169,7 +169,7 @@ class Event extends InstancedModel
->withTimestamps(); ->withTimestamps();
} }
public function participants() : hasMany { public function participants() : HasMany {
return $this->hasMany(EventParticipant::class); return $this->hasMany(EventParticipant::class);
} }

View File

@@ -45,7 +45,7 @@ class EventParticipant extends InstancedModel
'intolerances', 'intolerances',
'medications', 'medications',
'tetanus_vaccination', 'tetanus_vaccination',
'eating_habits', 'eating_habit',
'swimming_permission', 'swimming_permission',
'first_aid_permission', 'first_aid_permission',

View File

@@ -166,7 +166,7 @@ class CostUnitRepository {
if ( if (
$invoice->status === InvoiceStatus::INVOICE_STATUS_DENIED || $invoice->status === InvoiceStatus::INVOICE_STATUS_DENIED ||
$invoice->donation || $invoice->donation ||
$invoice->type !== $invoice->slug $invoice->type !== $invoiceType->slug
) { ) {
continue; continue;
} }

View File

@@ -4,6 +4,7 @@ namespace App\Resources;
use App\Enumerations\ParticipationType; use App\Enumerations\ParticipationType;
use App\Models\EventParticipant; use App\Models\EventParticipant;
use App\ValueObjects\Age;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
class EventParticipantResource extends JsonResource class EventParticipantResource extends JsonResource
@@ -34,6 +35,11 @@ class EventParticipantResource extends JsonResource
return array_merge( return array_merge(
$this->resource->toArray(), $this->resource->toArray(),
[ [
'age' => new Age($this->resource->birthday)->getAge(),
'localgroup' => $this->resource->localGroup()->first()->name,
'swimmingPermission' => $this->resource->swimmingPermission()->first()->short,
'extendedFirstAid' => $this->resource->firstAidPermission()->first()->name,
'tetanusVaccination' => $this->resource->tetanus_vaccination?->format('d.m.Y'),
'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport], 'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport],
'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()->name, 'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()->name,
'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct, 'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct,
@@ -43,7 +49,8 @@ class EventParticipantResource extends JsonResource
'amount_left_string' => $amountLeft->toString(), 'amount_left_string' => $amountLeft->toString(),
'amount_left_value' => $amountLeft->getAmount(), 'amount_left_value' => $amountLeft->getAmount(),
'email_1' => $this->resource->email_1, 'email_1' => $this->resource->email_1,
'amountPaid' => ['value' => $this->resource->amount_paid, 'readable' => $this->resource->amount_paid?->toString() ?? '0,00 Euro'],
'amountExpected' => ['value' => $this->resource->amount, 'readable' => $this->resource->amount?->toString() ?? '0,00 Euro'],
] ]
); );

View File

@@ -67,13 +67,12 @@ class EventResource extends JsonResource{
$returnArray['participants'][$participationType] = $this->getParticipants($participationType); $returnArray['participants'][$participationType] = $this->getParticipants($participationType);
} }
$returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true); $returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true);
$returnArray['solidarityPayment'] = $this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY; $returnArray['solidarityPayment'] = $this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY;
$returnArray['payPerDay'] = $this->event->pay_per_day; $returnArray['payPerDay'] = $this->event->pay_per_day;
$returnArray['maxAmount'] = $this->event->total_max_amount->getFormattedAmount(); $returnArray['maxAmount'] = $this->event->total_max_amount->getFormattedAmount();
$returnArray['arrivalDefault'] = $this->event->start_date->format('Y-m-d');
$returnArray['departureDefault'] = $this->event->end_date->format('Y-m-d');
$returnArray['eventBegin'] = $this->event->start_date->format('d.m.Y'); $returnArray['eventBegin'] = $this->event->start_date->format('d.m.Y');
$returnArray['eventBeginInternal'] = $this->event->start_date; $returnArray['eventBeginInternal'] = $this->event->start_date;
$returnArray['eventEnd'] = $this->event->end_date->format('d.m.Y'); $returnArray['eventEnd'] = $this->event->end_date->format('d.m.Y');
@@ -94,7 +93,6 @@ class EventResource extends JsonResource{
$totalBalanceReal->subtractAmount($returnArray['costUnit']['overAllAmount']['value']); $totalBalanceReal->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
$totalBalanceExpected->subtractAmount($returnArray['costUnit']['overAllAmount']['value']); $totalBalanceExpected->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
$returnArray['totalBalance'] = [ $returnArray['totalBalance'] = [
'real' => [ 'real' => [
'value' => $totalBalanceReal->getAmount(), 'value' => $totalBalanceReal->getAmount(),

View File

@@ -5,6 +5,7 @@ namespace App\Scopes;
use App\Models\Tenant; use App\Models\Tenant;
use App\Providers\AuthCheckProvider; use App\Providers\AuthCheckProvider;
use App\Repositories\CostUnitRepository; use App\Repositories\CostUnitRepository;
use App\Repositories\EventParticipantRepository;
use App\Repositories\EventRepository; use App\Repositories\EventRepository;
use App\Repositories\InvoiceRepository; use App\Repositories\InvoiceRepository;
use App\Repositories\PageTextRepository; use App\Repositories\PageTextRepository;
@@ -19,6 +20,7 @@ abstract class CommonController {
protected InvoiceRepository $invoices; protected InvoiceRepository $invoices;
protected EventRepository $events; protected EventRepository $events;
protected EventParticipantRepository $eventParticipants;
public function __construct() { public function __construct() {
$this->tenant = app('tenant'); $this->tenant = app('tenant');
@@ -27,6 +29,7 @@ abstract class CommonController {
$this->pageTexts = new PageTextRepository(); $this->pageTexts = new PageTextRepository();
$this->invoices = new InvoiceRepository(); $this->invoices = new InvoiceRepository();
$this->events = new EventRepository(); $this->events = new EventRepository();
$this->eventParticipants = new EventParticipantRepository();
} }
protected function checkAuth() { protected function checkAuth() {

View File

@@ -27,6 +27,7 @@ return new class extends Migration
Schema::create('swimming_permissions', function (Blueprint $table) { Schema::create('swimming_permissions', function (Blueprint $table) {
$table->string('slug')->unique()->primary(); $table->string('slug')->unique()->primary();
$table->string('name'); $table->string('name');
$table->string('short');
$table->timestamps(); $table->timestamps();
}); });

View File

@@ -46,10 +46,10 @@ return new class extends Migration {
$table->string('location'); $table->string('location');
$table->string('postal_code'); $table->string('postal_code');
$table->string('email'); $table->string('email');
$table->dateTime('start_date'); $table->date('start_date');
$table->dateTime('end_date'); $table->date('end_date');
$table->dateTime('early_bird_end'); $table->date('early_bird_end');
$table->dateTime('registration_final_end'); $table->date('registration_final_end');
$table->integer('early_bird_end_amount_increase'); $table->integer('early_bird_end_amount_increase');
$table->string('account_owner'); $table->string('account_owner');
$table->string('account_iban'); $table->string('account_iban');

View File

@@ -50,8 +50,8 @@ return new class extends Migration {
$table->boolean('foto_webseite')->default(false); $table->boolean('foto_webseite')->default(false);
$table->boolean('foto_partner')->default(false); $table->boolean('foto_partner')->default(false);
$table->boolean('foto_intern')->default(false); $table->boolean('foto_intern')->default(false);
$table->dateTime('arrival_date'); $table->date('arrival_date');
$table->dateTime('departure_date'); $table->date('departure_date');
$table->integer('arrival_eating'); $table->integer('arrival_eating');
$table->integer('departure_eating'); $table->integer('departure_eating');
$table->longText('notes')->nullable(); $table->longText('notes')->nullable();