Zahlungsparser
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\BookBankStatementPayments;
|
||||
|
||||
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentCommand;
|
||||
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentRequest;
|
||||
use App\EventPaymentModules\EventPaymentModuleRegistry;
|
||||
use App\EventPaymentModules\ReadsBankStatements;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Models\PaymentMethod;
|
||||
use App\Repositories\EventParticipantRepository;
|
||||
use App\ValueObjects\Amount;
|
||||
use App\ValueObjects\BankTransaction;
|
||||
use Carbon\CarbonImmutable;
|
||||
|
||||
/**
|
||||
* Bucht die in der Prüfansicht bestätigten Zahlungseingänge -- alle in einem Durchgang.
|
||||
*
|
||||
* Bewusst **ohne** umspannende Transaktion: Gebucht wird über {@see ParticipantPaymentCommand}, und der
|
||||
* verschickt die Zahlungsmail gleich mit. Das Projekt kennt keine Queue, die Mails gehen also synchron
|
||||
* raus; eine offene Transaktion über achtzig Mailversände wäre die schlechtere Wahl. Jede Zeile ist
|
||||
* für sich vollständig, und das Wasserzeichen schützt sie gegen eine Wiederholung.
|
||||
*/
|
||||
class BookBankStatementPaymentsCommand
|
||||
{
|
||||
public function __construct(private readonly BookBankStatementPaymentsRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): BookBankStatementPaymentsResponse
|
||||
{
|
||||
$response = new BookBankStatementPaymentsResponse();
|
||||
|
||||
if ($this->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<string, mixed> $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;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\BookBankStatementPayments;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class BookBankStatementPaymentsRequest
|
||||
{
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $bookings Je Eintrag die bestätigte Zeile der Prüfansicht:
|
||||
* participantIdentifier, paymentDate, amount,
|
||||
* payerName, payerIban, purpose.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Event $event,
|
||||
public readonly array $bookings,
|
||||
) {
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\BookBankStatementPayments;
|
||||
|
||||
class BookBankStatementPaymentsResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
|
||||
public int $booked = 0;
|
||||
|
||||
/** Übersprungen, weil älter als die zuletzt erfasste Zahlung der Person. */
|
||||
public int $skipped = 0;
|
||||
|
||||
/** Nicht verarbeitbar (unbekannte Anmeldung, unbrauchbarer Betrag). */
|
||||
public int $failed = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user