88 lines
4.2 KiB
Markdown
88 lines
4.2 KiB
Markdown
---
|
|
phase: 3
|
|
title: Service Catalog & Quote Builder
|
|
status: discussed
|
|
date: 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/catalog` con 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_price` viene snapshotato al momento dell'aggiunta (non segue futuri cambi al catalogo).
|
|
- **Voce libera**: nome testo libero, prezzo unitario, quantità. `service_id` sarà NULL in `quote_items`.
|
|
|
|
> **Schema change needed**: `service_id` in `quote_items` deve diventare nullable (attualmente `notNull()`).
|
|
> Aggiungere campo `custom_label text` a `quote_items` per 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_items` non vengono mai esposti dalle API client-facing.
|
|
- `clients.accepted_total` è l'unico valore economico che il cliente vede.
|
|
|
|
## Schema Changes Required
|
|
|
|
```sql
|
|
-- 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:
|
|
```ts
|
|
service_id: text("service_id").references(() => service_catalog.id, { onDelete: "restrict" }), // removed .notNull()
|
|
custom_label: text("custom_label"), // new field
|
|
```
|
|
|
|
## Reusable Assets
|
|
|
|
- `service_catalog` e `quote_items` tables 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_total` separato con label chiara.
|
|
- Colore brand: #1A463C per accent, #DEF168 per highlight.
|