dow-prod-tracker/scripts/recompute-deliverable-statuses.ts
DJP 47a65d6498 Keep Deliverable.status in sync with stage state
Root cause of the mismatch: Deliverable.status is a denormalised
column that was only written at create-time (default NOT_STARTED)
and never refreshed when stages moved. The Projects board read it
live and showed "Not Started" while the pipeline ring + dominant-
stage view correctly showed "at Client Feedback (6/11 stages
complete)".

Fix in two parts:

1. New deliverable-status-service with:
   - computeDeliverableStatus(stageStatuses[]) — pure function with
     the summary rule:
        all stages terminal              → APPROVED
        any IN_REVIEW                    → IN_REVIEW
        any IN_PROGRESS/CHANGES_REQUESTED → IN_PROGRESS
        else                             → NOT_STARTED
     ON_HOLD is producer-managed and never overwritten.
   - recomputeDeliverableStatus(deliverableId, txClient?) —
     executes the rule + writes if different. Accepts an optional
     Prisma tx client so callers can run inside their own
     transaction.

2. Wired into every stage-write path:
   - stage-service.updateStageStatus (single-stage transitions)
   - stage-service bulk transaction (bulkUpdateStages) — dedups
     touched deliverable IDs so we don't recompute twice.
   - stage-transition-service forward + rework (board drag) —
     inline inside the same $transaction so the board bucket is
     correct on the next refetch.

3. Backfill script scripts/recompute-deliverable-statuses.ts —
   one-off sweep to fix existing stale rows:
     npx tsx scripts/recompute-deliverable-statuses.ts
   Run once after deploy.
2026-04-21 19:55:39 -04:00

70 lines
2 KiB
TypeScript

/**
* scripts/recompute-deliverable-statuses.ts
*
* One-off backfill for Deliverable.status. Sweeps every deliverable,
* runs the same recompute logic the live write path now uses, and
* updates any that were stale.
*
* Before this commit, Deliverable.status was only written at create
* time and never refreshed, so live stage activity didn't propagate
* to the denormalised column. Run this once after deploy to clean
* up existing rows; from then on the in-app recompute keeps things
* in sync automatically.
*
* npx tsx scripts/recompute-deliverable-statuses.ts
*/
import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../src/generated/prisma/client";
import { computeDeliverableStatus } from "../src/lib/services/deliverable-status-service";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
const prisma = new PrismaClient({ adapter });
async function main() {
console.log("Sweeping deliverable statuses…");
const deliverables = await prisma.deliverable.findMany({
select: {
id: true,
name: true,
status: true,
stages: { select: { status: true } },
},
});
let scanned = 0;
let updated = 0;
let skippedOnHold = 0;
for (const d of deliverables) {
scanned++;
// Producer-managed park state — never overwrite based on derived
// values, same as the live recompute helper.
if (d.status === "ON_HOLD") {
skippedOnHold++;
continue;
}
const next = computeDeliverableStatus(d.stages.map((s) => s.status));
if (next !== d.status) {
await prisma.deliverable.update({
where: { id: d.id },
data: { status: next },
});
console.log(` ${d.name}: ${d.status}${next}`);
updated++;
}
}
console.log(
`\nDone. scanned=${scanned} updated=${updated} skipped-on-hold=${skippedOnHold}`
);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());