diff --git a/app/Domains/Event/Views/Partials/ParticipantsList.vue b/app/Domains/Event/Views/Partials/ParticipantsList.vue index 00333b0..567e610 100644 --- a/app/Domains/Event/Views/Partials/ParticipantsList.vue +++ b/app/Domains/Event/Views/Partials/ParticipantsList.vue @@ -10,6 +10,7 @@ import AmountInput from "../../../../Views/Components/AmountInput.vue"; import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue"; import DialableTelephoneNumber from "../../../../Views/Components/DialableTelephoneNumber.vue"; import ErrorText from "../../../../Views/Components/ErrorText.vue"; +import IbanInput from "../../../../Views/Components/IbanInput.vue"; const props = defineProps({ data: { @@ -47,10 +48,11 @@ const openCancelDialog = ref(false); const openPartialPaymentDialogSwitch = ref(false); const openRefundDialogSwitch = ref(false); -// Der Erstattungsdialog. Betrag und Grund werden hier gesetzt; die Bankverbindung erfasst der Teili -// selbst über den Link, den die Freigabe ihm schickt. -const refundForm = reactive({amount: '', reason: '', reasonNote: ''}); -const refundErrors = reactive({amount: '', reason: '', reasonNote: ''}); +// Der Erstattungsdialog. `captureMode` steuert den Weg: 'participant' schickt dem Teili einen Link, über +// den er seine Bankverbindung selbst einträgt; 'management' heißt, sie liegt der Aktionsleitung bereits +// vor -- dann wird die Erstattung sofort eingereicht. +const refundForm = reactive({amount: '', reason: '', reasonNote: '', captureMode: 'participant', accountOwner: '', accountIban: ''}); +const refundErrors = reactive({amount: '', reason: '', reasonNote: '', accountOwner: '', accountIban: ''}); const refundReasons = ref([]); const refundSaving = ref(false); @@ -317,9 +319,12 @@ async function openRefundDialog(participant) { refundForm.amount = participant.amountPaid?.short ?? ''; refundForm.reason = ''; refundForm.reasonNote = ''; - refundErrors.amount = ''; - refundErrors.reason = ''; - refundErrors.reasonNote = ''; + // Vorgabe ist der übliche Weg über den Teili. + refundForm.captureMode = 'participant'; + refundForm.accountOwner = ''; + refundForm.accountIban = ''; + + Object.keys(refundErrors).forEach(key => refundErrors[key] = ''); if (refundReasons.value.length === 0) { const reasons = await request('/api/v1/core/retrieve-refund-reasons', {method: 'GET'}); @@ -333,9 +338,7 @@ function validateRefund() { const amount = Number((refundForm.amount ?? '').replace(',', '.')); const paid = Number(showParticipant.value?.amountPaidValue ?? 0); - refundErrors.amount = ''; - refundErrors.reason = ''; - refundErrors.reasonNote = ''; + Object.keys(refundErrors).forEach(key => refundErrors[key] = ''); if (!refundForm.amount || !(amount > 0)) { refundErrors.amount = 'Bitte gib einen Betrag größer als 0 ein.'; @@ -349,7 +352,19 @@ function validateRefund() { refundErrors.reasonNote = 'Bitte erläutere den Grund.'; } - return !refundErrors.amount && !refundErrors.reason && !refundErrors.reasonNote; + // Beim Direktweg wird sofort eingereicht -- danach gibt es keine Gelegenheit mehr zu berichtigen. + // Ob die IBAN wirklich stimmt, prüft der Server mit Prüfziffer und länderabhängiger Länge. + if (refundForm.captureMode === 'management') { + if (!refundForm.accountOwner.trim()) { + refundErrors.accountOwner = 'Bitte gib an, wem das Konto gehört.'; + } + + if (!refundForm.accountIban.trim()) { + refundErrors.accountIban = 'Bitte gib die IBAN des Kontos ein.'; + } + } + + return Object.values(refundErrors).every(message => !message); } async function execRefund() { @@ -366,13 +381,22 @@ async function execRefund() { amount: refundForm.amount, reason: refundForm.reason, reasonNote: refundForm.reasonNote, + // Leer beim Weg über den Teili -- dann verschickt der Server nur den Link. + accountOwner: refundForm.captureMode === 'management' ? refundForm.accountOwner : '', + accountIban: refundForm.captureMode === 'management' ? refundForm.accountIban : '', }, }); if (data?.status === 'success') { toast.success(data.message); // Reaktiv statt per getElementById: die Meta-Zeile hängt am Vorgang und wechselt mit ihm. + // Beim Direktweg steht dort sofort "Erstattet" samt Abrechnungsnummer. showParticipant.value.refund = data.refund; + // Der gezahlte Beitrag wird beim Einreichen auf 0 gesetzt -- sonst zeigte die Zeile weiter + // den alten Stand, bis jemand neu lädt. + if (data.refund?.status === 'accepted') { + showParticipant.value.amountPaidValue = 0; + } openRefundDialogSwitch.value = false; } else { toast.error(data?.message ?? 'Die Erstattung konnte nicht freigegeben werden.'); @@ -603,8 +627,7 @@ function mailToGroup(groupKey) { >
{{ showParticipant?.fullname }} hat - {{ showParticipant?.amountPaid?.readable }} gezahlt. Nach der Freigabe erhält - der Teili eine E-Mail und trägt seine Bankverbindung selbst ein. + {{ showParticipant?.amountPaid?.readable }} gezahlt.
+ Die Erstattung wird sofort als Abrechnung eingereicht. Der Teili erhält den Beleg per + E-Mail und kann die Angaben prüfen. +
+ + @@ -668,10 +732,32 @@ function mailToGroup(groupKey) { } .refund-field select, -.refund-field textarea { +.refund-field textarea, +.refund-field .form-input { width: 100%; } +.refund-choice { + display: block; + margin-bottom: 6px; + font-size: 0.9rem; + color: #1a1a1a; + cursor: pointer; +} + +.refund-choice input { + margin-right: 6px; +} + +.refund-hint { + margin-bottom: 14px; + padding: 8px 10px; + border-left: 3px solid #f5c400; + background-color: #fffef5; + font-size: 0.85rem; + color: #4b5563; +} + .participants-table { width: 95%; margin: 20px auto; diff --git a/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundCommand.php b/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundCommand.php index 31825b7..977a832 100644 --- a/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundCommand.php +++ b/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundCommand.php @@ -67,7 +67,10 @@ class AcceptRefundCommand // Serverseitig und nicht nur im Formular: die Erklärung ist der einzige Grund, warum der Beleg // als Eigenbeleg etwas wert ist. Ließe sie sich mit einem direkten Aufruf übergehen, stünde auf // dem PDF eine Zusicherung, die niemand abgegeben hat. - if (!$this->request->declarationAccepted) { + // + // Nimmt die Aktionsleitung die Angaben auf, kreuzt naturgemäß niemand etwas an. Nachvollziehbar + // bleibt es trotzdem: `captured_by` hält fest, wer sie aufgenommen hat, und der Beleg weist es aus. + if (!$this->request->declarationAccepted && $this->request->capturedBy === null) { $response->errorTypes['declaration'] = 'Bitte bestätige die Erklärung, damit wir erstatten können.'; } @@ -107,6 +110,7 @@ class AcceptRefundCommand $document = DB::transaction(function () use ($refund, $owner, $iban, $costUnit) { $refund->account_owner = $owner; $refund->account_iban = $iban; + $refund->captured_by = $this->request->capturedBy; $refund->status = ParticipantRefund::STATUS_ACCEPTED; $refund->accepted_at = now(); $refund->save(); diff --git a/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundRequest.php b/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundRequest.php index 637e0ee..f2daa72 100644 --- a/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundRequest.php +++ b/app/Domains/ParticipantRefund/Actions/AcceptRefund/AcceptRefundRequest.php @@ -12,6 +12,13 @@ class AcceptRefundRequest public readonly string $accountIban, /** Ob der Teili die Erklärung auf der Seite angekreuzt hat. Ohne sie taugt der Beleg nichts. */ public readonly bool $declarationAccepted = false, + /** + * Die Aktionsleitung, wenn sie die Bankverbindung aufgenommen hat, weil sie ihr vorlag. + * + * Dann kreuzt niemand die Erklärung an -- sie wird stellvertretend aufgenommen, und der Beleg + * weist genau das aus. + */ + public readonly ?int $capturedBy = null, ) { } } diff --git a/app/Domains/ParticipantRefund/Actions/CreateRefundDocument/CreateRefundDocumentCommand.php b/app/Domains/ParticipantRefund/Actions/CreateRefundDocument/CreateRefundDocumentCommand.php index eaa41f4..65e61e5 100644 --- a/app/Domains/ParticipantRefund/Actions/CreateRefundDocument/CreateRefundDocumentCommand.php +++ b/app/Domains/ParticipantRefund/Actions/CreateRefundDocument/CreateRefundDocumentCommand.php @@ -104,6 +104,28 @@ class CreateRefundDocumentCommand * Fallback stünde hier ein Fatal Error auf `null` -- so steht es heute im Deckblatt-Code der * Auslagenerstattung, und daran soll sich der Beleg kein Beispiel nehmen. */ + /** + * Der Vermerk, wenn die Aktionsleitung die Angaben aufgenommen hat. + * + * Er nennt Name und Datum, weil in diesem Fall niemand die Erklärung darüber angekreuzt hat: Wer den + * Beleg prüft, soll erkennen, dass dort eine aufgenommene Angabe steht und keine Bestätigung des + * Teilis selbst. Beim gewöhnlichen Weg bleibt der Platzhalter leer und der Block fällt weg. + */ + private function captureNote(): string + { + if (!$this->refund->wasCapturedByManagement()) { + return ''; + } + + $name = $this->refund->capturedBy()->first()?->getOfficialName(); + + return sprintf( + 'Angaben aufgenommen durch %s am %s.', + trim((string) $name) !== '' ? $name : 'die Aktionsleitung', + $this->refund->accepted_at?->format('d.m.Y') ?? '' + ); + } + private function declarationText(): string { $text = PageText::where('name', self::DECLARATION_TEXT)->first()?->content; @@ -178,6 +200,7 @@ class CreateRefundDocumentCommand 'account_iban' => $this->formatIban((string) $refund->account_iban), 'declaration_text' => $this->declarationText(), + 'capture_note' => $this->captureNote(), 'details_table' => $this->renderDetails(), ]; diff --git a/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundCommand.php b/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundCommand.php index 83c5382..c39b64c 100644 --- a/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundCommand.php +++ b/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundCommand.php @@ -2,13 +2,18 @@ namespace App\Domains\ParticipantRefund\Actions\ReleaseRefund; +use App\Domains\ParticipantRefund\Actions\AcceptRefund\AcceptRefundCommand; +use App\Domains\ParticipantRefund\Actions\AcceptRefund\AcceptRefundRequest; use App\Enumerations\RefundReason; use App\Mail\ParticipantRefundMails\RefundReleasedMail; use App\Models\EventParticipant; use App\Models\ParticipantRefund; use App\Repositories\ParticipantRefundRepository; +use App\Support\Iban; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; +use RuntimeException; /** * Gibt die Erstattung eines Teilnahmebeitrags frei. @@ -40,28 +45,66 @@ class ReleaseRefundCommand return $response; } - $refund = ParticipantRefund::create([ - 'tenant' => $this->participant->tenant, - 'event_id' => $this->participant->event_id, - 'event_participant_id' => $this->participant->id, - 'token' => Str::random(32), - 'status' => ParticipantRefund::STATUS_PENDING, - 'amount' => $this->request->amount, - 'reason' => $this->request->reason, - 'reason_note' => $this->reasonNote(), - 'released_by' => auth()->id(), - 'released_at' => now(), - ]); + // Liegt die Bankverbindung schon vor, entsteht in einem Zug auch die Abrechnung. Scheitert die, + // soll keine halbe Freigabe zurückbleiben -- deshalb beides in einer Transaktion. + $refund = DB::transaction(function (): ParticipantRefund { + $refund = ParticipantRefund::create([ + 'tenant' => $this->participant->tenant, + 'event_id' => $this->participant->event_id, + 'event_participant_id' => $this->participant->id, + 'token' => Str::random(32), + 'status' => ParticipantRefund::STATUS_PENDING, + 'amount' => $this->request->amount, + 'reason' => $this->request->reason, + 'reason_note' => $this->reasonNote(), + 'released_by' => auth()->id(), + 'released_at' => now(), + ]); - $this->notify($refund); + if ($this->request->hasBankDetails()) { + $this->submitDirectly($refund); + } + + return $refund; + }); + + if (!$this->request->hasBankDetails()) { + $this->notify($refund); + } $response->success = true; - $response->refund = $refund; - $response->message = 'Die Erstattung wurde freigegeben. Der Teili wurde per E-Mail informiert.'; + $response->refund = $refund->fresh(); + $response->message = $this->request->hasBankDetails() + ? 'Die Erstattung wurde eingereicht. Der Teili hat den Beleg per E-Mail erhalten.' + : 'Die Erstattung wurde freigegeben. Der Teili wurde per E-Mail informiert.'; return $response; } + /** + * Reicht die Erstattung sofort ein, ohne den Umweg über den Teili. + * + * Über denselben Command, den sonst der Bestätigungslink auslöst: Beleg, Abrechnung, das Nullstellen + * des gezahlten Beitrags und die Mail mit dem Beleg laufen dadurch in beiden Wegen identisch ab. + */ + private function submitDirectly(ParticipantRefund $refund): void + { + $acceptResponse = new AcceptRefundCommand(new AcceptRefundRequest( + refund: $refund, + accountOwner: (string) $this->request->accountOwner, + accountIban: (string) $this->request->accountIban, + // Niemand kreuzt hier eine Erklärung an; wer die Angaben aufgenommen hat, hält `captured_by` + // fest, und der Beleg weist es aus. + capturedBy: auth()->id(), + ))->execute(); + + if (!$acceptResponse->success) { + // Rollt die Freigabe zurück -- die Aktionsleitung soll den Fehler sehen und nicht einen + // Vorgang vorfinden, der nirgends eingereicht ist. + throw new RuntimeException($acceptResponse->message ?? 'Die Erstattung konnte nicht eingereicht werden.'); + } + } + /** * Alle Gründe, aus denen eine Freigabe nicht zulässig ist. * @@ -98,6 +141,34 @@ class ReleaseRefundCommand return 'Für diesen Grund ist eine Erläuterung erforderlich.'; } + return $this->rejectBankDetails(); + } + + /** + * Prüfungen, die nur den Direktweg betreffen -- die Erstattung wird dabei sofort eingereicht, es gibt + * also keine zweite Gelegenheit, Angaben zu berichtigen. + */ + private function rejectBankDetails(): ?string + { + // Halb ausgefüllt ist keine Absicht: entweder beides oder der Weg über den Teili. + if (filled($this->request->accountOwner) !== filled($this->request->accountIban)) { + return 'Für die sofortige Erstattung werden Kontoinhaber*in und IBAN benötigt.'; + } + + if (!$this->request->hasBankDetails()) { + return null; + } + + if (!Iban::isValid((string) $this->request->accountIban)) { + return 'Diese IBAN stimmt nicht. Bitte prüfe die Eingabe.'; + } + + // Ohne Kostenstelle ließe sich die Abrechnung nicht anlegen. Hier abfangen und nicht erst in der + // Transaktion, damit die Aktionsleitung eine verständliche Meldung sieht. + if ($this->participant->event->cost_unit_id === null) { + return 'Die Veranstaltung hat keine Kostenstelle -- die Erstattung kann nicht eingereicht werden.'; + } + return null; } diff --git a/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundRequest.php b/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundRequest.php index a1fbc8a..d6f0930 100644 --- a/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundRequest.php +++ b/app/Domains/ParticipantRefund/Actions/ReleaseRefund/ReleaseRefundRequest.php @@ -12,6 +12,20 @@ class ReleaseRefundRequest public readonly Amount $amount, public readonly string $reason, public readonly ?string $reasonNote = null, + /** + * Die Bankverbindung, wenn sie der Aktionsleitung bereits vorliegt. + * + * Sind beide gesetzt, entfällt der Umweg über den Teili: die Erstattung wird sofort eingereicht. + * Bleiben sie leer, läuft der übliche Weg über den Bestätigungslink. + */ + public readonly ?string $accountOwner = null, + public readonly ?string $accountIban = null, ) { } + + /** Ob die Erstattung ohne Zutun des Teilis eingereicht werden kann. */ + public function hasBankDetails(): bool + { + return filled($this->accountOwner) && filled($this->accountIban); + } } diff --git a/app/Domains/ParticipantRefund/Controllers/ReleaseRefundController.php b/app/Domains/ParticipantRefund/Controllers/ReleaseRefundController.php index 5b62771..a92bb02 100644 --- a/app/Domains/ParticipantRefund/Controllers/ReleaseRefundController.php +++ b/app/Domains/ParticipantRefund/Controllers/ReleaseRefundController.php @@ -25,6 +25,9 @@ class ReleaseRefundController extends CommonController amount: Amount::fromString((string) $request->input('amount'), 'Euro'), reason: (string) $request->input('reason'), reasonNote: Text::nullIfBlank($request->input('reasonNote')), + // Leer, wenn der Teili die Bankverbindung selbst eintragen soll. + accountOwner: Text::nullIfBlank($request->input('accountOwner')), + accountIban: Text::nullIfBlank($request->input('accountIban')), ); $response = new ReleaseRefundCommand($refundRequest)->execute(); diff --git a/app/Domains/ParticipantRefund/ParticipantRefundTokens.php b/app/Domains/ParticipantRefund/ParticipantRefundTokens.php index cbd954c..5e90382 100644 --- a/app/Domains/ParticipantRefund/ParticipantRefundTokens.php +++ b/app/Domains/ParticipantRefund/ParticipantRefundTokens.php @@ -66,6 +66,7 @@ final class ParticipantRefundTokens 'account_owner' => ['description' => 'Kontoinhaber*in', 'sample' => 'Mika Muster'], 'account_iban' => ['description' => 'IBAN', 'sample' => 'DE02 1203 0000 0000 2020 51'], 'declaration_text' => ['description' => 'Die Erklärung, die die teilnehmende Person bestätigt hat (gepflegt als Seitentext CONFIRMATION_PARTICIPANT_REFUND)', 'sample' => 'Ich versichere, dass ich den genannten Betrag beglichen habe und nicht anderweitig zurückerstattet bekomme.'], + 'capture_note' => ['description' => 'Vermerk, wenn die Aktionsleitung die Bankverbindung aufgenommen hat — sonst leer', 'sample' => 'Angaben aufgenommen durch Aktions Leitung am 18.06.2026.'], ], ], 'body' => [ diff --git a/app/Mail/ParticipantRefundMails/RefundAcceptedMail.php b/app/Mail/ParticipantRefundMails/RefundAcceptedMail.php index 3b12655..a50ca50 100644 --- a/app/Mail/ParticipantRefundMails/RefundAcceptedMail.php +++ b/app/Mail/ParticipantRefundMails/RefundAcceptedMail.php @@ -54,6 +54,9 @@ class RefundAcceptedMail extends Mailable 'accountIban' => Iban::format((string) $this->refund->account_iban), 'hasDocument' => $this->pdfContent !== null, 'invoiceNumber' => $invoice?->invoice_number, + // Hat die Aktionsleitung die Bankverbindung aufgenommen, hat der Teili selbst nichts + // eingetragen -- dann darf die Mail sich nicht für seine Angaben bedanken. + 'capturedByManagement' => $this->refund->wasCapturedByManagement(), // Der Hinweis auf "Meine Abrechnungen" nur, wenn die Anmeldung an einem Konto hängt -- // die Seite filtert über die Nutzer-Verknüpfung und bliebe sonst leer. 'myInvoicesUrl' => $invoice?->user_id !== null ? url('/invoice/my-invoices/new') : null, diff --git a/app/Models/ParticipantRefund.php b/app/Models/ParticipantRefund.php index 4084543..5a0948d 100644 --- a/app/Models/ParticipantRefund.php +++ b/app/Models/ParticipantRefund.php @@ -22,6 +22,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string|null $reason_note * @property string|null $account_owner * @property string|null $account_iban + * @property int|null $captured_by * @property int|null $invoice_id * @property int|null $released_by * @property \Illuminate\Support\Carbon|null $released_at @@ -52,6 +53,7 @@ class ParticipantRefund extends InstancedModel 'reason_note', 'account_owner', 'account_iban', + 'captured_by', 'invoice_id', 'released_by', 'released_at', @@ -93,6 +95,20 @@ class ParticipantRefund extends InstancedModel return $this->belongsTo(Invoice::class); } + /** + * Wer die Bankverbindung aufgenommen hat -- leer, wenn der Teili sie selbst eingetragen hat. + */ + public function capturedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'captured_by'); + } + + /** Ob die Angaben von der Aktionsleitung stammen und nicht vom Teili selbst. */ + public function wasCapturedByManagement(): bool + { + return $this->captured_by !== null; + } + public function isPending(): bool { return $this->status === self::STATUS_PENDING; diff --git a/database/migrations/2026_09_06_140010_add_captured_by_to_participant_refunds.php b/database/migrations/2026_09_06_140010_add_captured_by_to_participant_refunds.php new file mode 100644 index 0000000..f6f2303 --- /dev/null +++ b/database/migrations/2026_09_06_140010_add_captured_by_to_participant_refunds.php @@ -0,0 +1,34 @@ +foreignId('captured_by')->nullable()->after('account_iban') + ->constrained('users', 'id')->nullOnDelete()->cascadeOnUpdate(); + }); + } + + public function down(): void + { + Schema::table('participant_refunds', function (Blueprint $table) { + $table->dropForeign(['captured_by']); + $table->dropColumn('captured_by'); + }); + } +}; diff --git a/database/seed-scripts/participant-refund-template.sql b/database/seed-scripts/participant-refund-template.sql index f400776..1e0a4e1 100644 --- a/database/seed-scripts/participant-refund-template.sql +++ b/database/seed-scripts/participant-refund-template.sql @@ -84,6 +84,9 @@ body { font-family: \'DejaVu Sans\', sans-serif; font-size: 9.5pt; color: #1a1a1 /* Die Versicherung des Teilis unter den Angaben */ .declaration { font-size: 9.5pt; line-height: 1.6; margin-top: 7mm; } +/* Vermerk, wenn die Aktionsleitung die Angaben aufgenommen hat -- steht sonst nicht auf dem Beleg. */ +.capture-note { font-size: 8pt; color: #555; line-height: 1.55; margin-top: 3mm; } + /* Gelber Randstreifen mit Knick -- position:fixed, damit er auf jeder Seite steht. */ .edge { position: fixed; top: 0; left: 0; width: 16mm; height: 297mm; }', 20, 1, NOW(), NOW()), ('participant_refund', 'header_sender_return', '- vielen Dank – deine Angaben zur Rückerstattung für die Veranstaltung "{{$eventTitle}}" liegen - uns vor. Du musst nichts weiter tun: Die Erstattung ist bereits als Abrechnung - eingereicht und wird nun bearbeitet. -
+@if ($capturedByManagement) ++ die Aktionsleitung hat deine Bankverbindung für die Rückerstattung deines Teilnahmebeitrags zur + Veranstaltung "{{$eventTitle}}" erfasst. Du musst nichts weiter tun: Die + Erstattung ist bereits als Abrechnung eingereicht und wird nun bearbeitet. +
++ Bitte prüfe die unten stehenden Angaben – besonders die IBAN. +
+@else ++ vielen Dank – deine Angaben zur Rückerstattung für die Veranstaltung "{{$eventTitle}}" liegen + uns vor. Du musst nichts weiter tun: Die Erstattung ist bereits als Abrechnung + eingereicht und wird nun bearbeitet. +
+@endif