5177a3700a
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>
5.3 KiB
5.3 KiB
ClientHub (IAMCAVALLI) — Status
Ultimo aggiornamento: 2026-08-01
Stato attuale
In prod su Coolify (Gitea → deploy automatico su push a main). Build verde, npm audit pulito.
Milestone v2.3 "Email & Accesso" chiusa e in produzione dal 2026-07-29: il portale cliente non è più apribile col solo link. In corso v2.4 Phase 13 — ciclo di vita dei servizi ricorrenti.
Fatto (recente, cumulativo)
- [v2.4, in corso] Ciclo di vita dei servizi ricorrenti:
project_offers.status(attivo/sospeso/cessato) +end_date(migr. 0016, in prod). Prima un retainer non poteva finire e il forecast lo sommava a ogni mese in eterno. Comandi Sospendi/Riattiva/Cessa nella tab Offerte del progetto; il cliente vede stato, "attivo dal" e canone mensile. - [v2.3] Gate OTP sul portale cliente: whitelist email per cliente (
client_emails), codice a 6 cifre via Resend, sessione firmata 90 giorni, revoca in blocco dall'admin (clients.sessions_valid_from). Migr. 0015. Verificato E2E in produzione. - [2026-07] Audit di sicurezza chiuso: 4 vulnerabilità risolte e deployate, slug clienti ruotati a 12 char CSPRNG,
INTERNAL_SECRETeADMIN_PASSWORDconfigurati su Coolify. Report in.planning/SECURITY-*.md. - Tassonomie centralizzate in Impostazioni (modello Notion, pool persistenti
src/lib/taxonomy.ts). - Lead → Cliente:
clients.email/phone+leads.archived(migr. 0011).convertLeadToClientriusacreateClientCore, porta i transcript, archivia il lead mantenendo "won". - Offerta → Fasi/Task:
importOfferIntoProjectcrea fasi raggruppando i servizi del tier perservices.fase. - Offerte (modello + UI):
offer_macros.offer_type('una_tantum'|'retainer') + toggle "Modalità" nell'editor (migr. 0012). Tab Offerte a 2 step (Offerta → Tier con prezzo).
Da fare
- Whitelist portale: dei 4 clienti solo alcuni hanno email autorizzate. Chi ha la whitelist vuota non entra nel proprio portale — si popola da
/admin/clients/<id>→ "Accessi al portale". - Fasi/Task dall'offerta funzionano solo se i servizi hanno il campo Fase valorizzato nel Catalogo (altrimenti finiscono in "Generale").
- Micro legacy "Mantenimento" senza tier: valutare se rimuoverlo/normalizzarlo.
- Debito design: 11 pagine ancora a palette raw invece che a token semantici. Le più pesanti:
/admin/projects/[id](~140 occorrenze fra i suoi tab),/admin/offers/[id]/edit, e tutto/quote/[token](~40, ed è rivolto al cliente). Ancheui/dialog.tsx, che propaga il look vecchio a ogni modale. - Backlog v2.5: canoni mensili tracciabili (agosto pagato / settembre no) — serve una tabella nuova,
paymentsè protetta e la sua riscalatura è pensata per i piani una tantum. Più PROP-03 (Stripe sul deck), PROP-04 (auto-provisioning al "Vinto"), SEND-01/02 (invio preventivo via email — il mailer è già pronto).
Note tecniche
- Il DB di
.env.localÈ la produzione (178.104.27.55). Non esiste un database di sviluppo separato: qualunque cosa si esegua in locale scrive su dati reali. Verificare i conteggi delle tabelle protette prima e dopo ogni prova. - Migrazioni: SQL scritto a mano in
src/db/migrations/(drizzle-kit generateè rotto — vanno tenuti in syncschema.tse l'SQL). Si applicano da locale via SSH + docker exec, senza tunnel:cat src/db/migrations/NNNN.sql | ssh root@178.104.27.55 "docker exec -i xwkk0040w0kk0gsgcgog8owk psql -U clienthub -d clienthub -v ON_ERROR_STOP=1 --single-transaction"Il tunnelssh -f -N -L 54321:localhost:54321serve solo per puntare il tooling locale (es.npx tsx) al DB di prod, riscrivendo l'host diDATABASE_URLa127.0.0.1:54321. - Ordine di deploy con schema: applicare la migrazione a prod prima del push (il deploy fa girare subito il codice nuovo).
NEXTAUTH_SECRETlocale ≠ produzione: per costruire firme o hash validi in prod va letto da Coolify, non da.env.local.- Coolify API: credenziali in
~/.coolify.env(formatoexport VAR=…, va sorgentato). App uuidxsksow44g4kcoo8wocsgkscc. Il POST su/api/v1/applications/<uuid>/envsrifiuta il campois_build_timecon 422. overridesin package.json forzanopostcss >= 8.5.18esharp >= 0.35.0: le versioni che Next si porta dietro hanno CVE high e non c'è fix upstream. Se un aggiornamento di Next rompe qualcosa, è il primo posto dove guardare.offer_microsnon hacreated_at(no "tier più vecchio" affidabile).
File chiave
| File | Scopo |
|---|---|
| src/proxy.ts | Middleware (Next 16 lo chiama proxy, non middleware) |
| src/lib/client-gate.ts | Gate OTP — va chiamato in cima a ogni page sotto /client/[token]/ |
| src/lib/otp.ts, client-session.ts | Codici OTP e cookie di sessione 90gg |
| src/lib/mailer.ts | Unico punto di invio email (Resend) |
| src/lib/forecast-queries.ts | Forecast 12 mesi — rispetta stato e end_date dei retainer |
| src/lib/taxonomy.ts | Pool tassonomie (Impostazioni) |
| src/app/admin/projects/project-actions.ts | importOfferIntoProject, setProjectOfferLifecycle, piani pagamento |
| src/components/admin/tabs/OffersTab.tsx | Tab Offerte + comandi ciclo di vita |
| src/lib/admin-queries.ts / client-view.ts | I due layer separati: admin vs proiezioni client-safe |
| src/db/migrations/ | 0011 (email/phone), 0012 (offer_type), 0015 (OTP), 0016 (ciclo di vita) |