Files
rar-autopass/app/schema.sql
Claude Agent 6f6b163867 feat(web): editare celule in preview + Acasa unificata (PRD 3.6)
Implementeaza PRD 3.6 (US-001..007), pe canalul de import + stratul web;
worker / masina stari / idempotenta / mapare raman neatinse.

- US-003/004: tab-ul "Trimiteri" eliminat; Trimiterile devin sectiune
  permanenta sub upload pe Acasa ("Trimiterile tale"); upload comprimat la
  bara slim (hero pastrat la first-run); ?tab=coada si /_fragments/coada
  servesc Acasa (fara fragment orfan); poll gated pe visibilityState.
- US-001: coloana noua import_rows.override_json (nullable, Fernet, Approach B)
  + _migrate defensiv; ruta v1 + alias web .../rand/{i}/editeaza aplica patch
  canonic ULTIMUL in _resolve_row_for_preview si commit_import (mutatie pura,
  status rederivat, fara drift). Scoping JOIN -> 404, guard committed -> 409,
  semantica empty=clear, decrypt fail -> no-op.
- US-002: buton "Editeaza" pe rand; swap pe <tr> + OOB contoare (nu pe sectiune);
  form propriu (confirm dezactivat la editare); refoloseste grila responsiva +
  error-map din _trimitere_detaliu.html; mutual-exclusion intre randuri.
- US-005/006: "De rezolvat", "Operatii salvate" si "Formate de coloane" ca
  tabele (.tablewrap); H4: comutatorul reflecta auto_send STOCAT.
- US-007: bifa "auto-send" devine comutator etichetat pe COADA ("Pune automat
  in coada" / "Tine pentru verificare"), scoped pe operatie; name="auto_send"
  pastrat (semantica de prezenta -> bool corect cu ambele parsere, zero backend).

Fix-uri gasite la verificarea E2E in browser (htmx 1.9.12, JS — invizibile la
TestClient): useTemplateFragments=true (raspuns <tr>+OOB era parsat in context
de tabel -> swapError + contoare pierdute); re-activarea confirm-btn dupa salvare
deferita pe tick (evita editing=true tranzitoriu); n-hint actualizat de updateN.

Teste: 523 passed. E2E browser: Acasa unificata, upload slim, editare rand
(needs_data -> ok, swap pe rand, contoare OOB), Mapari tabelar + comutator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:52:17 +00:00

158 lines
8.0 KiB
SQL

-- 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,
active INTEGER NOT NULL DEFAULT 1, -- lifecycle cont (3.1); gate „in asteptare" consumat de 3.3
rar_creds_enc TEXT, -- creds RAR criptate (Fernet) durabile per-cont (D4/Eng#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
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'))
);
-- 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');