Über der Tabelle der Bankverbindungen ruft ein neuer Knopf jede aktive Bankverbindung einzeln nacheinander ab. Die Warteschlange (Services\Sammelabruf) übersteht TAN-Runden in der Sitzung; nach der Freigabe geht es mit der nächsten Bank weiter. Die TAN-Maske zeigt den Stand und erlaubt, eine Bank zu überspringen. Am Ende steht je Bankverbindung eine eigene Meldung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
5.6 KiB
PHP
176 lines
5.6 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Finanzmanager\Services;
|
||
|
||
/**
|
||
* „Alle Umsätze abrufen“: jede aktive Bankverbindung der Reihe nach.
|
||
*
|
||
* Die Klasse führt nur die Warteschlange – abgerufen wird weiterhin einzeln
|
||
* über `FinTsAbruf` beziehungsweise `PaypalAbruf`. Der Grund ist die TAN: Verlangt
|
||
* eine Bank eine Freigabe, endet der Request mit der TAN-Maske, und erst der
|
||
* nächste Request setzt den Abruf fort. Die Warteschlange muss diese Pause
|
||
* überstehen; der Controller legt ihren Zustand deshalb in die Sitzung und
|
||
* holt ihn dort wieder ab.
|
||
*
|
||
* Ein Fehler bei einer Bank hält die übrigen nicht auf: Er wird vermerkt, und
|
||
* es geht mit der nächsten weiter. Am Ende steht je Bankverbindung eine Meldung.
|
||
*/
|
||
final class Sammelabruf
|
||
{
|
||
/** Schlüssel des Zustands in der Sitzung. */
|
||
public const SITZUNG = 'banking_sammelabruf';
|
||
|
||
/**
|
||
* @param array{offen: list<array{id: int, name: string}>,
|
||
* aktuell: array{id: int, name: string}|null,
|
||
* gesamt: int,
|
||
* meldungen: list<array{typ: string, meldung: string}>} $zustand
|
||
*/
|
||
private function __construct(private array $zustand)
|
||
{
|
||
}
|
||
|
||
/**
|
||
* Eine neue Warteschlange über die übergebenen Bankverbindungen – in der
|
||
* Reihenfolge, in der sie auch in der Übersicht stehen.
|
||
*
|
||
* @param list<array<string,mixed>> $zugaenge
|
||
*/
|
||
public static function fuer(array $zugaenge): self
|
||
{
|
||
$offen = [];
|
||
foreach ($zugaenge as $zugang) {
|
||
$offen[] = ['id' => (int)$zugang['id'], 'name' => (string)$zugang['name']];
|
||
}
|
||
|
||
return new self([
|
||
'offen' => $offen,
|
||
'aktuell' => null,
|
||
'gesamt' => count($offen),
|
||
'meldungen' => [],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Die Warteschlange aus einem gespeicherten Zustand – `null`, wenn keiner
|
||
* (mehr) da ist.
|
||
*/
|
||
public static function aus(mixed $zustand): ?self
|
||
{
|
||
if (!is_array($zustand) || !is_array($zustand['offen'] ?? null)) {
|
||
return null;
|
||
}
|
||
|
||
return new self([
|
||
'offen' => array_values($zustand['offen']),
|
||
'aktuell' => is_array($zustand['aktuell'] ?? null) ? $zustand['aktuell'] : null,
|
||
'gesamt' => (int)($zustand['gesamt'] ?? 0),
|
||
'meldungen' => is_array($zustand['meldungen'] ?? null) ? $zustand['meldungen'] : [],
|
||
]);
|
||
}
|
||
|
||
/** @return array<string,mixed> Der Zustand zum Ablegen in der Sitzung. */
|
||
public function zustand(): array
|
||
{
|
||
return $this->zustand;
|
||
}
|
||
|
||
/**
|
||
* Die nächste Bankverbindung – `null`, wenn alle durch sind. Sie gilt ab
|
||
* jetzt als die laufende, bis ihr Ergebnis vermerkt ist.
|
||
*
|
||
* @return array{id: int, name: string}|null
|
||
*/
|
||
public function naechste(): ?array
|
||
{
|
||
$this->zustand['aktuell'] = array_shift($this->zustand['offen']);
|
||
|
||
return $this->zustand['aktuell'];
|
||
}
|
||
|
||
/** @return array{id: int, name: string}|null Die gerade laufende Bankverbindung. */
|
||
public function aktuell(): ?array
|
||
{
|
||
return $this->zustand['aktuell'];
|
||
}
|
||
|
||
/**
|
||
* Das Ergebnis der laufenden Bankverbindung festhalten.
|
||
*
|
||
* @param array<string,mixed> $ergebnis wie von `FinTsAbruf`/`PaypalAbruf`
|
||
*/
|
||
public function vermerke(array $ergebnis): void
|
||
{
|
||
if ($this->zustand['aktuell'] === null) {
|
||
return;
|
||
}
|
||
|
||
$fehler = ($ergebnis['status'] ?? 'fehler') === 'fehler';
|
||
$this->notiere(
|
||
$fehler ? 'fehler' : 'erfolg',
|
||
($fehler ? 'Abruf fehlgeschlagen: ' : '') . (string)($ergebnis['meldung'] ?? '')
|
||
);
|
||
}
|
||
|
||
/** Die laufende Bankverbindung ohne Ergebnis hinter sich lassen. */
|
||
public function ueberspringe(): void
|
||
{
|
||
if ($this->zustand['aktuell'] !== null) {
|
||
$this->notiere('fehler', 'Übersprungen, die Freigabe wurde abgebrochen.');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Den ganzen Lauf abbrechen: Die laufende und alle noch offenen
|
||
* Bankverbindungen werden nicht mehr abgerufen.
|
||
*/
|
||
public function abbrechen(): void
|
||
{
|
||
$offen = count($this->zustand['offen']) + ($this->zustand['aktuell'] !== null ? 1 : 0);
|
||
$this->zustand['aktuell'] = null;
|
||
$this->zustand['offen'] = [];
|
||
|
||
if ($offen > 0) {
|
||
$this->zustand['meldungen'][] = [
|
||
'typ' => 'fehler',
|
||
'meldung' => sprintf(
|
||
'Abruf abgebrochen – %d Bankverbindung%s nicht abgerufen.',
|
||
$offen,
|
||
$offen === 1 ? '' : 'en'
|
||
),
|
||
];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Wo der Lauf steht – für die TAN-Maske, damit dort klar ist, welche Bank
|
||
* gerade fragt und wie viele noch folgen.
|
||
*
|
||
* @return array{nummer: int, gesamt: int, name: string}
|
||
*/
|
||
public function stand(): array
|
||
{
|
||
return [
|
||
'nummer' => $this->zustand['gesamt'] - count($this->zustand['offen']),
|
||
'gesamt' => $this->zustand['gesamt'],
|
||
'name' => (string)($this->zustand['aktuell']['name'] ?? ''),
|
||
];
|
||
}
|
||
|
||
/** @return list<array{typ: string, meldung: string}> Je Bankverbindung eine Meldung. */
|
||
public function meldungen(): array
|
||
{
|
||
return $this->zustand['meldungen'];
|
||
}
|
||
|
||
private function notiere(string $typ, string $meldung): void
|
||
{
|
||
$this->zustand['meldungen'][] = [
|
||
'typ' => $typ,
|
||
'meldung' => $this->zustand['aktuell']['name'] . ': ' . $meldung,
|
||
];
|
||
$this->zustand['aktuell'] = null;
|
||
}
|
||
}
|