From d18de006d8aa706e3a541a7c5a229a924239b584 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 16 Jun 2026 00:33:22 -0500 Subject: [PATCH] admin approve: block filing when intake incomplete (force override + warning) Paul Wilson's UCR (CO-FE07212A) sat at fulfillment_status=ready_to_file with intake_data_validated=false, so the Approve & File button would have dispatched it for government submission with incomplete intake and no document to review. Backend: /approve now refuses an order whose intake_data_validated is false unless {force:true} is passed (409 code=intake_incomplete); the override is recorded in order_audit_log. The fulfillment_status=ready_to_file requirement is unchanged, so awaiting_intake orders (e.g. Mitchell's MCS-150s) still 409. UI: the drawer shows an amber 'intake not complete' warning above the approve button, and approving an intake-incomplete order triggers an explicit override confirmation before sending force=true. --- api/src/routes/admin.ts | 20 ++++++++++++-- .../public/admin/compliance-orders/index.html | 27 ++++++++++++++----- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/api/src/routes/admin.ts b/api/src/routes/admin.ts index 6bc3166..4a3b2e6 100644 --- a/api/src/routes/admin.ts +++ b/api/src/routes/admin.ts @@ -474,7 +474,8 @@ router.post("/api/v1/admin/compliance-orders/:order_number/approve", requireAdmi const id = req.params.order_number; try { const { rows } = await pool.query( - `SELECT order_number, service_slug, fulfillment_status + `SELECT order_number, service_slug, fulfillment_status, + COALESCE(intake_data_validated, FALSE) AS intake_data_validated FROM compliance_orders WHERE order_number = $1`, [id], ); @@ -486,6 +487,17 @@ router.post("/api/v1/admin/compliance-orders/:order_number/approve", requireAdmi }); return; } + // Safety gate: never let an order be filed while its intake is still + // incomplete (e.g. an admin-assisted service parked at ready_to_file before + // the customer finished intake). The caller must pass {force:true} to + // override, which is logged in the audit trail. + if (!order.intake_data_validated && req.body?.force !== true) { + res.status(409).json({ + error: "Intake is not complete for this order. Review the documents and resend the intake reminder, or pass force to override.", + code: "intake_incomplete", + }); + return; + } await pool.query( `UPDATE compliance_orders @@ -496,7 +508,11 @@ router.post("/api/v1/admin/compliance-orders/:order_number/approve", requireAdmi await pool.query( `INSERT INTO order_audit_log (order_type, order_id, order_number, action, from_status, to_status, actor_type, actor_id, actor_name, note) VALUES ('compliance', 0, $1, 'approved_for_submission', 'ready_to_file', 'authorization_signed', 'admin', $2, $3, $4)`, - [id, req.admin!.id, req.admin!.username, (req.body?.note as string) || "Approved + dispatched for government submission"], + [id, req.admin!.id, req.admin!.username, + (req.body?.note as string) + || (order.intake_data_validated + ? "Approved + dispatched for government submission" + : "Approved + dispatched (intake-incomplete override)")], ); const workerUrl = process.env.WORKER_URL || "http://workers:8090"; diff --git a/site/public/admin/compliance-orders/index.html b/site/public/admin/compliance-orders/index.html index 644fdf6..9eb946b 100644 --- a/site/public/admin/compliance-orders/index.html +++ b/site/public/admin/compliance-orders/index.html @@ -289,7 +289,7 @@ ? `${g.max_reminder_count} reminder(s)${g.last_reminded_at ? ", last " + fmtDay(g.last_reminded_at) : ", none sent"}` : ""; const services = g.services.map((s) => { const approve = s.ready_to_approve - ? `` : ""; + ? `` : ""; return `
${esc(s.order_number)} @@ -314,16 +314,26 @@
`; }).join(""); - document.querySelectorAll("[data-approve]").forEach((b) => b.addEventListener("click", () => approveOrder(b.getAttribute("data-approve"), b.getAttribute("data-svc")))); + document.querySelectorAll("[data-approve]").forEach((b) => b.addEventListener("click", () => approveOrder(b.getAttribute("data-approve"), b.getAttribute("data-svc"), b.getAttribute("data-intake") === "1"))); document.querySelectorAll("[data-rearm]").forEach((b) => b.addEventListener("click", () => rearmIntake(b.getAttribute("data-rearm")))); document.querySelectorAll("[data-detail]").forEach((b) => b.addEventListener("click", () => openDetail(b.getAttribute("data-detail")))); } - async function approveOrder(orderNumber, svc) { + async function approveOrder(orderNumber, svc, intakeOk) { + // Hard warn when intake isn't complete — the filing may be missing data or + // have no prepared document to review. + if (intakeOk === false) { + if (!confirm(`⚠️ Intake is NOT complete for "${svc}" (${orderNumber}).\n\n` + + `The prepared filing may be missing required data, and there may be no ` + + `document to review. Filing now could submit an incomplete/incorrect ` + + `form to the government.\n\nProceed ANYWAY (override)?`)) return; + } if (!confirm(`Approve "${svc}" (${orderNumber}) and dispatch it for government submission?\n\nThis cannot be undone.`)) return; + const body = JSON.stringify(intakeOk === false ? { force: true } : {}); try { - const r = await api("/api/v1/admin/compliance-orders/" + encodeURIComponent(orderNumber) + "/approve", { method: "POST", body: "{}" }); + const r = await api("/api/v1/admin/compliance-orders/" + encodeURIComponent(orderNumber) + "/approve", { method: "POST", body }); alert(r.dispatched ? "Approved and dispatched to the worker." : "Approved, but worker dispatch did not confirm — check worker logs."); + $("drawer").classList.add("hidden"); refresh(); } catch (e) { alert("Approve failed: " + e.message); } } @@ -360,13 +370,18 @@ row("ERPNext SO", esc(order.erpnext_sales_order || "—")) + (order.batch_id ? row("Batch", esc(order.batch_id)) : "") + row("Created", fmtDate(order.created_at)) + - (order.fulfillment_status === "ready_to_file" ? `` : "") + + (order.fulfillment_status === "ready_to_file" + ? ((!order.intake_data_validated + ? `
⚠️ Intake is not complete — review documents before filing. Approving will require an override.
` + : "") + + ``) + : "") + ((order.payment_status === "paid" && !order.intake_data_validated) ? `` : "") + `
Intake data
${esc(JSON.stringify(intake, null, 2))}
` + `
Documents
Loading documents…
` + `
Audit log
${auditHtml}`; const da = $("drawer-approve"); - if (da) da.addEventListener("click", () => approveOrder(order.order_number, order.service_name || order.service_slug)); + if (da) da.addEventListener("click", () => approveOrder(order.order_number, order.service_name || order.service_slug, !!order.intake_data_validated)); const dr = $("drawer-rearm"); if (dr) dr.addEventListener("click", () => rearmIntake(order.order_number)); loadDocuments(order.order_number);