POS Deposit Flow — Bug Analysis & RCA Specification

Comprehensive defect investigation, root cause analysis, and resolution verification for the Sales Order deposit workflow in Flutter POS Client (v2-pos-client).

Repository: SellAble.ai Target App: apps/v2-pos-client Tickets: #880, #890, #837 PR References: PR 11198, PR 11171, PR 11035 Status: Resolved & Merged

1 Executive Summary

In POS client (POPC), customer pre-orders and special orders accept down-payments / deposits prior to final fulfillment. Several critical and UI issues were discovered in the deposit collection dialog and lifecycle handler:

ID Ticket Severity Component Bug Summary Status
BUG-01 #880 Critical list_deposit_dialog_controller.dart Full deposit prematurely marked order COMPLETED, locking Edit Order and preventing Sales Invoice (SI) generation. Fixed
BUG-02 #880 Medium list_deposit_dialog.dart Tender name wrapping ("MYR - Malaysian Ringgit") triggered RenderFlex overflow due to fixed row height. Fixed
BUG-03 #890 Low list_deposit_dialog.dart Deposit dialog qty + / - buttons used tertiary fill with primary icon, rendering icons invisible. Fixed
BUG-04 #837 High list_deposit_dialog_controller.dart Scanning a second gift card of the same denomination collided with the qty-increment guard, dropping the card. Fixed
BUG-05 PR 1dc7b56 Medium list_deposit_dialog_controller.dart Foreign-currency deposit conversions truncated instead of rounding half-up, causing RM 0.01 discrepancies. Fixed

2 Deep-Dive: BUG-01 — Premature Order Completion on Full Deposit (Ticket #880)

⚠️
Business Impact: Customer orders paid in full via deposit were frozen. Cashiers could not fulfill the order or generate a legal Sales Invoice (SI) upon merchandise handover because the order was locked.

A. Defect Reproduction Steps

  1. Create a new Special Order or Pre-Order with total RM 100.00.
  2. Navigate to Sales History > Pre/Special Order > Edit Order.
  3. Open Add Deposit dialog and pay RM 100.00 (the full remaining balance).
  4. Observed Bug: The order is immediately saved with status = COMPLETED and isPaid = 1.
  5. Failure Point: When trying to complete pickup/fulfillment, Finalize Order is unreachable because ableEditCheck in POS blocks modifying COMPLETED orders. Since only Finalize Order issues the Sales Invoice, the order is stranded without an SI!

B. Workflow Comparison: Buggy vs Fixed

❌ Before (Broken Lifecycle)

1. Order Balance reaches RM 0.00

2. saveSales sets status = COMPLETED

3. ableEditCheck evaluates to FALSE

4. Finalize Order is BLOCKED

Outcome: Order locked; NO Sales Invoice generated

➔

✅ After (Correct Lifecycle)

1. Order Balance reaches RM 0.00

2. saveSales keeps status = DRAFT

3. ableEditCheck remains TRUE

4. Cashier clicks Finalize Order upon pickup

Outcome: SI generated; Inventory deducted; Completed cleanly

C. Root Cause & Code Diff

In apps/v2-pos-client/client/lib/app/modules/components/dialogs/controllers/list_deposit_dialog_controller.dart:

// list_deposit_dialog_controller.dart ~ Line 1339
  double balance = (saleHeader!.totalAmtAfterDiscTaxScRd ?? 0.0);
  balance = balance - currentDpPaid;

- int isPaid = 1;
- String status = SaleConstant.COMPLETED;
- if (balance != 0) {
-   isPaid = 0;
-   status = SaleConstant.DRAFT;
- }
+ // The order stays DRAFT even when the deposits cover the full price. Only
+ // Finalize Order (completePayment) creates the Sales Invoice and marks the
+ // order COMPLETED; completing it here would lock it (ableEditCheck) with no
+ // invoice.
  saleHeader = saleHeader!.copyWith(
-   isPaid: dr.Value(isPaid),
-   status: dr.Value(status),
-   paidDate: dr.Value(isPaid == 1 ? now : null),
+   isPaid: const dr.Value(0),
+   status: const dr.Value(SaleConstant.DRAFT),
+   paidDate: const dr.Value(null),
    currentDepositPaid: dr.Value(currentDpPaid),
    totalBalAmt: dr.Value(balance),
    totalDepositPaid: dr.Value(currentDpPaid),
  );

3 Deep-Dive: UI & UX Defects in Deposit Dialog (Tickets #880 & #890)

A. RenderFlex Height Overflow on Long Tender Names (#880)

When cashiers selected payment methods with long labels such as MYR - Malaysian Ringgit or custom bank names, the tender name wrapped into two lines inside depositBodyTile. Because the container had a strict hardcoded height, Flutter threw a yellow-and-black stripe RenderFlex overflow exception.

// apps/v2-pos-client/client/lib/app/modules/components/dialogs/list_deposit_dialog.dart
- height: 64.0,
+ constraints: const BoxConstraints(minHeight: 64.0),

B. Button Fill & Icon Contrast Issue (#890)

In list_deposit_dialog.dart, the item quantity - and + buttons failed WCAG accessibility and brand design guidelines:

4 Deep-Dive: Gift Cards & Multi-Currency in Deposit Flow (#837 & PR 1dc7b56)

A. Gift Card Multi-Scan Collision Guard (#837)

Gift cards in POPC represent individual voucher tokens with unique serial codes and strict 1:1 denomination records. In the deposit dialog:

B. Foreign Currency Decimal Rounding

Multi-currency deposit calculations (such as IDR/SGD cash deposits against MYR sales orders) used truncating decimal extensions (twoDecimalWithoutRounding). An 851,956.52 IDR payment @ 0.00023 rate produced 195.9499996 MYR, which truncated to 195.94 — 1 cent short of a 195.95 deposit obligation. Fixed by replacing with half-up rounding (toPrecision(2)) applied at the cumulative boundary.

5 Verification & QA Test Matrix

Scenario Test Input Expected Behavior Result
1. Full Deposit on Special Order Order total RM 250.00, Add Deposit RM 250.00 Balance becomes RM 0.00. Status remains DRAFT. "Finalize Order" button is enabled. Clicking Finalize generates SI and marks COMPLETED. PASS
2. Partial Deposit on Pre-Order Order total RM 100.00, Add Deposit RM 30.00 Balance becomes RM 70.00. Status remains DRAFT. Deposit receipt prints. Order can be reopened later to pay remainder. PASS
3. Long Tender Name Render Tender name: "MYR - Malaysian Ringgit" Text wraps cleanly within the deposit history row without any yellow/black RenderFlex overflow lines. PASS
4. Multiple Gift Cards in Deposit Scan 2x RM 50 gift cards Both gift cards appear as 2 distinct lines with qty = 1 each; total deposit RM 100.00 applied cleanly. PASS
5. Deposit Dialog Button Contrast Inspect +/- qty buttons Buttons display prominent brand primary orange fill with crisp white icons. PASS

6 Architecture Notes & Follow-up Recommendations

💡
Open Architectural Question: In Back-Office, orders that had full deposits prior to Finalize Order showed isPaid = 0 in the list view until final invoice generation. If a business requirement requires displaying "Fully Deposited" distinctly in BO prior to pickup, a dedicated status chip or sub-status (e.g. DEPOSIT_PAID) should be considered in a future schema upgrade.

Legacy Stranded Orders: Pre-orders created before PR 11198 that were already saved as COMPLETED without a Sales Invoice must be migrated or converted via a targeted DB script or customer service adjustment.