admin: mark-filed action to advance manual/admin-assisted orders to completed
Admin-assisted services (UCR, MC authority, etc.) have no automated submission, so approving them only flips to authorization_signed and then sits there -- there was no way to advance to completed. Add POST /mark-filed (filed_waiting_state | completed, optional confirmation #, transactional + audit-logged) and drawer buttons 'Mark as filed (waiting on agency)' / 'Mark completed' shown for orders in authorization_signed / ready_to_file / filed_waiting_state. Confirmation number is recorded into intake_data.filing_status.manual_confirmation.
This commit is contained in:
parent
6c10c6a6cd
commit
bf69960e8c
2 changed files with 94 additions and 0 deletions
|
|
@ -611,6 +611,74 @@ router.post("/api/v1/admin/compliance-orders/:order_number/rearm-intake", requir
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/v1/admin/compliance-orders/:order_number/mark-filed
|
||||
* Advance an order's fulfillment_status after a human has handled it. Used for
|
||||
* admin-assisted services (UCR, MC authority, etc.) that have no automated
|
||||
* filing path: once you've filed it on the government portal, mark it
|
||||
* filed_waiting_state (submitted, awaiting the agency) or completed (done).
|
||||
* body: { status: 'filed_waiting_state' | 'completed', note?, confirmation? }
|
||||
*/
|
||||
router.post("/api/v1/admin/compliance-orders/:order_number/mark-filed", requireAdmin, async (req, res) => {
|
||||
const id = req.params.order_number;
|
||||
const ALLOWED = new Set(["filed_waiting_state", "completed"]);
|
||||
try {
|
||||
const status = String(req.body?.status || "");
|
||||
if (!ALLOWED.has(status)) {
|
||||
res.status(400).json({ error: "status must be filed_waiting_state or completed." });
|
||||
return;
|
||||
}
|
||||
const { rows } = await pool.query(
|
||||
`SELECT order_number, fulfillment_status FROM compliance_orders WHERE order_number = $1`,
|
||||
[id],
|
||||
);
|
||||
const order = rows[0];
|
||||
if (!order) { res.status(404).json({ error: "Order not found." }); return; }
|
||||
|
||||
const confirmation = (req.body?.confirmation as string) || "";
|
||||
const note = (req.body?.note as string)
|
||||
|| `Marked ${status} by admin${confirmation ? ` (confirmation: ${confirmation})` : ""}`;
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
await client.query(
|
||||
`UPDATE compliance_orders
|
||||
SET fulfillment_status = $2, fulfillment_status_at = now(), updated_at = now()
|
||||
WHERE order_number = $1`,
|
||||
[id, status],
|
||||
);
|
||||
// Record the confirmation number into intake_data.filing_status when given.
|
||||
if (confirmation) {
|
||||
await client.query(
|
||||
`UPDATE compliance_orders
|
||||
SET intake_data = jsonb_set(
|
||||
jsonb_set(COALESCE(intake_data, '{}'::jsonb), '{filing_status}',
|
||||
COALESCE(intake_data->'filing_status', '{}'::jsonb)),
|
||||
'{filing_status,manual_confirmation}', to_jsonb($2::text))
|
||||
WHERE order_number = $1`,
|
||||
[id, confirmation],
|
||||
);
|
||||
}
|
||||
await client.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, 'marked_filed', $2, $3, 'admin', $4, $5, $6)`,
|
||||
[id, order.fulfillment_status, status, req.admin!.id, req.admin!.username, note],
|
||||
);
|
||||
await client.query("COMMIT");
|
||||
} catch (txErr) {
|
||||
await client.query("ROLLBACK").catch(() => {});
|
||||
throw txErr;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
res.json({ success: true, order_number: id, status });
|
||||
} catch (err) {
|
||||
console.error(`[admin/compliance-orders] mark-filed error for ${id}:`, err);
|
||||
res.status(500).json({ error: "Mark-filed failed." });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Document discovery + viewing ─────────────────────────────────────────────
|
||||
//
|
||||
// Every prepared/signed filing PDF lives in MinIO. We surface them so you can
|
||||
|
|
|
|||
|
|
@ -345,6 +345,18 @@
|
|||
refresh();
|
||||
} catch (e) { alert("Re-arm failed: " + e.message); }
|
||||
}
|
||||
async function markFiled(orderNumber, status) {
|
||||
const label = status === "completed" ? "completed" : "filed (waiting on the agency)";
|
||||
const conf = ($("drawer-confirmation") && $("drawer-confirmation").value.trim()) || "";
|
||||
if (!confirm(`Mark ${orderNumber} as ${label}?` + (conf ? `\nConfirmation #: ${conf}` : ""))) return;
|
||||
try {
|
||||
await api("/api/v1/admin/compliance-orders/" + encodeURIComponent(orderNumber) + "/mark-filed",
|
||||
{ method: "POST", body: JSON.stringify({ status, confirmation: conf }) });
|
||||
alert(`Marked ${label}.`);
|
||||
$("drawer").classList.add("hidden");
|
||||
refresh();
|
||||
} catch (e) { alert("Mark failed: " + e.message); }
|
||||
}
|
||||
|
||||
async function openDetail(orderNumber) {
|
||||
$("drawer").classList.remove("hidden");
|
||||
|
|
@ -377,6 +389,16 @@
|
|||
+ `<button id="drawer-approve" class="btn btn-blue wfull" style="margin-top:8px;">Approve & file this order</button>`)
|
||||
: "") +
|
||||
((order.payment_status === "paid" && !order.intake_data_validated) ? `<button id="drawer-rearm" class="btn btn-amber wfull" style="margin-top:8px;">Re-arm intake reminder</button>` : "") +
|
||||
// Manual filing controls: once an order is approved/awaiting agency, let
|
||||
// the admin record that they've filed it (admin-assisted services have
|
||||
// no automated submission, so this is how they reach completed).
|
||||
(["authorization_signed", "ready_to_file", "filed_waiting_state"].includes(order.fulfillment_status)
|
||||
? `<div class="section-h">Manual filing</div>`
|
||||
+ `<input id="drawer-confirmation" class="field" type="text" placeholder="Confirmation # (optional)" style="margin-bottom:6px;" />`
|
||||
+ (order.fulfillment_status !== "filed_waiting_state"
|
||||
? `<button id="drawer-mark-waiting" class="btn btn-outline wfull" style="margin-bottom:6px;">Mark as filed (waiting on agency)</button>` : "")
|
||||
+ `<button id="drawer-mark-completed" class="btn btn-primary wfull">Mark completed</button>`
|
||||
: "") +
|
||||
`<div class="section-h">Intake data</div><pre>${esc(JSON.stringify(intake, null, 2))}</pre>` +
|
||||
`<div class="section-h">Documents</div><div id="drawer-docs"><div class="muted" style="font-size:12px;">Loading documents…</div></div>` +
|
||||
`<div class="section-h">Audit log</div>${auditHtml}`;
|
||||
|
|
@ -384,6 +406,10 @@
|
|||
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));
|
||||
const mw = $("drawer-mark-waiting");
|
||||
if (mw) mw.addEventListener("click", () => markFiled(order.order_number, "filed_waiting_state"));
|
||||
const mc = $("drawer-mark-completed");
|
||||
if (mc) mc.addEventListener("click", () => markFiled(order.order_number, "completed"));
|
||||
loadDocuments(order.order_number);
|
||||
} catch (e) { $("drawer-body").innerHTML = `<div style="color:#b91c1c;">Failed to load: ${esc(e.message)}</div>`; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue