Telegram notifications: - Add shared scripts/workers/telegram_notify.py (send_telegram, notify_fulfillment_todo, create_admin_todo) so every worker alerts the operator the same way; fire-and-forget. - Fire notify_fulfillment_todo after each admin_todos insert across all 8 service handlers (9 sites) so no fulfillment task waits unseen. (Orders + quotes + tickets already notified via checkout/quotes/tickets routes.) Client portal order progress: - order-timeline: derive real per-step status from live signals (payment paid, e-signature signed, fulfillment_status) instead of a static template; add current_step to the response. - Extract pure applyLiveStatus into order-timeline-status.ts (DB-free) + unit test (api/test/test_timeline_status.ts, 8 cases). - portal /me now returns compliance_orders.fulfillment_status. - Dashboard renders a client-safe Progress badge (In progress / Action needed / Filed-awaiting-confirmation / Completed); batches show the most actionable status. No back-office mechanics exposed. ERPNext sync parity: - Create a Sales Order for formation and fcc_carrier_registration orders (previously only canada_crtc + compliance synced); write erpnext_sales_order back to each table. Non-blocking, matches existing pattern. Verified: API tsc clean, timeline unit tests 8/8, Astro build 58 pages, cms10114/ink/paper_batch Python tests still green, no mechanics leaks.
72 lines
2.8 KiB
TypeScript
72 lines
2.8 KiB
TypeScript
/**
|
|
* Pure live-progress derivation for the order timeline.
|
|
*
|
|
* Kept free of DB/config imports so it can be unit-tested in isolation and
|
|
* reused. The route (order-timeline.ts) imports applyLiveStatus from here.
|
|
*
|
|
* We translate real signals (payment, e-signature, fulfillment_status) into
|
|
* per-step "completed | in_progress | pending" so the client portal shows true
|
|
* progress rather than a static template.
|
|
*/
|
|
|
|
/** Step-name matchers used to find phase boundaries within any timeline. */
|
|
export const SIGNATURE_STEP_RE = /signature|e-?sign/i;
|
|
export const FILED_STEP_RE = /filed|filing|submitted|application filed|registration/i;
|
|
|
|
// fulfillment_status values that mean "the filing has been submitted to the agency".
|
|
export const FILED_STATUSES = new Set<string>([
|
|
"filed_waiting_state",
|
|
"ready_to_file", // queued for ops to file — treat prep+signature as done
|
|
]);
|
|
// fulfillment_status values that mean "fully done".
|
|
export const COMPLETED_STATUSES = new Set<string>(["completed", "complete"]);
|
|
|
|
export interface ProgressSignals {
|
|
paid: boolean;
|
|
signed: boolean;
|
|
fulfillmentStatus: string | null;
|
|
}
|
|
|
|
/**
|
|
* Recompute each step's status from live signals. Returns a new step array.
|
|
*
|
|
* We resolve a "reached index" — the index of the furthest step we can prove is
|
|
* done — then mark everything up to it completed, the next in_progress, the
|
|
* rest pending. We never regress a step that the static definition already
|
|
* marked completed.
|
|
*/
|
|
export function applyLiveStatus<T extends { name: string; status: string }>(
|
|
steps: T[],
|
|
sig: ProgressSignals,
|
|
): T[] {
|
|
const sigIdx = steps.findIndex((s) => SIGNATURE_STEP_RE.test(s.name));
|
|
const filedIdx = steps.findIndex((s) => FILED_STEP_RE.test(s.name));
|
|
const confirmIdx = steps.length - 1; // last step is always the terminal one
|
|
|
|
let reached = -1;
|
|
|
|
// Payment confirmed → at minimum "Order Received" (index 0) is done.
|
|
if (sig.paid) reached = Math.max(reached, 0);
|
|
|
|
// Signature captured → the signature step (and everything before it) is done.
|
|
if (sig.signed && sigIdx >= 0) reached = Math.max(reached, sigIdx);
|
|
|
|
// Filing submitted to agency → the "Filed" step (and everything before) done.
|
|
if (sig.fulfillmentStatus && FILED_STATUSES.has(sig.fulfillmentStatus) && filedIdx >= 0) {
|
|
reached = Math.max(reached, filedIdx);
|
|
}
|
|
|
|
// Fully completed → everything done.
|
|
if (sig.fulfillmentStatus && COMPLETED_STATUSES.has(sig.fulfillmentStatus)) {
|
|
reached = confirmIdx;
|
|
}
|
|
|
|
return steps.map((step, i) => {
|
|
const staticallyDone = step.status === "completed";
|
|
let status: "pending" | "in_progress" | "completed";
|
|
if (i <= reached || staticallyDone) status = "completed";
|
|
else if (i === reached + 1) status = "in_progress";
|
|
else status = "pending";
|
|
return { ...step, status };
|
|
});
|
|
}
|