4.2 KiB
4.2 KiB
phase, title, status, date
| phase | title | status | date |
|---|---|---|---|
| 3 | Service Catalog & Quote Builder | discussed | 2026-05-16 |
Phase 3 — Decisions & Context
Phase Goal
L'admin può costruire un catalogo servizi riutilizzabile e comporre preventivi da esso; il cliente vede solo il totale accettato (accepted_total).
Key Decisions (LOCKED)
1. Service Catalog — Location: /admin/catalog
- Pagina dedicata
/admin/catalogcon link aggiunto in NavBar (Clienti | Statistiche | Catalogo). - Tabella con colonne: Nome, Descrizione, Prezzo unitario, Stato (Attivo/Disattivato).
- CRUD completo: aggiungi, modifica inline, disattiva (soft delete via
active = false). - Items disattivati restano visibili in elenco (filtro toggle) ma non appaiono nel selettore quote.
2. Quote Builder — Location: Tab "Preventivo" in /admin/clients/[id]
- Nuovo tab nell'admin client detail page, accanto a Fasi, Pagamenti, Documenti.
- Mostra le voci preventivo del cliente con totale calcolato.
- L'admin può aggiungere voci dal catalogo (dropdown con
active = true) o voci libere (nome + prezzo custom). - Nessun blocco dopo la finalizzazione — voci sempre editabili.
3. Voci Preventivo — Catalogo + Free-form
-
Da catalogo: seleziona voce, inserisce quantità;
unit_priceviene snapshotato al momento dell'aggiunta (non segue futuri cambi al catalogo). -
Voce libera: nome testo libero, prezzo unitario, quantità.
service_idsarà NULL inquote_items.Schema change needed:
service_idinquote_itemsdeve diventare nullable (attualmentenotNull()). Aggiungere campocustom_label textaquote_itemsper le voci libere.
4. Accepted Total — Admin-controlled, not auto-calculated
- Il builder mostra la somma calcolata delle voci come riferimento.
- Esiste un campo separato "Totale accettato dal cliente" (editable input) con pulsante "Salva".
- Il pulsante scrive il valore (che l'admin può modificare liberamente) su
clients.accepted_total. - Rationale: il cliente accetta una cifra commerciale (es. €1.500 tondo) che può differire dalla somma analitica interna. Il preventivo interno è solo uno strumento di stima.
5. Pagamenti — Nessun aggiornamento automatico
- Finalizzare il preventivo NON tocca i record
payments. - L'admin aggiorna manualmente gli importi di acconto e saldo nella tab Pagamenti.
6. Constraint già in vigore (IMMUTABLE)
quote_itemsnon vengono mai esposti dalle API client-facing.clients.accepted_totalè l'unico valore economico che il cliente vede.
Schema Changes Required
-- quote_items.service_id diventa nullable
ALTER TABLE quote_items ALTER COLUMN service_id DROP NOT NULL;
-- aggiunta colonna per voci libere
ALTER TABLE quote_items ADD COLUMN custom_label text;
In Drizzle schema.ts:
service_id: text("service_id").references(() => service_catalog.id, { onDelete: "restrict" }), // removed .notNull()
custom_label: text("custom_label"), // new field
Reusable Assets
service_catalogequote_itemstables già presenti in schema.ts con relazioni e TS types.- Pattern Server Actions già stabilito (vedi
clients/[id]/actions.ts). - Pattern tab UI già stabilito (
tabs/PhasesTab.tsx,tabs/PaymentsTab.tsx, etc.). - Pattern inline edit già stabilito (
DocumentRow.tsx). fmtEur()già definita in analytics/page.tsx — estrarre in lib/utils o duplicare.
Pages & Routes to Create
| Route | Type | Purpose |
|---|---|---|
/admin/catalog |
Server Component page | Lista + CRUD catalogo servizi |
/admin/catalog/actions.ts |
Server Actions | createService, updateService, toggleActive |
src/components/admin/tabs/QuoteTab.tsx |
Client Component | Quote builder UI |
src/app/admin/clients/[id]/quote-actions.ts |
Server Actions | addQuoteItem, removeQuoteItem, updateAcceptedTotal |
UI Notes
- Stile coerente con tab esistenti (border-b tabs navigation nell'admin client page).
- Catalogo: tabella simile a admin clients list (bg-white rounded-xl border border-[#e5e7eb]).
- Quote builder: due colonne su desktop (catalogo disponibile | voci selezionate) o lista unica con selettore.
- Totale calcolato mostrato in bold come sommario; campo
accepted_totalseparato con label chiara. - Colore brand: #1A463C per accent, #DEF168 per highlight.