chore: export ensureComplianceSalesOrder for rescue/backfill use

This commit is contained in:
justin 2026-06-09 14:44:28 -05:00
parent 68e6b60951
commit e2467752dc
2 changed files with 74 additions and 1 deletions

View file

@ -285,7 +285,7 @@ const CDR_STUDY_GRANTING_SLUGS = new Set([
* /checkout/create-session path did, so webhook-confirmed card orders had no SO * /checkout/create-session path did, so webhook-confirmed card orders had no SO
* and the workers logged "Sales Order not found 404". * and the workers logged "Sales Order not found 404".
*/ */
async function ensureComplianceSalesOrder( export async function ensureComplianceSalesOrder(
orderId: string, orderId: string,
orderType: string, orderType: string,
rows: Record<string, unknown>[], rows: Record<string, unknown>[],

View file

@ -0,0 +1,73 @@
/**
* Rescue Mitchell Allen's batch CB-95BA6C90: the 5 DOT services were paid + the
* workers ran, but (a) no customers row (login bug, card order predates fix),
* (b) no ERPNext Sales Order (webhook SO bug), and (c) the authorization/signing
* emails failed (localhost:25 SMTP bug) so he never got his e-sign links.
*
* Now that the SMTP + SO fixes are deployed, this:
* 1. creates his customers row (so he can log in),
* 2. creates the ERPNext SO via the deployed ensureComplianceSalesOrder path,
* 3. re-dispatches each service worker so the (now-working) emails go out.
*
* Run: docker exec performancewest-api-1 node /app/scripts/rescue-mitchell.mjs
*/
import pg from "pg";
const BATCH = "CB-95BA6C90";
const WORKER_URL = process.env.WORKER_URL || "http://workers:8090";
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const { rows } = await pool.query(
`SELECT order_number, service_slug, customer_email, customer_name, erpnext_sales_order, intake_data
FROM compliance_orders WHERE batch_id=$1 ORDER BY created_at`, [BATCH]);
if (!rows.length) { console.log("no orders for batch"); process.exit(1); }
const email = rows[0].customer_email.toLowerCase().trim();
const name = rows[0].customer_name || "Customer";
let company = null;
try { const i = typeof rows[0].intake_data === "string" ? JSON.parse(rows[0].intake_data) : rows[0].intake_data; company = i?.company || i?.legal_name || i?.entity_name || null; } catch {}
// 1) customers row (portal login)
const c = await pool.query(
`INSERT INTO customers (email, name, company) VALUES ($1,$2,$3)
ON CONFLICT (email) DO UPDATE SET name=COALESCE(customers.name,EXCLUDED.name), company=COALESCE(customers.company,EXCLUDED.company)
RETURNING id, (password_hash IS NOT NULL) AS has_pw`, [email, name, company]);
console.log(`[rescue] customers row id=${c.rows[0].id} email=${email} has_password=${c.rows[0].has_pw}`);
// 2) ERPNext Sales Order (reuse the deployed handlePaymentComplete SO path is internal;
// here call the worker-friendly path: build the SO via the same helper by importing checkout)
// Simpler + safe: re-run handlePaymentComplete is guarded (already paid -> returns early),
// so directly create the SO using the exported ensureComplianceSalesOrder if available.
const checkout = await import("/app/dist/routes/checkout.js");
if (typeof checkout.ensureComplianceSalesOrder === "function") {
await checkout.ensureComplianceSalesOrder(BATCH, "compliance_batch", rows, "card");
console.log("[rescue] ensureComplianceSalesOrder ran");
} else {
console.log("[rescue] NOTE: ensureComplianceSalesOrder not exported; SO will be built-from-PG by worker (404 warning is harmless)");
}
// refresh SO id
const after = await pool.query(`SELECT order_number, service_slug, erpnext_sales_order FROM compliance_orders WHERE batch_id=$1`, [BATCH]);
const soName = after.rows.find(r => r.erpnext_sales_order)?.erpnext_sales_order || BATCH;
console.log(`[rescue] SO = ${soName}`);
// 3) re-dispatch each worker so authorization/signing emails resend (now that SMTP works)
for (const r of after.rows) {
try {
const res = await fetch(`${WORKER_URL}/jobs`, {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "process_compliance_service",
order_name: r.erpnext_sales_order || soName,
order_number: r.order_number,
service_slug: r.service_slug,
}),
});
console.log(`[rescue] re-dispatched ${r.order_number} (${r.service_slug}) -> HTTP ${res.status}`);
} catch (e) {
console.log(`[rescue] dispatch error ${r.order_number}: ${e.message}`);
}
}
await pool.end();
console.log("[rescue] DONE");