Files
clienthub/.planning/phases/13-ciclo-vita-servizi-ricorrenti/13-SUMMARY.md
T
simone f7eb7eec23 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>
2026-08-08 22:38:36 +02:00

2.8 KiB

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.