Files
wms-app/docs/reviewed/live-dashboard.md
T

7.7 KiB

Live Dashboard — Real-Time Event Specification

Mechanism

PHP engine files emit named events to a Node.js process over HTTP after a successful DB commit. Node.js broadcasts the event to all browser tabs in the same company room via Socket.IO. Each dashboard page listens for relevant events and reloads only the sections that changed — without a full page refresh.

PHP (engine file)
  └─ notify_node(event, payload, company_id)   // fire-and-forget POST to Node
       └─ Node.js /emit endpoint
            └─ io.to('company_{id}').emit(event, payload)
                 └─ Browser (dashboard JS)
                      └─ targeted section reload + flash effect

notify_node() (app/assets/utils/notify_node.php)

Fire-and-forget HTTP POST from PHP to the local Node.js /emit endpoint. Called after $pdo->commit() so the event is never sent for rolled-back transactions.

notify_node(string $event, array $payload, int $company_id): void

Failure is silently ignored — the DB write is authoritative; the live update is a UX enhancement only.


Socket.IO Room

Each company gets its own room: company_{company_id}. The browser joins this room on page load via window._socket (initialized in include_ending.php, always before page <script> blocks).


Events

stock_updated

Fired when stock inventory changes.

Trigger file Action
ics/api/engine/approve_stock.php Stock In / Out / Transfer approved

Payload: { warehouse_id, type }

Dashboard sections reloaded: stock-in total, stock-out total, low stock count, stock movement chart, stock health donut, most-moved list, low-stock list, recent activity list.

stock_overview.php does not listen — it is an analytical report page, not a live monitor.


order_updated

Fired when a sales order is created, confirmed, cancelled, or deleted.

Trigger file Action
order/api/engine/manage_order.php Order saved (create or update)
order/api/engine/confirm_order.php Order confirmed
order/api/engine/cancel_order.php Order cancelled
order/api/engine/delete_order.php Order soft-deleted

Payload: {}

Dashboard sections reloaded: total orders card.


invoice_updated

Fired when any invoice-type document changes status or is created/deleted. Also fired as a side-effect from confirm_order when auto_invoice = true, and from confirm_return when auto_cn = true.

Trigger file Action
order/api/engine/proceed_to_invoice.php Invoice created from order
order/api/engine/manage_invoice.php Invoice saved (create or update)
order/api/engine/void_invoice.php Invoice voided
order/api/engine/delete_invoice.php Invoice soft-deleted
order/api/engine/update_invoice_status.php Invoice status updated
order/api/engine/confirm_order.php Auto-invoice on order confirm (conditional)
order/api/engine/confirm_return.php Auto credit note on return confirm (conditional)
revenue/api/engine/proceed_to_invoice.php Revenue invoice created
revenue/api/engine/delete_credit_note.php Credit note deleted
expense/api/engine/delete_supplier_credit_note.php Supplier credit note deleted

Payload: {}

Dashboard sections reloaded: total revenue card, unpaid invoices card.


return_updated

Fired when a customer return is created, confirmed, cancelled, or deleted.

Trigger file Action
order/api/engine/manage_return.php Return saved (create or update)
order/api/engine/confirm_return.php Return confirmed
order/api/engine/cancel_return.php Return cancelled
order/api/engine/delete_return.php Return soft-deleted

Payload: {}

Dashboard sections reloaded: pending returns card.


gl_posted

Fired when a GL entry is posted, replaced, or saved as a manual journal.

Trigger file Action
accounting/api/engine/post_gl_entry.php Source document GL posted or replaced
accounting/api/engine/save_manual_journal.php Manual journal saved or replaced
order/api/engine/issue_invoice.php Invoice issued and posted to GL
finance/api/engine/manage_receipt.php Receipt saved and posted to GL
finance/api/engine/manage_payment.php Payment saved and posted to GL

Payload is built by gl_posted_payload() for most emitters and includes document type, source id, action, and journal lines where available.

Accounting dashboard sections reloaded: dashboard cards and charts handled by ac_dashboard/index.php.


receipt_posted and payment_posted

Fired as finance-specific signals immediately after receipt or payment creation.

Event Trigger file Payload
receipt_posted finance/api/engine/manage_receipt.php { id }
payment_posted finance/api/engine/manage_payment.php { id }

The current accounting dashboard refresh is driven by gl_posted; these finance events are available for targeted listeners if a page adds them later.


WMS Dashboard Section Map (dashboard/index.php)

Event Sections updated DOM IDs Reload function
stock_updated Stock in/out cards, low stock card, movement chart, health donut, most-moved list, low-stock list, activity list stat_total_in, stat_total_out, stat_low_stock_count, #ics_movementChart, #overallChart, #list_most_moved, #list_low_stock, #list_recent_activity retrieve_stock_sections()
order_updated Total orders card stat_total_orders retrieve_order_sections()
invoice_updated Revenue + unpaid invoices cards stat_total_revenue, stat_unpaid_invoices retrieve_invoice_sections()
return_updated Pending returns card stat_pending_returns retrieve_return_sections()

All four section functions call the same API endpoint (dashboard/api/engine_report/reports_stats.php) but update only the relevant DOM elements.


Accounting Dashboard (ac_dashboard/index.php)

The accounting dashboard listens for gl_posted and reloads its accounting KPI sections. It does not listen to the WMS events unless a future page explicitly adds those listeners.


Flash Card Effect

When a silent socket-triggered section reload completes, each updated card receives a 1-second yellow glow animation to signal the change without disrupting the user.

CSS:

@keyframes card-flash {
  0%   { box-shadow: 0 0 0 3px rgba(250,204,21,0.8); }
  100% { box-shadow: none; }
}
.card-flash { animation: card-flash 1s ease-out; }

JS helper (flash_card):

function flash_card(el) {
  if (!el) return;
  el.classList.remove('card-flash');
  void el.offsetWidth;               // force reflow so re-trigger works
  el.classList.add('card-flash');
  setTimeout(function() { el.classList.remove('card-flash'); }, 1000);
}

Called immediately after the overlay .hide() for each updated card element. Flash does not fire on the initial full-page load — only on silent socket-triggered background refreshes.


Pages That Do NOT Listen

Page Reason
ics/stock_overview.php Analytical report — user loads on demand, no live monitoring needed
All other non-dashboard pages Point-in-time views; manual refresh is sufficient

Key Files

File Role
app/assets/utils/notify_node.php PHP → Node.js HTTP POST helper
nodejs/server.js Node.js Socket.IO server, /emit endpoint, room broadcast
app/include_ending.php Initializes window._socket before page scripts run
app/dashboard/index.php WMS dashboard: 4 socket listeners, 4 section-reload functions, flash_card()
app/ac_dashboard/index.php Accounting dashboard: own listener pattern, flash_card()