chore: plan implementare Agent Teams + API contract
- docs/PLAN.md - plan arhitectural complet (creat anterior) - docs/api-contract.json - contract API intre backend/frontend agenti - docs/superpowers/plans/2026-03-13-roaauto-implementation.md - plan implementare cu Agent Teams - HANDOFF.md - context pentru sesiuni viitoare Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
50
HANDOFF.md
Normal file
50
HANDOFF.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Handoff - ROA AUTO SaaS
|
||||||
|
|
||||||
|
## Ce s-a facut in aceasta sesiune
|
||||||
|
|
||||||
|
### Planificare completa
|
||||||
|
|
||||||
|
1. Citit `docs/PLAN.md` - planul arhitectural complet al proiectului
|
||||||
|
2. Creat plan de implementare cu **Claude Code Agent Teams** (functionalitate experimentala reala)
|
||||||
|
|
||||||
|
### Fisiere create
|
||||||
|
|
||||||
|
- `docs/superpowers/plans/2026-03-13-roaauto-implementation.md` - planul complet de implementare
|
||||||
|
|
||||||
|
### Despre planul creat
|
||||||
|
|
||||||
|
Planul foloseste **Claude Code Agent Teams** (nu subagenti):
|
||||||
|
- Necesita `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` in `~/.claude/settings.json`
|
||||||
|
- Necesita Claude Code v2.1.32+
|
||||||
|
- 3 teammates specializati: `backend-agent`, `frontend-agent`, `devops-agent`
|
||||||
|
- Task list comun cu 11 taskuri (TASK-001 pana la TASK-011)
|
||||||
|
- Comunicare directa intre agenti via mailbox
|
||||||
|
- `plan_approval_required: true` pentru TASK-002 si TASK-003
|
||||||
|
|
||||||
|
### Structura taskuri
|
||||||
|
|
||||||
|
```
|
||||||
|
TASK-001 (Lead) → API contract + structura directoare
|
||||||
|
TASK-002 (backend) → FastAPI + libSQL + Auth [paralel cu 003]
|
||||||
|
TASK-003 (frontend) → Vue 3 + wa-sqlite + Sync [paralel cu 002]
|
||||||
|
TASK-004 (devops) → Docker dev + Makefile [paralel cu 002+003]
|
||||||
|
TASK-005 (backend) → Sync endpoints + Models + Seed + Orders
|
||||||
|
TASK-006 (frontend) → Dashboard + Orders UI + Vehicle Picker
|
||||||
|
TASK-007 (backend) → PDF WeasyPrint + Portal Client + SMS + Invoices
|
||||||
|
TASK-008 (frontend) → Portal public + Order Detail + PDF download
|
||||||
|
TASK-009 (backend) → Invite system + User management
|
||||||
|
TASK-010 (devops) → Docker production + nginx
|
||||||
|
TASK-011 (frontend) → PWA + Backup/Restore + Upgrade prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cum pornesti implementarea
|
||||||
|
|
||||||
|
1. Activeaza Agent Teams in `~/.claude/settings.json`
|
||||||
|
2. Deschide Claude Code in `/mnt/e/proiecte/roaauto/`
|
||||||
|
3. Spune: *"Citeste docs/superpowers/plans/2026-03-13-roaauto-implementation.md si creeaza un agent team cu 3 teammates (backend-agent, frontend-agent, devops-agent) sa implementeze proiectul ROA AUTO SaaS."*
|
||||||
|
|
||||||
|
### Stare repo
|
||||||
|
|
||||||
|
- Git init facut (fara commits inca)
|
||||||
|
- Remote: `git@gitea.romfast.ro:marius/roaauto.git`
|
||||||
|
- Nu s-a scris niciun cod de implementare - doar planul
|
||||||
609
docs/PLAN.md
Normal file
609
docs/PLAN.md
Normal file
@@ -0,0 +1,609 @@
|
|||||||
|
# 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
|
||||||
1695
docs/superpowers/plans/2026-03-13-roaauto-implementation.md
Normal file
1695
docs/superpowers/plans/2026-03-13-roaauto-implementation.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user