feat(offers): ciclo di vita dei servizi ricorrenti (v2.4 Phase 13)
Un retainer, una volta assegnato, non si poteva fermare: project_offers aveva solo start_date e il forecast sommava il canone a ogni mese dell'orizzonte da li in poi, per sempre. Un cliente che disdiceva continuava a gonfiare il forecast a 12 mesi e a vedersi l'abbonamento attivo nel portale. - migration 0016 (gia applicata a prod): project_offers.status (attivo|sospeso|cessato, CHECK) + end_date. Additiva pura, default 'attivo' cosi le righe esistenti conservano il comportamento di prima - forecast: i retainer si fermano a end_date, sospesi e cessati escono. getOffersSoldBreakdown NON filtra per stato: e uno storico di vendita, escludere le cessate riscriverebbe il passato - offersAcceptedTotal esclude le cessate (default del piano pagamenti) - admin: comandi Sospendi/Riattiva/Cessa + data fine nella tab Offerte, solo per i ricorrenti. setProjectOfferLifecycle valida con Zod e filtra anche per project_id, cosi un id arbitrario non tocca altri progetti - portale: "Attivo dal", "fino al", badge In pausa, "Canone mensile" invece di "Prezzo finale"; le cessate non arrivano al client - fix: un retainer sospeso continuava a intestare i pagamenti "Totale Pagamento Mensile" e a sovrascrivere l'importo Igiene nello stesso giro: - STATUS.md riscritto: era fermo al 22 giugno e diceva che node/docker non sono disponibili sul server e che le migrazioni si applicano da locale con uno script postgres.js — il contrario della procedura reale - rimossi ChatSection/CommentList/CommentForm, senza importatori (308 righe) - overrides postcss>=8.5.18 e sharp>=0.35.0: 3 CVE high transitive di Next senza fix upstream. npm audit ora pulito, build verde Verifica: forecast controllato sui dati veri in 5 scenari (baseline invariata, end_date, sospeso, cessato, ripristino); portale verificato nei 4 stati; tab admin verificata con Playwright sul build di produzione (i comandi non compaiono sulle una tantum). Dati di test ripuliti, tabelle protette invariate 4/5/11/10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -431,9 +431,19 @@ export const project_offers = pgTable("project_offers", {
|
||||
start_date: timestamp("start_date", { withTimezone: true }).notNull().defaultNow(),
|
||||
// Offer-level accepted total — separate from projects.accepted_total (quote builder total)
|
||||
accepted_total: numeric("accepted_total", { precision: 10, scale: 2 }),
|
||||
// Ciclo di vita (v2.4 Phase 13): attivo | sospeso | cessato.
|
||||
// Rilevante soprattutto per i retainer, che senza uno stato restavano nel
|
||||
// forecast per sempre. CHECK sui valori in 0016_retainer_lifecycle.sql.
|
||||
status: text("status").notNull().default("attivo"),
|
||||
// NULL = continuativo (semantica implicita prima della 0016).
|
||||
end_date: timestamp("end_date", { withTimezone: true }),
|
||||
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
/** Stati del ciclo di vita di un'offerta assegnata a un progetto. */
|
||||
export const PROJECT_OFFER_STATUSES = ["attivo", "sospeso", "cessato"] as const;
|
||||
export type ProjectOfferStatus = (typeof PROJECT_OFFER_STATUSES)[number];
|
||||
|
||||
// ============ OFFER PHASES (hierarchical breakdown of offer micros) ============
|
||||
export const offer_phases = pgTable("offer_phases", {
|
||||
id: text("id")
|
||||
|
||||
Reference in New Issue
Block a user