# 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) -- Core Business vehicles (id, tenant_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, 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, nr_factura, serie_factura, data_factura, 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 --- ## 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 | | `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 | | `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