## 1. Test infrastructure prerequisite

- [x] 1.1 Add a testing DB connection to `phpunit.xml` (`DB_CONNECTION=sqlite`, `DB_DATABASE=:memory:`, or a dedicated test database) — none is configured today, and Feature tests below need migrations to run
- [x] 1.2 Confirm `php artisan migrate` runs clean against that test connection, and a trivial placeholder test passes, before writing real assertions
- [x] 1.3 (found during verification) `tests/TestCase.php`'s hand-rolled `createSchema()` was missing `$table->softDeletes()` on the `customers` table, even though the `Customer` model uses `SoftDeletes` — any query through `Customer::code()` (used by `OrderController`) failed with `no such column: customers.deleted_at` against sqlite. Fixed by adding it, matching the `products` table's schema block in the same file.

## 2. Backend: line-indexed order submission (`OrderController`)

- [x] 2.1 `store()` (`OrderController.php:91-113`): change `foreach($valided['items'] as $code => $item)` to iterate items as a plain indexed list, reading `code`/`unit_id`/`qty` from each entry instead of using the array key as the product code
- [x] 2.2 `store()`: change `$order_attach[$prod->id] = [...]` to append `[$prod->id, $pivotData]` per line and call `$order->products()->attach($id, $pivotData)` once per line, instead of building one associative array keyed by product id
- [x] 2.3 `update()` (`OrderController.php:253-279`): apply the identical fix (2.1 + 2.2) — same code-keyed / product-id-keyed bug in the same shape
- [x] 2.4 `create_now_mode` rebuild block (`OrderController.php:214-221`): change `$products[$item->code] = [...]` to append each of the order's existing pivot rows as its own list entry, so an order already holding multiple lines for one product isn't collapsed before it even reaches the loop in 2.1
- [x] 2.5 Update the `items` validation rule shape to match the new per-line structure (each entry validates `code`, `qty`, optional `unit_id`)

## 3. Backend tests (PHPUnit Feature — new, this controller had none before)

- [x] 3.1 Feature test: submitting two items for the same product `code` with two different `unit_id`s persists as two separate `order_product` rows (not one overwritten by the other)
- [x] 3.2 Feature test: each of those two rows has its own correct `qty`, `unit_id`, `conversion_qty`, and `price`; the order's `subtotal`/`total` reflects the sum of both
- [x] 3.3 Feature test: `update()` on an existing order with two same-product, different-unit lines preserves both after resubmission (not collapsed to one)
- [x] 3.4 Feature test: the `create_now_mode` draft-to-done path preserves an existing order's multiple same-product lines
- [x] 3.5 Regression test: a submitted `unit_id` that does not belong to the submitted product falls back to base-unit pricing (existing spec behavior — guard against breaking it while reshaping the loop)
- [x] 3.6 Regression test: a normal single-line-per-product order (today's common case) still creates exactly one `order_product` row, correctly priced — proves the reshaped loop didn't change behavior for the unaffected common case
- [x] 3.7 (found during verification) The real POS form always submits `notes` and `discount_amount` (always-present fields, even empty/0), but `OrderController::store`/`update` read them as `$valided['notes']` / `$valided['discount_amount']` without a null-coalesce — omitting them (as the tests initially did) throws an undefined-index error. Fixed the tests' request payloads to include both, matching what the real form always sends, rather than loosening the controller for a request shape that can't happen via the actual UI.
- [x] 3.8 (found during verification) Row-matching assertions compared `$row->unit_id === $unitId` (strict); the sqlite test connection returns column values as strings, so this was always false. Fixed to a loose/cast comparison.
- [x] 3.9 All 5 Feature tests pass (`./vendor/bin/phpunit --testsuite=Feature`), 20 assertions.

## 4. Frontend: cart-line identity keyed by `(code, unit_id)`

- [x] 4.1 `#scannerItem` template (`resources/views/vendor/admin/index.blade.php:84-122`): change the row's DOM `id` from `[[:code]]` to a composite key `code + '__' + (unit_id || 'base')` — **use `__`, not `::`**: an id containing a literal `::` breaks every `$('#' + lineKey)` jQuery selector lookup (`::` is CSS pseudo-element syntax), found and fixed during this change's own verification (see 1.3-adjacent note in design.md Decision 1)
- [x] 4.2 Same template: change form field names from `items[<code>][...]` to `items[<lineKey>][...]`, keeping `code` and `unit_id` as explicit submitted values on each line (the hidden `code` input and `.unit-id-input` already carry these — only the outer key changes)
- [x] 4.3 Update every `duplicate()` call site in `PosController.php` to match on `(code, unit_id)` instead of `code` alone: `addItem`, `updateItem`, `deleteItem`, the exact-scan check, the qty `+`/`-` handler, the customer-type-change re-render loop
- [x] 4.4 `fillOrderDraft()`/`addProductRow()`: verify reloading a draft order with two existing lines for the same product at different units restores both as separate cart lines, not one merged line

## 5. Frontend: per-unit autocomplete entries

- [x] 5.1 `addProductRow`'s non-barcode branch: render one `<li>` per configured unit for a matched product (base unit + every `ProductUnit`), each showing that unit's own price/label, instead of one entry priced at only the default unit
- [x] 5.2 Each unit's `<li>` carries a shallow-cloned product payload with `selected_unit_id` pre-set to that specific unit (bypassing `getDefaultUnit()`) so clicking it adds/increments the correct `(code, unit_id)` line
- [x] 5.3 Confirm the exact-barcode-match path (`is_barcode: true`) is untouched — still adds immediately at the product's single default unit

## 6. Frontend: unit-select dropdown — kept, relabel-only, merge on collision

- [x] 6.1 Keep `<select class="form-control unit-select">` in the `#scannerItem` template (populated from `units`, `data-linekey` instead of the old `data-code`), and the hidden `.unit-id-input`
- [x] 6.2 `addItem`/`updateItem`: sync each unit's `is_default` flag to the line's actual `selected_unit_id` before rendering, so the dropdown always shows the correct option selected
- [x] 6.3 `.unit-select` change handler: on switch, recompute price/subtotal/label at the new unit; **do not** scale or convert the quantity number — leave it as-is
- [x] 6.4 Same handler: if a separate line already exists at `(code, newUnitId)`, merge by adding the switched line's quantity directly onto that existing line's quantity (no conversion — both are the same unit at that point), remove the switched-from line, and recompute the surviving line's price/subtotal
- [x] 6.5 If no colliding line exists, the switch just updates the one line's own unit/label/price in place

## 7. Empty state for "Đơn hàng đã mua" (unrelated to Part 1, carried over)

- [x] 7.1 `customer-orders.blade.php`: add an `@empty` branch to the order-list `@forelse` (was missing, and closed with `@endforeach` instead of `@endforelse`) rendering a single centered `colspan="7"` row: "Không có dữ liệu"

## 8. Manual QA checklist (POS cart JS — no automated test runner exists for it)

- [ ] 8.1 Add a product with 2 configured units via search (not barcode) at unit A, then again at unit B → cart shows 2 separate lines
- [ ] 8.2 Add the same product+unit a second time → existing line's quantity increments, no new line
- [ ] 8.3 Switch a line's unit dropdown to another configured unit → price/subtotal/label update, quantity number stays the same
- [ ] 8.4 With two existing lines for one product (different units), switch one line's dropdown to match the other's unit → the two lines merge into one with quantity = sum of both, no conversion applied
- [ ] 8.5 Scan a barcode (exact match) for a product with multiple units → adds at the default unit; switch its dropdown afterward to correct it if needed (this is the only correction path for a barcode-added line)
- [ ] 8.6 Save a draft with 2 lines for one product (different units), reload the draft in POS → both lines still present, not collapsed
- [ ] 8.7 Submit an order with 2 lines for one product (different units) → resulting order (`orders-detail.blade.php`, `pos-print.blade.php`) shows both lines correctly, with the right subtotal
- [ ] 8.8 Delete a line (`btn_delete`) → only that line is removed, others unaffected, DOM lookups (id containing `__`) work correctly
