Wire fulfillment alerts to Telegram + surface order progress in portal + even out ERPNext sync
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.
This commit is contained in:
parent
41df4d9553
commit
28b1af341d
15 changed files with 706 additions and 73 deletions
|
|
@ -165,6 +165,50 @@
|
|||
return '<span style="display:inline-block;padding:0.2rem 0.65rem;border-radius:9999px;font-size:0.7rem;font-weight:600;background:' + c.bg + ';color:' + c.text + ';">' + c.label + '</span>';
|
||||
}
|
||||
|
||||
// Client-safe progress label derived from the order's fulfillment_status.
|
||||
// We translate internal lifecycle values into plain-language stages and
|
||||
// deliberately avoid exposing any back-office mechanics.
|
||||
function fulfillmentBadge(status) {
|
||||
if (!status) return '';
|
||||
var map = {
|
||||
authorization_required: { bg: '#fef9c3', text: '#a16207', label: 'Action needed: signature' },
|
||||
awaiting_customer_delegation: { bg: '#fef9c3', text: '#a16207', label: 'Action needed' },
|
||||
awaiting_secure_credentials: { bg: '#fef9c3', text: '#a16207', label: 'Action needed' },
|
||||
awaiting_government_fee_approval: { bg: '#fef9c3', text: '#a16207', label: 'Action needed: approval' },
|
||||
authorization_signed: { bg: '#dbeafe', text: '#1d4ed8', label: 'In progress' },
|
||||
ready_to_file: { bg: '#dbeafe', text: '#1d4ed8', label: 'In progress' },
|
||||
awaiting_insurance_filing: { bg: '#dbeafe', text: '#1d4ed8', label: 'Awaiting insurance' },
|
||||
filed_waiting_state: { bg: '#dbeafe', text: '#1d4ed8', label: 'Filed — awaiting confirmation' },
|
||||
completed: { bg: '#dcfce7', text: '#15803d', label: 'Completed' }
|
||||
};
|
||||
var c = map[status];
|
||||
if (!c) return '';
|
||||
return '<span style="display:inline-block;padding:0.2rem 0.65rem;border-radius:9999px;font-size:0.7rem;font-weight:600;background:' + c.bg + ';color:' + c.text + ';">' + c.label + '</span>';
|
||||
}
|
||||
|
||||
// For a batch, show the single most actionable status: anything needing the
|
||||
// client's attention wins, then in-progress, then completed.
|
||||
function pickBatchFulfillment(items) {
|
||||
var actionNeeded = [
|
||||
'authorization_required', 'awaiting_customer_delegation',
|
||||
'awaiting_secure_credentials', 'awaiting_government_fee_approval'
|
||||
];
|
||||
var inProgress = [
|
||||
'authorization_signed', 'ready_to_file',
|
||||
'awaiting_insurance_filing', 'filed_waiting_state'
|
||||
];
|
||||
var statuses = items.map(function(o) { return o.fulfillment_status; });
|
||||
for (var i = 0; i < statuses.length; i++) {
|
||||
if (actionNeeded.indexOf(statuses[i]) !== -1) return statuses[i];
|
||||
}
|
||||
for (var j = 0; j < statuses.length; j++) {
|
||||
if (inProgress.indexOf(statuses[j]) !== -1) return statuses[j];
|
||||
}
|
||||
// All completed?
|
||||
var allDone = statuses.length > 0 && statuses.every(function(s) { return s === 'completed'; });
|
||||
return allDone ? 'completed' : (statuses[0] || null);
|
||||
}
|
||||
|
||||
function paymentIcon(method) {
|
||||
if (!method) return '';
|
||||
var m = method.toLowerCase();
|
||||
|
|
@ -261,6 +305,8 @@
|
|||
totalCents += (o.service_fee_cents || 0) - (o.discount_cents || 0) + (o.surcharge_cents || 0);
|
||||
if (names.indexOf(o.service_name) === -1) names.push(o.service_name);
|
||||
});
|
||||
// Surface the most actionable fulfillment status across the batch.
|
||||
var batchFulfillment = pickBatchFulfillment(items);
|
||||
|
||||
html += '<div style="background:#fff;border:1px solid #e5e7eb;border-radius:0.75rem;padding:1.25rem;transition:box-shadow 0.2s;" onmouseover="this.style.boxShadow=\'0 4px 12px rgba(0,0,0,0.08)\'" onmouseout="this.style.boxShadow=\'none\'">';
|
||||
html += '<div style="display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:0.75rem;">';
|
||||
|
|
@ -279,7 +325,15 @@
|
|||
html += '<span style="font-weight:700;color:#111827;font-size:1rem;">' + formatCents(totalCents) + '</span>';
|
||||
html += statusBadge(first.payment_status);
|
||||
html += '</div>';
|
||||
html += '</div></div>';
|
||||
html += '</div>';
|
||||
// Fulfillment progress row (only when we have a status to show)
|
||||
if (fulfillmentBadge(batchFulfillment)) {
|
||||
html += '<div style="margin-top:0.75rem;padding-top:0.75rem;border-top:1px solid #f3f4f6;display:flex;align-items:center;gap:0.5rem;">';
|
||||
html += '<span style="color:#9ca3af;font-size:0.75rem;">Progress:</span>';
|
||||
html += fulfillmentBadge(batchFulfillment);
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
// Render standalone orders
|
||||
|
|
@ -301,7 +355,15 @@
|
|||
html += '<span style="font-weight:700;color:#111827;font-size:1rem;">' + formatCents(totalCents) + '</span>';
|
||||
html += statusBadge(o.payment_status);
|
||||
html += '</div>';
|
||||
html += '</div></div>';
|
||||
html += '</div>';
|
||||
// Fulfillment progress row (only when we have a status to show)
|
||||
if (fulfillmentBadge(o.fulfillment_status)) {
|
||||
html += '<div style="margin-top:0.75rem;padding-top:0.75rem;border-top:1px solid #f3f4f6;display:flex;align-items:center;gap:0.5rem;">';
|
||||
html += '<span style="color:#9ca3af;font-size:0.75rem;">Progress:</span>';
|
||||
html += fulfillmentBadge(o.fulfillment_status);
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
$grid.innerHTML = html;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue