# Plan: ROA AUTO SaaS - Proiect Nou Separat ## Context Marius (ROMFAST SRL, Constanta) construieste un **SaaS pentru service-uri auto mici si vulcanizari** din Romania. Serverul initial e pe Proxmox la birou (nu VPS cloud), deci aplicatia trebuie sa functioneze si cand serverul e offline. Cand proiectul se autosustine financiar, se muta pe VPS cloud. Exista un prototip in `roa2web/roa-auto-mobile/` (Ionic Vue + SQLite server, single-tenant, fara auth) - folosit doar ca referinta conceptuala. **Proiect nou de la zero, repository git separat**. --- ## Decizii Confirmate | Decizie | Valoare | |---------|---------| | Nume proiect | **roaauto** | | Director | `E:\proiecte\roaauto\` | | Git remote | `gitea.romfast.ro:marius/roaauto` | | Domeniu | **roaauto.romfast.ro** | | CSS framework | **Tailwind CSS** | | Cod existent | **De la zero** (prototip doar referinta) | | DB server | **libSQL** (fisier, in-process cu FastAPI) | | DB browser | **wa-sqlite** (SQLite WASM) | | Sync | **Custom simplu** (timestamp-based) | | Offline | **Da** - reads + writes locale, sync cand e net | --- ## Arhitectura ``` [Browser] [Server Proxmox / VPS] ┌─────────────────────┐ ┌──────────────────────┐ │ Vue 3 PWA │ │ FastAPI │ │ wa-sqlite (WASM) │ ◄──── sync ────► │ libSQL (fisier) │ │ └── OPFS storage │ │ └── roaauto.db │ │ └── _sync_queue │ │ │ │ └── ALL reads local│ │ WeasyPrint (PDF) │ │ └── writes instant │ │ SMSAPI.ro (SMS) │ │ │ │ JWT auth │ │ Service Worker │ │ │ │ └── app shell cache│ │ Cloudflare Tunnel │ └─────────────────────┘ └──────────────────────┘ ``` ### Cum functioneaza sync-ul **Initial sync (la login):** ``` GET /api/sync/full?tables=orders,order_lines,vehicles,catalog_* → Server returneaza toate datele tenant-ului → Client INSERT-eaza in wa-sqlite local ``` **Write (offline OK):** ``` 1. User creeaza comanda 2. INSERT in wa-sqlite local (instant, UUID generat client-side) 3. INSERT in _sync_queue (table, id, operation, data, timestamp) 4. Daca online: POST /api/sync/push → server valideaza → INSERT in libSQL 5. Daca offline: queue asteapta → sync la reconectare ``` **Incremental sync (periodic, cand e online):** ``` GET /api/sync/changes?since=2026-03-13T10:00:00 → Server returneaza doar randurile modificate dupa timestamp → Client UPSERT in wa-sqlite local ``` **Conflict resolution:** Server wins (last-write-wins). Simplu, predictibil. ### De ce libSQL pe server (nu PostgreSQL) - **Un fisier** (`roaauto.db`) - nu un server/container separat - **In-process** - FastAPI acceseaza direct, zero overhead retea - **Replication built-in** - backup = copie fisier sau libSQL replication - **Schema identica** cu wa-sqlite din browser (ambele SQLite) - **Resurse minime** - perfect pentru Proxmox la birou - **Migrare la PostgreSQL** cand cresc cerintele (change connection string + fix SQL specifics) **Limitari acceptate:** - Write concurrency: un writer la un moment dat (WAL mode). OK pentru <50 useri. - ALTER TABLE limitat: Alembic batch operations rezolva asta. - No RLS: izolare multi-tenant application-level (WHERE tenant_id = ?). --- ## Tech Stack | Layer | Tehnologie | De ce | |-------|-----------|-------| | **Backend** | FastAPI + SQLAlchemy 2.0 + Alembic | Known territory | | **DB server** | libSQL (via `aiosqlite` dialect) | Fisier, in-process, replication | | **DB browser** | wa-sqlite (`@journeyapps/wa-sqlite`) | SQLite WASM, OPFS persistence | | **Sync** | Custom (timestamp-based push/pull) | Simplu, fara dependente extra | | **Auth** | python-jose + passlib (bcrypt) | JWT 30 zile, offline valid | | **PDF** | WeasyPrint | Server-side, diacritice ROM OK | | **SMS** | SMSAPI.ro | Provider romanesc, REST API | | **Frontend** | Vue 3 + Tailwind CSS 4 | Responsive, utility-first | | **State** | Pinia + reactive SQL queries | Local-first | | **PWA** | vite-plugin-pwa | App shell cache, install prompt | | **Build** | Vite 6 | Fast | | **Deploy** | Docker (single container) + Dokploy + Cloudflare Tunnel | Self-hosted Proxmox | | **Git** | Gitea (gitea.romfast.ro) | Self-hosted | --- ## Structura Proiect ``` E:\proiecte\roaauto\ ├── docker-compose.yml # Backend (FastAPI + libSQL) + frontend build ├── docker-compose.dev.yml ├── Makefile # make dev, make migrate, make seed, make backup ├── .env.example │ ├── backend/ │ ├── Dockerfile │ ├── requirements.txt │ ├── alembic.ini │ ├── alembic/versions/ │ ├── app/ │ │ ├── main.py # FastAPI + lifespan │ │ ├── config.py # Settings din env │ │ ├── deps.py # get_db, get_current_user, get_tenant_id │ │ │ │ │ ├── auth/ │ │ │ ├── router.py # register, login, refresh │ │ │ ├── service.py # JWT 30 zile, bcrypt, trial logic │ │ │ └── schemas.py │ │ │ │ │ ├── sync/ │ │ │ ├── router.py # GET /sync/full, GET /sync/changes, POST /sync/push │ │ │ └── service.py # Sync logic, conflict resolution │ │ │ │ │ ├── tenants/ # settings, subscription │ │ ├── users/ # invite, list, deactivate │ │ ├── vehicles/ # CRUD │ │ ├── orders/ # CRUD + workflow │ │ │ ├── router.py │ │ │ ├── service.py # DRAFT->VALIDAT->FACTURAT, recalc totals │ │ │ └── schemas.py │ │ ├── invoices/ # create from order │ │ ├── catalog/ # nomenclatures │ │ ├── appointments/ # CRUD │ │ ├── client_portal/ # GET /p/{token} (public, no auth) │ │ ├── sms/ # SMSAPI.ro │ │ ├── pdf/ │ │ │ ├── service.py # WeasyPrint │ │ │ └── templates/ # deviz.html, factura.html │ │ │ │ │ └── db/ │ │ ├── base.py # SQLAlchemy Base, UUID mixin, TenantMixin │ │ ├── session.py # libSQL/aiosqlite engine + session │ │ └── models/ # tenant, user, vehicle, order, etc. │ │ │ ├── data/ # libSQL database file(s) │ │ └── .gitkeep │ └── tests/ │ ├── frontend/ │ ├── Dockerfile + nginx.conf │ ├── package.json │ ├── vite.config.js │ ├── tailwind.config.js │ ├── index.html │ │ │ └── src/ │ ├── main.js # Vue + Pinia + wa-sqlite init │ ├── App.vue │ │ │ ├── db/ │ │ ├── schema.js # Tabele SQLite locale (mirror server) │ │ ├── database.js # wa-sqlite init + OPFS │ │ ├── sync.js # Sync engine (full sync, incremental, push queue) │ │ └── queries.js # Reactive query helper │ │ │ ├── router/index.js │ │ │ ├── stores/ │ │ ├── auth.js # JWT, login/register, offline token validation │ │ ├── orders.js # SQL queries pe wa-sqlite local │ │ ├── vehicles.js │ │ ├── catalog.js │ │ └── sync.js # Sync status, last sync time │ │ │ ├── composables/ │ │ ├── useAuth.js │ │ ├── useBreakpoint.js │ │ ├── useSqlQuery.js # Reactive SQL query (re-run on table change) │ │ └── useSync.js # Online/offline status, trigger sync │ │ │ ├── layouts/ │ │ ├── AppLayout.vue # Sidebar desktop / bottom nav mobile │ │ ├── AuthLayout.vue │ │ └── ClientLayout.vue # Portal client (minimal) │ │ │ ├── views/ │ │ ├── auth/ # Login, Register, InviteAccept │ │ ├── dashboard/ # Today's orders, quick stats │ │ ├── orders/ # List, Create, Detail, Invoice │ │ ├── vehicles/ # List, Detail │ │ ├── appointments/ # Calendar/list │ │ ├── catalog/ # Tabbed: marci, norme, preturi │ │ ├── settings/ # Profile, Users, Subscription │ │ └── client/ # DevizView (public), AppointmentBook │ │ │ ├── components/ │ │ ├── common/ # Button, Input, Modal, Table, StatusBadge, SyncIndicator │ │ ├── orders/ # OrderCard, OperationForm, MaterialForm, OrderTotals │ │ └── vehicles/ # VehiclePicker │ │ │ └── assets/css/ │ └── main.css # Tailwind imports │ └── docs/ ├── ARCHITECTURE.md └── CLAUDE.md # Reguli pentru Claude Code in proiectul nou ``` --- ## Schema Database (identica server + browser) Toate tabelele: `id TEXT PRIMARY KEY` (UUID v7 generat client-side), `tenant_id TEXT NOT NULL`, `oracle_id INTEGER` (NULL, mapare la Oracle ROAAUTO), `updated_at TEXT` (ISO timestamp pentru sync). ```sql -- Tenants & Users tenants (id, nume, cui, reg_com, adresa, telefon, email, iban, banca, plan, trial_expires_at, created_at, updated_at) users (id, tenant_id, email, password_hash, nume, rol, activ, created_at, updated_at) invites (id, tenant_id, email, rol, token, expires_at, accepted_at, created_by, created_at) -- Nomenclatures catalog_marci (id, tenant_id, denumire, activ) catalog_modele (id, marca_id, denumire) catalog_ansamble (id, tenant_id, denumire) catalog_norme (id, tenant_id, cod, denumire, ore_normate, ansamblu_id) catalog_preturi (id, tenant_id, denumire, pret, um) catalog_tipuri_deviz (id, tenant_id, denumire) catalog_tipuri_motoare (id, tenant_id, denumire) mecanici (id, tenant_id, user_id, nume, prenume, activ) -- Clients (nomenclator clienti cu date eFactura ANAF) clients (id, tenant_id, tip_persoana, denumire, cod_fiscal, reg_com, adresa, judet, oras, cod_postal, tara, telefon, email, cont_iban, banca, observatii, activ, created_at, updated_at) -- Core Business vehicles (id, tenant_id, client_id, client_nume, client_telefon, client_email, client_cod_fiscal, client_adresa, nr_inmatriculare, marca_id, model_id, an_fabricatie, serie_sasiu, tip_motor_id, created_at, updated_at) orders (id, tenant_id, nr_comanda, data_comanda, vehicle_id, client_id, tip_deviz_id, status, km_intrare, observatii, -- client snapshot (denormalized) client_nume, client_telefon, nr_auto, marca_denumire, model_denumire, -- totals total_manopera, total_materiale, total_general, -- client portal token_client, created_by, created_at, updated_at) order_lines (id, order_id, tenant_id, tip, descriere, norma_id, ore, pret_ora, -- manopera um, cantitate, pret_unitar, -- material total, mecanic_id, ordine, created_at, updated_at) invoices (id, tenant_id, order_id, client_id, nr_factura, serie_factura, data_factura, tip_document, modalitate_plata, client_nume, client_cod_fiscal, nr_auto, total_fara_tva, tva, total_general, created_at, updated_at) appointments (id, tenant_id, vehicle_id, client_nume, client_telefon, data_ora, durata_minute, observatii, status, order_id, created_at, updated_at) -- Sync (doar in browser, nu pe server) _sync_queue (id, table_name, row_id, operation, data_json, created_at, synced_at) _sync_state (table_name, last_sync_at) ``` --- ## Faze de Implementare ### Faza 0: Setup Proiect (aceasta sesiune) **Livrabil: Director creat, plan salvat in proiect** 1. Creeaza directorul `E:\proiecte\roaauto\` 2. Salveaza acest plan ca `E:\proiecte\roaauto\docs\PLAN.md` pentru referinte viitoare 3. STOP - nu continua cu implementarea ### Faza 1: Fundatie + Auth + Sync (Saptamana 1-2) **Livrabil: Register/Login, wa-sqlite local, sync functional** **Backend:** 1. `git init` la `E:\proiecte\roaauto\`, remote pe Gitea 2. FastAPI skeleton: `main.py`, `config.py`, `deps.py` 3. libSQL setup: `session.py` cu `aiosqlite` 4. Alembic init + prima migrare: `tenants`, `users`, `invites` 5. Auth: `POST /auth/register` (creeaza tenant + owner), `POST /auth/login` (JWT 30 zile) 6. Sync endpoints: `GET /sync/full`, `GET /sync/changes?since=`, `POST /sync/push` 7. JWT middleware: `get_current_user`, `get_tenant_id` **Frontend:** 8. Vue 3 + Vite + Tailwind + Pinia scaffold 9. wa-sqlite setup: `database.js` (init WASM + OPFS), `schema.js` (create tables) 10. Sync engine: `sync.js` (full sync, incremental sync, push queue) 11. Router cu auth guards 12. Pages: Login, Register 13. App layout responsive (sidebar desktop, bottom nav mobile) 14. Sync status indicator (online/offline/syncing) **Verificare:** Register → login → full sync → datele apar in wa-sqlite local → offline: app functioneaza → reconectare: sync push ### Faza 2: Business Logic Core (Saptamana 3-4) **Livrabil: Creare comanda cu operatii/materiale, workflow complet** 1. Models + migrations: `vehicles`, `orders`, `order_lines`, `catalog_*`, `mecanici` 2. Seed data (Alembic): 24 marci, 11 ansamble, 6 tipuri deviz, 5 tipuri motoare, 3 preturi 3. OrderService: CRUD + workflow DRAFT→VALIDAT→FACTURAT + recalc totals 4. wa-sqlite schema update (mirror noile tabele) 5. Frontend: Dashboard (comenzi azi, stats) 6. Frontend: Orders list (query local SQLite, filtrare, search) 7. Frontend: Order create (vehicle picker, operatii, materiale, totals live) 8. Frontend: Order detail (view/edit, status actions) 9. Frontend: Catalog management (tabbed) 10. Frontend: Vehicle picker (search-as-you-type pe SQLite local) **Verificare:** Creare comanda offline → operatii + materiale → totals corecte → reconectare → sync → comanda pe server ### Faza 3: Facturare + PDF (Saptamana 5) **Livrabil: Deviz/factura PDF** 1. Model `invoices`, InvoiceService (VALIDAT→FACTURAT, TVA 19%) 2. WeasyPrint templates HTML/CSS (deviz.html, factura.html cu diacritice) 3. Endpoint `GET /api/orders/{id}/pdf/deviz` 4. Frontend: Invoice page, PDF download/share **Verificare:** DRAFT → operatii → VALIDAT → factura → PDF cu diacritice corecte ### Faza 4: Portal Client + SMS (Saptamana 6) **Livrabil: Client primeste SMS, vede deviz, accepta/refuza** 1. `GET /api/p/{token}` (public, no auth) → deviz data 2. `POST /api/p/{token}/accept`, `/reject` 3. SMSAPI.ro integration 4. Frontend: DevizViewPage (public, mobile-first) 5. Frontend: "Trimite deviz" button (SMS + WhatsApp link) 6. Model `appointments` + CRUD + client booking **Verificare:** Comanda → trimite SMS → client link → accepta → status updated ### Faza 5: Management Angajati + Settings (Saptamana 7) **Livrabil: Owner invita angajati, role-based access** 1. Invite system (email link 48h) 2. User management (list, deactivate, roles) 3. Settings: profil service 4. Role-based UI (mecanic vede doar comenzile lui) ### Faza 6: Deployment (Saptamana 8) **Livrabil: roaauto.romfast.ro live** 1. Dockerfile (FastAPI + libSQL, single container) 2. Frontend Dockerfile (nginx) 3. docker-compose.yml 4. Dokploy pe Proxmox + Cloudflare Tunnel 5. Trial expiry middleware 6. Backup strategy (libSQL replication / cron cp) ### Faza 7: Polish + PWA (Saptamana 9-10) **Livrabil: Production-ready, instalabil** 1. PWA: service worker, install prompt, icons 2. Sync indicator UI (online/offline/syncing/error) 3. Error handling, toasts, loading states 4. Responsive testing (phone, tablet, desktop) 5. Reports: sumar lunar, export CSV ### Faza 8: Nomenclator Clienti (Clients) **Livrabil: CRUD clienti cu date eFactura ANAF, legatura 1:N cu vehicule** 1. Model `clients` + migrare Alembic (backend) 2. `client_id` FK pe `vehicles`, `orders`, `invoices` 3. CRUD endpoints: `GET/POST /api/clients`, `GET/PUT/DELETE /api/clients/{id}` 4. wa-sqlite schema update (tabel `clients`, FK-uri) 5. Frontend: pagina Clienti (list, create, edit, delete) 6. Frontend: selector client in VehiclePicker si OrderCreate 7. Sync: adauga `clients` in `SYNCABLE_TABLES` 8. Playwright E2E tests (desktop + mobile) ### Faza 9: Edit/Delete/Devalidare Comenzi **Livrabil: Gestionare completa comenzi - edit, stergere, devalidare** 1. `PUT /api/orders/{id}` - edit header comanda (doar in DRAFT) 2. `DELETE /api/orders/{id}` - stergere comanda (orice nefacturat) 3. `POST /api/orders/{id}/devalidate` - VALIDAT → DRAFT 4. `DELETE /api/invoices/{id}` - stergere factura (permite stergere comanda FACTURAT) 5. Frontend: butoane edit/delete/devalidare pe OrderDetail 6. Confirmare stergere cu modal 7. Playwright E2E tests ### Faza 10: Integrare Nomenclator Clienti **Livrabil: Clienti integrati in flux comenzi si facturi** 1. Auto-populare date client pe comanda din nomenclator 2. Selectie client existent sau creare client nou la vehicul 3. Validare date client complete la facturare (CUI, adresa) 4. PDF factura cu date client din nomenclator ### Faza 11: Bon Fiscal (tip_document) **Livrabil: Suport dual FACTURA + BON_FISCAL pe invoices** 1. `tip_document` pe invoices: FACTURA (B2B, eFactura) sau BON_FISCAL (B2C, casa de marcat) 2. Factura: necesita date client complete (CUI, adresa) 3. Bon fiscal: format simplificat, fara date client obligatorii 4. UI: selectie tip document la facturare 5. PDF template diferentiat pentru bon fiscal --- ## Referinta din Prototip (doar consultare) | Ce | Fisier | Ce portam conceptual | |----|--------|---------------------| | Workflow | `roa-auto-mobile/backend/services/order_service.py` | State machine, `_recalc_totals` | | Seed data | `roa-auto-mobile/backend/seed.py` | 24 marci, ansamble, tipuri | | PDF layout | `roa-auto-mobile/src/services/pdf.js` | Format coloane, header, totals | | UI flow | `roa-auto-mobile/src/views/OrderCreatePage.vue` | Flow creare comanda | | Vehicle search | `roa-auto-mobile/src/components/VehiclePicker.vue` | Search pattern | --- ## Compatibilitate ROAAUTO Oracle SaaS-ul trebuie sa fie compatibil cu ROAAUTO VFP9+Oracle. Clientii care cresc pot migra la sistemul complet ROAAUTO cu Oracle. Coloanele SaaS mapeaza 1:1 la tabelele Oracle `MARIUSM_AUTO.dev_*`. ### Mapare tabele SaaS → Oracle ROAAUTO | SaaS (libSQL) | Oracle (MARIUSM_AUTO) | Note | |---------------|----------------------|------| | `orders` | `dev_ordl` | +tenant_id, +token_client, UUID vs Integer PK | | `order_lines` (tip=manopera) | `dev_oper` | SaaS unifica oper+materiale in order_lines | | `order_lines` (tip=material) | `dev_estimari_produse` | Acelasi tabel, filtrat pe `tip` | | `vehicles` | `dev_masiniclienti` | Renamed, aceleasi coloane client+vehicul | | `clients` | `nom_parteneri` + `adrese_parteneri` | Adrese simplificate flat | | `clients.tip_persoana` | `nom_parteneri.tip_persoana` | PF/PJ | | `clients.cod_fiscal` | `nom_parteneri.cod_fiscal` | CUI sau CNP | | `catalog_marci` | `dev_nom_marci` | +tenant_id | | `catalog_modele` | `dev_nom_masini` | Identic | | `catalog_ansamble` | `dev_nom_ansamble` | +tenant_id | | `catalog_norme` | `dev_nom_norme` | +tenant_id | | `catalog_preturi` | `dev_nom_preturi` | +tenant_id | | `catalog_tipuri_deviz` | `dev_tip_deviz` | +tenant_id | | `catalog_tipuri_motoare` | `dev_tipuri_motoare` | +tenant_id | | `mecanici` | `dev_mecanici` | +tenant_id, +user_id | | `invoices` | `facturi` (local) | Identic structural | | `invoices.tip_document` | `vanzari.tip_factura` | FACTURA/BON_FISCAL | | `invoices.client_id` | `vanzari.id_part` | FK la client | | `orders.client_id` | (denormalizat) | Referinta directa la client | | `vehicles.client_id` | (implicit in dev_masiniclienti) | 1:N client → vehicule | | `tenants` | - | Doar SaaS (nu exista in Oracle) | | `users` | - | Doar SaaS | | `appointments` | - | Doar SaaS (feature nou) | ### Coloane compatibile (pastreaza aceleasi nume) Coloanele business raman identice cu Oracle pentru migrare usoara: - `nr_comanda`, `data_comanda`, `status`, `km_intrare`, `observatii` - `client_nume`, `client_telefon`, `client_cod_fiscal`, `nr_auto` - `marca_denumire`, `model_denumire` - `total_manopera`, `total_materiale`, `total_general` - `denumire`, `ore`, `pret_ora`, `cantitate`, `pret_unitar`, `total` - `nr_inmatriculare`, `serie_sasiu`, `an_fabricatie` - `cod`, `ore_normate`, `pret`, `um` - `nr_factura`, `serie_factura`, `data_factura`, `modalitate_plata` - `tva`, `total_fara_tva` ### Coloane adaugate fata de Oracle (doar in SaaS) - `id TEXT` (UUID v7) - in loc de `id INTEGER` (Oracle sequence) - `tenant_id TEXT` - izolare multi-tenant - `oracle_id INTEGER` - mapare la ID-ul Oracle, NULL pana la migrare - `token_client TEXT` - portal client (feature SaaS) - `updated_at TEXT` - timestamp sync - `created_at TEXT` - audit ### Script migrare SaaS → Oracle (Phase viitoare) ```python # Migrare: export din libSQL SaaS → import in Oracle ROAAUTO # 1. Map UUID → Integer (Oracle sequences) # 2. Split order_lines → dev_oper (manopera) + dev_estimari_produse (material) # 3. Rename tables: vehicles → dev_masiniclienti, orders → dev_ordl # 4. Drop columns: tenant_id, token_client, oracle_id # 5. Import nomenclatures shared (global marci etc) ``` --- ## Sistem Tier-uri si Business Model ### Free Forever (0 RON/luna) - **100% local** - wa-sqlite in browser, ZERO cost server per user - Toate features-urile de baza: comenzi, operatii, materiale, vehicule, nomenclator - **PDF generation client-side** (jsPDF) - deviz + factura - **Web Share API** - share PDF via WhatsApp/email - **Backup/restore manual** - export DB ca fisier JSON/SQLite pe telefon, restore din fisier - **Un singur device** (datele in browser-ul respectiv) - **Nu necesita cont pe server** - app-ul se incarca ca PWA, functioneaza standalone - **Limite soft**: ~50 comenzi/luna (nu blocheaza, doar sugereaza upgrade gratios) - NU: sync cloud, client portal, SMS, multi-device, backup automat ### Basic (49 RON/luna - de stabilit) - Tot ce e in Free + - **Cloud sync** (libSQL server) - datele salvate in cloud, backup automat - **Multi-device** - lucreaza de pe telefon SI calculator - **Client portal** - link unic pentru client sa vada devizul - **WhatsApp link** - generare link deviz pentru WhatsApp (gratuit) - 1 user ### Pro (99 RON/luna - de stabilit) - Tot ce e in Basic + - **SMS deviz** - trimite SMS cu link via SMSAPI.ro - **Multi-user** - pana la 3 useri (owner + 2 angajati) - **Programari** - modul appointments - **Rapoarte** - sumar lunar, export CSV - **Priority support** ### Cum functioneaza tehnic **Free tier (zero server cost):** ``` 1. User acceseaza roaauto.romfast.ro 2. PWA se incarca si se instaleaza (service worker cache) 3. wa-sqlite se initializeaza cu schema goala 4. User lucreaza 100% local - fara cont, fara server 5. Datele raman DOAR in browser (OPFS) 6. Daca sterge browser data → pierde tot (avertizare clara) ``` **Upgrade Free → Basic:** ``` 1. User da click "Creeaza cont" (POST /auth/register) 2. Se creeaza tenant + user pe server 3. Full sync: datele locale se uploadeaza pe server (POST /sync/push) 4. De acum: sync bidirectional activ 5. Datele safe pe server + backup ``` **Downgrade Basic → Free:** ``` 1. Subscription expirat sau anulat 2. Sync se opreste (server refuza sync requests) 3. Datele locale raman in browser (functioneaza ca inainte) 4. Datele pe server se pastreaza 90 zile (re-upgrade posibil) 5. Dupa 90 zile: datele server se sterg ``` **Trial flow:** ``` 1. Register → 30 zile trial Basic gratuit 2. Sync activ, toate features-urile Basic 3. La expirare: downgrade automat la Free 4. Datele locale raman, sync se opreste 5. "Upgrade" button prominent ``` ### Backup manual (Free tier) ``` Export: wa-sqlite → JSON blob → download ca fisier "roaauto-backup-2026-03-13.json" - Buton "Salveaza backup" in Settings - Foloseste File System Access API sau download fallback - Include: toate tabelele, settings, metadata Restore: fisier JSON → parse → INSERT in wa-sqlite - Buton "Restaureaza din backup" in Settings - Confirmare: "Asta va inlocui datele curente. Continui?" - Validare: verifica schema version, integritate date ``` **UX important**: La fiecare 7 zile fara backup, reminder subtil (banner, nu modal): "Ultima salvare: acum 12 zile. Salveaza un backup pe telefon." ### Limite soft + sugestii gratioase (stil Revolut) **Filosofie**: Nu blocam NICIODATA. Doar sugeram, pozitiv, la momentul potrivit. **Limite soft Free:** - ~50 comenzi/luna (numar orientativ, nu hard limit) - Fara limita pe vehicule/nomenclator **Cum arata sugestiile (non-intrusive, pozitive):** | Moment | Tip | Mesaj (exemplu) | |--------|-----|-----------------| | Comanda #20 | Banner mic, dismissable | "Ai creat 20 de comenzi luna asta. Stiai ca poti sincroniza datele pe mai multe dispozitive?" | | Comanda #40 | Card in dashboard | "Afacerea ta creste! Cu planul Basic ai backup automat si poti trimite devize direct clientilor." | | Comanda #50 | Bottom sheet (o singura data) | "Ai atins 50 de comenzi. Felicitari! Activeaza planul Basic pentru sync, backup automat si portal client. Continui gratuit, fara restrictii." | | Share PDF manual | Tooltip subtil | "Poti trimite devizul direct pe WhatsApp clientului cu un click. Vezi planul Basic." | | Dupa 3 luni activ | Card in dashboard | "Folosesti ROA AUTO de 3 luni. Multumim! Protejeaza-ti datele cu backup automat in cloud." | **Reguli:** - Fiecare sugestie apare MAX o data (dismiss = nu mai apare niciodata) - Niciodata popup/modal blocant - Ton pozitiv: "Felicitari", "Afacerea ta creste", "Stiai ca..." - Buton "Nu, multumim" vizibil si respectat - Nu afiseaza mai mult de 1 sugestie pe zi - Dupa dismiss: minimum 7 zile pana la urmatoarea sugestie ### Logica in cod ```javascript // Frontend: check tier before feature const tier = authStore.tier // 'free', 'basic', 'pro' (din JWT sau local) // Sync: doar paid if (tier !== 'free') { await syncEngine.start() // start bidirectional sync } // Client portal: doar basic+ if (tier === 'free') { showUpgradeModal('client_portal') } else { generateClientLink(order.token_client) } // SMS: doar pro if (tier !== 'pro') { showUpgradeModal('sms') } ``` ```python # Backend: middleware tier check @app.middleware("http") async def check_subscription(request, call_next): if request.url.path.startswith("/api/sync"): tenant = await get_tenant(request) if tenant.plan == "free" or tenant.is_expired(): return JSONResponse(status_code=402, content={"detail": "Upgrade required"}) return await call_next(request) ``` --- ## Migrare viitoare la PostgreSQL Cand proiectul creste si se muta pe VPS cloud: 1. Schimba `aiosqlite` → `asyncpg` in `session.py` 2. Adapteaza Alembic migrations (remove batch operations) 3. Adauga PostgreSQL RLS pentru izolare tenant 4. Optional: adauga PowerSync pentru sync automat (inlocuieste custom sync) 5. Schema e compatibila - aceleasi tabele, aceleasi coloane