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
` +
`
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);