238 lines
16 KiB
Markdown
238 lines
16 KiB
Markdown
<!-- status: done -->
|
|
<!-- started: 2026-04-25 -->
|
|
<!-- completed: 2026-04-25 -->
|
|
<!-- component: gateway -->
|
|
|
|
# Trustee: Echte Schlusssalden aus Buchhaltungssystem importieren
|
|
|
|
## Beschreibung und Kontext
|
|
|
|
**Bug-Report Kunde (PROD-Mandant `BuHa SoHa`, Connector RMA):** Fragt man Nyla
|
|
nach dem Banksaldo per 31.12.2025 (Konto 1020), antwortet sie mit
|
|
`CHF 79'939.86`. Der echte Schlusssaldo ist `CHF 48'507.41`. Auch alle daraus
|
|
abgeleiteten Auswertungen (Geldflussrechnung, Umsatz-Diagramme, Plausichecks)
|
|
sind dadurch falsch.
|
|
|
|
**Root cause:** Die Tabelle `TrusteeDataAccountBalance` wird nicht aus dem
|
|
Buchhaltungssystem importiert, sondern in `accountingDataSync._persistBalances`
|
|
lokal aus den Journalzeilen aggregiert -- mit drei eigenstaendigen Fehlern:
|
|
|
|
1. `closingBalance = debit - credit` **nur** aus den Buchungen der jeweiligen
|
|
Periode (Monat oder Jahres-Bucket). Das ist die Periodenbewegung, kein
|
|
Schlusssaldo. Schon innerhalb desselben Jahres summieren sich die Vormonate
|
|
nicht auf den Stichtag.
|
|
2. `openingBalance` ist hart auf `0.0` kodiert -- der Saldovortrag aus den
|
|
Vorjahren / vor dem Importfenster fehlt komplett.
|
|
3. Auch der Jahres-Bucket `(accNo, year, 0)` laeuft durch dieselbe Formel,
|
|
d.h. der "Jahres-Schlusssaldo" ist eigentlich nur die Jahres-Bewegung.
|
|
|
|
**Architekturloch:** `BaseAccountingConnector` kennt schlicht keine
|
|
`getAccountBalances`-Methode. Der Defekt trifft alle drei aktuell registrierten
|
|
Connectoren (RMA / Bexio / Abacus).
|
|
|
|
**Warum jetzt:** Das Trustee-Feature steht im aktiven Roll-out beim Pilot-Kunden
|
|
(`BuHa SoHa`, Customer Trustee Demo). Falsche Saldenzahlen unterminieren das
|
|
Vertrauen in die ganze Plattform und blockieren saemtliche AI-Auswertungen, die
|
|
auf Salden aufbauen (Geldfluss, Plausichecks, Bilanz-/ER-Vergleiche).
|
|
|
|
**Risiko bei Verzicht:** Jede Frage an den Agenten zu Salden / Bilanzpositionen
|
|
liefert subtile Falschwerte (kein offensichtlicher Crash, sondern stillschweigend
|
|
falsche Zahlen). Auditor-Risiko.
|
|
|
|
## Fokus und kritische Details
|
|
|
|
- **RMA hat einen dedizierten Saldo-Endpunkt** (`GET /gl/saldo` mit `accno`,
|
|
`from`, `to`, `bookkeeping_main_curr`, `exclude_yearend_bookings`). Liefert
|
|
pro Konto den echten, durch RMA berechneten Schlusssaldo unter
|
|
Beruecksichtigung von Vorjahres-Vortraegen und Jahresabschluss-Buchungen.
|
|
**Quelle der Wahrheit ist immer der Connector**, nicht eine lokale
|
|
Berechnung -- Letztere kann Vortraege und Jahresabschluss-Offsets nicht
|
|
rekonstruieren, wenn das Importfenster nur einen Teil der Historie enthaelt.
|
|
- **Bexio + Abacus haben keinen entsprechenden Endpunkt** in dem Detailgrad.
|
|
Bexio liefert `GET /3.0/accounting/journal` (alle Journal-Eintraege mit
|
|
`debit_account_id`, `credit_account_id`, `amount`, `date`); Abacus
|
|
`GET GeneralJournalEntries` (OData V4). Dort muessen wir lokal
|
|
**kumulativ** rechnen und den **Vorjahres-Vortrag** explizit aus den
|
|
Journal-Daten ableiten (Eroeffnungsbuchungen am Geschaeftsjahres-Anfang
|
|
bzw. Abschluss-Buchungen am Vorjahres-Ende).
|
|
- **Datenmodell `TrusteeDataAccountBalance` bleibt unveraendert** -- die
|
|
Felder `openingBalance`, `debitTotal`, `creditTotal`, `closingBalance` sind
|
|
bereits vorhanden. Der Bug ist rein in der Befuellung; kein DB-Migration
|
|
noetig (`_bulkClear` + `_bulkCreate` ueberschreibt jeden Sync ohnehin).
|
|
- **Konsumenten** der Tabelle: `mainTrustee.DATA_OBJECTS`,
|
|
`routeFeatureTrustee` (data-tables UI), `methodTrustee/actions/queryData`
|
|
(entity=`balances`, mode=`raw`/`aggregate`), AI-Agent ueber
|
|
`/api/automation2/catalog`. Keine Schema-Aenderung, also kein Frontend-Impact.
|
|
- **Persistenz-Performance**: Wir reden von 200--500 Konten x 13 Perioden
|
|
(Annual + 12 Monate) = max ~6500 Rows pro Sync. Der bestehende
|
|
`_bulkCreate`-Pfad ist dafuer dimensioniert (vgl. accountingDataSync.py
|
|
Header-Kommentar zur Asyncio-Architektur).
|
|
- **Connector-Fallback-Vertrag**: Wenn `getAccountBalances` `[]`
|
|
zurueckgibt **und** keine Exception wirft, gilt das als "Connector kann
|
|
das nicht / hat nichts geliefert" -- in dem Fall faellt der Sync auf
|
|
die korrigierte lokale Aggregation zurueck. Wirft der Connector eine
|
|
Exception, wird sie in `summary["errors"]` aufgenommen und der Sync laeuft
|
|
weiter (gleiches Pattern wie bei den uebrigen Phasen).
|
|
- **i18n**: Keine neuen User-facing Strings.
|
|
- **Naming**: Funktionen `_`-Prefix fuer intern (`_buildBalanceUrl`,
|
|
`_aggregateBalancesFromLines`); camelCase ueberall (Python + TS).
|
|
- **Logging**: Keine Emojis (vgl. `.cursor/rules/python-coding.mdc`).
|
|
|
|
## Ziel und Nicht-Ziele
|
|
|
|
- **Ziel**: `TrusteeDataAccountBalance` enthaelt **echte** Salden aus dem
|
|
Buchhaltungssystem (RMA, soweit verfuegbar) bzw. eine korrekt **kumulierte**
|
|
lokale Berechnung (Bexio, Abacus). `closingBalance` = Stichtagsbestand,
|
|
`openingBalance` = Stand zu Periodenbeginn, inkl. Vortraege.
|
|
- **Ziel**: Erweiterung von `BaseAccountingConnector` um optionale Methode
|
|
`getAccountBalances(...)`. Default-Implementierung gibt `[]` zurueck (keine
|
|
externe Quelle) -- Sync faellt auf den korrigierten Fallback zurueck.
|
|
- **Ziel**: RMA-Connector implementiert echte Saldenabfrage; Bexio + Abacus
|
|
bekommen entweder eine voll funktionsfaehige `getAccountBalances`-
|
|
Implementierung oder, falls nicht moeglich, einen detaillierten Code-
|
|
Kommentar mit Endpunkt-Recherche und Skizze des fehlenden Stuecks
|
|
(User-Direktive: "besser gleich umsetzen bei allen 3").
|
|
- **Ziel**: Korrekter Fallback in `_persistBalances`: Wenn der Connector
|
|
nichts liefert, **kumuliere** Journal-Lines pro Konto chronologisch und
|
|
fuelle `openingBalance` aus den Vorjahresbuchungen (oder `0`, wenn das
|
|
Importfenster den Account-Lifecycle abdeckt).
|
|
- **Ziel**: Unit-Test pro Connector + Integrationstest, der den BuHa-SoHa-
|
|
Fall reproduziert (Konto mit Vortrag, mehreren Monaten Bewegungen,
|
|
korrekter Schlusssaldo Ende Dezember).
|
|
- **Explizit NICHT**: Schema- oder DB-Migration. Felder existieren bereits;
|
|
jeder Sync schreibt die Tabelle ohnehin neu (`_bulkClear`).
|
|
- **Explizit NICHT**: Aenderung am Frontend (data-tables-Seite). Die
|
|
Tabelle bekommt nur korrekte Werte -- die Spalten sind bereits da.
|
|
- **Explizit NICHT**: Aenderung am AI-Agent / `queryData`-Action. Die Action
|
|
liefert weiter `entity="balances"` aus derselben Tabelle.
|
|
- **Explizit NICHT**: Saldo-Logik ausserhalb von Trustee (Sanctions-Plattform
|
|
etc.).
|
|
|
|
## Betroffene Module
|
|
|
|
- **Gateway**:
|
|
- `platform-core/modules/features/trustee/accounting/accountingConnectorBase.py`
|
|
-- neue Datenklasse `AccountingPeriodBalance`, neue optionale Methode
|
|
`getAccountBalances`.
|
|
- `platform-core/modules/features/trustee/accounting/connectors/accountingConnectorRma.py`
|
|
-- `getAccountBalances` via `GET /gl/saldo`. Annual-Bucket + 12 Monats-
|
|
Buckets, jeweils kumulativ bis Periodenende. Opening-Balance = Saldo per
|
|
Periodenstart - 1 Tag (zweiter API-Call) oder via vorheriger Periode.
|
|
- `platform-core/modules/features/trustee/accounting/connectors/accountingConnectorBexio.py`
|
|
-- `getAccountBalances` aggregiert lokal aus `GET /3.0/accounting/journal`
|
|
(filtert per `from`/`to`-Param) -- Bexio bietet keine Saldoliste.
|
|
- `platform-core/modules/features/trustee/accounting/connectors/accountingConnectorAbacus.py`
|
|
-- `getAccountBalances` aggregiert lokal aus `GET GeneralJournalEntries`
|
|
(OData-Filter `JournalDate ge ... and le ...`). Mit Schemahinweis im Code,
|
|
falls Abacus-Instanz die Entity `AccountBalances` (offiziell vorhanden,
|
|
aber Verfuegbarkeit instanzabhaengig) ausliefern kann.
|
|
- `platform-core/modules/features/trustee/accounting/accountingDataSync.py`
|
|
-- Phase 4 (`_persistBalances`) umbauen: zuerst Connector fragen; bei
|
|
leerer / fehlerhafter Antwort den **korrigierten** lokalen Fallback nutzen.
|
|
- `platform-core/modules/workflows/methods/methodTrustee/actions/refreshAccountingData.py`
|
|
-- nur Doc-String anpassen (verweist auf neuen Datenfluss).
|
|
- **Frontend**: keine Aenderung.
|
|
- **DB-Migration**: nein.
|
|
- **Andere Komponenten**: keine.
|
|
|
|
## Entscheidungen
|
|
|
|
| Datum | Entscheidung | Begruendung |
|
|
|-------|-------------|------------|
|
|
| 2026-04-25 | Connector-Werte haben Vorrang, lokale Aggregation ist Fallback. | Vorjahres-Vortraege und Jahresabschluss-Offsets kann nur das Buchhaltungssystem zuverlaessig liefern; lokale Aggregation aus dem Importfenster bleibt naehrungsweise. |
|
|
| 2026-04-25 | Per Konto + Periode 13 Buckets (Annual + 12 Monate). Jeder Bucket = Schlusssaldo per Periodenende. | Konsistent mit bestehendem Datenmodell und der `BuHa SoHa`-Tabelle (Monat 12 = Dezember-Stichtag). Annual-Bucket = Schlusssaldo per Geschaeftsjahres-Ende. |
|
|
| 2026-04-25 | RMA: Echte Saldenabfrage via `/gl/saldo` pro Periode (Annual + 12 Monate). 13 sequenzielle GETs pro Sync. | RMA-API bietet pro Aufruf nur einen Stichtag; mehrere Aufrufe sind unkritisch (Sync laeuft im Hintergrund, schon heute 30+s). |
|
|
| 2026-04-25 | Bexio: Aggregation aus `/3.0/accounting/journal` (kein dedizierter Saldo-Endpunkt vorhanden). | Bexio Journal-API liefert alle benoetigten Felder; lokale Kumulation ist hier exakt, weil der Bexio-Mandant typischerweise im Importfenster komplett liegt. |
|
|
| 2026-04-25 | Abacus: Aggregation aus `GeneralJournalEntries`. Falls Instanz `AccountBalances`-Entity hat, kann sie spaeter bevorzugt werden (Code-Kommentar fuer Folge-Iteration). | OData-Schema variiert pro Abacus-Instanz; sichere Default-Strategie ist Aggregation. |
|
|
| 2026-04-25 | `AccountingPeriodBalance`-Pydantic-Modell (statt dict) fuer Connector-Output. | Konsistent mit `AccountingChart`, `AccountingBooking`; Typsicherheit + Pydantic-Validierung. |
|
|
| 2026-04-25 | `getAccountBalances` ist OPTIONAL (Default: `[]`). | Erlaubt graduelles Rollout pro Connector ohne Breaking Change; bestehender Fallback bleibt verfuegbar. |
|
|
| 2026-04-25 | Lokaler Fallback rechnet jetzt korrekt kumulativ pro Konto, sortiert Journal-Lines nach Buchungsdatum, und propagiert den Saldo Monat-fuer-Monat. `openingBalance` der ersten Periode = `0` (besser nichts als ein erfundener Wert). | Vortrags-Berechnung ohne Connector-Daten ist nicht zuverlaessig; explizites `openingBalance=0` + Kommentar im UI/Doku ist ehrlicher. |
|
|
|
|
## Umsetzungs-Checkliste
|
|
|
|
### Backend
|
|
|
|
- [x] `AccountingPeriodBalance` (Pydantic) in `accountingConnectorBase.py`:
|
|
Felder `accountNumber`, `periodYear`, `periodMonth`, `openingBalance`,
|
|
`debitTotal`, `creditTotal`, `closingBalance`, `currency`, `asOfDate`.
|
|
- [x] `BaseAccountingConnector.getAccountBalances(config, year, accountNumbers=None)`
|
|
mit Default-Implementierung `return []`. Doc-String dokumentiert
|
|
Vertrag (Annual-Bucket = `periodMonth=0`).
|
|
- [x] RMA: `getAccountBalances` via `GET /gl/saldo`. Pro Konto-Filter `accno`
|
|
(z.B. `xxxx`) und Datum `to=YYYY-MM-DD`, einmal pro Periode (Annual +
|
|
12 Monate). Monatlicher `openingBalance` = Schlusssaldo Vormonat.
|
|
Fehler / leere Antwort -> Konto skippen (nicht Sync abbrechen).
|
|
- [x] Bexio: `getAccountBalances` ueber `_loadRawAccounts` (Account-Map)
|
|
+ `GET /3.0/accounting/journal?from=YYYY-MM-DD&to=YYYY-MM-DD`. Lokale
|
|
Kumulation (debit - credit) pro Konto pro Monat. Jahres-Bucket separat.
|
|
- [x] Abacus: `getAccountBalances` ueber `GET GeneralJournalEntries`
|
|
mit OData-Filter; gleiche Aggregations-Logik. Code-Kommentar mit
|
|
Hinweis auf optionale `AccountBalances`-Entity (instanzabhaengig).
|
|
- [x] `accountingDataSync._persistBalances` umbauen:
|
|
1. `await connector.getAccountBalances(plainConfig, year=...)` aufrufen
|
|
(Jahre aus dateFrom/dateTo ableiten -- kann ueber mehrere Jahre gehen).
|
|
2. Bei nicht-leerer Antwort: direkt persistieren.
|
|
3. Sonst Fallback `_aggregateBalancesFromLines(...)` -- korrekt kumulativ.
|
|
- [x] Logging: pro Phase eindeutige Log-Zeile mit Connector-Name + Source
|
|
(`buha` / `local-fallback`) + Zeilenzahl.
|
|
|
|
### Tests
|
|
|
|
- [x] `platform-core/tests/unit/features/trustee/test_accountingConnectorRma_balances.py`:
|
|
`getAccountBalances` mit gemocktem `/gl/saldo` (17 Tests: BuHa-SoHa-Szenario,
|
|
ER-Reset, Parser, Helpers).
|
|
- [x] `platform-core/tests/unit/features/trustee/test_accountingConnectorBexio_balances.py`:
|
|
lokale Aggregation aus gemockter Journal-Liste (8 Tests: BS cumulative,
|
|
carry-over, ER-Reset).
|
|
- [x] `platform-core/tests/unit/features/trustee/test_accountingConnectorAbacus_balances.py`:
|
|
OData-Aggregation (6 Tests: BS carry-over, ER-Reset).
|
|
- [x] `platform-core/tests/unit/features/trustee/test_accountingDataSync_balances.py`:
|
|
End-to-End mit FakeDb (11 Tests: Connector-Path verbatim persist,
|
|
Local-Fallback kumulative BS/ER-Berechnung, _resolveBalanceYears,
|
|
_isIncomeStatementAccount).
|
|
|
|
### Wiki
|
|
|
|
- [x] Plan: dieses Dokument von `1-plan/` -> `4-done/` verschoben.
|
|
- [x] `wiki/c-work/_CHANGELOG.md`: 2 Zeilen (fix + test).
|
|
- [x] `wiki/b-reference/platform-core/features/trustee.md`: Abschnitt
|
|
"Saldenliste (`TrusteeDataAccountBalance`)" mit Datenquelle + Fallback
|
|
ergaenzt; Tests-Tabelle erweitert; Link auf dieses Dokument.
|
|
- [x] `lastReviewed: 2026-04-25` + `verifiedAgainst: ... + Account-Balance-Import`
|
|
aktualisiert.
|
|
|
|
## Akzeptanzkriterien
|
|
|
|
| # | Kriterium (Given-When-Then) | Prio |
|
|
|---|---------------------------|------|
|
|
| 1 | Given Mandant `BuHa SoHa` mit RMA-Connector und Konto 1020 (echter Schlusssaldo per 31.12.2025 = `48'507.41`), When der Sync laeuft, Then enthaelt `TrusteeDataAccountBalance` fuer `accountNumber='1020'`, `periodYear=2025`, `periodMonth=12` einen `closingBalance = 48'507.41` (Toleranz +/- 0.01 CHF). | must |
|
|
| 2 | Given derselbe Sync, When man die Annual-Zeile abfragt (`periodMonth=0`), Then `closingBalance` = Schlusssaldo per Geschaeftsjahres-Ende (= `48'507.41` bei Kalenderjahr-Mandant). | must |
|
|
| 3 | Given derselbe Sync, When man die Zeile fuer `periodMonth=11` abfragt, Then `closingBalance` = Stand per 30.11.2025 (kumulativ, NICHT November-Bewegung). | must |
|
|
| 4 | Given Bexio-Connector mit lokal aggregiertem Saldo, When alle Journal-Eintraege im Importfenster liegen und kein Vortrag existiert, Then `closingBalance` einer Periode = Summe aller debits - Summe aller credits bis Periodenende fuer dieses Konto. | must |
|
|
| 5 | Given Connector wirft Exception in `getAccountBalances`, When der Sync laeuft, Then erscheint die Fehlermeldung in `summary["errors"]`, der Sync laeuft weiter und der Fallback befuellt die Tabelle aus den Journalzeilen. | must |
|
|
| 6 | Given Frage an AI-Agent "Wie hoch war der Saldo Konto 1020 per 31.12.2025?", When `queryData(entity="balances", mode="raw")` aufgerufen wird, Then enthaelt das Resultat den Datensatz mit `closingBalance=48507.41`. | must |
|
|
| 7 | Given Sync-Lauf, When er fertig ist, Then enthaelt das Log eine Zeile `Persisted N balances for <id> in Xs (source=buha\|local-fallback)`. | should |
|
|
|
|
## Testplan
|
|
|
|
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|
|
|----|----|-----|--------------|-----------|--------|
|
|
| T1 | 1, 2, 3 | unit | ja | platform-core/tests/unit/features/trustee/test_accountingConnectorRma_balances.py | 17/17 passed |
|
|
| T2 | 4 | unit | ja | platform-core/tests/unit/features/trustee/test_accountingConnectorBexio_balances.py | 8/8 passed |
|
|
| T3 | 4 | unit | ja | platform-core/tests/unit/features/trustee/test_accountingConnectorAbacus_balances.py | 6/6 passed |
|
|
| T4 | 1-3, 5 | integration | ja | platform-core/tests/unit/features/trustee/test_accountingDataSync_balances.py (FakeDb) | 11/11 passed |
|
|
| T5 | 1, 2, 6 | manual | nein | PROD-Mandant `BuHa SoHa`, vorher/nachher Vergleich | pending (nach Deployment) |
|
|
|
|
## Links
|
|
|
|
- Bug-Report (Kunden-E-Mail vom 2026-04-25)
|
|
- RMA API Doc Saldo: <https://runmyaccountsag.github.io/runmyaccounts-rest-api/Resources/resources.html#general-ledger>
|
|
- Bexio Journal API: <https://docs.bexio.com/#tag/Accounting/operation/listJournal>
|
|
- Abacus REST API: <https://downloads.abacus.ch/fileadmin/ablage/abaconnect/htmlfiles/docs/restapi/abacus_rest_api.html>
|
|
|
|
## Abschluss
|
|
|
|
- [x] `wiki/b-reference/platform-core/features/trustee.md` aktualisiert (Quelle der
|
|
Saldenliste).
|
|
- [x] `wiki/TOPICS.md` -- kein neuer Eintrag noetig (faellt unter Trustee).
|
|
- [x] Dieses Dokument in `wiki/c-work/4-done/` verschoben.
|