fix critical trustee db sync
This commit is contained in:
parent
75f4698488
commit
aeb75af49d
1 changed files with 66 additions and 2 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-04-12 -->
|
||||
<!-- lastReviewed: 2026-04-21 -->
|
||||
|
||||
# Coding-Konventionen
|
||||
|
||||
|
|
@ -87,6 +87,30 @@ function _tabLabel(tabId: string, t: (k: string) => string): string {
|
|||
- Fehlende Keys erscheinen als `[Text]` (eckige Klammern = unuebersetzt)
|
||||
- Admin synchronisiert Sprachsets ueber Administration → System → UI-Sprachen → "Alle aktualisieren"
|
||||
|
||||
#### Platzhalter-Namen sind Code, nicht Text
|
||||
|
||||
Die Token in `{...}` (z. B. `{konten}`, `{count}`) sind interne Variablen-Namen
|
||||
und werden zur Laufzeit durch das Param-Objekt ersetzt. **Sie duerfen nie
|
||||
uebersetzt werden** — auch wenn sie wie deutsche oder englische Woerter aussehen.
|
||||
|
||||
```tsx
|
||||
// Quelle (immer Deutsch, weil Source-Sprache):
|
||||
t('{konten} Konten, {buchungen} Buchungen', { konten: '215', buchungen: '72' })
|
||||
|
||||
// Korrekte EN-Uebersetzung in der DB:
|
||||
'{konten} accounts, {buchungen} entries'
|
||||
// ^^^^^^^ ^^^^^^^^^^^ <-- Token bleiben Deutsch!
|
||||
|
||||
// FALSCH (wuerde zu rohem '{accounts}' im UI fuehren):
|
||||
'{accounts} accounts, {entries} entries'
|
||||
```
|
||||
|
||||
Der AI-Uebersetzer verletzt diese Regel ab und zu, deshalb laeuft in
|
||||
`routeI18n._enforcePlaceholdersOnBatch` und `i18nRegistry._loadCache` ein
|
||||
deterministischer Guard, der bei gleicher Token-Anzahl die Source-Namen
|
||||
positionsweise wiederherstellt. Bei abweichender Anzahl bleibt der Wert
|
||||
unangetastet — das muss der Reviewer manuell pruefen.
|
||||
|
||||
## Backend (FastAPI/Python)
|
||||
|
||||
- **Pydantic-Models** als einzige Quelle fuer UI-Feld-Definitionen
|
||||
|
|
@ -94,6 +118,44 @@ function _tabLabel(tabId: string, t: (k: string) => string): string {
|
|||
- Fehler propagieren -- Exceptions explizit werfen, nicht schlucken
|
||||
- Config ueber `APP_CONFIG` (aus `modules/shared/configuration.py`)
|
||||
|
||||
### Async-Routen / Background-Jobs: niemals den Event-Loop blockieren
|
||||
|
||||
Alle FastAPI-`async def`-Routen und alle Background-Job-Handler
|
||||
(`registerJobHandler`) laufen auf dem **gemeinsamen** Event-Loop des Workers.
|
||||
Synchroner, blockierender Code (psycopg2, requests, time.sleep, ...) friert
|
||||
damit den ganzen Worker ein -- inklusive Health-Checks. Eine grosse Schleife
|
||||
mit `db.recordCreate(...)` reicht, um die Instanz fuer Minuten unerreichbar
|
||||
zu machen.
|
||||
|
||||
**Regeln:**
|
||||
|
||||
- HTTP / IO mit nativem `await` aufrufen (httpx, aiohttp, asyncpg, ...).
|
||||
- Sync-Code (psycopg2, lange CPU-Schleifen) **immer** in
|
||||
`await asyncio.to_thread(_syncFn, ...)` verpacken.
|
||||
- Bei grossen Datenmengen (>100 Zeilen) **niemals** `recordCreate` /
|
||||
`recordDelete` in der Schleife aufrufen. Stattdessen die Bulk-Methoden
|
||||
des Connectors verwenden:
|
||||
- `db.recordCreateBulk(model, rows)` -> ein `execute_values` + ein COMMIT
|
||||
- `db.recordDeleteWhere(model, recordFilter)` -> ein `DELETE WHERE ...`
|
||||
- Bei laengeren Phasen Heartbeat-Logs alle 500-1000 Items, damit Hangs
|
||||
im Log sichtbar sind.
|
||||
|
||||
Referenz-Implementation: `modules/features/trustee/accounting/accountingDataSync.py`
|
||||
(jede Phase: `await connector.fetch(...)` -> `await asyncio.to_thread(self._persistXxx, ...)`).
|
||||
|
||||
### Debug-File-Dumps: nur DEV, niemals INT/PROD
|
||||
|
||||
Code, der zu Debug-Zwecken raw payloads / Prompts / Sync-Daten auf die Disk
|
||||
schreibt, **muss** ein dediziertes Env-Flag pruefen. Pattern:
|
||||
|
||||
- `APP_DEBUG_<FEATURE>_ENABLED` (`True` nur in `env_dev.env`)
|
||||
- `APP_DEBUG_<FEATURE>_DIR` (gateway-relativer Pfad ist OK; absoluter Pfad
|
||||
nur fuer DEV-Workstations)
|
||||
|
||||
Beispiele: `APP_DEBUG_CHAT_WORKFLOW_*`, `APP_DEBUG_ACCOUNTING_SYNC_*`. Hardcoded
|
||||
Windows-Pfade wie `D:/...` sind verboten (laufen auf Linux als
|
||||
Relativ-Pfad an und schreiben unkontrolliert ins Dateisystem).
|
||||
|
||||
### i18n-Pflicht: `t()` fuer alle UI-sichtbaren Gateway-Texte
|
||||
|
||||
Jeder Text der im Frontend angezeigt wird (HTTPException-Details, API-Response-Messages, Erfolgs-/Fehlermeldungen) **muss** mit `t()` getaggt werden (nur **String-Literale** in `t()`).
|
||||
|
|
@ -142,7 +204,9 @@ def _formatBlockItem(item):
|
|||
|
||||
**Warum zwei Schritte?** `t()` wird bei Import-Zeit ausgewertet — der Rueckgabewert ist immer deutsch (Default-Sprache). Der Wert im Dict ist daher ein fester deutscher String. `resolveText()` nimmt diesen deutschen String zur Request-Zeit und liefert die korrekte Uebersetzung.
|
||||
|
||||
**Warum Modul-Ebene?** `t()` registriert Keys nur beim ersten Aufruf. Wenn `t()` nur in einer Funktion steht, wird der Key erst beim ersten Request registriert — **nach** dem Boot-Sync. Der Key fehlt dann im `xx`-Set und kann nicht uebersetzt werden.
|
||||
**Warum Modul-Ebene?** `t()` registriert Keys nur beim ersten Aufruf. Wenn `t()` nur in einer Funktion steht, wird der Key erst beim ersten Request registriert — **nach** dem Boot-Sync. Der Key fehlt dann im `xx`-Set und kann nicht uebersetzt werden. Im UI erscheint er als `[Schluessel]` (eckige Klammern = Cache-Miss).
|
||||
|
||||
**Pflicht-Hook fuer dynamisch geladene Quellen:** Plugin-/Lazy-Module (z.B. Connector-Plugins, dynamisch entdeckte Klassen) deren `t()`-Aufrufe in einer Methode stecken (nicht auf Modul-Ebene), brauchen einen eigenen `_register…Labels()`-Hook in `_syncRegistryToDb()` (`modules/shared/i18nRegistry.py`). Beispiel: `_registerAccountingConnectorLabels()` importiert die Accounting-Connector-Registry und ruft `getRequiredConfigFields()` einmal beim Boot, damit die Field-Labels im `xx`-Set landen. Analog zu `_registerNodeLabels`, `_registerRbacLabels`, etc.
|
||||
|
||||
### TextMultilingual-Felder: Backend loest auf via resolveText()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue