request->bookings === []) { $response->message = 'Es wurden keine Zahlungen zum Buchen ausgewählt.'; return $response; } $module = EventPaymentModuleRegistry::forSlug(PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION); $participants = new EventParticipantRepository(); foreach ($this->request->bookings as $booking) { $participant = $participants->findInEventByIdentifier( $this->request->event, (string) ($booking['participantIdentifier'] ?? ''), ); $transaction = $this->toTransaction($booking); if ($participant === null || $transaction === null) { $response->failed++; continue; } // Zweite Prüfung, nicht bloß Gürtel und Hosenträger: In der Prüfansicht lässt sich eine // Zeile von Hand einer anderen Person zuordnen, und deren Wasserzeichen hat der erste // Durchlauf nie gesehen. if ($this->isBeforeWatermark($participant, $transaction)) { $response->skipped++; continue; } if ($module instanceof ReadsBankStatements) { $module->recordTransaction($participant, $transaction); } $participant->last_payment_date = $transaction->paymentDate; $this->book($participant, $transaction->amount); $response->booked++; } $response->success = true; $response->message = $this->summaryMessage($response); return $response; } /** * Bucht den Betrag **zusätzlich** zum bereits Gezahlten. * * Die manuelle Erfassung setzt `amount_paid` absolut, weil dort jemand den Gesamtstand eintippt. * Hier kommt eine einzelne Zahlung an -- eine zweite Rate darf die erste nicht überschreiben. * Der {@see ParticipantPaymentCommand} speichert dabei die zuvor gesetzten Felder mit und schickt * die passende Mail (bezahlt / fehlt noch / überzahlt). */ private function book(EventParticipant $participant, Amount $amount): void { $total = new Amount( round(($participant->amount_paid?->getAmount() ?? 0.0) + $amount->getAmount(), 2), 'Euro', ); new ParticipantPaymentCommand(new ParticipantPaymentRequest($participant, $total))->execute(); } /** @param array $booking */ private function toTransaction(array $booking): ?BankTransaction { $date = CarbonImmutable::createFromFormat('!Y-m-d', (string) ($booking['paymentDate'] ?? '')); $amount = $booking['amount'] ?? null; if ($date === false || !is_numeric($amount) || (float) $amount <= 0) { return null; } return new BankTransaction( paymentDate: $date, amount: new Amount(round((float) $amount, 2), 'Euro'), purpose: (string) ($booking['purpose'] ?? ''), payerName: (string) ($booking['payerName'] ?? ''), payerIban: (string) ($booking['payerIban'] ?? ''), rowNumber: (int) ($booking['rowNumber'] ?? 0), ); } private function isBeforeWatermark(EventParticipant $participant, BankTransaction $transaction): bool { $watermark = $participant->last_payment_date; return $watermark !== null && $transaction->paymentDate->startOfDay()->lt($watermark->startOfDay()); } private function summaryMessage(BookBankStatementPaymentsResponse $response): string { $message = sprintf('%d Zahlungen wurden gebucht.', $response->booked); if ($response->skipped > 0) { $message .= sprintf( ' %d wurden übersprungen, weil sie älter sind als die zuletzt erfasste Zahlung der Person.', $response->skipped, ); } if ($response->failed > 0) { $message .= sprintf(' %d konnten nicht zugeordnet werden.', $response->failed); } return $message; } }