## 1. Database

- [x] 1.1 Migration: create `product_units` table (`id`, `product_id` FK → `products`, `unit_id` FK → `units`, `conversion_qty` decimal, `is_default` boolean default false, timestamps)
- [x] 1.2 Migration: add nullable `unit_id` (FK → `units`) and nullable `conversion_qty` (decimal) columns to `order_product`

## 2. Models

- [x] 2.1 Create `ProductUnit` model (`belongsTo Product`, `belongsTo Unit`)
- [x] 2.2 Add `units()` hasMany relation to `Product` (→ `ProductUnit`)
- [x] 2.3 Extend `Product::getPriceByCustomerType($type, $unitId = null)`: resolve base price as today, then if `$unitId` is set and matches one of this product's `product_units`, divide by that row's `conversion_qty`; ignore/fall back to base price if `$unitId` doesn't belong to the product
- [x] 2.4 Add `unit_id`/`conversion_qty` to the `withPivot([...])` list on `Order::products()`

## 3. Product create/edit form

- [x] 3.1 `ProductController::create`/`edit`: pass full `units` list (id + name) needed for the conversion-unit dropdown, and (on edit) the product's existing `product_units` rows
- [x] 3.2 `products-add.blade.php`: add "Giá bán theo đơn vị quy đổi" box with an initial empty state and an "Add" button
- [x] 3.3 `products-edit.blade.php`: same box, pre-populated with the product's existing conversion-unit rows
- [x] 3.4 JS (inline or shared partial): add/remove row, each row = unit `<select>` + conversion-qty `<input>` + "default" checkbox (mutually exclusive across rows) + read-only computed price display; recompute displayed price live from the base price field ÷ conversion qty on input
- [x] 3.5 `ProductController::store`: validate and persist submitted conversion-unit rows into `product_units` after creating the product (skip rows with empty unit or conversion qty ≤ 0); enforce at most one `is_default = true`
- [x] 3.6 `ProductController::update`: replace the product's `product_units` rows with the submitted set (delete-then-recreate or diff/sync), same validation and default-enforcement as store

## 4. POS scan & pricing endpoints

- [x] 4.1 `PosController::scan`: include a `units` array per returned product (base unit as an implicit `unit_id: null` entry, plus each `product_units` row with `unit_id`, `label`, `conversion_qty`, `is_default`)
- [x] 4.2 `ProductController::getPriceByCustomerType` (route `/products/get-price`): accept optional `unit_id` query param and pass through to `Product::getPriceByCustomerType`

## 5. POS cart UI

- [x] 5.1 `#scannerItem` jsrender template (`resources/views/vendor/admin/index.blade.php`): add a unit `<select>` per row, options from `order.units`, preselected to the default unit; add hidden input `items[[code]][unit_id]`
- [x] 5.2 `PosController@index` inline cart JS: when rendering/updating a line, pass the resolved default `unit_id` into `getPriceByCustomerType`/`addItem`/`updateItem` so initial price matches the default unit
- [x] 5.3 Add a `change` handler on the cart line's unit `<select>` that re-fetches price via `/products/get-price` with the new `unit_id`, updates that line's stored `unit_id`/price/total, and re-renders the row and running totals

## 6. Order persistence

- [x] 6.1 `OrderController::store`: read `items[code][unit_id]` if present, validate it belongs to the resolved product's `product_units` (else treat as base unit), price the line via `getPriceByCustomerType($customerType, $unitId)`, and include `unit_id`/`conversion_qty` (snapshotted from the matched `product_units` row, or null for base unit) in `$order_attach`
- [x] 6.2 `OrderController::update`: mirror the same `unit_id` handling as store
- [x] 6.3 Verify order display/print views (`pages.pos-print` and any order listing) show the sold unit where a product name/qty is already shown, without breaking existing base-unit orders

## 7. Verification

- [x] 7.1 Manual test: create a product with base price 100000, add "Vỉ" (qty 5) and "Viên" (qty 50) conversion units, confirm computed prices show 20000 / 2000
- [x] 7.2 Manual test: edit the base `sale_price` afterward and confirm both conversion units' displayed prices update without touching their rows
- [x] 7.3 Manual test: configure a wholesale price for a customer type and confirm a customer of that type sees the correctly-divided per-unit wholesale price in POS
- [x] 7.4 Manual test: add a product with a marked default unit to the POS cart, confirm that unit is preselected; switch units on the line and confirm price/subtotal update
- [x] 7.5 Manual test: submit an order with a non-base unit selected, confirm the created order's stored price/unit/conversion_qty match server-side computation, not any client-displayed value
- [x] 7.6 Manual test: place an order at the base unit for a product with no conversion units configured, confirm behavior is unchanged from before this feature

## 8. Hoá đơn: hiển thị điểm thưởng khách hàng

- [x] 8.1 `pos-print.blade.php`: thêm dòng "Điểm thưởng hiện tại" vào header hoá đơn, ngay dưới dòng SĐT, chỉ hiển thị khi `$order->customer` tồn tại
- [x] 8.2 Giá trị hiển thị = `$order->customer->points`, định dạng bằng `number_format(..., 1)` (nhất quán với `customer-statis.blade.php`), đọc tại thời điểm render — không snapshot từ lúc tạo đơn
- [x] 8.3 Manual test: in hoá đơn cho đơn có khách hàng gắn kèm → dòng điểm thưởng hiện đúng số dư hiện tại của khách
- [x] 8.4 Manual test: in hoá đơn cho đơn không có khách hàng → không có dòng điểm thưởng nào xuất hiện
