admin: view order PDFs from MinIO (signed forms, prepared filings, evidence)
Adds a Documents section to the compliance-order detail drawer so you can review the actual filing PDFs before approving an order: GET /api/v1/admin/compliance-orders/:id/documents list viewable objects GET /api/v1/admin/compliance-orders/:id/document?key=&token= stream one Key discovery pulls from esign_records (unsigned + signed docs per order), intake_data.filing_status (pdf_minio_path, attested_pdf, evidence/*), and the order's engagement_letter / rmd_packet columns. Rather than hand out presigned URLs (MinIO's public host is IP-allowlisted to a few office IPs, so links break elsewhere), the API streams the object through itself from internal minio:9000, gated by the admin JWT. The stream endpoint accepts the token via ?token= (new middleware requireAdminQueryOrHeader) so a PDF opens in a new tab, and refuses any key that isn't one of the order's own documents.
This commit is contained in:
parent
d65f5ea279
commit
bce5db4a09
3 changed files with 192 additions and 1 deletions
|
|
@ -363,13 +363,40 @@
|
|||
(order.fulfillment_status === "ready_to_file" ? `<button id="drawer-approve" class="btn btn-blue wfull" style="margin-top:16px;">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>` : "") +
|
||||
`<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}`;
|
||||
const da = $("drawer-approve");
|
||||
if (da) da.addEventListener("click", () => approveOrder(order.order_number, order.service_name || order.service_slug));
|
||||
const dr = $("drawer-rearm");
|
||||
if (dr) dr.addEventListener("click", () => rearmIntake(order.order_number));
|
||||
loadDocuments(order.order_number);
|
||||
} catch (e) { $("drawer-body").innerHTML = `<div style="color:#b91c1c;">Failed to load: ${esc(e.message)}</div>`; }
|
||||
}
|
||||
async function loadDocuments(orderNumber) {
|
||||
const box = $("drawer-docs");
|
||||
if (!box) return;
|
||||
try {
|
||||
const { documents } = await api("/api/v1/admin/compliance-orders/" + encodeURIComponent(orderNumber) + "/documents");
|
||||
if (!documents || !documents.length) {
|
||||
box.innerHTML = '<div class="muted" style="font-size:12px;">No documents on file yet.</div>';
|
||||
return;
|
||||
}
|
||||
// Stream endpoint takes the JWT via ?token= so it opens in a new tab.
|
||||
const tok = encodeURIComponent(token());
|
||||
box.innerHTML = documents.map((d) => {
|
||||
const u = API + "/api/v1/admin/compliance-orders/" + encodeURIComponent(orderNumber)
|
||||
+ "/document?key=" + encodeURIComponent(d.key) + "&token=" + tok;
|
||||
return `<div class="svc-row"><div class="svc-left">
|
||||
<span style="font-size:13px;">${esc(d.label)}</span>
|
||||
<span class="small">${esc(d.key.split("/").pop())}</span>
|
||||
</div><div class="svc-right">
|
||||
<a href="${u}" target="_blank" rel="noopener" class="btn btn-blue btn-sm" style="text-decoration:none;">View</a>
|
||||
</div></div>`;
|
||||
}).join("");
|
||||
} catch (e) {
|
||||
box.innerHTML = `<div style="color:#b91c1c;font-size:12px;">Could not load documents: ${esc(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
$("drawer-close").addEventListener("click", () => $("drawer").classList.add("hidden"));
|
||||
$("drawer-backdrop").addEventListener("click", () => $("drawer").classList.add("hidden"));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue