Comprehensive defect investigation, root cause analysis, and resolution verification for the Sales Order deposit workflow in Flutter POS Client (v2-pos-client).
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 |
status = COMPLETED and isPaid = 1.ableEditCheck in POS blocks modifying COMPLETED orders. Since only Finalize Order issues the Sales Invoice, the order is stranded without an SI!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
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
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),
);
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),
In list_deposit_dialog.dart, the item quantity - and + buttons failed WCAG accessibility and brand design guidelines:
tertiary with primary icon color, creating low contrast where the icon was nearly unreadable on POS touch screens.theme.colorScheme.primary) and white icon (theme.colorScheme.onPrimary).Gift cards in POPC represent individual voucher tokens with unique serial codes and strict 1:1 denomination records. In the deposit dialog:
itemId), POPC previously attempted to call incrementProductQty() on the existing cart line.list_deposit_dialog_controller.dart, gift cards explicitly bypass quantity incrementing and spawn an independent cart line with a freshly generated offlineSalesDetailId.
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.
| 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 |
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.