docs(planning): archivia v2.1/v2.2/v2.3 e documenta v2.4
.planning/ documentava in dettaglio cio che era vecchio e per niente cio
che e in produzione: le fasi 11-22 (v2.1 e v2.2, chiuse a giugno) erano
ancora in phases/ mentre v1.0 e v2.0 stavano gia in milestones/, e il
lavoro degli ultimi due mesi - gate OTP e ciclo di vita dei retainer, cioe
quello che gira su hub.iamcavalli.net - non aveva nessuna cartella.
- phases/{11,12,14} -> milestones/v2.1-phases/, phases/{18..22} ->
milestones/v2.2-phases/. Ora phases/ contiene solo la milestone in
corso, che e quello che state.cjs conta per il progresso
- v2.1-ROADMAP.md ricostruito: era l'unica milestone senza archivio,
interrotta dal reset del 19/06 e mai chiusa formalmente
- v2.3-ROADMAP.md + v2.3-REQUIREMENTS.md: v2.3 e stata eseguita fuori dal
ciclo GSD, non esistono PLAN/SUMMARY per fase. L'archivio E la doc
- REQUIREMENTS.md riscritto per v2.4 con il backlog reale
- phases/13 e phases/26: SUMMARY ricostruiti da commit, migration e
STATUS.md. 26 e il primo numero libero
- research/: cancellate 4 varianti dello stesso PITFALLS e FEATURES/
SUMMARY, superati da PROJECT.md. Diverse anti-feature erano ormai
contraddette dai fatti (il Kanban e stato costruito in Phase 19,
l'email in v2.3, il time tracking esiste)
- cancellati UI-RULES.md e DESIGN-SYSTEM.md (CLAUDE.md li dichiara
superseded: impongono l'inverso della regola attuale) e HANDOFF.md,
fermo al 13/06
- SECURITY-*.md -> security/: audit chiuso, ma i report restano la doc di
cosa e stato ruotato e perche
- PROJECT.md/MILESTONES.md/ROADMAP.md allineati: milestone corrente v2.4,
sessione OTP 90gg non 30, migrazioni fino alla 0016
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Phase 13 — Ciclo di vita dei servizi ricorrenti
|
||||
|
||||
**Milestone:** v2.4 Post-vendita · **Stato:** ✅ in produzione, verificata end-to-end
|
||||
**Consegnata:** 2026-08-01 · **Commit:** `5177a37` · **Migration:** `0016_project_offer_lifecycle.sql`
|
||||
|
||||
> **Ricostruito a posteriori il 2026-08-08** dal commit, dalla migration e da
|
||||
> `STATUS.md`. La fase è stata eseguita fuori dal ciclo GSD: non esiste un PLAN.
|
||||
> Phase 13 nasce congelata in v2.1 (giugno) e riaperta come milestone v2.4.
|
||||
|
||||
## Il problema
|
||||
|
||||
Un retainer, una volta assegnato, non si poteva fermare. `project_offers` aveva solo
|
||||
`start_date`, e il ramo retainer di `src/lib/forecast-queries.ts` sommava il canone a
|
||||
**ogni mese** dell'orizzonte da lì in poi, per sempre. Un cliente che disdiceva
|
||||
continuava a gonfiare il forecast a 12 mesi e a vedersi l'abbonamento attivo nel
|
||||
proprio portale.
|
||||
|
||||
## Cosa è stato fatto
|
||||
|
||||
**Schema** — migration `0016`, additiva pura, applicata a prod **prima** del push:
|
||||
`project_offers.status` (`attivo|sospeso|cessato`, CHECK `NOT VALID` per evitare un
|
||||
lock lungo) e `project_offers.end_date` (nullable, NULL = continuativo). Default
|
||||
`'attivo'` così ogni riga esistente conserva esattamente il comportamento precedente.
|
||||
Nessun DROP, nessun TRUNCATE. Idempotente.
|
||||
|
||||
**Forecast** — i retainer si fermano a `end_date`; sospesi e cessati escono dal
|
||||
calcolo. `getOffersSoldBreakdown` **non** filtra per stato di proposito: è uno
|
||||
storico di vendita, ed escludere le cessate riscriverebbe il passato.
|
||||
`offersAcceptedTotal` esclude le cessate (default del piano pagamenti).
|
||||
|
||||
**Admin** — comandi Sospendi / Riattiva / Cessa più data di fine nella tab Offerte,
|
||||
mostrati solo per i ricorrenti. `setProjectOfferLifecycle` valida con Zod e filtra
|
||||
**anche per `project_id`**, così un id arbitrario non può toccare un altro progetto.
|
||||
|
||||
**Portale cliente** — "Attivo dal", "fino al", badge *In pausa*, "Canone mensile" al
|
||||
posto di "Prezzo finale". Le offerte cessate non arrivano mai al client.
|
||||
|
||||
**Fix collaterale** — un retainer sospeso continuava a intestare i pagamenti "Totale
|
||||
Pagamento Mensile" e a sovrascriverne l'importo.
|
||||
|
||||
## Decisioni
|
||||
|
||||
- **Storico di vendita ≠ forecast.** Due letture diverse degli stessi dati: il
|
||||
breakdown del venduto ignora lo stato, il forecast lo rispetta. Unificarle
|
||||
avrebbe fatto sparire fatturato già incassato dai report.
|
||||
- **Default `'attivo'` invece di NULL.** Rende la migration a comportamento
|
||||
invariato senza una backfill separata.
|
||||
- **Filtro per `project_id` nell'action**, non solo per id dell'offerta: difesa in
|
||||
profondità contro un id manipolato.
|
||||
|
||||
## Fuori scope, rimandato
|
||||
|
||||
Tracciamento dei canoni mese per mese (agosto pagato / settembre no). Serve una
|
||||
tabella nuova: `payments` è protetta dai vincoli di Data Safety e la sua riscalatura
|
||||
è pensata per i piani una tantum. Voce di backlog in `REQUIREMENTS.md`.
|
||||
Reference in New Issue
Block a user