Signup: - /signup aliniat ca format la formularul din landing (campuri, etichete, placeholder-uri, select plan, checkbox GDPR, buton). Eticheta `name` = "Companie" (corecta: backendul salveaza nume de firma), uniform si in landing. - Consimtamant GDPR validat server-side (functional, nu doar client-side) + salvat cu marca temporala (accounts.consent_at). - Plan ales la signup salvat in accounts.requested_plan (intentie, NU drept): tier ramane sursa de adevar pentru gate-ul API; coloana pregateste integrarea platilor. - landing: valorile `plan` = coduri tier (free/standard/pro/premium), data-plan sincronizat pe butoanele de pret; checkbox consimtamant primeste name. Schema/DB: - accounts: coloane noi requested_plan + consent_at (cu migrare aditiva in db.py). Panou admin: - Coloane noi: Plan curent (plan EFECTIV acum + zile trial ramase) si Plan cerut. - Buton "Aplica" (POST /admin/set-tier): aloca plan real si INCHEIE trial-ul (efect imediat; altfel trial-ul Pro universal de 30z masca alegerea). - Control "Trial Pro N zile" (POST /admin/set-trial via accounts.set_trial): acorda/prelungeste trial fara a schimba tier-ul de baza. Teste: signup (consent obligatoriu, requested_plan persistat, tier ramane free), panou admin (set-tier incheie trial, free opreste Pro imediat, set-trial, validari + CSRF). Call-site-urile existente POST /signup actualizate cu consent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
263 lines
15 KiB
SQL
263 lines
15 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,
|
|
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)),
|
|
-- Plan de cont (5.17). Tier de baza al contului (admin aloca manual via CLI set-tier).
|
|
-- trial_until: daca != NULL si > now -> effective_tier() intoarce 'pro' (trial Pro activ).
|
|
-- Cont nou primeste tier='free' + trial_until=now+30z via create_account.
|
|
-- Contul implicit id=1 (dev) primeste DEFAULT 'free' + trial_until=NULL (fara trial).
|
|
tier TEXT NOT NULL DEFAULT 'free'
|
|
CHECK (tier IN ('free','standard','pro','premium')),
|
|
trial_until TEXT, -- ISO datetime UTC sau NULL; nullable
|
|
-- Planul CERUT de client la signup (separat de `tier`). NU acorda drepturi:
|
|
-- `tier` ramane sursa unica de adevar pentru gate-ul API (require_api_access) si volum.
|
|
-- Folosit la integrarea platilor: client cere plan -> plateste -> admin/webhook urca `tier`
|
|
-- -> API se deblocheaza. NULL = necunoscut (cont creat via CLI / inainte de coloana).
|
|
requested_plan TEXT
|
|
CHECK (requested_plan IS NULL OR requested_plan IN ('free','standard','pro','premium')),
|
|
-- Marca temporala a acceptarii Termenilor + politicii de confidentialitate (GDPR, L.142).
|
|
-- Setata la signup (proba de consimtamant). NULL = cont fara flux de consimtamant (CLI/legacy).
|
|
consent_at TEXT,
|
|
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);
|
|
|
|
-- Sugestii de mapare (strat SILVER, L14-S3 PRD 5.14).
|
|
-- Etichete LLM/embedding — bootstrap; citita DOAR de suggest_codes/pending_unmapped,
|
|
-- NICIODATA de load_mapping/resolve_prestatii (separare structurala #13).
|
|
-- Cheia = denumire normalizata (fara diacritice, uppercase, spatii colapsate).
|
|
-- is_nul=1: non-operatie (ITP, discount, nr. inmatriculare) -> suprima (#4), cod NULL.
|
|
-- INSERT OR IGNORE la re-seed: nu suprascrie randuri existente (#2).
|
|
CREATE TABLE IF NOT EXISTS mapping_suggestions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
denumire_normalizata TEXT NOT NULL UNIQUE,
|
|
cod_prestatie TEXT, -- NULL cand is_nul=1 (supresie)
|
|
is_nul INTEGER NOT NULL DEFAULT 0 CHECK (is_nul IN (0, 1)),
|
|
source TEXT NOT NULL, -- 'llm', 'embedding', etc. (#5)
|
|
confidence REAL NOT NULL DEFAULT 0.0 CHECK (confidence >= 0.0 AND confidence <= 1.0),
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_mapping_suggestions_cod
|
|
ON mapping_suggestions(cod_prestatie) WHERE cod_prestatie IS NOT NULL;
|
|
|
|
-- Mapari validate de oameni (strat GOLD partajat cross-account, L14-S3 PRD 5.14).
|
|
-- Confirmarile umane din ORICE cont contribuie la acest store (#8).
|
|
-- cross-account = suggestion-only (pre-completeaza editorul, F-A/#11), NU auto-send.
|
|
-- Auto-send DOAR din operations_mapping (GOLD propriu per-cont, Eng-F2).
|
|
-- Cheia = denumire_normalizata (NU cod_op_service: spatii de chei diferite, #14).
|
|
CREATE TABLE IF NOT EXISTS shared_mappings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
denumire_normalizata TEXT NOT NULL UNIQUE,
|
|
cod_prestatie TEXT NOT NULL, -- cod RAR valid (GOLD = validat de om)
|
|
source TEXT NOT NULL DEFAULT 'human', -- 'human', 'human_import' (#5)
|
|
provenance TEXT, -- detalii: cont, email, batch (#5)
|
|
confidence REAL NOT NULL DEFAULT 1.0,
|
|
confirmations INTEGER NOT NULL DEFAULT 1, -- contor confirmari din orice cont
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_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');
|