Files
mareike/app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
T
2026-09-04 09:27:48 +02:00

141 lines
6.0 KiB
PHP

<?php
namespace App\Domains\Event\Actions\CreateEvent;
use App\Enumerations\EatingHabit;
use App\Models\AvailablePaymentMethod;
use App\Models\Event;
use App\Models\Tenant;
use App\RelationModels\EventEatingHabits;
use App\RelationModels\EventLocalGroups;
use App\RelationModels\EventPaymentMethods;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class CreateEventCommand {
private CreateEventRequest $request;
public function __construct(CreateEventRequest $request) {
$this->request = $request;
}
public function execute(): CreateEventResponse {
$response = new CreateEventResponse();
$prefix = $this->request->begin->format('Y-m_');
if (!str_starts_with($this->request->name, $prefix)) {
$this->request->name = $prefix . $this->request->name;
}
$tenant = currentTenant();
// Anlage als Ganzes: der Event-Teil der Rechnungsnummer wird unter der Sperre vergeben und muss
// im selben Zug geschrieben werden, sonst könnten zwei gleichzeitige Anlagen denselben erhalten.
$event = DB::transaction(function () use ($tenant): Event {
$event = Event::create([
'tenant' => $tenant->slug,
'name' => $this->request->name,
'identifier' => Str::random(10),
'location' => $this->request->location,
'postal_code' => $this->request->postalCode,
'email' => $this->request->email,
'start_date' => $this->request->begin,
'end_date' => $this->request->end,
'early_bird_end' => $this->request->earlyBirdEnd,
'registration_final_end' => $this->request->registrationFinalEnd,
'early_bird_end_amount_increase' => $this->request->earlyBirdEndAmountIncrease,
'account_owner' => $this->request->accountOwner,
'account_iban' => $this->request->accountIban,
'participation_fee_type' => $this->request->participationFeeType->slug,
'pay_per_day' => $this->request->payPerDay,
'pay_direct' => false,
'total_max_amount' => 0,
'support_per_person' => 0,
'support_flat' => 0,
// Umsatzsteuerliche Grundlage bei Anlage einfrieren (unveränderlich), damit Preisermittlung und
// spätere Rechnungen von späteren Tenant-Änderungen unberührt bleiben.
'tax_liable' => $tenant->tax_liable,
'vat_rate' => $tenant->vat_rate,
'vat_pricing_mode' => $tenant->vat_pricing_mode,
'tax_exemption_reason' => $tenant->tax_exemption_reason,
'tax_exemption_note' => $tenant->tax_exemption_note,
// Den Event-Teil der Rechnungsnummer einfrieren: Tenant-Präfix und Startdatum sind später
// änderbar, eine herausgegebene Rechnung würde sonst zu einer anderen Nummer gehören.
//
// Der Rechnungssteller wird bewusst NICHT mitkopiert -- er ist der Mandant und wird beim
// Erzeugen der Rechnung von dort gelesen, damit eine Korrektur an Name oder Anschrift auch
// auf bestehende Veranstaltungen wirkt.
'invoice_key' => $this->nextInvoiceKey($tenant),
]);
return $event;
});
if ($event !== null) {
EventEatingHabits::create([
'event_id' => $event->id,
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGAN)->first()->id,
]);
EventEatingHabits::create([
'event_id' => $event->id,
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGETARIAN)->first()->id,
]);
foreach (AvailablePaymentMethod::where('active', true)->get() as $availablePaymentMethod) {
EventPaymentMethods::create([
'event_id' => $event->id,
'slug' => $availablePaymentMethod->slug,
'configuration' => $availablePaymentMethod->configuration ?? [],
]);
}
if (currentTenant()->slug === 'lv') {
foreach(Tenant::where(['is_active_local_group' => true])->get() as $tenant) {
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => $tenant->id]);
}
} else {
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => currentTenant()->id]);
}
$response->success = true;
$response->event = $event;
}
return $response;
}
/**
* Event-Teil der Rechnungsnummer, z.B. `WM-V-20260701`: Tenant-Präfix, Dokumentart „V" für
* Veranstaltung, dann ohne Trenner Jahr, Monat des Beginns und die laufende Nummer der
* Veranstaltungen dieses Tenants in diesem Monat.
*
* Gezählt wird über die bereits vergebenen Schlüssel desselben Monats. Die Sperre auf der
* Tenant-Zeile serialisiert gleichzeitige Anlagen; der Aufruf erfolgt innerhalb der
* Anlage-Transaktion, sodass die Sperre bis zum Schreiben des Events steht.
*/
private function nextInvoiceKey(Tenant $tenant): string
{
// Jahr und Monat ohne Trenner; die laufende Nummer haengt unmittelbar daran. Alle drei Teile
// haben feste Laenge, der Schluessel bleibt dadurch eindeutig zerlegbar.
$month = $this->request->begin->format('Ym');
$prefix = sprintf('%s-V-%s', $tenant->invoice_prefix ?? strtoupper($tenant->slug), $month);
DB::table('tenants')->where('id', $tenant->id)->lockForUpdate()->first();
$used = DB::table('events')
->where('tenant', $tenant->slug)
->where('invoice_key', 'like', $prefix . '%')
->pluck('invoice_key');
$highest = 0;
foreach ($used as $key) {
$highest = max($highest, (int) substr((string) $key, strlen($prefix)));
}
return $prefix . str_pad((string) ($highest + 1), 2, '0', STR_PAD_LEFT);
}
}