Skip to content

Demo 3.16: Air-Gapped Retail POS & Edge Sync (m_pos)

Functional Objective: Proves that a retail outlet or subsidised canteen can continue processing sales invoices during a complete network outage. Once connectivity is restored, the edge node’s local outbox is synchronised back to the central Finance and WMS ledgers via NATS JetStream, with automatic Last-Write-Wins (LWW) conflict resolution ensuring zero data loss and no duplicate postings.


Offline Billing, JetStream Edge Sync & Beneficiary Quota Guard

Demonstrates a retail counter processing two sales invoices while disconnected from the cloud. The outbox is then flushed over JetStream once the network is restored. A stale edge payload is correctly rejected by the LWW resolver, and a beneficiary card over-spend attempt is blocked by the quota controller.


  • Zero-Network Resilience: A local counter billing engine (SQLite at the edge or IndexedDB on the frontend) allows retail outlets and canteens to process SalesInvoice records without any cloud connectivity.
  • JetStream Outbox Sync: When the network is restored, the accumulated edge outbox is pushed in configurable batches via POST /api/v1/pos/sync_outbox, which enqueues each payload into a NATS JetStream task processed by the m_pos.process_jetstream_sync worker.
  • Last-Write-Wins Conflict Resolution: The JetStreamSyncService compares the modified timestamp on each incoming edge payload against the current cloud document. Stale payloads (edge timestamp ≤ cloud timestamp) are rejected with rejected_stale; fresh payloads update or insert accordingly.
  • Smart Card / Beneficiary Quota Management: PosBeneficiaryCard enforces a monthly purchase quota per card holder. The PosBeneficiaryCardController blocks any save where utilized_quota > monthly_quota, and a scheduled cron job (m_pos.reset_beneficiary_quotas) auto-resets all active card quotas on the 1st of each month.
  • Master Data Pull API: GET /api/v1/pos/master_data provides edge nodes with the latest item catalogue and customer list before going offline.

🏗️ Technical Architecture & DocType Mapping

Section titled “🏗️ Technical Architecture & DocType Mapping”

In Business M and Framework M, this domain is powered by the m_pos library, which integrates with m_invoice for SalesInvoice sync and with framework_m_standard for the shared session and DB adapter.

PosProfile, PosBeneficiaryCard, SalesInvoice (via m_invoice)

graph LR
    Edge["Retail POS Edge Node\n(SQLite / IndexedDB)"] -- "GET master_data" --> Gateway["API Gateway"]
    Edge -- "POST sync_outbox (batch)" --> Gateway
    Gateway --> API["OfflineBillingAPIController\n(/api/v1/pos)"]
    API --> JS["NATS JetStream\nm_pos.process_jetstream_sync"]
    JS --> Worker["Sync Worker\nJetStreamSyncService (LWW)"]
    Worker --> DB[("PostgreSQL Cloud DB\n(SalesInvoice / GL)")]
    CronJob["Monthly Cron\nm_pos.reset_beneficiary_quotas"] --> DB

🧪 Verifiable Test Script (Human Review Flow)

Section titled “🧪 Verifiable Test Script (Human Review Flow)”

Follow these steps in the running Business M instance to replicate the live PoC demonstration:

  1. Create a POS Profile: Navigate to wms → PosProfile → New. Link it to your Company and a default Warehouse. Set sync_batch_size to 10.

  2. Issue a Beneficiary Card: Navigate to wms → PosBeneficiaryCard → New. Enter a unique card_number, link it to a Customer, and set monthly_quota to ₹1,000. Save. Confirm the card appears in the list view.

  3. Simulate Offline Billing: With the network conceptually disconnected, create two SalesInvoice payloads locally at the edge (e.g., ₹150 and ₹200 for the beneficiary card holder). Verify these are only stored locally.

  4. Restore Network & Push Outbox: Call POST /api/v1/pos/sync_outbox with the two payload objects. Verify the response returns {"status": "success", "queued": 2}. This queues both invoices into JetStream for asynchronous processing.

  5. Verify LWW Rejection: Re-send one of the same invoice payloads but with a modified timestamp older than the one already processed. Confirm the sync worker rejects it with rejected_stale in the logs, proving no stale overwrites occur.

  6. Trigger Quota Violation: Open the beneficiary card and set utilized_quota to a value exceeding monthly_quota (e.g., ₹1,500). Attempt to save. Confirm the system blocks the save with a validation error: “Utilized quota cannot exceed monthly quota.”

  7. Verify Monthly Reset: Inspect the m_pos.reset_beneficiary_quotas cron job configuration. Confirm it is scheduled for the 1st of each month and that it would set all active card utilized_quota values back to ₹0.00.


This PoC is automated end-to-end in flow-15-pos-edge-sync.spec.ts, covering all 7 steps above in a repeatable, isolated test run using unique POSFLOW-<runId> identifiers.