-- Schema SQLite (WAL) pentru gateway RAR AUTOPASS. -- Vezi plan.md sect. 5 + plan-treapta2.md sect. 4. -- Treapta 2: adauga conturi cu creds RAR durabile, tabele import, atestari. PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; -- Conturi ROAAUTO (clientii care folosesc gateway-ul). CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, cui TEXT, email TEXT, -- email canonic de contact al firmei (US-001, PRD 5.12); nullable pt. conturi legacy active INTEGER NOT NULL DEFAULT 1, -- lifecycle cont (3.1); gate „in asteptare" consumat de 3.3 -- Stare de ciclu de viata explicita (5.5). Superset al lui `active`: mentinem invariantul -- active=1 <=> status='active' (vezi accounts.set_status / set_active). Worker gate-uieste pe status. -- pending=neactivat · active=operational · blocked=suspendat reversibil · archived=scos din liste, -- date read-only · deleted=stergere soft (tombstone; PII/creds + CUI purjate imediat la stergere, -- vezi accounts.delete_account — randul ramane doar pentru audit). status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('pending','active','blocked','archived','deleted')), rar_creds_enc TEXT, -- creds RAR criptate (Fernet) durabile per-cont (D4/Eng#1) -- Comportament implicit la cod prestatie necunoscut/nemapat pe canalul API: -- 0 (default, non-distructiv: submission 'needs_mapping', intra in editorul de mapare) sau -- 1 (respinge cererea fara enqueue). Override per-cerere via PrezentareRequest.on_unmapped_error. on_unmapped_error_default INTEGER NOT NULL DEFAULT 0 CHECK (on_unmapped_error_default IN (0, 1)), created_at TEXT NOT NULL DEFAULT (datetime('now')) ); -- Un CUI = un cont (cand e prezent). NULL ramane distinct nativ in SQLite -> conturi -- fara CUI (ex. default) se pot crea multiplu. Unicitate la nivel de index (nu check -- in helper) ca sa nu existe fereastra de coliziune intre doi create_account concurenti. CREATE UNIQUE INDEX IF NOT EXISTS ux_accounts_cui ON accounts(cui) WHERE cui IS NOT NULL; -- Cont implicit (id=1): auth API-key (CORE) inca neimplementat, deci ingestiile vin -- cu account_id NULL. Le atribuim contului default ca FK + UNIQUE(account_id,...) din -- operations_mapping sa fie valide; cand auth livreaza, account_id real va curge natural. INSERT OR IGNORE INTO accounts (id, name) VALUES (1, 'default'); -- Chei API per cont (separate de creds RAR). Stocam doar hash-ul. CREATE TABLE IF NOT EXISTS api_keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, key_hash TEXT NOT NULL UNIQUE, active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT (datetime('now')), revoked_at TEXT ); -- Mapare operatie service -> codPrestatie RAR (← mapare_prestatii.DBF, T5). CREATE TABLE IF NOT EXISTS operations_mapping ( id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, cod_op_service TEXT NOT NULL, cod_prestatie TEXT NOT NULL, auto_send INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT (datetime('now')), UNIQUE (account_id, cod_op_service) ); -- Cache nomenclator RAR {codPrestatie, numePrestatie} (← prestatii_rar.DBF / live). CREATE TABLE IF NOT EXISTS nomenclator_rar ( cod_prestatie TEXT PRIMARY KEY, nume_prestatie TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); -- Coada de prezentari catre RAR. Masina de stari: plan.md sect. 3. CREATE TABLE IF NOT EXISTS submissions ( id INTEGER PRIMARY KEY AUTOINCREMENT, idempotency_key TEXT NOT NULL UNIQUE, account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL, status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued','sending','sent','needs_mapping','needs_data','error')), payload_json TEXT NOT NULL, rar_creds_enc TEXT, -- creds RAR criptate (Fernet), sterse dupa primul login reusit rar_status_code INTEGER, rar_error TEXT, id_prezentare INTEGER, -- data.id intors de RAR la succes retry_count INTEGER NOT NULL DEFAULT 0, next_attempt_at TEXT, -- backoff: randul nu se ia inainte de acest moment (T2) sending_since TEXT, -- pentru lease/timeout pe randuri 'sending' orfane (T2) purge_after TEXT, -- sent + 90z (T16) batch_id INTEGER, -- import batch (T7; NULL = canal API) row_index INTEGER, -- rand in batch (T7; NULL = canal API) created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_submissions_status ON submissions(status); CREATE INDEX IF NOT EXISTS idx_submissions_account_status ON submissions(account_id, status); -- Nota: idx_submissions_batch se creeaza in _migrate (dupa ALTER care adauga batch_id pe DB veche). -- Mapare coloane fisier -> campuri canonice (retinuta per cont, semnatura coloane). CREATE TABLE IF NOT EXISTS column_mappings ( id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, signature_coloane TEXT NOT NULL, -- hash/lista sortata a coloanelor fisierului json_mapare TEXT NOT NULL, -- {col_fisier: camp_canonic, ...} JSON format_data TEXT, -- ex. "DD.MM.YYYY" created_at TEXT NOT NULL DEFAULT (datetime('now')), UNIQUE (account_id, signature_coloane) ); -- Loturi de import (fisiere incarcate). CREATE TABLE IF NOT EXISTS import_batches ( id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, filename TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'staging' CHECK (status IN ('staging','committed','error')), total INTEGER NOT NULL DEFAULT 0, ok INTEGER NOT NULL DEFAULT 0, needs_mapping INTEGER NOT NULL DEFAULT 0, needs_data INTEGER NOT NULL DEFAULT 0, needs_review INTEGER NOT NULL DEFAULT 0, already_sent INTEGER NOT NULL DEFAULT 0, duplicate_in_file INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')), purge_after TEXT -- created_at + 90z (T16) ); -- Randuri din lot de import (PII criptat cu Fernet). CREATE TABLE IF NOT EXISTS import_rows ( id INTEGER PRIMARY KEY AUTOINCREMENT, batch_id INTEGER NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE, row_index INTEGER NOT NULL, raw_json TEXT NOT NULL, -- PII criptat (Fernet, ca submissions) override_json TEXT, -- patch CANONIC editat in preview, criptat Fernet (3.6, Approach B); NULL = fara editare reviewed INTEGER NOT NULL DEFAULT 0, -- US-007 (PRD 5.12): 0=neconfirmat, 1=confirmat de operator; NU intra in payload/idempotenta resolved_status TEXT NOT NULL DEFAULT 'pending' CHECK (resolved_status IN ( 'pending','ok','needs_mapping','needs_data', 'needs_review','already_sent','duplicate_in_file' )), error TEXT ); CREATE INDEX IF NOT EXISTS idx_import_rows_batch ON import_rows(batch_id); -- Log atestare legala (confirmare import batch, L.142/2023). CREATE TABLE IF NOT EXISTS import_attestations ( id INTEGER PRIMARY KEY AUTOINCREMENT, batch_id INTEGER NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE, account_id INTEGER NOT NULL, confirmed_by TEXT, -- email/identifier utilizator ts TEXT NOT NULL DEFAULT (datetime('now')), rows_hash TEXT NOT NULL, -- sha256 peste valorile rezolvate confirmate n_confirmed INTEGER NOT NULL ); -- Utilizatori web (email+parola, legati de un cont). Parola stocata doar ca scrypt hash. CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, email TEXT NOT NULL UNIQUE COLLATE NOCASE, password_hash TEXT NOT NULL, -- hex scrypt(salt, parola) salt TEXT NOT NULL, -- hex secrets.token_bytes(16), per-user scrypt_params TEXT NOT NULL, -- eticheta versiune parametri: 'n16384_r8_p1' email_verified INTEGER NOT NULL DEFAULT 0, -- C19: pregatire viitor is_admin INTEGER NOT NULL DEFAULT 0, -- pregatire 3.3b created_at TEXT NOT NULL DEFAULT (datetime('now')) ); -- Jurnal de aplicatie la nivel de eveniment (PRD 5.6). Dublu canal: aici (vizibil -- in tab "Jurnal") + log text rotativ (depanare). `tip` e text liber documentat -- (lista extensibila, decizie §5) — adaugam tipuri fara migrare. Toate valorile -- sunt REDACTATE la scriere (app/observ.py via app/security.py): parole/token -> -- ***REDACTED***, VIN/nr partial. `context_json` = metadate (NU payload PII integral). CREATE TABLE IF NOT EXISTS app_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT NOT NULL DEFAULT (datetime('now')), request_id TEXT, -- corelare cu raspunsul clientului (US-002) account_id INTEGER, -- NULL = eveniment de sistem (fara cont) sursa TEXT NOT NULL DEFAULT 'api' CHECK (sursa IN ('api','worker')), tip TEXT NOT NULL, -- ex. api_prezentari, rar_login, submission_repus nivel TEXT NOT NULL DEFAULT 'INFO', cod TEXT, -- cod din catalogul de erori (app/errors.py) daca aplica mesaj TEXT, -- mesaj scurt redactat context_json TEXT, -- JSON metadate redactate (submission_id, count, status...) purge_after TEXT -- ts + log_retention_days (US-008) ); CREATE INDEX IF NOT EXISTS idx_app_events_ts ON app_events(ts); CREATE INDEX IF NOT EXISTS idx_app_events_account ON app_events(account_id, ts); CREATE INDEX IF NOT EXISTS idx_app_events_tip ON app_events(tip); -- Reguli automate de mapare pe text (substring, per cont). PRD 5.8 US-001. -- auto_send DEFAULT 0 (decizie CEO 2026-06-24): substring are blast-radius mai mare -- decat maparea exacta; o regula noua rezolva codul dar tine randul pentru verificare -- umana pana cand operatorul activeaza explicit "In coada". CREATE TABLE IF NOT EXISTS operation_text_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, pattern TEXT NOT NULL, cod_prestatie TEXT NOT NULL, auto_send INTEGER NOT NULL DEFAULT 0, priority INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')), UNIQUE (account_id, pattern) ); CREATE INDEX IF NOT EXISTS idx_text_rules_account ON operation_text_rules(account_id); -- Heartbeat worker (un singur rand, id=1). /healthz citeste de aici. CREATE TABLE IF NOT EXISTS worker_heartbeat ( id INTEGER PRIMARY KEY CHECK (id = 1), last_beat TEXT, last_rar_login_ok TEXT, detail TEXT ); INSERT OR IGNORE INTO worker_heartbeat (id, last_beat, detail) VALUES (1, NULL, 'never started');