4.3 KiB
CLAUDE.md
Laravel 12 + Inertia/Vue, mandantenfähig (custom tenant-Scoping über app('tenant'), gesetzt in
app/Middleware/IdentifyTenant.php). PHP 8.5 — Tests/Artisan laufen im Container:
docker exec mareike-mareike-app-1 php artisan test.
Projektkonventionen (verbindlich)
Die verbindlichen Konventionen (Actions/Request-Command-Response, Controller, Repositories, Models/Resources, Tenant,
Routing, Mails) stehen in .ai/conventions.md und werden hier eingebunden — immer beachten:
@.ai/conventions.md
Bereichs-Dokumentation
- Zahlungsmodule — interface-gesteuerte Zahlungsarten (Optionen, Config, Anmelde-Zusammenfassung, GiroCode/Fähigkeits-Interfaces, neues Modul hinzufügen).
Umsatzsteuer / Steuerinformationen
Rechtlich korrekter Begriff ist Umsatzsteuer (USt), nicht MwSt. Ziel: tagesbasierte Anmelde-Rechnungen („Teilnahme Veranstaltung X in Gruppe Y" × Anwesenheitstage × Beitrag/Tag). Grundlage ist gebaut, die Rechnungs-PDF/-berechnung folgt noch.
Tenant-Einstellungen (Tab „Steuerinformationen")
Felder auf tenants (Self-Service TenantData.vue + verwaltet TenantEdit.vue, Partial TenantTaxInfo.vue,
Controller TenantTax* / ManagedTenantTax*, Action UpdateTenantTax):
tax_liable(bool) — ob USt-Pflicht besteht.vat_rate(int, %) — USt-Satz, nur relevant wenntax_liable(0–100, geclamped).vat_pricing_mode(EnumApp\Enumerations\VatPricingMode, natives PHP-Enum:inclusive|add_on).tax_exemption_reason(FK →tax_exemption_reasons.slug, nullable) — Grund wenn nicht steuerpflichtig. DB-Default neuer Tenants:ustg_4_25a(Jugendhilfe).tax_exemption_note(text, nullable) — Freitext, nur bei Grundcustom(requires_note).
Preismodus — das Kernverhalten
Es wird immer der finale Brutto-Beitrag pro Anmeldung gespeichert (EventParticipant.amount, keine USt-Spalten am
Teilnehmer). Der Modus wirkt nur bei der Preisermittlung im Anmeldeprozess:
inclusive(Brutto / „zusammenfassen", Supermarkt-Prinzip, Default): der konfigurierte Beitrag ist der Endpreis, USt ist bereits enthalten → Preis bleibt gleich; die USt wird erst auf der Rechnung herausgerechnet:USt = brutto × Satz / (100 + Satz),netto = brutto − USt.add_on(Netto / „aufaddieren"): der konfigurierte Beitrag ist netto → Endpreis =Beitrag × (1 + Satz/100). Die USt wird bei der Anmeldung aufgeschlagen → Teilnehmer zahlt mehr.
Umsetzung in App\Resources\EventResource::calculateAmount(): nach allen Multiplikatoren (Early-Bird, pay_per_day,
Geschwister) gilt
if ($event->tax_liable && $event->vat_pricing_mode === VatPricingMode::AddOn->value) $fee->multiply(1 + $event->vat_rate/100);
danach round(…, 2). Bei inclusive/nicht steuerpflichtig bleibt der Beitrag unverändert.
Event-Snapshot (unveränderlich)
Bei Event-Anlage (CreateEventCommand::execute()) werden tax_liable, vat_rate, vat_pricing_mode,
tax_exemption_reason, tax_exemption_note aus dem Tenant auf das Event kopiert und dort eingefroren (Spalten auf
events). Preisermittlung und spätere Rechnungen lesen vom Event, nicht vom Tenant → spätere Tenant-Änderungen berühren
bestehende Events/Rechnungen nicht.
Befreiungsgründe (DB-gestützt)
Tabelle tax_exemption_reasons, Model App\Enumerations\TaxExemptionReason (Eloquent, slug PK; name,
invoice_text, requires_note, sort_order). options() liefert {value,label,text} sortiert nach sort_order. FK
von tenants und events. Seed in Migration 170010, verfeinert in 170020. Gründe (in Reihenfolge):
ustg_19 (§ 19 Kleinunternehmer), ustg_4_24 (§ 4 Nr. 24 Jugendherbergen),
ustg_4_25a (§ 4 Nr. 25 Buchst. a — Jugendhilfe-Veranstaltungen, Default), ustg_4_22a (§ 4 Nr. 22 Buchst. a —
Vorträge/Kurse), custom (Freitext). Jeder Grund kapselt den Rechnungs-Pflichthinweis nach § 14 Abs. 4 Nr. 8 UStG
(invoice_text; bei custom der Tenant-Freitext via invoiceText(?note)). Gemeinnützigkeit allein befreit nicht —
es braucht immer einen konkreten Rechtsgrund.
Noch offen (nächster Schritt)
Die eigentliche Rechnung: tagesbasierte Positionen, USt aus dem gespeicherten Brutto herausrechnen (Satz vom Event-Snapshot); bei Befreiung kein USt-Ausweis, stattdessen der Pflichthinweis des Grundes.