Files
roaauto/docs/PLAN.md
Marius Mutu 9db4e746e3 feat: add clients nomenclator, order edit/delete/devalidate, invoice types, dashboard redesign
- New clients table with PF/PJ support, fiscal data (CUI, IBAN, eFactura fields)
- Full CRUD API for clients with search, sync integration
- Order lifecycle: edit header (DRAFT), devalidate (VALIDAT→DRAFT), delete order/invoice
- Invoice types: FACTURA (B2B) vs BON_FISCAL (B2C) with different nr formats
- OrderCreateView redesigned as multi-step flow (client→vehicle→details)
- Autocomplete from catalog_norme/catalog_preturi in OrderLineForm
- Dashboard now combines stats + full orders table with filter tabs and search
- ClientPicker and VehiclePicker with inline creation capability
- Frontend schema aligned with backend (missing columns causing sync errors)
- Mobile responsive fixes for OrderDetailView buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:36:40 +02:00

28 KiB

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).

-- 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)

# 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

// 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')
}
# 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 aiosqliteasyncpg 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