Changelog — March 2026

All changes deployed to MaxShipping during March 2026

March 31, 2026

Invoice Transaction Collision Fix (3-Part)

Incident: An invoice for client Maria Santos (container wGbF1MteMcCaRxSeGzjW) was not auto-sent to Square. Root cause: two packages (889577200647 and 889577200658) were scanned 4 seconds apart during unloading. The Firestore transaction in updateInvoiceOnPackageStatusChange collided — one update succeeded, the other failed after 3 retries, leaving stale cached packageStatus in the invoice and isAllUnloaded: false.

FixWhat Changed
Part 1 Jitter on retries updateInvoiceOnPackageStatusChange: Increased MAX_RETRIES from 3 to 5. Added random jitter (0–2s) to retry delay to prevent thundering herd when multiple packages update the same invoice simultaneously.
Part 2 Daily reconciliation cron New reconcileStuckInvoices function runs daily at 5:00 AM Manila time. Checks last 3 containers for invoices where isAllUnloaded == false and invoiceSent == false, reads actual package statuses, and fixes mismatches. 256MB, 300s timeout.
Part 3 Split invoice bug fix splitInvoice: Now sets isAllUnloaded: true on the original invoice after splitting out non-unloaded packages, so the original can proceed to auto-send.

Notifications System (New)

A new in-app notification system for admins to see cloud function alerts, errors, and reconciliation results without relying on email.

Backend:

ComponentDetails
writeNotification() helper Shared function in index.js that any cloud function can call. Writes to Notifications Firestore collection with type, group, subject, HTML message, and optional IDs.
Notifications collection New Firestore collection. Fields: type (error/alert/reconciliation/info), group (invoices/packages/clients/containers/system), subject, message (HTML), isRead, timestamp, optional invoiceID/clientID/containerID.

Frontend (MaxTracks Web):

Bell Icon
Real-time unread count badge on nav bar. Streams from Firestore.
Side Panel
380px slide-out panel. Grouped by date (Today/Yesterday/date). Shows last 30 notifications.
Full Page
Search, date range picker, group filter chips (All/Invoices/Packages/Clients). Click to expand with full HTML message.

Badge types: FAILED (type: error) ALERT (type: alert) INFO (all others)

Files: Notifications.dart (new), main.dart (bell icon + panel integration), index.js (writeNotification helper)

Notification Messages (Simplified)

Reconciliation notifications show a concise message:

FieldContent
SubjectAuto-Send Invoice Failed — Retried (Client Name)
MessageWhich invoice failed, which packages had mismatched statuses, and a note to manually send from Invoices page if needed.
Typereconciliation → shows as INFO badge
Groupinvoices
March 27–28, 2026

Invoice Auto-Send: Race Condition Fix

Incident: During container unloading on March 27, 80 "Failed Auto-Send Invoice" emails were sent. 22 of these were false alarms caused by a race condition in the Pub/Sub invoice processing pipeline.

Root cause: Firebase Cloud Functions have at-least-once execution. The enqueueInvoiceForProcessing trigger fired twice for the same isAllUnloaded change, publishing two Pub/Sub messages with different operation IDs. The first succeeded at Square, the second was rejected with error 400 ("invoice number already used"). The catch block then overwrote invoiceSent: true with false and sent false failure emails.

FixWhereWhat It Prevents
Layer 1 PubSubQueue dedup enqueueInvoiceForProcessing Atomic Firestore transaction using invoiceID as key. Only one instance can enqueue.
Layer 2 invoiceSent check processInvoiceInternal Re-reads invoice before calling Square. If already sent, skips.
Layer 3 Catch block check processInvoiceFromQueue Before sending failure email or overwriting status, checks if invoice already succeeded.

36 Square Customer Records Fixed

Issue: The Make.com paid plan expired around March 8–9. The addClientViaWebhook function stored the raw webhook response text ("Accepted") as squareCustomerId instead of a real Square customer ID. This affected 36 client records.

Resolution: Created 36 new Square customer accounts, updated all 36 Firebase Client records with real Square IDs. The updateSquareCustomerIdInInvoices trigger auto-propagated IDs to invoices. 6 stuck invoices were manually re-sent.

Improved Failure Notification Emails

The sendInvoiceFailureEmail function now includes:

BeforeAfter
Client name, date, invoice IDClient name, container name, date, invoice ID
"unknown/API error"Specific error type, HTTP status code, and error message
March 12, 2026

Make.com Workflows Migrated to n8n

Automation workflows previously running on Make.com (SaaS) have been migrated to n8n, a self-hosted workflow automation platform.

BeforeAfter
Make.com (third-party SaaS)n8n (self-hosted)
Monthly subscription costNo recurring cost
Limited by Make.com planFull control over execution

Password Bypass — All Screens

The "Do not ask for a password for the next 10 minutes" checkbox has been enabled on Container Unloading and Client Pickup screens, completing coverage across all package management screens.

ScreenFileStatus
ReceivingReceiving.dartAlready had it
Check InCheckIn.dartAlready had it
Container UnloadingContainerUnloading.dartEnabled
Client PickupClientPickUp.dartAdded
Unloaded Not Checked InUnloadedNotCheckedIn.dartAlready had it
UnprocessedUnprocessed.dartAlready had it

Password Bypass Flow

1
User triggers edit or delete action on a package
2
App checks Temp/timer.isPasswordExpired in Firestore
3
If within 10-min window: skip modal, proceed with action
4
If expired: show password modal with bypass checkbox
5
On correct password + checkbox: call startNoPasswordPeriod cloud function
6
resetPasswordExpiry scheduled function resets after 10 minutes

What Changed in Code

ContainerUnloading.dart — Uncommented the existing checkbox UI in deleteOrMarkMissingPackage() and showEditForm(). Timer check and cloud function call were already wired up.

ClientPickUp.dart — Added from scratch: timer check against Temp/timer, checkbox UI, startNoPasswordPeriod cloud function call, loading spinner, and cloud_functions import in both deletePackage() and showEditForm().

📖 Invoice Auto-Send Flow docs · Full changelog details · Password bypass feature docs · Back to MaxShipping