message = 'Für die Überweisung ist kein Kontoauszug-Import eingerichtet.'; return $response; } $contents = $this->readFile($response); if ($contents === null) { return $response; } $ruleset = $module instanceof ProvidesStatementRuleset ? $module->statementRuleset($this->request->configuration) : BankStatementRuleset::default(); try { $transactions = new BankStatementParseProvider()->parse($contents, $ruleset); } catch (BankStatementParseException $exception) { $response->message = $exception->getMessage(); return $response; } $candidates = new EventParticipantRepository()->getForPaymentMatching($this->request->event); foreach ($transactions as $transaction) { if (!$module->isRelevantTransaction($transaction)) { continue; } $match = $module->matchTransaction($transaction, $candidates); // Vor der letzten erfassten Zahlung dieser Person: schon gebucht, oder aus einem Auszug, // der bereits verarbeitet wurde. Gar nicht erst anzeigen. if ($match !== null && $this->isBeforeWatermark($match->participant, $transaction)) { $response->skippedOlderThanWatermark++; continue; } $response->rows[] = $transaction->toArray() + [ 'suggestedIdentifier' => $match?->participant->identifier, 'confidence' => $match?->confidence, ]; } $response->participants = $candidates ->map(fn (EventParticipant $participant): array => $this->participantOption($participant)) ->values() ->all(); $response->success = true; $response->message = $this->summaryMessage($response); return $response; } private function readFile(ParseBankStatementResponse $response): ?string { $file = $this->request->file; if ($file === null || !$file->isValid()) { $response->message = 'Es wurde keine Datei hochgeladen.'; return null; } if ($file->getSize() > self::MAX_BYTES) { $response->message = 'Die Datei ist zu groß (maximal 8 MB).'; return null; } if (!in_array(strtolower($file->getClientOriginalExtension()), self::ALLOWED_EXTENSIONS, true)) { $response->message = 'Bitte den CSV-Export der Bank hochladen (.csv).'; return null; } $contents = file_get_contents($file->getRealPath()); if ($contents === false || trim($contents) === '') { $response->message = 'Die Datei ließ sich nicht lesen oder ist leer.'; return null; } return $contents; } private function isBeforeWatermark(EventParticipant $participant, BankTransaction $transaction): bool { $watermark = $participant->last_payment_date; // Echt kleiner, nicht kleiner-gleich: Zwei Zahlungen am selben Tag sollen beide ankommen. return $watermark !== null && $transaction->paymentDate->startOfDay()->lt($watermark->startOfDay()); } /** @return array */ private function participantOption(EventParticipant $participant): array { $amountLeft = clone $participant->amount; if ($participant->amount_paid !== null) { $amountLeft->subtractAmount($participant->amount_paid); } return [ 'identifier' => $participant->identifier, 'name' => $participant->lastname . ', ' . $participant->firstname, 'amount' => $participant->amount?->toString(), 'amountPaid' => $participant->amount_paid?->toString(), 'amountOpen' => $amountLeft->toString(), 'isSettled' => round($amountLeft->getAmount(), 2) <= 0, 'lastPaymentDate' => $participant->last_payment_date?->format('d.m.Y'), // Zahlt jemand, der sich abgemeldet hat, wird die Zahlung trotzdem erfasst -- danach // steht aber eine Erstattung an. Die Prüfansicht weist darauf hin. 'isSignedOff' => $participant->unregistered_at !== null, 'signedOffAt' => $participant->unregistered_at?->format('d.m.Y'), ]; } private function summaryMessage(ParseBankStatementResponse $response): string { if ($response->rows === []) { return $response->skippedOlderThanWatermark > 0 ? 'Alle Zahlungseingänge dieser Datei wurden bereits erfasst.' : 'In der Datei sind keine Zahlungseingänge zu dieser Aktion enthalten.'; } $assigned = count(array_filter($response->rows, static fn (array $row): bool => $row['suggestedIdentifier'] !== null)); $message = sprintf( '%d Zahlungseingänge gelesen, %d davon konnten zugeordnet werden.', count($response->rows), $assigned, ); if ($response->skippedOlderThanWatermark > 0) { $message .= sprintf(' %d bereits erfasste Zahlungen wurden übersprungen.', $response->skippedOlderThanWatermark); } return $message; } }