a9358da96f
"Quanto e' costata questa fase" non era una domanda che si potesse fare: time_entries aveva la sola project_id. Migration 0018 (gia' applicata a prod): phase_id e task_id su time_entries, piu' due indici, piu' projects.due_date che serve al blocco successivo. Solo ADD COLUMN e CREATE INDEX. Due scelte che vale la pena spiegare: - ON DELETE SET NULL, non CASCADE. Cancellare un task NON deve cancellare le ore lavorate su di esso: sono storico fatturabile. L'entry ricade a livello progetto e il totale del progetto non cambia mai. Con CASCADE, ripulire una fase avrebbe silenziosamente abbassato il fatturato tracciato. Verificato sul DB di produzione dentro una transazione con ROLLBACK: cancellato il task, l'entry sopravvive con task_id NULL, phase_id intatto e i secondi invariati. - Il timer su un task scrive ENTRAMBE le colonne. Cosi' il totale di una fase e' un group-by diretto su phase_id, senza risalire dai task, e comprende anche il tempo imputato alla fase ma a nessun task in particolare. Resta un solo timer attivo in tutto l'hub. Da qui una conseguenza in UI: se sta girando su un task, il timer del tab "Timer" NON si mostra acceso — mostrarlo acceso farebbe credere che siano due cronometri diversi. Il tab lo dice a parole e avvisa che avviarlo fermerebbe l'altro. Le 8 entry esistenti restano valide con entrambe le colonne a NULL, cioe' "tempo di progetto": e' esattamente cio' che sono. PhasesTab passa ai token semantici mentre lo si tocca. Non e' zelo: ci si infila dentro una TimerCell che i token li usa gia', e in dark mode un badge a token dentro una card bg-white si vede. Un pezzo di DEBT-01 in meno. Cade startTimerForClient, senza chiamanti da quando la lista progetti non ha piu' il timer. Build pulito. L'avvio/arresto dal browser non e' ancora stato provato: si verifica in produzione, che e' l'unico posto dove esiste il DB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41 lines
2.4 KiB
SQL
41 lines
2.4 KiB
SQL
-- Additive: timer per fase/task e data di consegna attesa sul progetto.
|
|
--
|
|
-- ── time_entries.phase_id / task_id ──────────────────────────────────────────
|
|
-- Il timer nasce a livello di progetto: time_entries aveva la sola project_id,
|
|
-- quindi "quanto e' costata questa fase" non era una domanda che si potesse
|
|
-- fare. Le due colonne sono NULLABLE e project_id resta obbligatoria: ogni
|
|
-- entry e' sempre attribuita a un progetto, il dettaglio e' un di piu'.
|
|
-- Le righe esistenti restano valide con entrambe a NULL, cioe' "tempo di
|
|
-- progetto, non imputato a una fase" — che e' esattamente cio' che sono.
|
|
--
|
|
-- ON DELETE SET NULL, non CASCADE, ed e' la scelta che conta qui: cancellare un
|
|
-- task NON deve cancellare il tempo tracciato su di esso. Sono ore lavorate, e
|
|
-- quindi storico fatturabile; l'entry ricade a livello progetto e il totale del
|
|
-- progetto non cambia mai. Con CASCADE, ripulire una fase avrebbe silenziosamente
|
|
-- abbassato il fatturato tracciato.
|
|
--
|
|
-- ── projects.due_date ────────────────────────────────────────────────────────
|
|
-- La consegna attesa e' normalmente DERIVATA: project_offers.start_date +
|
|
-- offer_micros.duration_months. Questa colonna e' l'override manuale, e vince
|
|
-- sulla derivata quando e' valorizzata. Nullable per forza: la stragrande
|
|
-- maggioranza dei progetti continuera' a non averla, ed e' giusto cosi' —
|
|
-- un campo obbligatorio in piu' su ogni progetto e' un campo che invecchia.
|
|
--
|
|
-- Nessun DROP, nessun TRUNCATE, nessuna colonna rimossa o modificata.
|
|
-- Applicare a prod via SSH+docker exec PRIMA di pushare il codice dipendente.
|
|
-- Idempotente: safe to re-run.
|
|
|
|
ALTER TABLE time_entries
|
|
ADD COLUMN IF NOT EXISTS phase_id text REFERENCES phases(id) ON DELETE SET NULL;
|
|
|
|
ALTER TABLE time_entries
|
|
ADD COLUMN IF NOT EXISTS task_id text REFERENCES tasks(id) ON DELETE SET NULL;
|
|
|
|
-- Il rollup per fase raggruppa su queste due colonne a ogni apertura del
|
|
-- progetto: senza indice diventa una scansione piena appena le entry crescono.
|
|
CREATE INDEX IF NOT EXISTS time_entries_phase_idx ON time_entries (phase_id);
|
|
CREATE INDEX IF NOT EXISTS time_entries_task_idx ON time_entries (task_id);
|
|
|
|
ALTER TABLE projects
|
|
ADD COLUMN IF NOT EXISTS due_date timestamptz;
|