73 lines
3.7 KiB
JavaScript
73 lines
3.7 KiB
JavaScript
/**
|
|
* 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");
|