Coupon 100% discount now correctly saves total as 0 (no more silent fallback to subtotal). Past affected orders auto-corrected.
Statement of Account (SOA) is now issued as its own invoice once a month — pay one invoice to settle all trade invoices in that period. Open any invoice list to see the new purple SOA badge.
Each trade invoice still appears in the list. When an SOA exists, individual invoices show "Pay via SOA" instead of a separate Pay button.
Suppliers — New Capabilities
New PO submission instantly notifies suppliers via email + in-app socket — no more missed orders.
New "Staff" page for Supplier Admin (Advanced plan) — invite staff with their own login, role, PIN, and phone.
Supplier dashboard refreshed with cleaner layout, expanded skeleton loading, and unified status badges.
Empty states across Supplier pages now include helpful next-step CTAs (e.g., "View Pending Contracts").
Reliability — Race Conditions & Data Integrity
Purchase Order state changes (submit / confirm / ship / cancel / deliver) now run inside transactions with row-level locks. Two users clicking confirm + cancel at the same moment can no longer corrupt the order.
Buyer can no longer mark a system supplier order as "shipped" — that flow now belongs to the seller, who automatically updates stock in the same transaction.
Returns automatically reverse stock on both buyer and seller side (Brand and Foodcourt sellers included).
Carrier webhooks (Lalamove, Grab, JNT, Ninja Van, Pos Laju) now use HMAC SHA-256 signatures with a 5-minute timestamp tolerance and idempotent payload-hash uniqueness.
Mobile
Modals on mobile now use tighter padding (12px overlay / 16px content) — less wasted space on phones, identical look on desktop.
Restaurant invoices page is now fully responsive at 768px and 480px breakpoints.
Internal
alert() / window.confirm() removed from purchase order screens — all errors and confirmations now use the unified dialog component.
Test signup email pattern guard: automated test accounts can never spam the admin inbox, even if dev block is bypassed.