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); } }