diff --git a/docs/integration-weight-based-products.md b/docs/integration-weight-based-products.md new file mode 100644 index 0000000..1d9d658 --- /dev/null +++ b/docs/integration-weight-based-products.md @@ -0,0 +1,279 @@ +# Integrasi Produk Timbangan — POS Mobile & Backoffice + +**Migrasi:** `000089` · **Base URL:** `/api/v1` · **Kompatibilitas:** mundur penuh + +Panduan untuk menjual produk per timbangan (ikan, daging, buah) dari sisi klien. +Alasan di balik setiap keputusan desain ada di [`rfc-weight-based-products.md`](./rfc-weight-based-products.md). + +--- + +## 1. Konsep inti + +**Satu penimbangan = satu baris order.** + +Pelanggan memesan Ikan Tude 4,2 ons, lalu memesan Ikan Tude lagi 5,6 ons. Itu **dua +baris terpisah**, karena keduanya dua ikan berbeda yang ditimbang sendiri-sendiri. + +| Bukan begini | Melainkan begini | +|---|---| +| `quantity: 9.8` | `quantity: 1, weight: 4.2`
`quantity: 1, weight: 5.6` | +| Dua ikan hilang jejaknya, dan `quantity` bertipe integer sehingga pecahan ditolak | Tiap penimbangan berdiri sendiri, bisa di-void atau dibayar terpisah | + +Empat aturan yang berlaku di seluruh dokumen ini: + +1. `quantity` untuk produk timbangan **selalu 1**. Backend memaksanya, dan database + menolak nilai lain lewat constraint `chk_order_items_weight_single_line`. +2. `weight` menyimpan angka timbangan, dalam satuan produk itu sendiri (ons, kg, gram — + apa pun yang dipilih saat setup). +3. Harga baris dihitung `weight × unit_price`, bukan `quantity × unit_price`. + `unit_price` tetap berarti harga per satu satuan (per ons). +4. **Jangan pernah menggabungkan dua baris** produk timbangan menjadi satu, meski + produknya sama. + +Untuk produk biasa tidak ada yang berubah: `weight` tidak dikirim, `quantity` tetap +cacah seperti sekarang. + +--- + +## 2. Backoffice — setup produk + +### 2.1 Pastikan satuannya ada + +Satuan disimpan per organisasi. Buat sekali, pakai ulang untuk semua produk timbangan. + +`POST /api/v1/units` + +```json +{ + "name": "Ons", + "abbreviation": "ons", + "is_active": true +} +``` + +`abbreviation` yang dipakai POS untuk mencetak `4,2 ons` di struk — isi dengan bentuk +pendek yang benar-benar ingin ditampilkan. Daftar satuan dibaca lewat `GET /api/v1/units`. + +### 2.2 Buat produk sebagai produk timbangan + +`POST /api/v1/products` + +```json +{ + "category_id": "…", + "name": "Ikan Tude", + "price": 4500, + "cost": 3000, + "sell_by": "weight", + "unit_id": "" +} +``` + +| Field | Tipe | Keterangan | +|---|---|---| +| `sell_by` | `"unit"` \| `"weight"` | Opsional, default `"unit"`. Nilai lain **ditolak** dengan pesan jelas. | +| `unit_id` | UUID | **Wajib** saat `sell_by: "weight"`, ditolak backend bila kosong. Opsional untuk produk satuan. | +| `price` | number | Harga per satu satuan. Rp 4.500 per ons, bukan harga per ikan. | + +Pada `PUT /api/v1/products/:id`, `unit_id` **tidak perlu dikirim ulang** bila produknya +sudah punya satuan — mengubah `sell_by` menjadi `"weight"` saja sudah cukup. Yang ditolak +adalah kondisi akhirnya: produk yang dijual per timbangan tanpa satuan. + +Keduanya juga bisa diubah lewat `PUT /api/v1/products/:id` dengan bentuk yang sama, dan +ikut terbaca di setiap response produk (`GET /api/v1/products`, `/products/all`, +`/products/:id`). + +### 2.3 Catatan UI + +- Kunci `sell_by` **setelah produk punya transaksi**. Mengubah produk lama dari `unit` + ke `weight` tidak mengubah baris order yang sudah ada — baris lama tetap dihitung per + cacah — tapi akan membingungkan pengguna yang melihat riwayatnya. +- Saat `weight` dipilih, jadikan pemilih satuan sebagai field **wajib** di form. Backend + juga menolaknya, tapi ditangkap di form lebih baik daripada baru gagal saat simpan. +- Ubah label harga mengikuti satuan yang dipilih: *"Harga per ons"*. +- Untuk produk satuan, pemilih satuan boleh disembunyikan — `unit_id` opsional dan belum + dikonsumsi apa pun di POS. + +--- + +## 3. POS Mobile — transaksi + +### 3.1 Bentuk input mengikuti `sell_by` + +| `sell_by` | Input di POS | Yang dikirim | +|---|---|---| +| `"unit"` | Stepper − / + seperti sekarang | `quantity: n`, tanpa `weight` | +| `"weight"` | Papan angka desimal, satuan dari `unit` produk | `quantity: 1` + `weight: 4.2` | + +### 3.2 Mengirim order + +`POST /api/v1/orders` + +```json +{ + "outlet_id": "…", + "user_id": "…", + "order_type": "dine_in", + "order_items": [ + { "product_id": "", "quantity": 1, "weight": 4.2 }, + { "product_id": "", "quantity": 1, "weight": 5.6 }, + { "product_id": "", "quantity": 2 } + ] +} +``` + +Bentuk yang sama berlaku untuk `POST /api/v1/orders/:id/add-items` dan untuk pemesanan +mandiri `POST /api/v1/self-order/orders`. + +**Presisi.** Berat dibulatkan backend ke 3 desimal. Kirim `4.2` atau `4.237`; angka di +bawah `0.001` membulat ke nol dan ditolak. + +**Harga.** Field `unit_price` pada request **diabaikan** — harga selalu diambil backend +dari master produk. Jangan mengirim harga hasil hitungan sendiri. + +--- + +## 4. Menampilkan baris + +Setiap `order_items[]` di response membawa empat field tambahan: + +```json +{ + "product_name": "Ikan Tude", + "quantity": 1, + "weight": 4.2, + "unit_id": "…", + "unit_name": "Ons", + "unit_abbreviation": "ons", + "unit_price": 4500, + "total_price": 18900 +} +``` + +Semuanya `null` atau absen untuk produk biasa, jadi cabangkan tampilan pada `weight`: + +| Kondisi | Tampilkan | +|---|---| +| `weight == null` | `2 × Rp 25.000` | +| `weight != null` | `4,2 ons × Rp 4.500` | + +**Jangan menampilkan `quantity` untuk baris berbobot** — nilainya selalu 1 dan akan +terbaca seperti "satu ons". Gunakan `weight` dengan `unit_abbreviation`, dan pakai koma +desimal sesuai format Indonesia. + +--- + +## 5. Void, refund, split bill + +Baris berbobot bersifat **utuh**: dibatalkan seluruhnya atau tidak sama sekali. Karena +`quantity`-nya 1, semua endpoint cukup dikirimi `1`, dan backend menghitung nilai +rupiahnya dari `weight`. + +| Aksi | Endpoint | Field untuk baris berbobot | +|---|---|---| +| Void per item | `POST /orders/void` | `items[].quantity: 1` | +| Refund per item | `POST /orders/:id/refund` | `order_items[].refund_quantity: 1` (atau kosongkan) | +| Split bill per item | `POST /orders/split-bill` | `items[].quantity: 1` = bayar baris itu penuh | + +```json +{ + "order_id": "…", + "reason": "Salah timbang", + "type": "ITEM", + "items": [ + { "order_item_id": "", "quantity": 1 } + ] +} +``` + +Untuk split bill, baris berbobot hanya bisa berstatus belum dibayar atau lunas — tidak +ada nilai di antaranya. Sembunyikan stepper jumlah pada baris berbobot, ganti dengan +tombol pilih baris. + +**Batasan yang disengaja.** Mengembalikan *sebagian berat* — 1 ons dari baris 4,2 ons — +tidak didukung. Koreksi salah timbang ditangani dengan void baris itu lalu input ulang, +sehingga jejak auditnya tetap jujur. + +--- + +## 6. Referensi error + +Semua error mengikuti amplop standar. Pesan validasi baru muncul dengan kode `900`: + +```json +{ + "success": false, + "data": null, + "errors": [ + { "code": "900", "entity": "ORDER", + "cause": "product Ikan Tude is sold by weight and requires a weight" } + ] +} +``` + +| Pesan (`cause`) | Penyebab | Perbaikan di klien | +|---|---|---| +| `… is sold by weight and requires a weight` | Produk `sell_by: "weight"` dikirim tanpa `weight` | Wajibkan input timbangan sebelum item masuk keranjang | +| `… is not sold by weight and must not carry a weight` | `weight` dikirim untuk produk satuan | Kirim `weight` hanya bila `sell_by == "weight"` | +| `weight for … must be greater than 0` | Berat nol, negatif, atau membulat ke nol | Validasi minimal `0.001` di keypad | +| `quantity for … must be at least 1` | Produk satuan dengan `quantity` ≤ 0 | Perilaku lama, tidak berubah | + +Pesan menyebut **nama produk**, sehingga bisa ditampilkan apa adanya ke kasir. + +### Setup produk (Backoffice) + +| Pesan (`cause`) | Penyebab | Perbaikan di klien | +|---|---|---| +| `unit_id is required when sell_by is 'weight'` | Produk timbangan dibuat tanpa satuan | Wajibkan pemilih satuan saat Timbangan dipilih | +| `sell_by must be either 'unit' or 'weight'` | Nilai `sell_by` di luar dua itu | Kirim persis `"unit"` atau `"weight"` | +| `product '…' is sold by weight and requires a unit_id` | Update membuat produk jadi timbangan tanpa satuan | Kirim `unit_id` bersama perubahan `sell_by` | + +--- + +## 7. Kompatibilitas mundur + +- Semua produk lama otomatis `sell_by: "unit"`. Perilakunya identik dengan sebelumnya. +- `weight` opsional di request. Klien yang tidak mengenalnya tetap berfungsi penuh untuk + produk satuan. +- Field baru di response semuanya `omitempty` — tidak muncul untuk baris biasa, jadi + parser lama tidak terganggu. +- `quantity` tetap **integer** di seluruh API. Tidak ada field yang berubah tipe. + +Yang tidak berfungsi di klien lama hanyalah menjual produk timbangan itu sendiri — +permintaannya ditolak dengan pesan jelas, bukan gagal diam-diam. + +--- + +## 8. Batasan yang diketahui + +- **Pembulatan uang ke 2 desimal.** `4,237 ons × Rp 4.500` tersimpan `Rp 19.066,50`, + bukan dibulatkan ke rupiah utuh. Bila kasir harus menerima rupiah penuh, ini perlu + diputuskan dan diubah di backend lebih dulu (`RoundMoney`, satu tempat). +- **Stok belum otomatis berkurang** saat penjualan — untuk produk timbangan maupun + produk biasa. Pengurangan stok belum tersambung di backend, jadi jangan menampilkan + sisa stok yang mengandaikan itu berjalan. + +--- + +## 9. Checklist per tim + +**Backoffice Website** + +- [ ] CRUD satuan tersedia di menu master data +- [ ] Form produk punya pilihan cara jual: Satuan / Timbangan +- [ ] Pemilih satuan menjadi wajib saat Timbangan dipilih +- [ ] Label harga ikut satuan — "Harga per ons" +- [ ] `sell_by` dikunci untuk produk yang sudah bertransaksi +- [ ] Daftar produk menandai mana yang dijual per timbangan +- [ ] Laporan harian menampilkan kolom Berat + +**POS Mobile** + +- [ ] Menu membaca `sell_by` tiap produk +- [ ] Papan angka desimal untuk produk timbangan +- [ ] Kirim `quantity: 1` + `weight` +- [ ] Dua penimbangan menjadi dua baris, tidak digabung +- [ ] Keranjang & struk menampilkan `4,2 ons × Rp 4.500` +- [ ] Void & refund baris berbobot bersifat utuh +- [ ] Split bill: pilih baris, bukan stepper jumlah +- [ ] Pesan error validasi ditampilkan ke kasir diff --git a/lib/application/auth/login_form/login_form_bloc.dart b/lib/application/auth/login_form/login_form_bloc.dart index 98f19f4..7d480b9 100644 --- a/lib/application/auth/login_form/login_form_bloc.dart +++ b/lib/application/auth/login_form/login_form_bloc.dart @@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:injectable/injectable.dart'; import '../../../common/service/device_info_service.dart'; +import '../../../common/service/unit_registry.dart'; import '../../../common/service/fcm_service.dart'; import '../../../domain/auth/auth.dart'; @@ -18,11 +19,13 @@ class LoginFormBloc extends Bloc { final IAuthRepository _authRepository; final FcmService _fcmService; final DeviceInfoService _deviceInfoService; + final UnitRegistry _unitRegistry; LoginFormBloc( this._authRepository, this._fcmService, this._deviceInfoService, + this._unitRegistry, ) : super(LoginFormState.initial()) { on(_onLoginFormEvent); } @@ -62,6 +65,12 @@ class LoginFormBloc extends Bloc { devicePayload: devicePayload, ); + // Master satuan diambil sekali per sesi supaya keranjang bisa + // menulis "4,2 ons" tanpa menunggu request saat kasir menimbang. + if (failureOrLogin.isRight()) { + await _unitRegistry.load(forceRefresh: true); + } + emit( state.copyWith( isSubmitting: false, diff --git a/lib/application/checkout/checkout_form/checkout_form_bloc.dart b/lib/application/checkout/checkout_form/checkout_form_bloc.dart index 533352b..20e5db5 100644 --- a/lib/application/checkout/checkout_form/checkout_form_bloc.dart +++ b/lib/application/checkout/checkout_form/checkout_form_bloc.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:injectable/injectable.dart' hide Order; +import 'package:uuid/uuid.dart'; import '../../../common/types/order_type.dart'; import '../../../domain/delivery/delivery.dart'; @@ -19,6 +20,33 @@ class CheckoutFormBloc extends Bloc { CheckoutFormBloc() : super(CheckoutFormState.initial()) { on(_onCheckoutFormEvent); } + + /// Baris berbobot dihitung `weight × harga`, baris cacah `quantity × harga`. + /// Satu tempat saja supaya total keranjang, subtotal, dan ringkasan tidak + /// pernah berbeda hitungan. + CheckoutFormState _recalculate( + CheckoutFormState state, + List items, + ) { + final totalQuantity = items.fold( + 0, + // Baris berbobot selalu satu penimbangan, jadi dihitung satu. + (sum, item) => sum + (item.isWeighted ? 1 : item.quantity), + ); + + final totalPrice = items.fold( + 0, + (sum, item) => sum + item.lineTotal, + ); + + return state.copyWith( + items: items, + totalQuantity: totalQuantity, + totalPrice: totalPrice.toInt(), + isLoading: false, + ); + } + Future _onCheckoutFormEvent( CheckoutFormEvent event, Emitter emit, @@ -26,12 +54,21 @@ class CheckoutFormBloc extends Bloc { return event.map( addItem: (e) async { final currentState = state; + + // Produk timbangan tidak punya stepper — masuk lewat addWeightedItem + // setelah kasir memasukkan angka timbangan. + if (e.product.isSoldByWeight) { + log('⚖️ ${e.product.name} dijual per timbangan, butuh berat'); + return; + } + emit(currentState.copyWith(isLoading: true)); List items = [...currentState.items]; final index = items.indexWhere( (element) => + !element.isWeighted && element.product.id == e.product.id && element.variant?.id == e.variant?.id, ); @@ -49,35 +86,47 @@ class CheckoutFormBloc extends Bloc { quantity: 1, variant: e.variant, notes: '', + lineId: const Uuid().v4(), ), ); } log('🛒 Items updated: ${items.length} items total ${items.toList()}'); - final totalQuantity = items.fold( - 0, - (sum, item) => sum + item.quantity, - ); + emit(_recalculate(currentState, items)); + }, + addWeightedItem: (e) async { + final currentState = state; + emit(currentState.copyWith(isLoading: true)); - final totalPrice = state.items.isEmpty - ? 0.0 - : state.items - .map( - (e) => - (e.product.price * e.quantity) + - (e.variant?.priceModifier ?? 0), - ) - .reduce((value, element) => value + element); - - emit( - currentState.copyWith( - items: items, - totalQuantity: totalQuantity, - totalPrice: totalPrice.toInt(), - isLoading: false, + // Sengaja tidak digabung dengan baris lain: dua penimbangan adalah dua + // ikan berbeda, masing-masing bisa di-void atau dibayar terpisah. + final items = [ + ...currentState.items, + ProductQuantity( + product: e.product, + variant: e.variant, + quantity: 1, + notes: '', + lineId: const Uuid().v4(), + weight: e.weight, ), - ); + ]; + + log('⚖️ Penimbangan baru: ${e.product.name} ${e.weight}'); + + emit(_recalculate(currentState, items)); + }, + updateItemWeight: (e) async { + final currentState = state; + final items = [...currentState.items]; + + final index = items.indexWhere((item) => item.lineId == e.lineId); + if (index == -1) return; + + items[index] = items[index].copyWith(weight: e.weight); + + emit(_recalculate(currentState, items)); }, removeItem: (e) async { final currentState = state; @@ -86,6 +135,7 @@ class CheckoutFormBloc extends Bloc { final index = items.indexWhere( (element) => + !element.isWeighted && element.product.id == e.product.id && element.variant?.id == e.variant?.id, ); @@ -103,32 +153,18 @@ class CheckoutFormBloc extends Bloc { } } - final totalQuantity = items.fold( - 0, - (sum, item) => sum + item.quantity, - ); - final totalPrice = items.fold( - 0, - (sum, item) => - sum + - (item.quantity * - (item.variant?.priceModifier.toInt() ?? - item.product.price.toInt())), - ); + log('🗑️ Item removed. Total items: ${items.length}'); - log( - '🗑️ Item removed. Total items: ${items.length}, totalQuantity: $totalQuantity, totalPrice: $totalPrice', - ); + emit(_recalculate(currentState, items)); + }, + removeLine: (e) async { + final currentState = state; + final items = [...currentState.items] + ..removeWhere((item) => item.lineId == e.lineId); - // Emit state baru - emit( - currentState.copyWith( - items: items, - totalQuantity: totalQuantity, - totalPrice: totalPrice, - isLoading: false, - ), - ); + log('🗑️ Baris dihapus. Sisa ${items.length} baris'); + + emit(_recalculate(currentState, items)); }, started: (e) async { emit(CheckoutFormState.initial().copyWith(isLoading: true)); @@ -153,14 +189,13 @@ class CheckoutFormBloc extends Bloc { ); } }, - updateItemNotes: (e) async { + updateLineNotes: (e) async { final currentState = state; - // Clone list items agar tidak mutasi langsung + // Catatan diikat ke baris, bukan ke produk: dua penimbangan produk yang + // sama bisa punya catatan berbeda. final items = [...currentState.items]; - final index = items.indexWhere( - (element) => element.product.id == e.product.id, - ); + final index = items.indexWhere((item) => item.lineId == e.lineId); if (index != -1) { items[index] = items[index].copyWith(notes: e.notes); @@ -181,8 +216,8 @@ class CheckoutFormBloc extends Bloc { emit(state.copyWith(orderAdded: e.order)); }, customerNameChanged: (e) async { - emit(state.copyWith(customerName: e.name,)); - } + emit(state.copyWith(customerName: e.name)); + }, ); } } diff --git a/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart b/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart index 6087f76..c5b39f8 100644 --- a/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart +++ b/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart @@ -21,9 +21,17 @@ mixin _$CheckoutFormEvent { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -34,8 +42,12 @@ mixin _$CheckoutFormEvent { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -46,8 +58,12 @@ mixin _$CheckoutFormEvent { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -59,8 +75,11 @@ mixin _$CheckoutFormEvent { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -71,8 +90,11 @@ mixin _$CheckoutFormEvent { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -83,8 +105,11 @@ mixin _$CheckoutFormEvent { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -194,9 +219,17 @@ class _$StartedImpl implements _Started { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -211,8 +244,12 @@ class _$StartedImpl implements _Started { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -227,8 +264,12 @@ class _$StartedImpl implements _Started { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -247,8 +288,11 @@ class _$StartedImpl implements _Started { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -263,8 +307,11 @@ class _$StartedImpl implements _Started { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -279,8 +326,11 @@ class _$StartedImpl implements _Started { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -413,9 +463,17 @@ class _$AddItemImpl implements _AddItem { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -430,8 +488,12 @@ class _$AddItemImpl implements _AddItem { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -446,8 +508,12 @@ class _$AddItemImpl implements _AddItem { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -466,8 +532,11 @@ class _$AddItemImpl implements _AddItem { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -482,8 +551,11 @@ class _$AddItemImpl implements _AddItem { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -498,8 +570,11 @@ class _$AddItemImpl implements _AddItem { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -528,6 +603,492 @@ abstract class _AddItem implements CheckoutFormEvent { throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$AddWeightedItemImplCopyWith<$Res> { + factory _$$AddWeightedItemImplCopyWith( + _$AddWeightedItemImpl value, + $Res Function(_$AddWeightedItemImpl) then, + ) = __$$AddWeightedItemImplCopyWithImpl<$Res>; + @useResult + $Res call({Product product, ProductVariant? variant, double weight}); + + $ProductCopyWith<$Res> get product; + $ProductVariantCopyWith<$Res>? get variant; +} + +/// @nodoc +class __$$AddWeightedItemImplCopyWithImpl<$Res> + extends _$CheckoutFormEventCopyWithImpl<$Res, _$AddWeightedItemImpl> + implements _$$AddWeightedItemImplCopyWith<$Res> { + __$$AddWeightedItemImplCopyWithImpl( + _$AddWeightedItemImpl _value, + $Res Function(_$AddWeightedItemImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? product = null, + Object? variant = freezed, + Object? weight = null, + }) { + return _then( + _$AddWeightedItemImpl( + null == product + ? _value.product + : product // ignore: cast_nullable_to_non_nullable + as Product, + freezed == variant + ? _value.variant + : variant // ignore: cast_nullable_to_non_nullable + as ProductVariant?, + null == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double, + ), + ); + } + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProductCopyWith<$Res> get product { + return $ProductCopyWith<$Res>(_value.product, (value) { + return _then(_value.copyWith(product: value)); + }); + } + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProductVariantCopyWith<$Res>? get variant { + if (_value.variant == null) { + return null; + } + + return $ProductVariantCopyWith<$Res>(_value.variant!, (value) { + return _then(_value.copyWith(variant: value)); + }); + } +} + +/// @nodoc + +class _$AddWeightedItemImpl implements _AddWeightedItem { + const _$AddWeightedItemImpl(this.product, this.variant, this.weight); + + @override + final Product product; + @override + final ProductVariant? variant; + @override + final double weight; + + @override + String toString() { + return 'CheckoutFormEvent.addWeightedItem(product: $product, variant: $variant, weight: $weight)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AddWeightedItemImpl && + (identical(other.product, product) || other.product == product) && + (identical(other.variant, variant) || other.variant == variant) && + (identical(other.weight, weight) || other.weight == weight)); + } + + @override + int get hashCode => Object.hash(runtimeType, product, variant, weight); + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AddWeightedItemImplCopyWith<_$AddWeightedItemImpl> get copyWith => + __$$AddWeightedItemImplCopyWithImpl<_$AddWeightedItemImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(List items) started, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, + required TResult Function(OrderType orderType) updateOrderType, + required TResult Function(Delivery delivery) updateDelivery, + required TResult Function(Table? table) updateTable, + required TResult Function(Order? order) orderAddedItems, + required TResult Function(String name) customerNameChanged, + }) { + return addWeightedItem(product, variant, weight); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(List items)? started, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, + TResult? Function(OrderType orderType)? updateOrderType, + TResult? Function(Delivery delivery)? updateDelivery, + TResult? Function(Table? table)? updateTable, + TResult? Function(Order? order)? orderAddedItems, + TResult? Function(String name)? customerNameChanged, + }) { + return addWeightedItem?.call(product, variant, weight); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(List items)? started, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, + TResult Function(OrderType orderType)? updateOrderType, + TResult Function(Delivery delivery)? updateDelivery, + TResult Function(Table? table)? updateTable, + TResult Function(Order? order)? orderAddedItems, + TResult Function(String name)? customerNameChanged, + required TResult orElse(), + }) { + if (addWeightedItem != null) { + return addWeightedItem(product, variant, weight); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, + required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, + required TResult Function(_UpdateOrderType value) updateOrderType, + required TResult Function(_UpdateDelivery value) updateDelivery, + required TResult Function(_UpdateTable value) updateTable, + required TResult Function(_OrderAddedItems value) orderAddedItems, + required TResult Function(_CustomerNameChanged value) customerNameChanged, + }) { + return addWeightedItem(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, + TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, + TResult? Function(_UpdateOrderType value)? updateOrderType, + TResult? Function(_UpdateDelivery value)? updateDelivery, + TResult? Function(_UpdateTable value)? updateTable, + TResult? Function(_OrderAddedItems value)? orderAddedItems, + TResult? Function(_CustomerNameChanged value)? customerNameChanged, + }) { + return addWeightedItem?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, + TResult Function(_RemoveItem value)? removeItem, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, + TResult Function(_UpdateOrderType value)? updateOrderType, + TResult Function(_UpdateDelivery value)? updateDelivery, + TResult Function(_UpdateTable value)? updateTable, + TResult Function(_OrderAddedItems value)? orderAddedItems, + TResult Function(_CustomerNameChanged value)? customerNameChanged, + required TResult orElse(), + }) { + if (addWeightedItem != null) { + return addWeightedItem(this); + } + return orElse(); + } +} + +abstract class _AddWeightedItem implements CheckoutFormEvent { + const factory _AddWeightedItem( + final Product product, + final ProductVariant? variant, + final double weight, + ) = _$AddWeightedItemImpl; + + Product get product; + ProductVariant? get variant; + double get weight; + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AddWeightedItemImplCopyWith<_$AddWeightedItemImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UpdateItemWeightImplCopyWith<$Res> { + factory _$$UpdateItemWeightImplCopyWith( + _$UpdateItemWeightImpl value, + $Res Function(_$UpdateItemWeightImpl) then, + ) = __$$UpdateItemWeightImplCopyWithImpl<$Res>; + @useResult + $Res call({String lineId, double weight}); +} + +/// @nodoc +class __$$UpdateItemWeightImplCopyWithImpl<$Res> + extends _$CheckoutFormEventCopyWithImpl<$Res, _$UpdateItemWeightImpl> + implements _$$UpdateItemWeightImplCopyWith<$Res> { + __$$UpdateItemWeightImplCopyWithImpl( + _$UpdateItemWeightImpl _value, + $Res Function(_$UpdateItemWeightImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? lineId = null, Object? weight = null}) { + return _then( + _$UpdateItemWeightImpl( + null == lineId + ? _value.lineId + : lineId // ignore: cast_nullable_to_non_nullable + as String, + null == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double, + ), + ); + } +} + +/// @nodoc + +class _$UpdateItemWeightImpl implements _UpdateItemWeight { + const _$UpdateItemWeightImpl(this.lineId, this.weight); + + @override + final String lineId; + @override + final double weight; + + @override + String toString() { + return 'CheckoutFormEvent.updateItemWeight(lineId: $lineId, weight: $weight)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateItemWeightImpl && + (identical(other.lineId, lineId) || other.lineId == lineId) && + (identical(other.weight, weight) || other.weight == weight)); + } + + @override + int get hashCode => Object.hash(runtimeType, lineId, weight); + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UpdateItemWeightImplCopyWith<_$UpdateItemWeightImpl> get copyWith => + __$$UpdateItemWeightImplCopyWithImpl<_$UpdateItemWeightImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(List items) started, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, + required TResult Function(OrderType orderType) updateOrderType, + required TResult Function(Delivery delivery) updateDelivery, + required TResult Function(Table? table) updateTable, + required TResult Function(Order? order) orderAddedItems, + required TResult Function(String name) customerNameChanged, + }) { + return updateItemWeight(lineId, weight); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(List items)? started, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, + TResult? Function(OrderType orderType)? updateOrderType, + TResult? Function(Delivery delivery)? updateDelivery, + TResult? Function(Table? table)? updateTable, + TResult? Function(Order? order)? orderAddedItems, + TResult? Function(String name)? customerNameChanged, + }) { + return updateItemWeight?.call(lineId, weight); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(List items)? started, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, + TResult Function(OrderType orderType)? updateOrderType, + TResult Function(Delivery delivery)? updateDelivery, + TResult Function(Table? table)? updateTable, + TResult Function(Order? order)? orderAddedItems, + TResult Function(String name)? customerNameChanged, + required TResult orElse(), + }) { + if (updateItemWeight != null) { + return updateItemWeight(lineId, weight); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, + required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, + required TResult Function(_UpdateOrderType value) updateOrderType, + required TResult Function(_UpdateDelivery value) updateDelivery, + required TResult Function(_UpdateTable value) updateTable, + required TResult Function(_OrderAddedItems value) orderAddedItems, + required TResult Function(_CustomerNameChanged value) customerNameChanged, + }) { + return updateItemWeight(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, + TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, + TResult? Function(_UpdateOrderType value)? updateOrderType, + TResult? Function(_UpdateDelivery value)? updateDelivery, + TResult? Function(_UpdateTable value)? updateTable, + TResult? Function(_OrderAddedItems value)? orderAddedItems, + TResult? Function(_CustomerNameChanged value)? customerNameChanged, + }) { + return updateItemWeight?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, + TResult Function(_RemoveItem value)? removeItem, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, + TResult Function(_UpdateOrderType value)? updateOrderType, + TResult Function(_UpdateDelivery value)? updateDelivery, + TResult Function(_UpdateTable value)? updateTable, + TResult Function(_OrderAddedItems value)? orderAddedItems, + TResult Function(_CustomerNameChanged value)? customerNameChanged, + required TResult orElse(), + }) { + if (updateItemWeight != null) { + return updateItemWeight(this); + } + return orElse(); + } +} + +abstract class _UpdateItemWeight implements CheckoutFormEvent { + const factory _UpdateItemWeight(final String lineId, final double weight) = + _$UpdateItemWeightImpl; + + String get lineId; + double get weight; + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UpdateItemWeightImplCopyWith<_$UpdateItemWeightImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$RemoveItemImplCopyWith<$Res> { factory _$$RemoveItemImplCopyWith( @@ -634,9 +1195,17 @@ class _$RemoveItemImpl implements _RemoveItem { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -651,8 +1220,12 @@ class _$RemoveItemImpl implements _RemoveItem { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -667,8 +1240,12 @@ class _$RemoveItemImpl implements _RemoveItem { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -687,8 +1264,11 @@ class _$RemoveItemImpl implements _RemoveItem { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -703,8 +1283,11 @@ class _$RemoveItemImpl implements _RemoveItem { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -719,8 +1302,11 @@ class _$RemoveItemImpl implements _RemoveItem { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -752,109 +1338,95 @@ abstract class _RemoveItem implements CheckoutFormEvent { } /// @nodoc -abstract class _$$UpdateItemNotesImplCopyWith<$Res> { - factory _$$UpdateItemNotesImplCopyWith( - _$UpdateItemNotesImpl value, - $Res Function(_$UpdateItemNotesImpl) then, - ) = __$$UpdateItemNotesImplCopyWithImpl<$Res>; +abstract class _$$RemoveLineImplCopyWith<$Res> { + factory _$$RemoveLineImplCopyWith( + _$RemoveLineImpl value, + $Res Function(_$RemoveLineImpl) then, + ) = __$$RemoveLineImplCopyWithImpl<$Res>; @useResult - $Res call({Product product, String notes}); - - $ProductCopyWith<$Res> get product; + $Res call({String lineId}); } /// @nodoc -class __$$UpdateItemNotesImplCopyWithImpl<$Res> - extends _$CheckoutFormEventCopyWithImpl<$Res, _$UpdateItemNotesImpl> - implements _$$UpdateItemNotesImplCopyWith<$Res> { - __$$UpdateItemNotesImplCopyWithImpl( - _$UpdateItemNotesImpl _value, - $Res Function(_$UpdateItemNotesImpl) _then, +class __$$RemoveLineImplCopyWithImpl<$Res> + extends _$CheckoutFormEventCopyWithImpl<$Res, _$RemoveLineImpl> + implements _$$RemoveLineImplCopyWith<$Res> { + __$$RemoveLineImplCopyWithImpl( + _$RemoveLineImpl _value, + $Res Function(_$RemoveLineImpl) _then, ) : super(_value, _then); /// Create a copy of CheckoutFormEvent /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override - $Res call({Object? product = null, Object? notes = null}) { + $Res call({Object? lineId = null}) { return _then( - _$UpdateItemNotesImpl( - null == product - ? _value.product - : product // ignore: cast_nullable_to_non_nullable - as Product, - null == notes - ? _value.notes - : notes // ignore: cast_nullable_to_non_nullable + _$RemoveLineImpl( + null == lineId + ? _value.lineId + : lineId // ignore: cast_nullable_to_non_nullable as String, ), ); } - - /// Create a copy of CheckoutFormEvent - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $ProductCopyWith<$Res> get product { - return $ProductCopyWith<$Res>(_value.product, (value) { - return _then(_value.copyWith(product: value)); - }); - } } /// @nodoc -class _$UpdateItemNotesImpl implements _UpdateItemNotes { - const _$UpdateItemNotesImpl(this.product, this.notes); +class _$RemoveLineImpl implements _RemoveLine { + const _$RemoveLineImpl(this.lineId); @override - final Product product; - @override - final String notes; + final String lineId; @override String toString() { - return 'CheckoutFormEvent.updateItemNotes(product: $product, notes: $notes)'; + return 'CheckoutFormEvent.removeLine(lineId: $lineId)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$UpdateItemNotesImpl && - (identical(other.product, product) || other.product == product) && - (identical(other.notes, notes) || other.notes == notes)); + other is _$RemoveLineImpl && + (identical(other.lineId, lineId) || other.lineId == lineId)); } @override - int get hashCode => Object.hash(runtimeType, product, notes); + int get hashCode => Object.hash(runtimeType, lineId); /// Create a copy of CheckoutFormEvent /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$UpdateItemNotesImplCopyWith<_$UpdateItemNotesImpl> get copyWith => - __$$UpdateItemNotesImplCopyWithImpl<_$UpdateItemNotesImpl>( - this, - _$identity, - ); + _$$RemoveLineImplCopyWith<_$RemoveLineImpl> get copyWith => + __$$RemoveLineImplCopyWithImpl<_$RemoveLineImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, required TResult Function(Order? order) orderAddedItems, required TResult Function(String name) customerNameChanged, }) { - return updateItemNotes(product, notes); + return removeLine(lineId); } @override @@ -862,15 +1434,19 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, TResult? Function(Order? order)? orderAddedItems, TResult? Function(String name)? customerNameChanged, }) { - return updateItemNotes?.call(product, notes); + return removeLine?.call(lineId); } @override @@ -878,8 +1454,12 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -887,8 +1467,8 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult Function(String name)? customerNameChanged, required TResult orElse(), }) { - if (updateItemNotes != null) { - return updateItemNotes(product, notes); + if (removeLine != null) { + return removeLine(lineId); } return orElse(); } @@ -898,15 +1478,18 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, required TResult Function(_OrderAddedItems value) orderAddedItems, required TResult Function(_CustomerNameChanged value) customerNameChanged, }) { - return updateItemNotes(this); + return removeLine(this); } @override @@ -914,15 +1497,18 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, TResult? Function(_OrderAddedItems value)? orderAddedItems, TResult? Function(_CustomerNameChanged value)? customerNameChanged, }) { - return updateItemNotes?.call(this); + return removeLine?.call(this); } @override @@ -930,8 +1516,11 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -939,24 +1528,244 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult Function(_CustomerNameChanged value)? customerNameChanged, required TResult orElse(), }) { - if (updateItemNotes != null) { - return updateItemNotes(this); + if (removeLine != null) { + return removeLine(this); } return orElse(); } } -abstract class _UpdateItemNotes implements CheckoutFormEvent { - const factory _UpdateItemNotes(final Product product, final String notes) = - _$UpdateItemNotesImpl; +abstract class _RemoveLine implements CheckoutFormEvent { + const factory _RemoveLine(final String lineId) = _$RemoveLineImpl; - Product get product; + String get lineId; + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RemoveLineImplCopyWith<_$RemoveLineImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UpdateLineNotesImplCopyWith<$Res> { + factory _$$UpdateLineNotesImplCopyWith( + _$UpdateLineNotesImpl value, + $Res Function(_$UpdateLineNotesImpl) then, + ) = __$$UpdateLineNotesImplCopyWithImpl<$Res>; + @useResult + $Res call({String lineId, String notes}); +} + +/// @nodoc +class __$$UpdateLineNotesImplCopyWithImpl<$Res> + extends _$CheckoutFormEventCopyWithImpl<$Res, _$UpdateLineNotesImpl> + implements _$$UpdateLineNotesImplCopyWith<$Res> { + __$$UpdateLineNotesImplCopyWithImpl( + _$UpdateLineNotesImpl _value, + $Res Function(_$UpdateLineNotesImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? lineId = null, Object? notes = null}) { + return _then( + _$UpdateLineNotesImpl( + null == lineId + ? _value.lineId + : lineId // ignore: cast_nullable_to_non_nullable + as String, + null == notes + ? _value.notes + : notes // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$UpdateLineNotesImpl implements _UpdateLineNotes { + const _$UpdateLineNotesImpl(this.lineId, this.notes); + + @override + final String lineId; + @override + final String notes; + + @override + String toString() { + return 'CheckoutFormEvent.updateLineNotes(lineId: $lineId, notes: $notes)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateLineNotesImpl && + (identical(other.lineId, lineId) || other.lineId == lineId) && + (identical(other.notes, notes) || other.notes == notes)); + } + + @override + int get hashCode => Object.hash(runtimeType, lineId, notes); + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UpdateLineNotesImplCopyWith<_$UpdateLineNotesImpl> get copyWith => + __$$UpdateLineNotesImplCopyWithImpl<_$UpdateLineNotesImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(List items) started, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, + required TResult Function(OrderType orderType) updateOrderType, + required TResult Function(Delivery delivery) updateDelivery, + required TResult Function(Table? table) updateTable, + required TResult Function(Order? order) orderAddedItems, + required TResult Function(String name) customerNameChanged, + }) { + return updateLineNotes(lineId, notes); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(List items)? started, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, + TResult? Function(OrderType orderType)? updateOrderType, + TResult? Function(Delivery delivery)? updateDelivery, + TResult? Function(Table? table)? updateTable, + TResult? Function(Order? order)? orderAddedItems, + TResult? Function(String name)? customerNameChanged, + }) { + return updateLineNotes?.call(lineId, notes); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(List items)? started, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, + TResult Function(OrderType orderType)? updateOrderType, + TResult Function(Delivery delivery)? updateDelivery, + TResult Function(Table? table)? updateTable, + TResult Function(Order? order)? orderAddedItems, + TResult Function(String name)? customerNameChanged, + required TResult orElse(), + }) { + if (updateLineNotes != null) { + return updateLineNotes(lineId, notes); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, + required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, + required TResult Function(_UpdateOrderType value) updateOrderType, + required TResult Function(_UpdateDelivery value) updateDelivery, + required TResult Function(_UpdateTable value) updateTable, + required TResult Function(_OrderAddedItems value) orderAddedItems, + required TResult Function(_CustomerNameChanged value) customerNameChanged, + }) { + return updateLineNotes(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, + TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, + TResult? Function(_UpdateOrderType value)? updateOrderType, + TResult? Function(_UpdateDelivery value)? updateDelivery, + TResult? Function(_UpdateTable value)? updateTable, + TResult? Function(_OrderAddedItems value)? orderAddedItems, + TResult? Function(_CustomerNameChanged value)? customerNameChanged, + }) { + return updateLineNotes?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, + TResult Function(_RemoveItem value)? removeItem, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, + TResult Function(_UpdateOrderType value)? updateOrderType, + TResult Function(_UpdateDelivery value)? updateDelivery, + TResult Function(_UpdateTable value)? updateTable, + TResult Function(_OrderAddedItems value)? orderAddedItems, + TResult Function(_CustomerNameChanged value)? customerNameChanged, + required TResult orElse(), + }) { + if (updateLineNotes != null) { + return updateLineNotes(this); + } + return orElse(); + } +} + +abstract class _UpdateLineNotes implements CheckoutFormEvent { + const factory _UpdateLineNotes(final String lineId, final String notes) = + _$UpdateLineNotesImpl; + + String get lineId; String get notes; /// Create a copy of CheckoutFormEvent /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - _$$UpdateItemNotesImplCopyWith<_$UpdateItemNotesImpl> get copyWith => + _$$UpdateLineNotesImplCopyWith<_$UpdateLineNotesImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1036,9 +1845,17 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -1053,8 +1870,12 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -1069,8 +1890,12 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -1089,8 +1914,11 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -1105,8 +1933,11 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -1121,8 +1952,11 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -1226,9 +2060,17 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -1243,8 +2085,12 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -1259,8 +2105,12 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -1279,8 +2129,11 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -1295,8 +2148,11 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -1311,8 +2167,11 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -1427,9 +2286,17 @@ class _$UpdateTableImpl implements _UpdateTable { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -1444,8 +2311,12 @@ class _$UpdateTableImpl implements _UpdateTable { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -1460,8 +2331,12 @@ class _$UpdateTableImpl implements _UpdateTable { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -1480,8 +2355,11 @@ class _$UpdateTableImpl implements _UpdateTable { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -1496,8 +2374,11 @@ class _$UpdateTableImpl implements _UpdateTable { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -1512,8 +2393,11 @@ class _$UpdateTableImpl implements _UpdateTable { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -1631,9 +2515,17 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -1648,8 +2540,12 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -1664,8 +2560,12 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -1684,8 +2584,11 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -1700,8 +2603,11 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -1716,8 +2622,11 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, @@ -1819,9 +2728,17 @@ class _$CustomerNameChangedImpl implements _CustomerNameChanged { TResult when({ required TResult Function(List items) started, required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function( + Product product, + ProductVariant? variant, + double weight, + ) + addWeightedItem, + required TResult Function(String lineId, double weight) updateItemWeight, required TResult Function(Product product, ProductVariant? variant) removeItem, - required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function(String lineId) removeLine, + required TResult Function(String lineId, String notes) updateLineNotes, required TResult Function(OrderType orderType) updateOrderType, required TResult Function(Delivery delivery) updateDelivery, required TResult Function(Table? table) updateTable, @@ -1836,8 +2753,12 @@ class _$CustomerNameChangedImpl implements _CustomerNameChanged { TResult? whenOrNull({ TResult? Function(List items)? started, TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult? Function(String lineId, double weight)? updateItemWeight, TResult? Function(Product product, ProductVariant? variant)? removeItem, - TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(String lineId)? removeLine, + TResult? Function(String lineId, String notes)? updateLineNotes, TResult? Function(OrderType orderType)? updateOrderType, TResult? Function(Delivery delivery)? updateDelivery, TResult? Function(Table? table)? updateTable, @@ -1852,8 +2773,12 @@ class _$CustomerNameChangedImpl implements _CustomerNameChanged { TResult maybeWhen({ TResult Function(List items)? started, TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant, double weight)? + addWeightedItem, + TResult Function(String lineId, double weight)? updateItemWeight, TResult Function(Product product, ProductVariant? variant)? removeItem, - TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(String lineId)? removeLine, + TResult Function(String lineId, String notes)? updateLineNotes, TResult Function(OrderType orderType)? updateOrderType, TResult Function(Delivery delivery)? updateDelivery, TResult Function(Table? table)? updateTable, @@ -1872,8 +2797,11 @@ class _$CustomerNameChangedImpl implements _CustomerNameChanged { TResult map({ required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, + required TResult Function(_AddWeightedItem value) addWeightedItem, + required TResult Function(_UpdateItemWeight value) updateItemWeight, required TResult Function(_RemoveItem value) removeItem, - required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_RemoveLine value) removeLine, + required TResult Function(_UpdateLineNotes value) updateLineNotes, required TResult Function(_UpdateOrderType value) updateOrderType, required TResult Function(_UpdateDelivery value) updateDelivery, required TResult Function(_UpdateTable value) updateTable, @@ -1888,8 +2816,11 @@ class _$CustomerNameChangedImpl implements _CustomerNameChanged { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, + TResult? Function(_AddWeightedItem value)? addWeightedItem, + TResult? Function(_UpdateItemWeight value)? updateItemWeight, TResult? Function(_RemoveItem value)? removeItem, - TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_RemoveLine value)? removeLine, + TResult? Function(_UpdateLineNotes value)? updateLineNotes, TResult? Function(_UpdateOrderType value)? updateOrderType, TResult? Function(_UpdateDelivery value)? updateDelivery, TResult? Function(_UpdateTable value)? updateTable, @@ -1904,8 +2835,11 @@ class _$CustomerNameChangedImpl implements _CustomerNameChanged { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, + TResult Function(_AddWeightedItem value)? addWeightedItem, + TResult Function(_UpdateItemWeight value)? updateItemWeight, TResult Function(_RemoveItem value)? removeItem, - TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_RemoveLine value)? removeLine, + TResult Function(_UpdateLineNotes value)? updateLineNotes, TResult Function(_UpdateOrderType value)? updateOrderType, TResult Function(_UpdateDelivery value)? updateDelivery, TResult Function(_UpdateTable value)? updateTable, diff --git a/lib/application/checkout/checkout_form/checkout_form_event.dart b/lib/application/checkout/checkout_form/checkout_form_event.dart index 8290512..3cf44d8 100644 --- a/lib/application/checkout/checkout_form/checkout_form_event.dart +++ b/lib/application/checkout/checkout_form/checkout_form_event.dart @@ -10,15 +10,32 @@ class CheckoutFormEvent with _$CheckoutFormEvent { ProductVariant? variant, ) = _AddItem; + /// Satu penimbangan = satu baris baru. Dipanggil setiap kali kasir selesai + /// memasukkan angka timbangan, meski produknya sama dengan baris lain. + const factory CheckoutFormEvent.addWeightedItem( + Product product, + ProductVariant? variant, + double weight, + ) = _AddWeightedItem; + + /// Koreksi angka timbangan pada satu baris tertentu. + const factory CheckoutFormEvent.updateItemWeight( + String lineId, + double weight, + ) = _UpdateItemWeight; + const factory CheckoutFormEvent.removeItem( Product product, ProductVariant? variant, ) = _RemoveItem; - const factory CheckoutFormEvent.updateItemNotes( - Product product, + /// Hapus satu baris utuh — satu-satunya cara membatalkan baris berbobot. + const factory CheckoutFormEvent.removeLine(String lineId) = _RemoveLine; + + const factory CheckoutFormEvent.updateLineNotes( + String lineId, String notes, - ) = _UpdateItemNotes; + ) = _UpdateLineNotes; const factory CheckoutFormEvent.updateOrderType(OrderType orderType) = _UpdateOrderType; @@ -30,6 +47,6 @@ class CheckoutFormEvent with _$CheckoutFormEvent { const factory CheckoutFormEvent.orderAddedItems(Order? order) = _OrderAddedItems; - const factory CheckoutFormEvent.customerNameChanged(String name) = + const factory CheckoutFormEvent.customerNameChanged(String name) = _CustomerNameChanged; } diff --git a/lib/application/order/order_form/order_form_bloc.dart b/lib/application/order/order_form/order_form_bloc.dart index e287690..ff443f7 100644 --- a/lib/application/order/order_form/order_form_bloc.dart +++ b/lib/application/order/order_form/order_form_bloc.dart @@ -60,15 +60,7 @@ class OrderFormBloc extends Bloc { notes: '', customerName: state.customerName ?? "", orderItems: e.items - .map( - (item) => OrderItemRequest( - productId: item.product.id, - productVariantId: item.variant?.id ?? "", - quantity: item.quantity, - unitPrice: item.product.price.toInt(), - notes: item.notes, - ), - ) + .map(OrderItemRequest.fromProductQuantity) .toList(), ); @@ -100,15 +92,7 @@ class OrderFormBloc extends Bloc { notes: '', customerName: state.customerName ?? "", orderItems: e.items - .map( - (item) => OrderItemRequest( - productId: item.product.id, - productVariantId: item.variant?.id ?? "", - quantity: item.quantity, - unitPrice: item.product.price.toInt(), - notes: item.notes, - ), - ) + .map(OrderItemRequest.fromProductQuantity) .toList(), ); @@ -137,15 +121,7 @@ class OrderFormBloc extends Bloc { ); final request = e.items - .map( - (item) => AddItemOrderRequest( - productId: item.product.id, - productVariantId: item.variant?.id ?? '', - quantity: item.quantity, - unitPrice: item.product.price.toInt(), - notes: item.notes, - ), - ) + .map(AddItemOrderRequest.fromProductQuantity) .toList(); failureOrAddItemOrder = await _repository.addItemOrder( diff --git a/lib/application/split_bill/split_bill_form/split_bill_form_bloc.dart b/lib/application/split_bill/split_bill_form/split_bill_form_bloc.dart index ef768e7..7b253c3 100644 --- a/lib/application/split_bill/split_bill_form/split_bill_form_bloc.dart +++ b/lib/application/split_bill/split_bill_form/split_bill_form_bloc.dart @@ -67,7 +67,11 @@ class SplitBillFormBloc extends Bloc { final item = state.pendingItems.firstWhere( (item) => item.id == entry.key, ); - newTotal += item.unitPrice.toInt() * entry.value; + // Baris berbobot dibayar utuh: nilainya weight x harga, bukan + // quantity x harga. + newTotal += item.isWeighted + ? item.totalPrice + : item.unitPrice.toInt() * entry.value; } emit( diff --git a/lib/application/sync/sync_setting/sync_setting_bloc.dart b/lib/application/sync/sync_setting/sync_setting_bloc.dart index 89cbd47..3052fc1 100644 --- a/lib/application/sync/sync_setting/sync_setting_bloc.dart +++ b/lib/application/sync/sync_setting/sync_setting_bloc.dart @@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:injectable/injectable.dart'; +import '../../../common/service/unit_registry.dart'; import '../../../domain/category/category.dart'; import '../../../domain/product/product.dart'; @@ -14,9 +15,13 @@ part 'sync_setting_bloc.freezed.dart'; class SyncSettingBloc extends Bloc { final IProductRepository _productRepository; final ICategoryRepository _categoryRepository; + final UnitRegistry _unitRegistry; - SyncSettingBloc(this._productRepository, this._categoryRepository) - : super(SyncSettingState.initial()) { + SyncSettingBloc( + this._productRepository, + this._categoryRepository, + this._unitRegistry, + ) : super(SyncSettingState.initial()) { on(_onSyncSettingEvent); } @@ -97,6 +102,10 @@ class SyncSettingBloc extends Bloc { ), ); + // Satuan lebih dulu: produk timbangan butuh singkatannya untuk bisa + // ditulis "4,2 ons" begitu selesai disinkronkan. + await _unitRegistry.load(forceRefresh: true); + // Sync categories first final categoryResult = await _categoryRepository.syncAllCategories(); @@ -169,6 +178,8 @@ class SyncSettingBloc extends Bloc { Future _onSyncProducts(Emitter emit) async { emit(state.copyWith(isLoading: true, failureOptionSyncProduct: none())); + await _unitRegistry.load(forceRefresh: true); + final result = await _productRepository.syncAllProducts(); result.fold( diff --git a/lib/application/void/void_form/void_form_bloc.dart b/lib/application/void/void_form/void_form_bloc.dart index 3e5b415..abdae6b 100644 --- a/lib/application/void/void_form/void_form_bloc.dart +++ b/lib/application/void/void_form/void_form_bloc.dart @@ -55,7 +55,10 @@ class VoidFormBloc extends Bloc { (item) => item.id == entry.key, ); if (item == null) continue; - newTotal += item.unitPrice.toInt() * entry.value; + // Baris berbobot di-void utuh; rupiahnya sudah weight x harga. + newTotal += item.isWeighted + ? item.totalPrice + : item.unitPrice.toInt() * entry.value; } emit( @@ -92,7 +95,13 @@ class VoidFormBloc extends Bloc { productVariantName: originalItem.productVariantName, quantity: voidQty, unitPrice: originalItem.unitPrice, - totalPrice: (originalItem.unitPrice) * voidQty, + totalPrice: originalItem.isWeighted + ? originalItem.totalPrice + : (originalItem.unitPrice) * voidQty, + weight: originalItem.weight, + unitId: originalItem.unitId, + unitName: originalItem.unitName, + unitAbbreviation: originalItem.unitAbbreviation, modifiers: originalItem.modifiers, notes: originalItem.notes, status: originalItem.status, diff --git a/lib/common/constant/local_storage_key.dart b/lib/common/constant/local_storage_key.dart index 5be2a70..c645118 100644 --- a/lib/common/constant/local_storage_key.dart +++ b/lib/common/constant/local_storage_key.dart @@ -2,4 +2,5 @@ class LocalStorageKey { static const String token = 'token'; static const String user = 'user'; static const String outlet = 'outlet'; + static const String units = 'units'; } diff --git a/lib/common/database/database_helper.dart b/lib/common/database/database_helper.dart index 9d3d666..3082780 100644 --- a/lib/common/database/database_helper.dart +++ b/lib/common/database/database_helper.dart @@ -17,7 +17,7 @@ class DatabaseHelper { return await openDatabase( path, - version: 2, + version: 3, onCreate: _onCreate, onUpgrade: _onUpgrade, ); @@ -42,7 +42,11 @@ class DatabaseHelper { metadata TEXT, is_active INTEGER, created_at TEXT, - updated_at TEXT + updated_at TEXT, + sell_by TEXT DEFAULT 'unit', + unit_id TEXT, + unit_name TEXT, + unit_abbreviation TEXT ) '''); @@ -114,6 +118,17 @@ class DatabaseHelper { 'ALTER TABLE products ADD COLUMN print_to_checker INTEGER DEFAULT 0', ); } + + // Produk timbangan: cara jual dan satuan ikut disimpan lokal supaya POS + // tetap bisa menampilkan "4,2 ons" saat offline. + if (oldVersion < 3) { + await db.execute( + "ALTER TABLE products ADD COLUMN sell_by TEXT DEFAULT 'unit'", + ); + await db.execute('ALTER TABLE products ADD COLUMN unit_id TEXT'); + await db.execute('ALTER TABLE products ADD COLUMN unit_name TEXT'); + await db.execute('ALTER TABLE products ADD COLUMN unit_abbreviation TEXT'); + } } Future close() async { diff --git a/lib/common/extension/double_extension.dart b/lib/common/extension/double_extension.dart index a8cf993..c4fa0cc 100644 --- a/lib/common/extension/double_extension.dart +++ b/lib/common/extension/double_extension.dart @@ -13,3 +13,10 @@ extension DoubleExt on double { decimalDigits: 0, ).format(this); } + +extension WeightExt on double { + /// Format berat gaya Indonesia: koma desimal, tanpa nol di belakang. + /// `4.2` → `4,2`, `4.237` → `4,237`, `5.0` → `5`. + String get weightFormat => + NumberFormat('#,##0.###', 'id').format(this); +} diff --git a/lib/common/function/app_function.dart b/lib/common/function/app_function.dart index 3fd64b2..f159a81 100644 --- a/lib/common/function/app_function.dart +++ b/lib/common/function/app_function.dart @@ -75,3 +75,25 @@ int generateRandomNumber() { final random = Random(); return 1 + random.nextInt(10000); } + +/// Ambil pesan validasi dari amplop error standar backend. +/// +/// Untuk produk timbangan pesannya menyebut nama produk — misalnya +/// "product Ikan Tude is sold by weight and requires a weight" — jadi layak +/// ditampilkan apa adanya ke kasir alih-alih diganti pesan generik. +String? extractApiErrorCause(dynamic responseData) { + if (responseData is! Map) return null; + + final errors = responseData['errors']; + if (errors is! List || errors.isEmpty) return null; + + final causes = errors + .whereType() + .map((e) => (e['cause'] ?? e['message'])?.toString()) + .where((cause) => cause != null && cause.isNotEmpty) + .cast() + .toList(); + + if (causes.isEmpty) return null; + return causes.join('\n'); +} diff --git a/lib/common/service/unit_registry.dart b/lib/common/service/unit_registry.dart new file mode 100644 index 0000000..e70a2f7 --- /dev/null +++ b/lib/common/service/unit_registry.dart @@ -0,0 +1,48 @@ +import 'dart:developer'; + +import 'package:injectable/injectable.dart'; + +import '../../domain/unit/unit.dart'; + +/// Kamus satuan yang dipakai untuk menerjemahkan `unit_id` sebuah produk +/// menjadi singkatan yang dicetak di keranjang dan struk (`4,2 ons`). +/// +/// Response produk hanya dijamin membawa `unit_id`; nama dan singkatannya +/// datang dari `GET /api/v1/units`. Registry ini menjembatani keduanya tanpa +/// memaksa setiap pemanggil menunggu request. +@lazySingleton +class UnitRegistry { + final IUnitRepository _repository; + final _logName = 'UnitRegistry'; + + Map _unitsById = {}; + + UnitRegistry(this._repository); + + bool get isEmpty => _unitsById.isEmpty; + + ProductUnit? byId(String? id) { + if (id == null || id.isEmpty) return null; + return _unitsById[id]; + } + + String abbreviationOf(String? id) => byId(id)?.abbreviation ?? ''; + + String nameOf(String? id) => byId(id)?.name ?? ''; + + /// Muat satuan ke memori. Aman dipanggil berulang; [forceRefresh] menembak + /// server ulang saat pengguna melakukan sinkronisasi manual. + Future load({bool forceRefresh = false}) async { + final result = await _repository.getUnits(forceRefresh: forceRefresh); + + result.fold( + (failure) => log('⚠️ Gagal memuat satuan: $failure', name: _logName), + (units) { + _unitsById = {for (final unit in units) unit.id: unit}; + log('📏 ${units.length} satuan siap dipakai', name: _logName); + }, + ); + } + + void clear() => _unitsById = {}; +} diff --git a/lib/common/types/sell_by.dart b/lib/common/types/sell_by.dart new file mode 100644 index 0000000..b7b3806 --- /dev/null +++ b/lib/common/types/sell_by.dart @@ -0,0 +1,19 @@ +/// Cara sebuah produk dijual. +/// +/// Lihat `docs/integration-weight-based-products.md` — backend hanya menerima +/// dua nilai ini, dan produk lama otomatis dianggap [SellBy.unit]. +class SellBy { + const SellBy._(); + + /// Dijual per cacah. `quantity` integer, `weight` tidak dikirim. + static const String unit = 'unit'; + + /// Dijual per timbangan. `quantity` selalu 1, `weight` wajib. + static const String weight = 'weight'; + + static bool isWeight(String? value) => value == weight; + + /// Nilai apa pun di luar dua yang dikenal diperlakukan sebagai [unit], + /// sama seperti perilaku backend untuk produk lama. + static String normalize(String? value) => value == weight ? weight : unit; +} diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index f7c5c50..5f5358e 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -4,6 +4,7 @@ class ApiPath { static const String outlets = '/api/v1/outlets'; static const String categories = '/api/v1/categories'; static const String products = '/api/v1/products'; + static const String units = '/api/v1/units'; static const String tables = '/api/v1/tables'; static const String customers = '/api/v1/customers'; static const String paymentMethods = '/api/v1/payment-methods'; diff --git a/lib/domain/order/entities/add_item_order_request_entity.dart b/lib/domain/order/entities/add_item_order_request_entity.dart index 8c2634b..286c298 100644 --- a/lib/domain/order/entities/add_item_order_request_entity.dart +++ b/lib/domain/order/entities/add_item_order_request_entity.dart @@ -8,6 +8,9 @@ class AddItemOrderRequest with _$AddItemOrderRequest { required int quantity, required int unitPrice, required String notes, + + /// Hanya diisi untuk produk `sell_by: "weight"`. + double? weight, }) = _AddItemOrderRequest; factory AddItemOrderRequest.empty() => const AddItemOrderRequest( @@ -17,4 +20,14 @@ class AddItemOrderRequest with _$AddItemOrderRequest { unitPrice: 0, notes: '', ); + + factory AddItemOrderRequest.fromProductQuantity(ProductQuantity item) => + AddItemOrderRequest( + productId: item.product.id, + productVariantId: item.variant?.id ?? '', + quantity: item.isWeighted ? 1 : item.quantity, + unitPrice: item.product.price.toInt(), + notes: item.notes, + weight: item.weight, + ); } diff --git a/lib/domain/order/entities/order_entity.dart b/lib/domain/order/entities/order_entity.dart index a6433bb..c4d1851 100644 --- a/lib/domain/order/entities/order_entity.dart +++ b/lib/domain/order/entities/order_entity.dart @@ -197,19 +197,19 @@ class Order with _$Order { ); // For Add Item Order - factory Order.fromLastOrder(Order order, List products) => - order.copyWith( - orderItems: products - .map((e) => OrderItem.fromProductQuantity(e)) - .toList(), - totalAmount: products - .map((e) => e.product.price.toInt() * e.quantity) - .reduce((value, element) => value + element), - subtotal: products - .map((e) => e.product.price.toInt() * e.quantity) - .reduce((value, element) => value + element), - metadata: order.metadata, - ); + factory Order.fromLastOrder(Order order, List products) { + final subtotal = products.fold( + 0, + (sum, item) => sum + item.lineTotal.toInt(), + ); + + return order.copyWith( + orderItems: products.map(OrderItem.fromProductQuantity).toList(), + totalAmount: subtotal, + subtotal: subtotal, + metadata: order.metadata, + ); + } List get orderItemNotCancelled => orderItems.where((item) => item.status != 'cancelled').toList(); @@ -217,6 +217,8 @@ class Order with _$Order { @freezed class OrderItem with _$OrderItem { + const OrderItem._(); + const factory OrderItem({ required String id, required String orderId, @@ -237,6 +239,12 @@ class OrderItem with _$OrderItem { required int paidQuantity, required String categoryId, required String categoryName, + + /// Angka timbangan baris ini. `null` untuk produk cacah. + double? weight, + @Default('') String unitId, + @Default('') String unitName, + @Default('') String unitAbbreviation, }) = _OrderItem; factory OrderItem.empty() => OrderItem( @@ -269,10 +277,9 @@ class OrderItem with _$OrderItem { productName: productQuantity.product.name, productVariantId: productQuantity.variant?.id ?? '', productVariantName: productQuantity.variant?.name ?? '', - quantity: productQuantity.quantity, + quantity: productQuantity.isWeighted ? 1 : productQuantity.quantity, unitPrice: productQuantity.product.price.toInt(), - totalPrice: (productQuantity.product.price * productQuantity.quantity) - .toInt(), + totalPrice: productQuantity.lineTotal.toInt(), modifiers: [], notes: productQuantity.notes, status: 'pending', @@ -283,5 +290,30 @@ class OrderItem with _$OrderItem { paidQuantity: 0, categoryId: '', categoryName: '', + weight: productQuantity.weight, + unitId: productQuantity.product.unitId, + unitName: productQuantity.product.unitName, + unitAbbreviation: productQuantity.product.unitAbbreviation, ); + + /// Baris hasil penimbangan — utuh, tidak bisa dipecah sebagian. + bool get isWeighted => weight != null && weight! > 0; + + /// Singkatan satuan untuk ditampilkan; jatuh ke nama panjang bila kosong. + String get unitLabel => + unitAbbreviation.isNotEmpty ? unitAbbreviation : unitName; + + /// `4,2 ons` untuk baris berbobot, `2` untuk baris biasa. + /// + /// Jangan menampilkan [quantity] mentah pada baris berbobot — nilainya selalu + /// 1 dan terbaca seperti "satu ons". + String get quantityLabel { + if (!isWeighted) return quantity.toString(); + final value = weight!.weightFormat; + return unitLabel.isEmpty ? value : '$value $unitLabel'; + } + + /// `4,2 ons x Rp 4.500` atau `2 x Rp 25.000`. + String get quantityPriceLabel => + '$quantityLabel x ${unitPrice.currencyFormatRpV2}'; } diff --git a/lib/domain/order/entities/order_request_entity.dart b/lib/domain/order/entities/order_request_entity.dart index 42ad3cf..a5d2190 100644 --- a/lib/domain/order/entities/order_request_entity.dart +++ b/lib/domain/order/entities/order_request_entity.dart @@ -33,6 +33,10 @@ class OrderItemRequest with _$OrderItemRequest { required int quantity, required int unitPrice, required String notes, + + /// Hanya diisi untuk produk `sell_by: "weight"`; produk cacah tidak boleh + /// mengirim berat sama sekali. + double? weight, }) = _OrderItemRequest; factory OrderItemRequest.empty() => const OrderItemRequest( @@ -42,4 +46,16 @@ class OrderItemRequest with _$OrderItemRequest { unitPrice: 0, notes: '', ); + + /// Bentuk baris dari isi keranjang: produk timbangan selalu `quantity: 1` + /// ditambah `weight`, produk cacah tetap seperti sebelumnya. + factory OrderItemRequest.fromProductQuantity(ProductQuantity item) => + OrderItemRequest( + productId: item.product.id, + productVariantId: item.variant?.id ?? '', + quantity: item.isWeighted ? 1 : item.quantity, + unitPrice: item.product.price.toInt(), + notes: item.notes, + weight: item.weight, + ); } diff --git a/lib/domain/order/order.dart b/lib/domain/order/order.dart index a3f8f4d..843c878 100644 --- a/lib/domain/order/order.dart +++ b/lib/domain/order/order.dart @@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../../common/api/api_failure.dart'; import '../../common/types/split_type.dart'; +import '../../common/extension/extension.dart'; import '../product/product.dart'; part 'order.freezed.dart'; diff --git a/lib/domain/order/order.freezed.dart b/lib/domain/order/order.freezed.dart index 5e45d5e..553ab2a 100644 --- a/lib/domain/order/order.freezed.dart +++ b/lib/domain/order/order.freezed.dart @@ -1007,6 +1007,12 @@ mixin _$OrderItem { String get categoryId => throw _privateConstructorUsedError; String get categoryName => throw _privateConstructorUsedError; + /// Angka timbangan baris ini. `null` untuk produk cacah. + double? get weight => throw _privateConstructorUsedError; + String get unitId => throw _privateConstructorUsedError; + String get unitName => throw _privateConstructorUsedError; + String get unitAbbreviation => throw _privateConstructorUsedError; + /// Create a copy of OrderItem /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -1039,6 +1045,10 @@ abstract class $OrderItemCopyWith<$Res> { int paidQuantity, String categoryId, String categoryName, + double? weight, + String unitId, + String unitName, + String unitAbbreviation, }); } @@ -1076,6 +1086,10 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem> Object? paidQuantity = null, Object? categoryId = null, Object? categoryName = null, + Object? weight = freezed, + Object? unitId = null, + Object? unitName = null, + Object? unitAbbreviation = null, }) { return _then( _value.copyWith( @@ -1155,6 +1169,22 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem> ? _value.categoryName : categoryName // ignore: cast_nullable_to_non_nullable as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, + unitId: null == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String, + unitName: null == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String, + unitAbbreviation: null == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String, ) as $Val, ); @@ -1190,6 +1220,10 @@ abstract class _$$OrderItemImplCopyWith<$Res> int paidQuantity, String categoryId, String categoryName, + double? weight, + String unitId, + String unitName, + String unitAbbreviation, }); } @@ -1226,6 +1260,10 @@ class __$$OrderItemImplCopyWithImpl<$Res> Object? paidQuantity = null, Object? categoryId = null, Object? categoryName = null, + Object? weight = freezed, + Object? unitId = null, + Object? unitName = null, + Object? unitAbbreviation = null, }) { return _then( _$OrderItemImpl( @@ -1305,6 +1343,22 @@ class __$$OrderItemImplCopyWithImpl<$Res> ? _value.categoryName : categoryName // ignore: cast_nullable_to_non_nullable as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, + unitId: null == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String, + unitName: null == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String, + unitAbbreviation: null == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String, ), ); } @@ -1312,7 +1366,7 @@ class __$$OrderItemImplCopyWithImpl<$Res> /// @nodoc -class _$OrderItemImpl implements _OrderItem { +class _$OrderItemImpl extends _OrderItem { const _$OrderItemImpl({ required this.id, required this.orderId, @@ -1333,7 +1387,12 @@ class _$OrderItemImpl implements _OrderItem { required this.paidQuantity, required this.categoryId, required this.categoryName, - }) : _modifiers = modifiers; + this.weight, + this.unitId = '', + this.unitName = '', + this.unitAbbreviation = '', + }) : _modifiers = modifiers, + super._(); @override final String id; @@ -1380,9 +1439,22 @@ class _$OrderItemImpl implements _OrderItem { @override final String categoryName; + /// Angka timbangan baris ini. `null` untuk produk cacah. + @override + final double? weight; + @override + @JsonKey() + final String unitId; + @override + @JsonKey() + final String unitName; + @override + @JsonKey() + final String unitAbbreviation; + @override String toString() { - return 'OrderItem(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, printToChecker: $printToChecker, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName)'; + return 'OrderItem(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, printToChecker: $printToChecker, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName, weight: $weight, unitId: $unitId, unitName: $unitName, unitAbbreviation: $unitAbbreviation)'; } @override @@ -1425,7 +1497,13 @@ class _$OrderItemImpl implements _OrderItem { (identical(other.categoryId, categoryId) || other.categoryId == categoryId) && (identical(other.categoryName, categoryName) || - other.categoryName == categoryName)); + other.categoryName == categoryName) && + (identical(other.weight, weight) || other.weight == weight) && + (identical(other.unitId, unitId) || other.unitId == unitId) && + (identical(other.unitName, unitName) || + other.unitName == unitName) && + (identical(other.unitAbbreviation, unitAbbreviation) || + other.unitAbbreviation == unitAbbreviation)); } @override @@ -1450,6 +1528,10 @@ class _$OrderItemImpl implements _OrderItem { paidQuantity, categoryId, categoryName, + weight, + unitId, + unitName, + unitAbbreviation, ]); /// Create a copy of OrderItem @@ -1461,7 +1543,7 @@ class _$OrderItemImpl implements _OrderItem { __$$OrderItemImplCopyWithImpl<_$OrderItemImpl>(this, _$identity); } -abstract class _OrderItem implements OrderItem { +abstract class _OrderItem extends OrderItem { const factory _OrderItem({ required final String id, required final String orderId, @@ -1482,7 +1564,12 @@ abstract class _OrderItem implements OrderItem { required final int paidQuantity, required final String categoryId, required final String categoryName, + final double? weight, + final String unitId, + final String unitName, + final String unitAbbreviation, }) = _$OrderItemImpl; + const _OrderItem._() : super._(); @override String get id; @@ -1523,6 +1610,16 @@ abstract class _OrderItem implements OrderItem { @override String get categoryName; + /// Angka timbangan baris ini. `null` untuk produk cacah. + @override + double? get weight; + @override + String get unitId; + @override + String get unitName; + @override + String get unitAbbreviation; + /// Create a copy of OrderItem /// with the given fields replaced by the non-null parameter values. @override @@ -1847,6 +1944,10 @@ mixin _$OrderItemRequest { int get unitPrice => throw _privateConstructorUsedError; String get notes => throw _privateConstructorUsedError; + /// Hanya diisi untuk produk `sell_by: "weight"`; produk cacah tidak boleh + /// mengirim berat sama sekali. + double? get weight => throw _privateConstructorUsedError; + /// Create a copy of OrderItemRequest /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -1867,6 +1968,7 @@ abstract class $OrderItemRequestCopyWith<$Res> { int quantity, int unitPrice, String notes, + double? weight, }); } @@ -1890,6 +1992,7 @@ class _$OrderItemRequestCopyWithImpl<$Res, $Val extends OrderItemRequest> Object? quantity = null, Object? unitPrice = null, Object? notes = null, + Object? weight = freezed, }) { return _then( _value.copyWith( @@ -1913,6 +2016,10 @@ class _$OrderItemRequestCopyWithImpl<$Res, $Val extends OrderItemRequest> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ) as $Val, ); @@ -1934,6 +2041,7 @@ abstract class _$$OrderItemRequestImplCopyWith<$Res> int quantity, int unitPrice, String notes, + double? weight, }); } @@ -1956,6 +2064,7 @@ class __$$OrderItemRequestImplCopyWithImpl<$Res> Object? quantity = null, Object? unitPrice = null, Object? notes = null, + Object? weight = freezed, }) { return _then( _$OrderItemRequestImpl( @@ -1979,6 +2088,10 @@ class __$$OrderItemRequestImplCopyWithImpl<$Res> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ), ); } @@ -1993,6 +2106,7 @@ class _$OrderItemRequestImpl implements _OrderItemRequest { required this.quantity, required this.unitPrice, required this.notes, + this.weight, }); @override @@ -2006,9 +2120,14 @@ class _$OrderItemRequestImpl implements _OrderItemRequest { @override final String notes; + /// Hanya diisi untuk produk `sell_by: "weight"`; produk cacah tidak boleh + /// mengirim berat sama sekali. + @override + final double? weight; + @override String toString() { - return 'OrderItemRequest(productId: $productId, productVariantId: $productVariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes)'; + return 'OrderItemRequest(productId: $productId, productVariantId: $productVariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes, weight: $weight)'; } @override @@ -2024,7 +2143,8 @@ class _$OrderItemRequestImpl implements _OrderItemRequest { other.quantity == quantity) && (identical(other.unitPrice, unitPrice) || other.unitPrice == unitPrice) && - (identical(other.notes, notes) || other.notes == notes)); + (identical(other.notes, notes) || other.notes == notes) && + (identical(other.weight, weight) || other.weight == weight)); } @override @@ -2035,6 +2155,7 @@ class _$OrderItemRequestImpl implements _OrderItemRequest { quantity, unitPrice, notes, + weight, ); /// Create a copy of OrderItemRequest @@ -2056,6 +2177,7 @@ abstract class _OrderItemRequest implements OrderItemRequest { required final int quantity, required final int unitPrice, required final String notes, + final double? weight, }) = _$OrderItemRequestImpl; @override @@ -2069,6 +2191,11 @@ abstract class _OrderItemRequest implements OrderItemRequest { @override String get notes; + /// Hanya diisi untuk produk `sell_by: "weight"`; produk cacah tidak boleh + /// mengirim berat sama sekali. + @override + double? get weight; + /// Create a copy of OrderItemRequest /// with the given fields replaced by the non-null parameter values. @override @@ -3448,6 +3575,9 @@ mixin _$AddItemOrderRequest { int get unitPrice => throw _privateConstructorUsedError; String get notes => throw _privateConstructorUsedError; + /// Hanya diisi untuk produk `sell_by: "weight"`. + double? get weight => throw _privateConstructorUsedError; + /// Create a copy of AddItemOrderRequest /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -3468,6 +3598,7 @@ abstract class $AddItemOrderRequestCopyWith<$Res> { int quantity, int unitPrice, String notes, + double? weight, }); } @@ -3491,6 +3622,7 @@ class _$AddItemOrderRequestCopyWithImpl<$Res, $Val extends AddItemOrderRequest> Object? quantity = null, Object? unitPrice = null, Object? notes = null, + Object? weight = freezed, }) { return _then( _value.copyWith( @@ -3514,6 +3646,10 @@ class _$AddItemOrderRequestCopyWithImpl<$Res, $Val extends AddItemOrderRequest> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ) as $Val, ); @@ -3535,6 +3671,7 @@ abstract class _$$AddItemOrderRequestImplCopyWith<$Res> int quantity, int unitPrice, String notes, + double? weight, }); } @@ -3557,6 +3694,7 @@ class __$$AddItemOrderRequestImplCopyWithImpl<$Res> Object? quantity = null, Object? unitPrice = null, Object? notes = null, + Object? weight = freezed, }) { return _then( _$AddItemOrderRequestImpl( @@ -3580,6 +3718,10 @@ class __$$AddItemOrderRequestImplCopyWithImpl<$Res> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ), ); } @@ -3594,6 +3736,7 @@ class _$AddItemOrderRequestImpl implements _AddItemOrderRequest { required this.quantity, required this.unitPrice, required this.notes, + this.weight, }); @override @@ -3607,9 +3750,13 @@ class _$AddItemOrderRequestImpl implements _AddItemOrderRequest { @override final String notes; + /// Hanya diisi untuk produk `sell_by: "weight"`. + @override + final double? weight; + @override String toString() { - return 'AddItemOrderRequest(productId: $productId, productVariantId: $productVariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes)'; + return 'AddItemOrderRequest(productId: $productId, productVariantId: $productVariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes, weight: $weight)'; } @override @@ -3625,7 +3772,8 @@ class _$AddItemOrderRequestImpl implements _AddItemOrderRequest { other.quantity == quantity) && (identical(other.unitPrice, unitPrice) || other.unitPrice == unitPrice) && - (identical(other.notes, notes) || other.notes == notes)); + (identical(other.notes, notes) || other.notes == notes) && + (identical(other.weight, weight) || other.weight == weight)); } @override @@ -3636,6 +3784,7 @@ class _$AddItemOrderRequestImpl implements _AddItemOrderRequest { quantity, unitPrice, notes, + weight, ); /// Create a copy of AddItemOrderRequest @@ -3657,6 +3806,7 @@ abstract class _AddItemOrderRequest implements AddItemOrderRequest { required final int quantity, required final int unitPrice, required final String notes, + final double? weight, }) = _$AddItemOrderRequestImpl; @override @@ -3670,6 +3820,10 @@ abstract class _AddItemOrderRequest implements AddItemOrderRequest { @override String get notes; + /// Hanya diisi untuk produk `sell_by: "weight"`. + @override + double? get weight; + /// Create a copy of AddItemOrderRequest /// with the given fields replaced by the non-null parameter values. @override diff --git a/lib/domain/product/entities/product_entity.dart b/lib/domain/product/entities/product_entity.dart index f31dd08..80b5046 100644 --- a/lib/domain/product/entities/product_entity.dart +++ b/lib/domain/product/entities/product_entity.dart @@ -21,6 +21,8 @@ class ListProduct with _$ListProduct { @freezed class Product with _$Product { + const Product._(); + const factory Product({ required String id, required String organizationId, @@ -39,6 +41,11 @@ class Product with _$Product { required String createdAt, required String updatedAt, required List variants, + /// `unit` (default) atau `weight`. Lihat [SellBy]. + @Default(SellBy.unit) String sellBy, + @Default('') String unitId, + @Default('') String unitName, + @Default('') String unitAbbreviation, }) = _Product; factory Product.empty() => const Product( @@ -60,6 +67,18 @@ class Product with _$Product { updatedAt: '', variants: [], ); + + /// Produk yang dijual per timbangan: `quantity` selalu 1, `weight` wajib. + bool get isSoldByWeight => SellBy.isWeight(sellBy); + + /// Singkatan satuan untuk ditampilkan — `ons`. Jatuh ke nama panjang bila + /// backend hanya mengirim itu. + String get unitLabel => + unitAbbreviation.isNotEmpty ? unitAbbreviation : unitName; + + /// Label harga mengikuti satuan: "Harga per ons". + String get priceUnitLabel => + isSoldByWeight && unitLabel.isNotEmpty ? 'per $unitLabel' : ''; } @freezed diff --git a/lib/domain/product/entities/product_quantity_entity.dart b/lib/domain/product/entities/product_quantity_entity.dart index 7e98a6d..c06a24f 100644 --- a/lib/domain/product/entities/product_quantity_entity.dart +++ b/lib/domain/product/entities/product_quantity_entity.dart @@ -1,14 +1,53 @@ part of '../product.dart'; +/// Satu baris keranjang. +/// +/// Untuk produk timbangan, **satu penimbangan = satu baris**: dua kali menimbang +/// Ikan Tude menghasilkan dua baris terpisah dengan [quantity] 1 dan [weight] +/// masing-masing. Karena itu baris dibedakan lewat [lineId], bukan lewat +/// kombinasi produk + varian. @freezed class ProductQuantity with _$ProductQuantity { + const ProductQuantity._(); + const factory ProductQuantity({ required Product product, ProductVariant? variant, required int quantity, required String notes, + @Default('') String lineId, + + /// Angka timbangan dalam satuan produk. `null` untuk produk cacah. + double? weight, }) = _ProductQuantity; factory ProductQuantity.empty() => ProductQuantity(product: Product.empty(), quantity: 0, notes: ''); + + bool get isWeighted => weight != null; + + double get unitPrice => product.price + (variant?.priceModifier ?? 0); + + /// Harga baris: `weight × unit_price` untuk timbangan, `quantity × unit_price` + /// untuk produk cacah. + double get lineTotal => + isWeighted ? unitPrice * weight! : unitPrice * quantity; + + /// `4,2 ons` atau `2` — dipakai di kolom Qty keranjang. + String get quantityLabel { + if (!isWeighted) return quantity.toString(); + final label = product.unitLabel; + final value = weight!.weightFormat; + return label.isEmpty ? value : '$value $label'; + } +} + +extension ProductQuantityListExt on List { + /// Total keranjang: baris berbobot pakai `weight × harga`, baris cacah pakai + /// `quantity × harga`. + double get cartTotal => fold(0, (sum, item) => sum + item.lineTotal); + + /// Baris berbobot dihitung satu — satu penimbangan, satu barang. + int get cartItemCount => + fold(0, (sum, item) => sum + (item.isWeighted ? 1 : item.quantity)); } diff --git a/lib/domain/product/product.dart b/lib/domain/product/product.dart index fd4ebac..4a7cb0c 100644 --- a/lib/domain/product/product.dart +++ b/lib/domain/product/product.dart @@ -3,6 +3,8 @@ import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../common/api/api_failure.dart'; +import '../../common/extension/extension.dart'; +import '../../common/types/sell_by.dart'; part 'product.freezed.dart'; diff --git a/lib/domain/product/product.freezed.dart b/lib/domain/product/product.freezed.dart index cbe9d66..7e5eb5c 100644 --- a/lib/domain/product/product.freezed.dart +++ b/lib/domain/product/product.freezed.dart @@ -286,6 +286,12 @@ mixin _$Product { String get updatedAt => throw _privateConstructorUsedError; List get variants => throw _privateConstructorUsedError; + /// `unit` (default) atau `weight`. Lihat [SellBy]. + String get sellBy => throw _privateConstructorUsedError; + String get unitId => throw _privateConstructorUsedError; + String get unitName => throw _privateConstructorUsedError; + String get unitAbbreviation => throw _privateConstructorUsedError; + /// Create a copy of Product /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -315,6 +321,10 @@ abstract class $ProductCopyWith<$Res> { String createdAt, String updatedAt, List variants, + String sellBy, + String unitId, + String unitName, + String unitAbbreviation, }); } @@ -350,6 +360,10 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product> Object? createdAt = null, Object? updatedAt = null, Object? variants = null, + Object? sellBy = null, + Object? unitId = null, + Object? unitName = null, + Object? unitAbbreviation = null, }) { return _then( _value.copyWith( @@ -421,6 +435,22 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product> ? _value.variants : variants // ignore: cast_nullable_to_non_nullable as List, + sellBy: null == sellBy + ? _value.sellBy + : sellBy // ignore: cast_nullable_to_non_nullable + as String, + unitId: null == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String, + unitName: null == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String, + unitAbbreviation: null == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String, ) as $Val, ); @@ -453,6 +483,10 @@ abstract class _$$ProductImplCopyWith<$Res> implements $ProductCopyWith<$Res> { String createdAt, String updatedAt, List variants, + String sellBy, + String unitId, + String unitName, + String unitAbbreviation, }); } @@ -487,6 +521,10 @@ class __$$ProductImplCopyWithImpl<$Res> Object? createdAt = null, Object? updatedAt = null, Object? variants = null, + Object? sellBy = null, + Object? unitId = null, + Object? unitName = null, + Object? unitAbbreviation = null, }) { return _then( _$ProductImpl( @@ -558,6 +596,22 @@ class __$$ProductImplCopyWithImpl<$Res> ? _value._variants : variants // ignore: cast_nullable_to_non_nullable as List, + sellBy: null == sellBy + ? _value.sellBy + : sellBy // ignore: cast_nullable_to_non_nullable + as String, + unitId: null == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String, + unitName: null == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String, + unitAbbreviation: null == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String, ), ); } @@ -565,7 +619,7 @@ class __$$ProductImplCopyWithImpl<$Res> /// @nodoc -class _$ProductImpl with DiagnosticableTreeMixin implements _Product { +class _$ProductImpl extends _Product with DiagnosticableTreeMixin { const _$ProductImpl({ required this.id, required this.organizationId, @@ -584,8 +638,13 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product { required this.createdAt, required this.updatedAt, required final List variants, + this.sellBy = SellBy.unit, + this.unitId = '', + this.unitName = '', + this.unitAbbreviation = '', }) : _metadata = metadata, - _variants = variants; + _variants = variants, + super._(); @override final String id; @@ -633,9 +692,23 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product { return EqualUnmodifiableListView(_variants); } + /// `unit` (default) atau `weight`. Lihat [SellBy]. + @override + @JsonKey() + final String sellBy; + @override + @JsonKey() + final String unitId; + @override + @JsonKey() + final String unitName; + @override + @JsonKey() + final String unitAbbreviation; + @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'Product(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, printToChecker: $printToChecker, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; + return 'Product(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, printToChecker: $printToChecker, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants, sellBy: $sellBy, unitId: $unitId, unitName: $unitName, unitAbbreviation: $unitAbbreviation)'; } @override @@ -659,7 +732,11 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product { ..add(DiagnosticsProperty('isActive', isActive)) ..add(DiagnosticsProperty('createdAt', createdAt)) ..add(DiagnosticsProperty('updatedAt', updatedAt)) - ..add(DiagnosticsProperty('variants', variants)); + ..add(DiagnosticsProperty('variants', variants)) + ..add(DiagnosticsProperty('sellBy', sellBy)) + ..add(DiagnosticsProperty('unitId', unitId)) + ..add(DiagnosticsProperty('unitName', unitName)) + ..add(DiagnosticsProperty('unitAbbreviation', unitAbbreviation)); } @override @@ -693,11 +770,17 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product { other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt) && - const DeepCollectionEquality().equals(other._variants, _variants)); + const DeepCollectionEquality().equals(other._variants, _variants) && + (identical(other.sellBy, sellBy) || other.sellBy == sellBy) && + (identical(other.unitId, unitId) || other.unitId == unitId) && + (identical(other.unitName, unitName) || + other.unitName == unitName) && + (identical(other.unitAbbreviation, unitAbbreviation) || + other.unitAbbreviation == unitAbbreviation)); } @override - int get hashCode => Object.hash( + int get hashCode => Object.hashAll([ runtimeType, id, organizationId, @@ -716,7 +799,11 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product { createdAt, updatedAt, const DeepCollectionEquality().hash(_variants), - ); + sellBy, + unitId, + unitName, + unitAbbreviation, + ]); /// Create a copy of Product /// with the given fields replaced by the non-null parameter values. @@ -727,7 +814,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product { __$$ProductImplCopyWithImpl<_$ProductImpl>(this, _$identity); } -abstract class _Product implements Product { +abstract class _Product extends Product { const factory _Product({ required final String id, required final String organizationId, @@ -746,7 +833,12 @@ abstract class _Product implements Product { required final String createdAt, required final String updatedAt, required final List variants, + final String sellBy, + final String unitId, + final String unitName, + final String unitAbbreviation, }) = _$ProductImpl; + const _Product._() : super._(); @override String get id; @@ -783,6 +875,16 @@ abstract class _Product implements Product { @override List get variants; + /// `unit` (default) atau `weight`. Lihat [SellBy]. + @override + String get sellBy; + @override + String get unitId; + @override + String get unitName; + @override + String get unitAbbreviation; + /// Create a copy of Product /// with the given fields replaced by the non-null parameter values. @override @@ -1121,6 +1223,10 @@ mixin _$ProductQuantity { ProductVariant? get variant => throw _privateConstructorUsedError; int get quantity => throw _privateConstructorUsedError; String get notes => throw _privateConstructorUsedError; + String get lineId => throw _privateConstructorUsedError; + + /// Angka timbangan dalam satuan produk. `null` untuk produk cacah. + double? get weight => throw _privateConstructorUsedError; /// Create a copy of ProductQuantity /// with the given fields replaced by the non-null parameter values. @@ -1141,6 +1247,8 @@ abstract class $ProductQuantityCopyWith<$Res> { ProductVariant? variant, int quantity, String notes, + String lineId, + double? weight, }); $ProductCopyWith<$Res> get product; @@ -1166,6 +1274,8 @@ class _$ProductQuantityCopyWithImpl<$Res, $Val extends ProductQuantity> Object? variant = freezed, Object? quantity = null, Object? notes = null, + Object? lineId = null, + Object? weight = freezed, }) { return _then( _value.copyWith( @@ -1185,6 +1295,14 @@ class _$ProductQuantityCopyWithImpl<$Res, $Val extends ProductQuantity> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String, + lineId: null == lineId + ? _value.lineId + : lineId // ignore: cast_nullable_to_non_nullable + as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ) as $Val, ); @@ -1229,6 +1347,8 @@ abstract class _$$ProductQuantityImplCopyWith<$Res> ProductVariant? variant, int quantity, String notes, + String lineId, + double? weight, }); @override @@ -1255,6 +1375,8 @@ class __$$ProductQuantityImplCopyWithImpl<$Res> Object? variant = freezed, Object? quantity = null, Object? notes = null, + Object? lineId = null, + Object? weight = freezed, }) { return _then( _$ProductQuantityImpl( @@ -1274,6 +1396,14 @@ class __$$ProductQuantityImplCopyWithImpl<$Res> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String, + lineId: null == lineId + ? _value.lineId + : lineId // ignore: cast_nullable_to_non_nullable + as String, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ), ); } @@ -1281,15 +1411,16 @@ class __$$ProductQuantityImplCopyWithImpl<$Res> /// @nodoc -class _$ProductQuantityImpl - with DiagnosticableTreeMixin - implements _ProductQuantity { +class _$ProductQuantityImpl extends _ProductQuantity + with DiagnosticableTreeMixin { const _$ProductQuantityImpl({ required this.product, this.variant, required this.quantity, required this.notes, - }); + this.lineId = '', + this.weight, + }) : super._(); @override final Product product; @@ -1299,10 +1430,17 @@ class _$ProductQuantityImpl final int quantity; @override final String notes; + @override + @JsonKey() + final String lineId; + + /// Angka timbangan dalam satuan produk. `null` untuk produk cacah. + @override + final double? weight; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'ProductQuantity(product: $product, variant: $variant, quantity: $quantity, notes: $notes)'; + return 'ProductQuantity(product: $product, variant: $variant, quantity: $quantity, notes: $notes, lineId: $lineId, weight: $weight)'; } @override @@ -1313,7 +1451,9 @@ class _$ProductQuantityImpl ..add(DiagnosticsProperty('product', product)) ..add(DiagnosticsProperty('variant', variant)) ..add(DiagnosticsProperty('quantity', quantity)) - ..add(DiagnosticsProperty('notes', notes)); + ..add(DiagnosticsProperty('notes', notes)) + ..add(DiagnosticsProperty('lineId', lineId)) + ..add(DiagnosticsProperty('weight', weight)); } @override @@ -1325,12 +1465,21 @@ class _$ProductQuantityImpl (identical(other.variant, variant) || other.variant == variant) && (identical(other.quantity, quantity) || other.quantity == quantity) && - (identical(other.notes, notes) || other.notes == notes)); + (identical(other.notes, notes) || other.notes == notes) && + (identical(other.lineId, lineId) || other.lineId == lineId) && + (identical(other.weight, weight) || other.weight == weight)); } @override - int get hashCode => - Object.hash(runtimeType, product, variant, quantity, notes); + int get hashCode => Object.hash( + runtimeType, + product, + variant, + quantity, + notes, + lineId, + weight, + ); /// Create a copy of ProductQuantity /// with the given fields replaced by the non-null parameter values. @@ -1344,13 +1493,16 @@ class _$ProductQuantityImpl ); } -abstract class _ProductQuantity implements ProductQuantity { +abstract class _ProductQuantity extends ProductQuantity { const factory _ProductQuantity({ required final Product product, final ProductVariant? variant, required final int quantity, required final String notes, + final String lineId, + final double? weight, }) = _$ProductQuantityImpl; + const _ProductQuantity._() : super._(); @override Product get product; @@ -1360,6 +1512,12 @@ abstract class _ProductQuantity implements ProductQuantity { int get quantity; @override String get notes; + @override + String get lineId; + + /// Angka timbangan dalam satuan produk. `null` untuk produk cacah. + @override + double? get weight; /// Create a copy of ProductQuantity /// with the given fields replaced by the non-null parameter values. diff --git a/lib/domain/unit/entities/unit_entity.dart b/lib/domain/unit/entities/unit_entity.dart new file mode 100644 index 0000000..4374814 --- /dev/null +++ b/lib/domain/unit/entities/unit_entity.dart @@ -0,0 +1,23 @@ +part of '../unit.dart'; + +/// Satuan timbangan milik organisasi — ons, kg, gram. +/// +/// [abbreviation] yang dipakai POS untuk mencetak `4,2 ons` di struk. +@freezed +class ProductUnit with _$ProductUnit { + const factory ProductUnit({ + required String id, + required String organizationId, + required String name, + required String abbreviation, + required bool isActive, + }) = _ProductUnit; + + factory ProductUnit.empty() => const ProductUnit( + id: '', + organizationId: '', + name: '', + abbreviation: '', + isActive: false, + ); +} diff --git a/lib/domain/unit/failures/unit_failure.dart b/lib/domain/unit/failures/unit_failure.dart new file mode 100644 index 0000000..3567d0b --- /dev/null +++ b/lib/domain/unit/failures/unit_failure.dart @@ -0,0 +1,12 @@ +part of '../unit.dart'; + +@freezed +sealed class UnitFailure with _$UnitFailure { + const factory UnitFailure.serverError(ApiFailure failure) = _ServerError; + const factory UnitFailure.unexpectedError() = _UnexpectedError; + const factory UnitFailure.empty() = _Empty; + const factory UnitFailure.localStorageError(String erroMessage) = + _LocalStorageError; + const factory UnitFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/unit/repositories/i_unit_repository.dart b/lib/domain/unit/repositories/i_unit_repository.dart new file mode 100644 index 0000000..1cc6986 --- /dev/null +++ b/lib/domain/unit/repositories/i_unit_repository.dart @@ -0,0 +1,12 @@ +part of '../unit.dart'; + +abstract class IUnitRepository { + /// Ambil satuan dari server lalu simpan ke cache lokal. + /// Bila server tidak terjangkau, kembalikan isi cache supaya POS tetap bisa + /// mencetak `4,2 ons` saat offline. + Future>> getUnits({ + bool forceRefresh = false, + }); + + Future> cachedUnits(); +} diff --git a/lib/domain/unit/unit.dart b/lib/domain/unit/unit.dart new file mode 100644 index 0000000..5d3cab7 --- /dev/null +++ b/lib/domain/unit/unit.dart @@ -0,0 +1,10 @@ +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/api/api_failure.dart'; + +part 'unit.freezed.dart'; + +part 'entities/unit_entity.dart'; +part 'failures/unit_failure.dart'; +part 'repositories/i_unit_repository.dart'; diff --git a/lib/domain/unit/unit.freezed.dart b/lib/domain/unit/unit.freezed.dart new file mode 100644 index 0000000..69d3904 --- /dev/null +++ b/lib/domain/unit/unit.freezed.dart @@ -0,0 +1,1078 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'unit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ProductUnit { + String get id => throw _privateConstructorUsedError; + String get organizationId => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get abbreviation => throw _privateConstructorUsedError; + bool get isActive => throw _privateConstructorUsedError; + + /// Create a copy of ProductUnit + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductUnitCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductUnitCopyWith<$Res> { + factory $ProductUnitCopyWith( + ProductUnit value, + $Res Function(ProductUnit) then, + ) = _$ProductUnitCopyWithImpl<$Res, ProductUnit>; + @useResult + $Res call({ + String id, + String organizationId, + String name, + String abbreviation, + bool isActive, + }); +} + +/// @nodoc +class _$ProductUnitCopyWithImpl<$Res, $Val extends ProductUnit> + implements $ProductUnitCopyWith<$Res> { + _$ProductUnitCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductUnit + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? organizationId = null, + Object? name = null, + Object? abbreviation = null, + Object? isActive = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + organizationId: null == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + abbreviation: null == abbreviation + ? _value.abbreviation + : abbreviation // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductUnitImplCopyWith<$Res> + implements $ProductUnitCopyWith<$Res> { + factory _$$ProductUnitImplCopyWith( + _$ProductUnitImpl value, + $Res Function(_$ProductUnitImpl) then, + ) = __$$ProductUnitImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String organizationId, + String name, + String abbreviation, + bool isActive, + }); +} + +/// @nodoc +class __$$ProductUnitImplCopyWithImpl<$Res> + extends _$ProductUnitCopyWithImpl<$Res, _$ProductUnitImpl> + implements _$$ProductUnitImplCopyWith<$Res> { + __$$ProductUnitImplCopyWithImpl( + _$ProductUnitImpl _value, + $Res Function(_$ProductUnitImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductUnit + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? organizationId = null, + Object? name = null, + Object? abbreviation = null, + Object? isActive = null, + }) { + return _then( + _$ProductUnitImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + organizationId: null == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + abbreviation: null == abbreviation + ? _value.abbreviation + : abbreviation // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$ProductUnitImpl implements _ProductUnit { + const _$ProductUnitImpl({ + required this.id, + required this.organizationId, + required this.name, + required this.abbreviation, + required this.isActive, + }); + + @override + final String id; + @override + final String organizationId; + @override + final String name; + @override + final String abbreviation; + @override + final bool isActive; + + @override + String toString() { + return 'ProductUnit(id: $id, organizationId: $organizationId, name: $name, abbreviation: $abbreviation, isActive: $isActive)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductUnitImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.name, name) || other.name == name) && + (identical(other.abbreviation, abbreviation) || + other.abbreviation == abbreviation) && + (identical(other.isActive, isActive) || + other.isActive == isActive)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + organizationId, + name, + abbreviation, + isActive, + ); + + /// Create a copy of ProductUnit + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductUnitImplCopyWith<_$ProductUnitImpl> get copyWith => + __$$ProductUnitImplCopyWithImpl<_$ProductUnitImpl>(this, _$identity); +} + +abstract class _ProductUnit implements ProductUnit { + const factory _ProductUnit({ + required final String id, + required final String organizationId, + required final String name, + required final String abbreviation, + required final bool isActive, + }) = _$ProductUnitImpl; + + @override + String get id; + @override + String get organizationId; + @override + String get name; + @override + String get abbreviation; + @override + bool get isActive; + + /// Create a copy of ProductUnit + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductUnitImplCopyWith<_$ProductUnitImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$UnitFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UnitFailureCopyWith<$Res> { + factory $UnitFailureCopyWith( + UnitFailure value, + $Res Function(UnitFailure) then, + ) = _$UnitFailureCopyWithImpl<$Res, UnitFailure>; +} + +/// @nodoc +class _$UnitFailureCopyWithImpl<$Res, $Val extends UnitFailure> + implements $UnitFailureCopyWith<$Res> { + _$UnitFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$UnitFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'UnitFailure.serverError(failure: $failure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, failure); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return serverError?.call(failure); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(failure); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements UnitFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$UnitFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'UnitFailure.unexpectedError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return unexpectedError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements UnitFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$EmptyImplCopyWith<$Res> { + factory _$$EmptyImplCopyWith( + _$EmptyImpl value, + $Res Function(_$EmptyImpl) then, + ) = __$$EmptyImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$EmptyImplCopyWithImpl<$Res> + extends _$UnitFailureCopyWithImpl<$Res, _$EmptyImpl> + implements _$$EmptyImplCopyWith<$Res> { + __$$EmptyImplCopyWithImpl( + _$EmptyImpl _value, + $Res Function(_$EmptyImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$EmptyImpl implements _Empty { + const _$EmptyImpl(); + + @override + String toString() { + return 'UnitFailure.empty()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$EmptyImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return empty(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return empty?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (empty != null) { + return empty(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return empty(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return empty?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (empty != null) { + return empty(this); + } + return orElse(); + } +} + +abstract class _Empty implements UnitFailure { + const factory _Empty() = _$EmptyImpl; +} + +/// @nodoc +abstract class _$$LocalStorageErrorImplCopyWith<$Res> { + factory _$$LocalStorageErrorImplCopyWith( + _$LocalStorageErrorImpl value, + $Res Function(_$LocalStorageErrorImpl) then, + ) = __$$LocalStorageErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$LocalStorageErrorImplCopyWithImpl<$Res> + extends _$UnitFailureCopyWithImpl<$Res, _$LocalStorageErrorImpl> + implements _$$LocalStorageErrorImplCopyWith<$Res> { + __$$LocalStorageErrorImplCopyWithImpl( + _$LocalStorageErrorImpl _value, + $Res Function(_$LocalStorageErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$LocalStorageErrorImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$LocalStorageErrorImpl implements _LocalStorageError { + const _$LocalStorageErrorImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'UnitFailure.localStorageError(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LocalStorageErrorImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LocalStorageErrorImplCopyWith<_$LocalStorageErrorImpl> get copyWith => + __$$LocalStorageErrorImplCopyWithImpl<_$LocalStorageErrorImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return localStorageError(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return localStorageError?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (localStorageError != null) { + return localStorageError(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return localStorageError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return localStorageError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (localStorageError != null) { + return localStorageError(this); + } + return orElse(); + } +} + +abstract class _LocalStorageError implements UnitFailure { + const factory _LocalStorageError(final String erroMessage) = + _$LocalStorageErrorImpl; + + String get erroMessage; + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LocalStorageErrorImplCopyWith<_$LocalStorageErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$UnitFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'UnitFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DynamicErrorMessageImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return dynamicErrorMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements UnitFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of UnitFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/order/datasources/remote_data_provider.dart b/lib/infrastructure/order/datasources/remote_data_provider.dart index 16bc31a..03666d5 100644 --- a/lib/infrastructure/order/datasources/remote_data_provider.dart +++ b/lib/infrastructure/order/datasources/remote_data_provider.dart @@ -55,6 +55,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } @@ -77,6 +83,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } @@ -102,6 +114,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } @@ -128,6 +146,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } else { final order = OrderDto.fromJson( @@ -171,6 +195,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } @@ -201,6 +231,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } @@ -239,6 +275,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } @@ -262,6 +304,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } @@ -290,6 +338,12 @@ class OrderRemoteDataProvider { ); if (response.data['success'] == false) { + // Pesan validasi backend menyebut nama produk, jadi diteruskan apa + // adanya ke kasir alih-alih diganti pesan generik. + final cause = extractApiErrorCause(response.data); + if (cause != null) { + return DC.error(OrderFailure.dynamicErrorMessage(cause)); + } return DC.error(const OrderFailure.unexpectedError()); } diff --git a/lib/infrastructure/order/dtos/add_item_order_request_dto.dart b/lib/infrastructure/order/dtos/add_item_order_request_dto.dart index f26f1b4..2834be3 100644 --- a/lib/infrastructure/order/dtos/add_item_order_request_dto.dart +++ b/lib/infrastructure/order/dtos/add_item_order_request_dto.dart @@ -10,6 +10,7 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto { @JsonKey(name: 'quantity') int? quantity, @JsonKey(name: 'unit_price') int? unitPrice, @JsonKey(name: 'notes') String? notes, + @JsonKey(name: 'weight') double? weight, }) = _AddItemOrderRequestDto; factory AddItemOrderRequestDto.fromJson(Map json) => @@ -21,6 +22,7 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto { quantity: quantity ?? 0, unitPrice: unitPrice ?? 0, notes: notes ?? '', + weight: weight, ); factory AddItemOrderRequestDto.fromDomain( @@ -31,6 +33,7 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto { quantity: addItemOrderRequest.quantity, unitPrice: addItemOrderRequest.unitPrice, notes: addItemOrderRequest.notes, + weight: addItemOrderRequest.weight, ); Map toRequest() { @@ -45,6 +48,11 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto { data['product_variant_id'] = productvariantId; } + // Produk satuan ditolak backend bila membawa berat. + if (weight != null) { + data['weight'] = weight; + } + return data; } } diff --git a/lib/infrastructure/order/dtos/order_dto.dart b/lib/infrastructure/order/dtos/order_dto.dart index cdba3b1..c264584 100644 --- a/lib/infrastructure/order/dtos/order_dto.dart +++ b/lib/infrastructure/order/dtos/order_dto.dart @@ -118,6 +118,10 @@ class OrderItemDto with _$OrderItemDto { @JsonKey(name: "paid_quantity") int? paidQuantity, @JsonKey(name: "category_id") String? categoryId, @JsonKey(name: "category_name") String? categoryName, + @JsonKey(name: "weight") double? weight, + @JsonKey(name: "unit_id") String? unitId, + @JsonKey(name: "unit_name") String? unitName, + @JsonKey(name: "unit_abbreviation") String? unitAbbreviation, }) = _OrderItemDto; factory OrderItemDto.fromJson(Map json) => @@ -144,6 +148,10 @@ class OrderItemDto with _$OrderItemDto { paidQuantity: paidQuantity ?? 0, categoryId: categoryId ?? '', categoryName: categoryName ?? '', + weight: weight, + unitId: unitId ?? '', + unitName: unitName ?? '', + unitAbbreviation: unitAbbreviation ?? '', ); factory OrderItemDto.fromDomain(OrderItem orderItem) => OrderItemDto( @@ -166,5 +174,9 @@ class OrderItemDto with _$OrderItemDto { paidQuantity: orderItem.paidQuantity, categoryId: orderItem.categoryId, categoryName: orderItem.categoryName, + weight: orderItem.weight, + unitId: orderItem.unitId, + unitName: orderItem.unitName, + unitAbbreviation: orderItem.unitAbbreviation, ); } diff --git a/lib/infrastructure/order/dtos/order_request_dto.dart b/lib/infrastructure/order/dtos/order_request_dto.dart index 761230f..009f2b0 100644 --- a/lib/infrastructure/order/dtos/order_request_dto.dart +++ b/lib/infrastructure/order/dtos/order_request_dto.dart @@ -75,6 +75,7 @@ class OrderItemRequestDto with _$OrderItemRequestDto { @JsonKey(name: "quantity") int? quantity, @JsonKey(name: "unit_price") int? unitPrice, @JsonKey(name: "notes") String? notes, + @JsonKey(name: "weight") double? weight, }) = _OrderItemRequestDto; factory OrderItemRequestDto.fromJson(Map json) => @@ -87,6 +88,7 @@ class OrderItemRequestDto with _$OrderItemRequestDto { quantity: quantity ?? 0, unitPrice: unitPrice ?? 0, notes: notes ?? '', + weight: weight, ); factory OrderItemRequestDto.fromDomain(OrderItemRequest request) => @@ -96,6 +98,7 @@ class OrderItemRequestDto with _$OrderItemRequestDto { quantity: request.quantity, unitPrice: request.unitPrice, notes: request.notes, + weight: request.weight, ); Map toRequest() { @@ -110,6 +113,12 @@ class OrderItemRequestDto with _$OrderItemRequestDto { data["product_variant_id"] = productVariantId; } + // Produk satuan ditolak backend bila membawa berat, jadi field ini hanya + // ikut saat memang ada hasil timbangan. + if (weight != null) { + data["weight"] = weight; + } + return data; } } diff --git a/lib/infrastructure/order/order_dtos.freezed.dart b/lib/infrastructure/order/order_dtos.freezed.dart index 6131e12..ba31645 100644 --- a/lib/infrastructure/order/order_dtos.freezed.dart +++ b/lib/infrastructure/order/order_dtos.freezed.dart @@ -1177,6 +1177,14 @@ mixin _$OrderItemDto { String? get categoryId => throw _privateConstructorUsedError; @JsonKey(name: "category_name") String? get categoryName => throw _privateConstructorUsedError; + @JsonKey(name: "weight") + double? get weight => throw _privateConstructorUsedError; + @JsonKey(name: "unit_id") + String? get unitId => throw _privateConstructorUsedError; + @JsonKey(name: "unit_name") + String? get unitName => throw _privateConstructorUsedError; + @JsonKey(name: "unit_abbreviation") + String? get unitAbbreviation => throw _privateConstructorUsedError; /// Serializes this OrderItemDto to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -1215,6 +1223,10 @@ abstract class $OrderItemDtoCopyWith<$Res> { @JsonKey(name: "paid_quantity") int? paidQuantity, @JsonKey(name: "category_id") String? categoryId, @JsonKey(name: "category_name") String? categoryName, + @JsonKey(name: "weight") double? weight, + @JsonKey(name: "unit_id") String? unitId, + @JsonKey(name: "unit_name") String? unitName, + @JsonKey(name: "unit_abbreviation") String? unitAbbreviation, }); } @@ -1252,6 +1264,10 @@ class _$OrderItemDtoCopyWithImpl<$Res, $Val extends OrderItemDto> Object? paidQuantity = freezed, Object? categoryId = freezed, Object? categoryName = freezed, + Object? weight = freezed, + Object? unitId = freezed, + Object? unitName = freezed, + Object? unitAbbreviation = freezed, }) { return _then( _value.copyWith( @@ -1331,6 +1347,22 @@ class _$OrderItemDtoCopyWithImpl<$Res, $Val extends OrderItemDto> ? _value.categoryName : categoryName // ignore: cast_nullable_to_non_nullable as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, + unitId: freezed == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String?, + unitName: freezed == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String?, + unitAbbreviation: freezed == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val, ); @@ -1366,6 +1398,10 @@ abstract class _$$OrderItemDtoImplCopyWith<$Res> @JsonKey(name: "paid_quantity") int? paidQuantity, @JsonKey(name: "category_id") String? categoryId, @JsonKey(name: "category_name") String? categoryName, + @JsonKey(name: "weight") double? weight, + @JsonKey(name: "unit_id") String? unitId, + @JsonKey(name: "unit_name") String? unitName, + @JsonKey(name: "unit_abbreviation") String? unitAbbreviation, }); } @@ -1402,6 +1438,10 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res> Object? paidQuantity = freezed, Object? categoryId = freezed, Object? categoryName = freezed, + Object? weight = freezed, + Object? unitId = freezed, + Object? unitName = freezed, + Object? unitAbbreviation = freezed, }) { return _then( _$OrderItemDtoImpl( @@ -1481,6 +1521,22 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res> ? _value.categoryName : categoryName // ignore: cast_nullable_to_non_nullable as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, + unitId: freezed == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String?, + unitName: freezed == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String?, + unitAbbreviation: freezed == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String?, ), ); } @@ -1509,6 +1565,10 @@ class _$OrderItemDtoImpl extends _OrderItemDto { @JsonKey(name: "paid_quantity") this.paidQuantity, @JsonKey(name: "category_id") this.categoryId, @JsonKey(name: "category_name") this.categoryName, + @JsonKey(name: "weight") this.weight, + @JsonKey(name: "unit_id") this.unitId, + @JsonKey(name: "unit_name") this.unitName, + @JsonKey(name: "unit_abbreviation") this.unitAbbreviation, }) : _modifiers = modifiers, super._(); @@ -1580,10 +1640,22 @@ class _$OrderItemDtoImpl extends _OrderItemDto { @override @JsonKey(name: "category_name") final String? categoryName; + @override + @JsonKey(name: "weight") + final double? weight; + @override + @JsonKey(name: "unit_id") + final String? unitId; + @override + @JsonKey(name: "unit_name") + final String? unitName; + @override + @JsonKey(name: "unit_abbreviation") + final String? unitAbbreviation; @override String toString() { - return 'OrderItemDto(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, printToChecker: $printToChecker, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName)'; + return 'OrderItemDto(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, printToChecker: $printToChecker, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName, weight: $weight, unitId: $unitId, unitName: $unitName, unitAbbreviation: $unitAbbreviation)'; } @override @@ -1626,7 +1698,13 @@ class _$OrderItemDtoImpl extends _OrderItemDto { (identical(other.categoryId, categoryId) || other.categoryId == categoryId) && (identical(other.categoryName, categoryName) || - other.categoryName == categoryName)); + other.categoryName == categoryName) && + (identical(other.weight, weight) || other.weight == weight) && + (identical(other.unitId, unitId) || other.unitId == unitId) && + (identical(other.unitName, unitName) || + other.unitName == unitName) && + (identical(other.unitAbbreviation, unitAbbreviation) || + other.unitAbbreviation == unitAbbreviation)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -1652,6 +1730,10 @@ class _$OrderItemDtoImpl extends _OrderItemDto { paidQuantity, categoryId, categoryName, + weight, + unitId, + unitName, + unitAbbreviation, ]); /// Create a copy of OrderItemDto @@ -1689,6 +1771,10 @@ abstract class _OrderItemDto extends OrderItemDto { @JsonKey(name: "paid_quantity") final int? paidQuantity, @JsonKey(name: "category_id") final String? categoryId, @JsonKey(name: "category_name") final String? categoryName, + @JsonKey(name: "weight") final double? weight, + @JsonKey(name: "unit_id") final String? unitId, + @JsonKey(name: "unit_name") final String? unitName, + @JsonKey(name: "unit_abbreviation") final String? unitAbbreviation, }) = _$OrderItemDtoImpl; const _OrderItemDto._() : super._(); @@ -1752,6 +1838,18 @@ abstract class _OrderItemDto extends OrderItemDto { @override @JsonKey(name: "category_name") String? get categoryName; + @override + @JsonKey(name: "weight") + double? get weight; + @override + @JsonKey(name: "unit_id") + String? get unitId; + @override + @JsonKey(name: "unit_name") + String? get unitName; + @override + @JsonKey(name: "unit_abbreviation") + String? get unitAbbreviation; /// Create a copy of OrderItemDto /// with the given fields replaced by the non-null parameter values. @@ -2136,6 +2234,8 @@ mixin _$OrderItemRequestDto { int? get unitPrice => throw _privateConstructorUsedError; @JsonKey(name: "notes") String? get notes => throw _privateConstructorUsedError; + @JsonKey(name: "weight") + double? get weight => throw _privateConstructorUsedError; /// Serializes this OrderItemRequestDto to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -2160,6 +2260,7 @@ abstract class $OrderItemRequestDtoCopyWith<$Res> { @JsonKey(name: "quantity") int? quantity, @JsonKey(name: "unit_price") int? unitPrice, @JsonKey(name: "notes") String? notes, + @JsonKey(name: "weight") double? weight, }); } @@ -2183,6 +2284,7 @@ class _$OrderItemRequestDtoCopyWithImpl<$Res, $Val extends OrderItemRequestDto> Object? quantity = freezed, Object? unitPrice = freezed, Object? notes = freezed, + Object? weight = freezed, }) { return _then( _value.copyWith( @@ -2206,6 +2308,10 @@ class _$OrderItemRequestDtoCopyWithImpl<$Res, $Val extends OrderItemRequestDto> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ) as $Val, ); @@ -2227,6 +2333,7 @@ abstract class _$$OrderItemRequestDtoImplCopyWith<$Res> @JsonKey(name: "quantity") int? quantity, @JsonKey(name: "unit_price") int? unitPrice, @JsonKey(name: "notes") String? notes, + @JsonKey(name: "weight") double? weight, }); } @@ -2249,6 +2356,7 @@ class __$$OrderItemRequestDtoImplCopyWithImpl<$Res> Object? quantity = freezed, Object? unitPrice = freezed, Object? notes = freezed, + Object? weight = freezed, }) { return _then( _$OrderItemRequestDtoImpl( @@ -2272,6 +2380,10 @@ class __$$OrderItemRequestDtoImplCopyWithImpl<$Res> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ), ); } @@ -2286,6 +2398,7 @@ class _$OrderItemRequestDtoImpl extends _OrderItemRequestDto { @JsonKey(name: "quantity") this.quantity, @JsonKey(name: "unit_price") this.unitPrice, @JsonKey(name: "notes") this.notes, + @JsonKey(name: "weight") this.weight, }) : super._(); factory _$OrderItemRequestDtoImpl.fromJson(Map json) => @@ -2306,10 +2419,13 @@ class _$OrderItemRequestDtoImpl extends _OrderItemRequestDto { @override @JsonKey(name: "notes") final String? notes; + @override + @JsonKey(name: "weight") + final double? weight; @override String toString() { - return 'OrderItemRequestDto(productId: $productId, productVariantId: $productVariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes)'; + return 'OrderItemRequestDto(productId: $productId, productVariantId: $productVariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes, weight: $weight)'; } @override @@ -2325,7 +2441,8 @@ class _$OrderItemRequestDtoImpl extends _OrderItemRequestDto { other.quantity == quantity) && (identical(other.unitPrice, unitPrice) || other.unitPrice == unitPrice) && - (identical(other.notes, notes) || other.notes == notes)); + (identical(other.notes, notes) || other.notes == notes) && + (identical(other.weight, weight) || other.weight == weight)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -2337,6 +2454,7 @@ class _$OrderItemRequestDtoImpl extends _OrderItemRequestDto { quantity, unitPrice, notes, + weight, ); /// Create a copy of OrderItemRequestDto @@ -2363,6 +2481,7 @@ abstract class _OrderItemRequestDto extends OrderItemRequestDto { @JsonKey(name: "quantity") final int? quantity, @JsonKey(name: "unit_price") final int? unitPrice, @JsonKey(name: "notes") final String? notes, + @JsonKey(name: "weight") final double? weight, }) = _$OrderItemRequestDtoImpl; const _OrderItemRequestDto._() : super._(); @@ -2384,6 +2503,9 @@ abstract class _OrderItemRequestDto extends OrderItemRequestDto { @override @JsonKey(name: "notes") String? get notes; + @override + @JsonKey(name: "weight") + double? get weight; /// Create a copy of OrderItemRequestDto /// with the given fields replaced by the non-null parameter values. @@ -4025,6 +4147,8 @@ mixin _$AddItemOrderRequestDto { int? get unitPrice => throw _privateConstructorUsedError; @JsonKey(name: 'notes') String? get notes => throw _privateConstructorUsedError; + @JsonKey(name: 'weight') + double? get weight => throw _privateConstructorUsedError; /// Serializes this AddItemOrderRequestDto to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -4049,6 +4173,7 @@ abstract class $AddItemOrderRequestDtoCopyWith<$Res> { @JsonKey(name: 'quantity') int? quantity, @JsonKey(name: 'unit_price') int? unitPrice, @JsonKey(name: 'notes') String? notes, + @JsonKey(name: 'weight') double? weight, }); } @@ -4075,6 +4200,7 @@ class _$AddItemOrderRequestDtoCopyWithImpl< Object? quantity = freezed, Object? unitPrice = freezed, Object? notes = freezed, + Object? weight = freezed, }) { return _then( _value.copyWith( @@ -4098,6 +4224,10 @@ class _$AddItemOrderRequestDtoCopyWithImpl< ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ) as $Val, ); @@ -4119,6 +4249,7 @@ abstract class _$$AddItemOrderRequestDtoImplCopyWith<$Res> @JsonKey(name: 'quantity') int? quantity, @JsonKey(name: 'unit_price') int? unitPrice, @JsonKey(name: 'notes') String? notes, + @JsonKey(name: 'weight') double? weight, }); } @@ -4142,6 +4273,7 @@ class __$$AddItemOrderRequestDtoImplCopyWithImpl<$Res> Object? quantity = freezed, Object? unitPrice = freezed, Object? notes = freezed, + Object? weight = freezed, }) { return _then( _$AddItemOrderRequestDtoImpl( @@ -4165,6 +4297,10 @@ class __$$AddItemOrderRequestDtoImplCopyWithImpl<$Res> ? _value.notes : notes // ignore: cast_nullable_to_non_nullable as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, ), ); } @@ -4179,6 +4315,7 @@ class _$AddItemOrderRequestDtoImpl extends _AddItemOrderRequestDto { @JsonKey(name: 'quantity') this.quantity, @JsonKey(name: 'unit_price') this.unitPrice, @JsonKey(name: 'notes') this.notes, + @JsonKey(name: 'weight') this.weight, }) : super._(); factory _$AddItemOrderRequestDtoImpl.fromJson(Map json) => @@ -4199,10 +4336,13 @@ class _$AddItemOrderRequestDtoImpl extends _AddItemOrderRequestDto { @override @JsonKey(name: 'notes') final String? notes; + @override + @JsonKey(name: 'weight') + final double? weight; @override String toString() { - return 'AddItemOrderRequestDto(productId: $productId, productvariantId: $productvariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes)'; + return 'AddItemOrderRequestDto(productId: $productId, productvariantId: $productvariantId, quantity: $quantity, unitPrice: $unitPrice, notes: $notes, weight: $weight)'; } @override @@ -4218,7 +4358,8 @@ class _$AddItemOrderRequestDtoImpl extends _AddItemOrderRequestDto { other.quantity == quantity) && (identical(other.unitPrice, unitPrice) || other.unitPrice == unitPrice) && - (identical(other.notes, notes) || other.notes == notes)); + (identical(other.notes, notes) || other.notes == notes) && + (identical(other.weight, weight) || other.weight == weight)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -4230,6 +4371,7 @@ class _$AddItemOrderRequestDtoImpl extends _AddItemOrderRequestDto { quantity, unitPrice, notes, + weight, ); /// Create a copy of AddItemOrderRequestDto @@ -4257,6 +4399,7 @@ abstract class _AddItemOrderRequestDto extends AddItemOrderRequestDto { @JsonKey(name: 'quantity') final int? quantity, @JsonKey(name: 'unit_price') final int? unitPrice, @JsonKey(name: 'notes') final String? notes, + @JsonKey(name: 'weight') final double? weight, }) = _$AddItemOrderRequestDtoImpl; const _AddItemOrderRequestDto._() : super._(); @@ -4278,6 +4421,9 @@ abstract class _AddItemOrderRequestDto extends AddItemOrderRequestDto { @override @JsonKey(name: 'notes') String? get notes; + @override + @JsonKey(name: 'weight') + double? get weight; /// Create a copy of AddItemOrderRequestDto /// with the given fields replaced by the non-null parameter values. diff --git a/lib/infrastructure/order/order_dtos.g.dart b/lib/infrastructure/order/order_dtos.g.dart index d4c2548..df16ebd 100644 --- a/lib/infrastructure/order/order_dtos.g.dart +++ b/lib/infrastructure/order/order_dtos.g.dart @@ -113,6 +113,10 @@ _$OrderItemDtoImpl _$$OrderItemDtoImplFromJson(Map json) => paidQuantity: (json['paid_quantity'] as num?)?.toInt(), categoryId: json['category_id'] as String?, categoryName: json['category_name'] as String?, + weight: (json['weight'] as num?)?.toDouble(), + unitId: json['unit_id'] as String?, + unitName: json['unit_name'] as String?, + unitAbbreviation: json['unit_abbreviation'] as String?, ); Map _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) => @@ -136,6 +140,10 @@ Map _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) => 'paid_quantity': instance.paidQuantity, 'category_id': instance.categoryId, 'category_name': instance.categoryName, + 'weight': instance.weight, + 'unit_id': instance.unitId, + 'unit_name': instance.unitName, + 'unit_abbreviation': instance.unitAbbreviation, }; _$OrderRequestDtoImpl _$$OrderRequestDtoImplFromJson( @@ -174,6 +182,7 @@ _$OrderItemRequestDtoImpl _$$OrderItemRequestDtoImplFromJson( quantity: (json['quantity'] as num?)?.toInt(), unitPrice: (json['unit_price'] as num?)?.toInt(), notes: json['notes'] as String?, + weight: (json['weight'] as num?)?.toDouble(), ); Map _$$OrderItemRequestDtoImplToJson( @@ -184,6 +193,7 @@ Map _$$OrderItemRequestDtoImplToJson( 'quantity': instance.quantity, 'unit_price': instance.unitPrice, 'notes': instance.notes, + 'weight': instance.weight, }; _$PaymentRequestDtoImpl _$$PaymentRequestDtoImplFromJson( @@ -317,6 +327,7 @@ _$AddItemOrderRequestDtoImpl _$$AddItemOrderRequestDtoImplFromJson( quantity: (json['quantity'] as num?)?.toInt(), unitPrice: (json['unit_price'] as num?)?.toInt(), notes: json['notes'] as String?, + weight: (json['weight'] as num?)?.toDouble(), ); Map _$$AddItemOrderRequestDtoImplToJson( @@ -327,4 +338,5 @@ Map _$$AddItemOrderRequestDtoImplToJson( 'quantity': instance.quantity, 'unit_price': instance.unitPrice, 'notes': instance.notes, + 'weight': instance.weight, }; diff --git a/lib/infrastructure/product/dtos/product_dto.dart b/lib/infrastructure/product/dtos/product_dto.dart index 02ac822..16488cf 100644 --- a/lib/infrastructure/product/dtos/product_dto.dart +++ b/lib/infrastructure/product/dtos/product_dto.dart @@ -24,6 +24,25 @@ class ListProductDto with _$ListProductDto { ); } +/// Satuan yang menempel pada produk. +/// +/// Backend hanya dijamin mengirim `unit_id`; sebagian response ikut menyertakan +/// objek `unit` lengkap. Kalau ada, dipakai langsung supaya POS tidak perlu +/// menunggu master satuan termuat. +@freezed +class ProductUnitDto with _$ProductUnitDto { + const ProductUnitDto._(); + + const factory ProductUnitDto({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "name") String? name, + @JsonKey(name: "abbreviation") String? abbreviation, + }) = _ProductUnitDto; + + factory ProductUnitDto.fromJson(Map json) => + _$ProductUnitDtoFromJson(json); +} + @freezed class ProductDto with _$ProductDto { const ProductDto._(); @@ -46,11 +65,28 @@ class ProductDto with _$ProductDto { @JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "updated_at") String? updatedAt, @JsonKey(name: "variants") List? variants, + @JsonKey(name: "sell_by") String? sellBy, + @JsonKey(name: "unit_id") String? unitId, + @JsonKey(name: "unit_name") String? unitName, + @JsonKey(name: "unit_abbreviation") String? unitAbbreviation, + @JsonKey(name: "unit") ProductUnitDto? unit, }) = _ProductDto; factory ProductDto.fromJson(Map json) => _$ProductDtoFromJson(json); + String get _resolvedUnitId => + (unitId?.isNotEmpty == true ? unitId : unit?.id) ?? ''; + + String get _resolvedUnitName => + (unitName?.isNotEmpty == true ? unitName : unit?.name) ?? ''; + + String get _resolvedUnitAbbreviation => + (unitAbbreviation?.isNotEmpty == true + ? unitAbbreviation + : unit?.abbreviation) ?? + ''; + /// Mapping ke domain Product toDomain() => Product( id: id ?? '', @@ -70,6 +106,10 @@ class ProductDto with _$ProductDto { createdAt: createdAt ?? '', updatedAt: updatedAt ?? '', variants: variants?.map((v) => v.toDomain()).toList() ?? [], + sellBy: SellBy.normalize(sellBy), + unitId: _resolvedUnitId, + unitName: _resolvedUnitName, + unitAbbreviation: _resolvedUnitAbbreviation, ); Map toMap() => { @@ -89,6 +129,10 @@ class ProductDto with _$ProductDto { 'is_active': isActive == true ? 1 : 0, 'created_at': createdAt, 'updated_at': updatedAt, + 'sell_by': SellBy.normalize(sellBy), + 'unit_id': _resolvedUnitId, + 'unit_name': _resolvedUnitName, + 'unit_abbreviation': _resolvedUnitAbbreviation, }; factory ProductDto.fromMap( @@ -117,6 +161,10 @@ class ProductDto with _$ProductDto { createdAt: map['created_at'] as String?, updatedAt: map['updated_at'] as String?, variants: variants, + sellBy: map['sell_by'] as String?, + unitId: map['unit_id'] as String?, + unitName: map['unit_name'] as String?, + unitAbbreviation: map['unit_abbreviation'] as String?, ); factory ProductDto.fromDomain(Product product) => ProductDto( @@ -139,6 +187,10 @@ class ProductDto with _$ProductDto { variants: product.variants .map((v) => ProductVariantDto.fromDomain(v)) .toList(), + sellBy: product.sellBy, + unitId: product.unitId, + unitName: product.unitName, + unitAbbreviation: product.unitAbbreviation, ); } diff --git a/lib/infrastructure/product/product_dtos.dart b/lib/infrastructure/product/product_dtos.dart index 7008875..d02121e 100644 --- a/lib/infrastructure/product/product_dtos.dart +++ b/lib/infrastructure/product/product_dtos.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../common/types/sell_by.dart'; import '../../domain/product/product.dart'; part 'product_dtos.freezed.dart'; diff --git a/lib/infrastructure/product/product_dtos.freezed.dart b/lib/infrastructure/product/product_dtos.freezed.dart index b1707f0..454cf19 100644 --- a/lib/infrastructure/product/product_dtos.freezed.dart +++ b/lib/infrastructure/product/product_dtos.freezed.dart @@ -293,6 +293,223 @@ abstract class _ListProductDto extends ListProductDto { throw _privateConstructorUsedError; } +ProductUnitDto _$ProductUnitDtoFromJson(Map json) { + return _ProductUnitDto.fromJson(json); +} + +/// @nodoc +mixin _$ProductUnitDto { + @JsonKey(name: "id") + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: "name") + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: "abbreviation") + String? get abbreviation => throw _privateConstructorUsedError; + + /// Serializes this ProductUnitDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductUnitDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductUnitDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductUnitDtoCopyWith<$Res> { + factory $ProductUnitDtoCopyWith( + ProductUnitDto value, + $Res Function(ProductUnitDto) then, + ) = _$ProductUnitDtoCopyWithImpl<$Res, ProductUnitDto>; + @useResult + $Res call({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "name") String? name, + @JsonKey(name: "abbreviation") String? abbreviation, + }); +} + +/// @nodoc +class _$ProductUnitDtoCopyWithImpl<$Res, $Val extends ProductUnitDto> + implements $ProductUnitDtoCopyWith<$Res> { + _$ProductUnitDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductUnitDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? abbreviation = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + abbreviation: freezed == abbreviation + ? _value.abbreviation + : abbreviation // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductUnitDtoImplCopyWith<$Res> + implements $ProductUnitDtoCopyWith<$Res> { + factory _$$ProductUnitDtoImplCopyWith( + _$ProductUnitDtoImpl value, + $Res Function(_$ProductUnitDtoImpl) then, + ) = __$$ProductUnitDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "name") String? name, + @JsonKey(name: "abbreviation") String? abbreviation, + }); +} + +/// @nodoc +class __$$ProductUnitDtoImplCopyWithImpl<$Res> + extends _$ProductUnitDtoCopyWithImpl<$Res, _$ProductUnitDtoImpl> + implements _$$ProductUnitDtoImplCopyWith<$Res> { + __$$ProductUnitDtoImplCopyWithImpl( + _$ProductUnitDtoImpl _value, + $Res Function(_$ProductUnitDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductUnitDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? abbreviation = freezed, + }) { + return _then( + _$ProductUnitDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + abbreviation: freezed == abbreviation + ? _value.abbreviation + : abbreviation // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductUnitDtoImpl extends _ProductUnitDto { + const _$ProductUnitDtoImpl({ + @JsonKey(name: "id") this.id, + @JsonKey(name: "name") this.name, + @JsonKey(name: "abbreviation") this.abbreviation, + }) : super._(); + + factory _$ProductUnitDtoImpl.fromJson(Map json) => + _$$ProductUnitDtoImplFromJson(json); + + @override + @JsonKey(name: "id") + final String? id; + @override + @JsonKey(name: "name") + final String? name; + @override + @JsonKey(name: "abbreviation") + final String? abbreviation; + + @override + String toString() { + return 'ProductUnitDto(id: $id, name: $name, abbreviation: $abbreviation)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductUnitDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.abbreviation, abbreviation) || + other.abbreviation == abbreviation)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, abbreviation); + + /// Create a copy of ProductUnitDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductUnitDtoImplCopyWith<_$ProductUnitDtoImpl> get copyWith => + __$$ProductUnitDtoImplCopyWithImpl<_$ProductUnitDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$ProductUnitDtoImplToJson(this); + } +} + +abstract class _ProductUnitDto extends ProductUnitDto { + const factory _ProductUnitDto({ + @JsonKey(name: "id") final String? id, + @JsonKey(name: "name") final String? name, + @JsonKey(name: "abbreviation") final String? abbreviation, + }) = _$ProductUnitDtoImpl; + const _ProductUnitDto._() : super._(); + + factory _ProductUnitDto.fromJson(Map json) = + _$ProductUnitDtoImpl.fromJson; + + @override + @JsonKey(name: "id") + String? get id; + @override + @JsonKey(name: "name") + String? get name; + @override + @JsonKey(name: "abbreviation") + String? get abbreviation; + + /// Create a copy of ProductUnitDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductUnitDtoImplCopyWith<_$ProductUnitDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + ProductDto _$ProductDtoFromJson(Map json) { return _ProductDto.fromJson(json); } @@ -333,6 +550,16 @@ mixin _$ProductDto { String? get updatedAt => throw _privateConstructorUsedError; @JsonKey(name: "variants") List? get variants => throw _privateConstructorUsedError; + @JsonKey(name: "sell_by") + String? get sellBy => throw _privateConstructorUsedError; + @JsonKey(name: "unit_id") + String? get unitId => throw _privateConstructorUsedError; + @JsonKey(name: "unit_name") + String? get unitName => throw _privateConstructorUsedError; + @JsonKey(name: "unit_abbreviation") + String? get unitAbbreviation => throw _privateConstructorUsedError; + @JsonKey(name: "unit") + ProductUnitDto? get unit => throw _privateConstructorUsedError; /// Serializes this ProductDto to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -369,7 +596,14 @@ abstract class $ProductDtoCopyWith<$Res> { @JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "updated_at") String? updatedAt, @JsonKey(name: "variants") List? variants, + @JsonKey(name: "sell_by") String? sellBy, + @JsonKey(name: "unit_id") String? unitId, + @JsonKey(name: "unit_name") String? unitName, + @JsonKey(name: "unit_abbreviation") String? unitAbbreviation, + @JsonKey(name: "unit") ProductUnitDto? unit, }); + + $ProductUnitDtoCopyWith<$Res>? get unit; } /// @nodoc @@ -404,6 +638,11 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto> Object? createdAt = freezed, Object? updatedAt = freezed, Object? variants = freezed, + Object? sellBy = freezed, + Object? unitId = freezed, + Object? unitName = freezed, + Object? unitAbbreviation = freezed, + Object? unit = freezed, }) { return _then( _value.copyWith( @@ -475,10 +714,44 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto> ? _value.variants : variants // ignore: cast_nullable_to_non_nullable as List?, + sellBy: freezed == sellBy + ? _value.sellBy + : sellBy // ignore: cast_nullable_to_non_nullable + as String?, + unitId: freezed == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String?, + unitName: freezed == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String?, + unitAbbreviation: freezed == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String?, + unit: freezed == unit + ? _value.unit + : unit // ignore: cast_nullable_to_non_nullable + as ProductUnitDto?, ) as $Val, ); } + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProductUnitDtoCopyWith<$Res>? get unit { + if (_value.unit == null) { + return null; + } + + return $ProductUnitDtoCopyWith<$Res>(_value.unit!, (value) { + return _then(_value.copyWith(unit: value) as $Val); + }); + } } /// @nodoc @@ -508,7 +781,15 @@ abstract class _$$ProductDtoImplCopyWith<$Res> @JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "updated_at") String? updatedAt, @JsonKey(name: "variants") List? variants, + @JsonKey(name: "sell_by") String? sellBy, + @JsonKey(name: "unit_id") String? unitId, + @JsonKey(name: "unit_name") String? unitName, + @JsonKey(name: "unit_abbreviation") String? unitAbbreviation, + @JsonKey(name: "unit") ProductUnitDto? unit, }); + + @override + $ProductUnitDtoCopyWith<$Res>? get unit; } /// @nodoc @@ -542,6 +823,11 @@ class __$$ProductDtoImplCopyWithImpl<$Res> Object? createdAt = freezed, Object? updatedAt = freezed, Object? variants = freezed, + Object? sellBy = freezed, + Object? unitId = freezed, + Object? unitName = freezed, + Object? unitAbbreviation = freezed, + Object? unit = freezed, }) { return _then( _$ProductDtoImpl( @@ -613,6 +899,26 @@ class __$$ProductDtoImplCopyWithImpl<$Res> ? _value._variants : variants // ignore: cast_nullable_to_non_nullable as List?, + sellBy: freezed == sellBy + ? _value.sellBy + : sellBy // ignore: cast_nullable_to_non_nullable + as String?, + unitId: freezed == unitId + ? _value.unitId + : unitId // ignore: cast_nullable_to_non_nullable + as String?, + unitName: freezed == unitName + ? _value.unitName + : unitName // ignore: cast_nullable_to_non_nullable + as String?, + unitAbbreviation: freezed == unitAbbreviation + ? _value.unitAbbreviation + : unitAbbreviation // ignore: cast_nullable_to_non_nullable + as String?, + unit: freezed == unit + ? _value.unit + : unit // ignore: cast_nullable_to_non_nullable + as ProductUnitDto?, ), ); } @@ -639,6 +945,11 @@ class _$ProductDtoImpl extends _ProductDto { @JsonKey(name: "created_at") this.createdAt, @JsonKey(name: "updated_at") this.updatedAt, @JsonKey(name: "variants") final List? variants, + @JsonKey(name: "sell_by") this.sellBy, + @JsonKey(name: "unit_id") this.unitId, + @JsonKey(name: "unit_name") this.unitName, + @JsonKey(name: "unit_abbreviation") this.unitAbbreviation, + @JsonKey(name: "unit") this.unit, }) : _metadata = metadata, _variants = variants, super._(); @@ -713,9 +1024,25 @@ class _$ProductDtoImpl extends _ProductDto { return EqualUnmodifiableListView(value); } + @override + @JsonKey(name: "sell_by") + final String? sellBy; + @override + @JsonKey(name: "unit_id") + final String? unitId; + @override + @JsonKey(name: "unit_name") + final String? unitName; + @override + @JsonKey(name: "unit_abbreviation") + final String? unitAbbreviation; + @override + @JsonKey(name: "unit") + final ProductUnitDto? unit; + @override String toString() { - return 'ProductDto(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, printToChecker: $printToChecker, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; + return 'ProductDto(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, printToChecker: $printToChecker, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants, sellBy: $sellBy, unitId: $unitId, unitName: $unitName, unitAbbreviation: $unitAbbreviation, unit: $unit)'; } @override @@ -749,12 +1076,19 @@ class _$ProductDtoImpl extends _ProductDto { other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt) && - const DeepCollectionEquality().equals(other._variants, _variants)); + const DeepCollectionEquality().equals(other._variants, _variants) && + (identical(other.sellBy, sellBy) || other.sellBy == sellBy) && + (identical(other.unitId, unitId) || other.unitId == unitId) && + (identical(other.unitName, unitName) || + other.unitName == unitName) && + (identical(other.unitAbbreviation, unitAbbreviation) || + other.unitAbbreviation == unitAbbreviation) && + (identical(other.unit, unit) || other.unit == unit)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( + int get hashCode => Object.hashAll([ runtimeType, id, organizationId, @@ -773,7 +1107,12 @@ class _$ProductDtoImpl extends _ProductDto { createdAt, updatedAt, const DeepCollectionEquality().hash(_variants), - ); + sellBy, + unitId, + unitName, + unitAbbreviation, + unit, + ]); /// Create a copy of ProductDto /// with the given fields replaced by the non-null parameter values. @@ -808,6 +1147,11 @@ abstract class _ProductDto extends ProductDto { @JsonKey(name: "created_at") final String? createdAt, @JsonKey(name: "updated_at") final String? updatedAt, @JsonKey(name: "variants") final List? variants, + @JsonKey(name: "sell_by") final String? sellBy, + @JsonKey(name: "unit_id") final String? unitId, + @JsonKey(name: "unit_name") final String? unitName, + @JsonKey(name: "unit_abbreviation") final String? unitAbbreviation, + @JsonKey(name: "unit") final ProductUnitDto? unit, }) = _$ProductDtoImpl; const _ProductDto._() : super._(); @@ -865,6 +1209,21 @@ abstract class _ProductDto extends ProductDto { @override @JsonKey(name: "variants") List? get variants; + @override + @JsonKey(name: "sell_by") + String? get sellBy; + @override + @JsonKey(name: "unit_id") + String? get unitId; + @override + @JsonKey(name: "unit_name") + String? get unitName; + @override + @JsonKey(name: "unit_abbreviation") + String? get unitAbbreviation; + @override + @JsonKey(name: "unit") + ProductUnitDto? get unit; /// Create a copy of ProductDto /// with the given fields replaced by the non-null parameter values. diff --git a/lib/infrastructure/product/product_dtos.g.dart b/lib/infrastructure/product/product_dtos.g.dart index a25eb29..3756456 100644 --- a/lib/infrastructure/product/product_dtos.g.dart +++ b/lib/infrastructure/product/product_dtos.g.dart @@ -27,6 +27,21 @@ Map _$$ListProductDtoImplToJson( 'total_pages': instance.totalPages, }; +_$ProductUnitDtoImpl _$$ProductUnitDtoImplFromJson(Map json) => + _$ProductUnitDtoImpl( + id: json['id'] as String?, + name: json['name'] as String?, + abbreviation: json['abbreviation'] as String?, + ); + +Map _$$ProductUnitDtoImplToJson( + _$ProductUnitDtoImpl instance, +) => { + 'id': instance.id, + 'name': instance.name, + 'abbreviation': instance.abbreviation, +}; + _$ProductDtoImpl _$$ProductDtoImplFromJson(Map json) => _$ProductDtoImpl( id: json['id'] as String?, @@ -48,6 +63,13 @@ _$ProductDtoImpl _$$ProductDtoImplFromJson(Map json) => variants: (json['variants'] as List?) ?.map((e) => ProductVariantDto.fromJson(e as Map)) .toList(), + sellBy: json['sell_by'] as String?, + unitId: json['unit_id'] as String?, + unitName: json['unit_name'] as String?, + unitAbbreviation: json['unit_abbreviation'] as String?, + unit: json['unit'] == null + ? null + : ProductUnitDto.fromJson(json['unit'] as Map), ); Map _$$ProductDtoImplToJson(_$ProductDtoImpl instance) => @@ -69,6 +91,11 @@ Map _$$ProductDtoImplToJson(_$ProductDtoImpl instance) => 'created_at': instance.createdAt, 'updated_at': instance.updatedAt, 'variants': instance.variants, + 'sell_by': instance.sellBy, + 'unit_id': instance.unitId, + 'unit_name': instance.unitName, + 'unit_abbreviation': instance.unitAbbreviation, + 'unit': instance.unit, }; _$ProductVariantDtoImpl _$$ProductVariantDtoImplFromJson( diff --git a/lib/infrastructure/product/repositories/product_repository.dart b/lib/infrastructure/product/repositories/product_repository.dart index 2cebe38..83e6f81 100644 --- a/lib/infrastructure/product/repositories/product_repository.dart +++ b/lib/infrastructure/product/repositories/product_repository.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:dartz/dartz.dart'; import 'package:injectable/injectable.dart'; +import '../../../common/service/unit_registry.dart'; import '../../../domain/product/product.dart'; import '../datasources/local_data_provider.dart'; import '../datasources/remote_data_provider.dart'; @@ -12,10 +13,38 @@ import '../product_dtos.dart'; class ProductRepository implements IProductRepository { final ProductRemoteDataProvider _remoteDataProvider; final ProductLocalDataProvider _localDataProvider; + final UnitRegistry _unitRegistry; final _logName = 'ProductRepository'; - ProductRepository(this._remoteDataProvider, this._localDataProvider); + ProductRepository( + this._remoteDataProvider, + this._localDataProvider, + this._unitRegistry, + ); + + /// Response produk hanya dijamin membawa `unit_id`. Lengkapi nama dan + /// singkatannya dari master satuan supaya keranjang bisa menulis "4,2 ons". + Product _withUnit(Product product) { + if (!product.isSoldByWeight || product.unitId.isEmpty) return product; + if (product.unitAbbreviation.isNotEmpty && product.unitName.isNotEmpty) { + return product; + } + + final unit = _unitRegistry.byId(product.unitId); + if (unit == null) return product; + + return product.copyWith( + unitName: product.unitName.isNotEmpty ? product.unitName : unit.name, + unitAbbreviation: product.unitAbbreviation.isNotEmpty + ? product.unitAbbreviation + : unit.abbreviation, + ); + } + + List _withUnits(List products) => + products.map(_withUnit).toList(); + @override Future> getProductById(String id) async { @@ -33,7 +62,7 @@ class ProductRepository implements IProductRepository { ); } - final productDomain = product.data!.toDomain(); + final productDomain = _withUnit(product.data!.toDomain()); log('✅ Product loaded: ${productDomain.name}', name: _logName); return Right(productDomain); @@ -85,7 +114,9 @@ class ProductRepository implements IProductRepository { // 🧱 Bangun entity domain ListProduct final result = ListProduct( - products: cachedProducts.data!.map((p) => p.toDomain()).toList(), + products: _withUnits( + cachedProducts.data!.map((p) => p.toDomain()).toList(), + ), totalCount: totalCount, page: page, limit: limit, @@ -132,7 +163,9 @@ class ProductRepository implements IProductRepository { return left(cachedProducts.error!); } - final products = cachedProducts.data!.map((p) => p.toDomain()).toList(); + final products = _withUnits( + cachedProducts.data!.map((p) => p.toDomain()).toList(), + ); final totalCount = await _localDataProvider.getTotalCount( categoryId: categoryId, @@ -180,7 +213,9 @@ class ProductRepository implements IProductRepository { } // ✅ Mapping ke domain entity (kalau hasilnya masih berupa DTO) - final products = results.data!.map((p) => p.toDomain()).toList(); + final products = _withUnits( + results.data!.map((p) => p.toDomain()).toList(), + ); log( '✅ Local search completed: ${products.length} results', @@ -369,7 +404,7 @@ class ProductRepository implements IProductRepository { final products = result.data!.toDomain(); - return right(products); + return right(products.copyWith(products: _withUnits(products.products))); } catch (e, s) { log('getProducts', name: _logName, error: e, stackTrace: s); return left(const ProductFailure.unexpectedError()); diff --git a/lib/infrastructure/unit/datasources/local_data_provider.dart b/lib/infrastructure/unit/datasources/local_data_provider.dart new file mode 100644 index 0000000..66cdcc1 --- /dev/null +++ b/lib/infrastructure/unit/datasources/local_data_provider.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; + +import 'package:injectable/injectable.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../common/constant/local_storage_key.dart'; +import '../unit_dtos.dart'; + +/// Satuan berjumlah sedikit dan jarang berubah, jadi cukup disimpan sebagai +/// satu blob JSON di SharedPreferences — tidak perlu tabel sqflite sendiri. +@injectable +class UnitLocalDataProvider { + final SharedPreferences _sharedPreferences; + + UnitLocalDataProvider(this._sharedPreferences); + + Future saveUnits(List units) async { + final jsonString = jsonEncode(units.map((e) => e.toJson()).toList()); + await _sharedPreferences.setString(LocalStorageKey.units, jsonString); + } + + List getUnits() { + final jsonString = _sharedPreferences.getString(LocalStorageKey.units); + if (jsonString == null || jsonString.isEmpty) return const []; + + final decoded = jsonDecode(jsonString); + if (decoded is! List) return const []; + + return decoded + .whereType>() + .map(UnitDto.fromJson) + .toList(); + } + + Future clearUnits() async { + await _sharedPreferences.remove(LocalStorageKey.units); + } +} diff --git a/lib/infrastructure/unit/datasources/remote_data_provider.dart b/lib/infrastructure/unit/datasources/remote_data_provider.dart new file mode 100644 index 0000000..f4ed8d5 --- /dev/null +++ b/lib/infrastructure/unit/datasources/remote_data_provider.dart @@ -0,0 +1,37 @@ +import 'dart:developer'; + +import 'package:data_channel/data_channel.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../common/api/api_client.dart'; +import '../../../common/api/api_failure.dart'; +import '../../../common/function/app_function.dart'; +import '../../../common/url/api_path.dart'; +import '../../../domain/unit/unit.dart'; +import '../unit_dtos.dart'; + +@injectable +class UnitRemoteDataProvider { + final ApiClient _apiClient; + final _logName = 'UnitRemoteDataProvider'; + + UnitRemoteDataProvider(this._apiClient); + + Future>> fetchUnits() async { + try { + final response = await _apiClient.get( + ApiPath.units, + headers: getAuthorizationHeader(), + ); + + if (response.data['data'] == null) { + return DC.error(const UnitFailure.empty()); + } + + return DC.data(UnitDto.listFromResponse(response.data['data'])); + } on ApiFailure catch (e, s) { + log('fetchUnitsError', name: _logName, error: e, stackTrace: s); + return DC.error(UnitFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/unit/dtos/unit_dto.dart b/lib/infrastructure/unit/dtos/unit_dto.dart new file mode 100644 index 0000000..46ea0d9 --- /dev/null +++ b/lib/infrastructure/unit/dtos/unit_dto.dart @@ -0,0 +1,59 @@ +part of '../unit_dtos.dart'; + +@freezed +class UnitDto with _$UnitDto { + const UnitDto._(); + + const factory UnitDto({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "organization_id") String? organizationId, + @JsonKey(name: "name") String? name, + @JsonKey(name: "abbreviation") String? abbreviation, + @JsonKey(name: "is_active") bool? isActive, + }) = _UnitDto; + + factory UnitDto.fromJson(Map json) => + _$UnitDtoFromJson(json); + + ProductUnit toDomain() => ProductUnit( + id: id ?? '', + organizationId: organizationId ?? '', + name: name ?? '', + abbreviation: abbreviation ?? '', + isActive: isActive ?? true, + ); + + factory UnitDto.fromDomain(ProductUnit unit) => UnitDto( + id: unit.id, + organizationId: unit.organizationId, + name: unit.name, + abbreviation: unit.abbreviation, + isActive: unit.isActive, + ); + + /// Response `/units` belum tentu berbentuk sama di setiap versi backend — + /// bisa `data: [...]` atau `data: { units: [...] }`. Terima keduanya supaya + /// POS tidak kehilangan satuan hanya karena pembungkusnya berbeda. + static List listFromResponse(dynamic data) { + if (data is List) { + return data + .whereType>() + .map(UnitDto.fromJson) + .toList(); + } + + if (data is Map) { + for (final key in const ['units', 'items', 'data']) { + final value = data[key]; + if (value is List) { + return value + .whereType>() + .map(UnitDto.fromJson) + .toList(); + } + } + } + + return const []; + } +} diff --git a/lib/infrastructure/unit/repositories/unit_repository.dart b/lib/infrastructure/unit/repositories/unit_repository.dart new file mode 100644 index 0000000..7073708 --- /dev/null +++ b/lib/infrastructure/unit/repositories/unit_repository.dart @@ -0,0 +1,56 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/unit/unit.dart'; +import '../datasources/local_data_provider.dart'; +import '../datasources/remote_data_provider.dart'; + +@Injectable(as: IUnitRepository) +class UnitRepository implements IUnitRepository { + final UnitRemoteDataProvider _remoteDataProvider; + final UnitLocalDataProvider _localDataProvider; + + final _logName = 'UnitRepository'; + + UnitRepository(this._remoteDataProvider, this._localDataProvider); + + @override + Future>> getUnits({ + bool forceRefresh = false, + }) async { + try { + final cached = _localDataProvider.getUnits(); + + if (!forceRefresh && cached.isNotEmpty) { + log('📏 ${cached.length} satuan dari cache', name: _logName); + return right(cached.map((e) => e.toDomain()).toList()); + } + + final result = await _remoteDataProvider.fetchUnits(); + + if (result.hasError) { + // Offline bukan alasan untuk kehilangan satuan yang sudah pernah ada. + if (cached.isNotEmpty) { + log('⚠️ Gagal ambil satuan, pakai cache', name: _logName); + return right(cached.map((e) => e.toDomain()).toList()); + } + return left(result.error!); + } + + final units = result.data!; + await _localDataProvider.saveUnits(units); + + log('✅ ${units.length} satuan disinkronkan', name: _logName); + return right(units.map((e) => e.toDomain()).toList()); + } catch (e, s) { + log('❌ Error getUnits', name: _logName, error: e, stackTrace: s); + return left(UnitFailure.localStorageError(e.toString())); + } + } + + @override + Future> cachedUnits() async => + _localDataProvider.getUnits().map((e) => e.toDomain()).toList(); +} diff --git a/lib/infrastructure/unit/unit_dtos.dart b/lib/infrastructure/unit/unit_dtos.dart new file mode 100644 index 0000000..060d9bd --- /dev/null +++ b/lib/infrastructure/unit/unit_dtos.dart @@ -0,0 +1,8 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../domain/unit/unit.dart'; + +part 'unit_dtos.freezed.dart'; +part 'unit_dtos.g.dart'; + +part 'dtos/unit_dto.dart'; diff --git a/lib/infrastructure/unit/unit_dtos.freezed.dart b/lib/infrastructure/unit/unit_dtos.freezed.dart new file mode 100644 index 0000000..d7d6a66 --- /dev/null +++ b/lib/infrastructure/unit/unit_dtos.freezed.dart @@ -0,0 +1,280 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'unit_dtos.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +UnitDto _$UnitDtoFromJson(Map json) { + return _UnitDto.fromJson(json); +} + +/// @nodoc +mixin _$UnitDto { + @JsonKey(name: "id") + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: "organization_id") + String? get organizationId => throw _privateConstructorUsedError; + @JsonKey(name: "name") + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: "abbreviation") + String? get abbreviation => throw _privateConstructorUsedError; + @JsonKey(name: "is_active") + bool? get isActive => throw _privateConstructorUsedError; + + /// Serializes this UnitDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of UnitDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UnitDtoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UnitDtoCopyWith<$Res> { + factory $UnitDtoCopyWith(UnitDto value, $Res Function(UnitDto) then) = + _$UnitDtoCopyWithImpl<$Res, UnitDto>; + @useResult + $Res call({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "organization_id") String? organizationId, + @JsonKey(name: "name") String? name, + @JsonKey(name: "abbreviation") String? abbreviation, + @JsonKey(name: "is_active") bool? isActive, + }); +} + +/// @nodoc +class _$UnitDtoCopyWithImpl<$Res, $Val extends UnitDto> + implements $UnitDtoCopyWith<$Res> { + _$UnitDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UnitDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? organizationId = freezed, + Object? name = freezed, + Object? abbreviation = freezed, + Object? isActive = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + organizationId: freezed == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + abbreviation: freezed == abbreviation + ? _value.abbreviation + : abbreviation // ignore: cast_nullable_to_non_nullable + as String?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$UnitDtoImplCopyWith<$Res> implements $UnitDtoCopyWith<$Res> { + factory _$$UnitDtoImplCopyWith( + _$UnitDtoImpl value, + $Res Function(_$UnitDtoImpl) then, + ) = __$$UnitDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "organization_id") String? organizationId, + @JsonKey(name: "name") String? name, + @JsonKey(name: "abbreviation") String? abbreviation, + @JsonKey(name: "is_active") bool? isActive, + }); +} + +/// @nodoc +class __$$UnitDtoImplCopyWithImpl<$Res> + extends _$UnitDtoCopyWithImpl<$Res, _$UnitDtoImpl> + implements _$$UnitDtoImplCopyWith<$Res> { + __$$UnitDtoImplCopyWithImpl( + _$UnitDtoImpl _value, + $Res Function(_$UnitDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UnitDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? organizationId = freezed, + Object? name = freezed, + Object? abbreviation = freezed, + Object? isActive = freezed, + }) { + return _then( + _$UnitDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + organizationId: freezed == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + abbreviation: freezed == abbreviation + ? _value.abbreviation + : abbreviation // ignore: cast_nullable_to_non_nullable + as String?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$UnitDtoImpl extends _UnitDto { + const _$UnitDtoImpl({ + @JsonKey(name: "id") this.id, + @JsonKey(name: "organization_id") this.organizationId, + @JsonKey(name: "name") this.name, + @JsonKey(name: "abbreviation") this.abbreviation, + @JsonKey(name: "is_active") this.isActive, + }) : super._(); + + factory _$UnitDtoImpl.fromJson(Map json) => + _$$UnitDtoImplFromJson(json); + + @override + @JsonKey(name: "id") + final String? id; + @override + @JsonKey(name: "organization_id") + final String? organizationId; + @override + @JsonKey(name: "name") + final String? name; + @override + @JsonKey(name: "abbreviation") + final String? abbreviation; + @override + @JsonKey(name: "is_active") + final bool? isActive; + + @override + String toString() { + return 'UnitDto(id: $id, organizationId: $organizationId, name: $name, abbreviation: $abbreviation, isActive: $isActive)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UnitDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.name, name) || other.name == name) && + (identical(other.abbreviation, abbreviation) || + other.abbreviation == abbreviation) && + (identical(other.isActive, isActive) || + other.isActive == isActive)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + organizationId, + name, + abbreviation, + isActive, + ); + + /// Create a copy of UnitDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UnitDtoImplCopyWith<_$UnitDtoImpl> get copyWith => + __$$UnitDtoImplCopyWithImpl<_$UnitDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$UnitDtoImplToJson(this); + } +} + +abstract class _UnitDto extends UnitDto { + const factory _UnitDto({ + @JsonKey(name: "id") final String? id, + @JsonKey(name: "organization_id") final String? organizationId, + @JsonKey(name: "name") final String? name, + @JsonKey(name: "abbreviation") final String? abbreviation, + @JsonKey(name: "is_active") final bool? isActive, + }) = _$UnitDtoImpl; + const _UnitDto._() : super._(); + + factory _UnitDto.fromJson(Map json) = _$UnitDtoImpl.fromJson; + + @override + @JsonKey(name: "id") + String? get id; + @override + @JsonKey(name: "organization_id") + String? get organizationId; + @override + @JsonKey(name: "name") + String? get name; + @override + @JsonKey(name: "abbreviation") + String? get abbreviation; + @override + @JsonKey(name: "is_active") + bool? get isActive; + + /// Create a copy of UnitDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UnitDtoImplCopyWith<_$UnitDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/unit/unit_dtos.g.dart b/lib/infrastructure/unit/unit_dtos.g.dart new file mode 100644 index 0000000..3a01ef4 --- /dev/null +++ b/lib/infrastructure/unit/unit_dtos.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'unit_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$UnitDtoImpl _$$UnitDtoImplFromJson(Map json) => + _$UnitDtoImpl( + id: json['id'] as String?, + organizationId: json['organization_id'] as String?, + name: json['name'] as String?, + abbreviation: json['abbreviation'] as String?, + isActive: json['is_active'] as bool?, + ); + +Map _$$UnitDtoImplToJson(_$UnitDtoImpl instance) => + { + 'id': instance.id, + 'organization_id': instance.organizationId, + 'name': instance.name, + 'abbreviation': instance.abbreviation, + 'is_active': instance.isActive, + }; diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 47d615c..64ae1e3 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -88,6 +88,8 @@ import 'package:apskel_pos_flutter_v2/common/network/network_client.dart' import 'package:apskel_pos_flutter_v2/common/service/device_info_service.dart' as _i288; import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart' as _i312; +import 'package:apskel_pos_flutter_v2/common/service/unit_registry.dart' + as _i584; import 'package:apskel_pos_flutter_v2/domain/analytic/analytic.dart' as _i346; import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776; import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502; @@ -99,6 +101,7 @@ import 'package:apskel_pos_flutter_v2/domain/payment_method/payment_method.dart' import 'package:apskel_pos_flutter_v2/domain/printer/printer.dart' as _i104; import 'package:apskel_pos_flutter_v2/domain/product/product.dart' as _i44; import 'package:apskel_pos_flutter_v2/domain/table/table.dart' as _i983; +import 'package:apskel_pos_flutter_v2/domain/unit/unit.dart' as _i320; import 'package:apskel_pos_flutter_v2/env.dart' as _i923; import 'package:apskel_pos_flutter_v2/infrastructure/analytic/datasources/remote_data_provider.dart' as _i708; @@ -148,6 +151,12 @@ import 'package:apskel_pos_flutter_v2/infrastructure/table/datasources/remote_da as _i95; import 'package:apskel_pos_flutter_v2/infrastructure/table/repositories/table_repository.dart' as _i824; +import 'package:apskel_pos_flutter_v2/infrastructure/unit/datasources/local_data_provider.dart' + as _i393; +import 'package:apskel_pos_flutter_v2/infrastructure/unit/datasources/remote_data_provider.dart' + as _i1016; +import 'package:apskel_pos_flutter_v2/infrastructure/unit/repositories/unit_repository.dart' + as _i228; import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart' as _i800; import 'package:connectivity_plus/connectivity_plus.dart' as _i895; @@ -229,6 +238,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i693.OutletLocalDatasource>( () => _i693.OutletLocalDatasource(gh<_i460.SharedPreferences>()), ); + gh.factory<_i393.UnitLocalDataProvider>( + () => _i393.UnitLocalDataProvider(gh<_i460.SharedPreferences>()), + ); gh.lazySingleton<_i457.ApiClient>( () => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()), ); @@ -279,6 +291,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i95.TableRemoteDataProvider>( () => _i95.TableRemoteDataProvider(gh<_i457.ApiClient>()), ); + gh.factory<_i1016.UnitRemoteDataProvider>( + () => _i1016.UnitRemoteDataProvider(gh<_i457.ApiClient>()), + ); gh.factory<_i776.IAuthRepository>( () => _i941.AuthRepository( gh<_i370.AuthRemoteDataProvider>(), @@ -291,13 +306,6 @@ extension GetItInjectableX on _i174.GetIt { gh<_i708.CategoryLocalDataProvider>(), ), ); - gh.factory<_i46.LoginFormBloc>( - () => _i46.LoginFormBloc( - gh<_i776.IAuthRepository>(), - gh<_i312.FcmService>(), - gh<_i288.DeviceInfoService>(), - ), - ); gh.factory<_i983.ITableRepository>( () => _i824.TableRepository( gh<_i95.TableRemoteDataProvider>(), @@ -313,12 +321,6 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i424.TableLoaderBloc>( () => _i424.TableLoaderBloc(gh<_i983.ITableRepository>()), ); - gh.factory<_i44.IProductRepository>( - () => _i763.ProductRepository( - gh<_i707.ProductRemoteDataProvider>(), - gh<_i464.ProductLocalDataProvider>(), - ), - ); gh.factory<_i552.IOutletRepository>( () => _i845.OutletRepository( gh<_i132.OutletRemoteDataProvider>(), @@ -342,6 +344,12 @@ extension GetItInjectableX on _i174.GetIt { gh<_i552.IOutletRepository>(), ), ); + gh.factory<_i320.IUnitRepository>( + () => _i228.UnitRepository( + gh<_i1016.UnitRemoteDataProvider>(), + gh<_i393.UnitLocalDataProvider>(), + ), + ); gh.factory<_i143.ICustomerRepository>( () => _i385.CustomerRepository(gh<_i841.CustomerRemoteDataProvider>()), ); @@ -363,6 +371,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i822.VoidFormBloc>( () => _i822.VoidFormBloc(gh<_i299.IOrderRepository>()), ); + gh.lazySingleton<_i584.UnitRegistry>( + () => _i584.UnitRegistry(gh<_i320.IUnitRepository>()), + ); gh.factory<_i683.CustomerLoaderBloc>( () => _i683.CustomerLoaderBloc(gh<_i143.ICustomerRepository>()), ); @@ -381,19 +392,19 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i76.OutletLoaderBloc>( () => _i76.OutletLoaderBloc(gh<_i552.IOutletRepository>()), ); - gh.factory<_i13.ProductLoaderBloc>( - () => _i13.ProductLoaderBloc(gh<_i44.IProductRepository>()), - ); - gh.factory<_i741.SyncBloc>( - () => _i741.SyncBloc( - gh<_i44.IProductRepository>(), - gh<_i502.ICategoryRepository>(), + gh.factory<_i46.LoginFormBloc>( + () => _i46.LoginFormBloc( + gh<_i776.IAuthRepository>(), + gh<_i312.FcmService>(), + gh<_i288.DeviceInfoService>(), + gh<_i584.UnitRegistry>(), ), ); - gh.factory<_i729.SyncSettingBloc>( - () => _i729.SyncSettingBloc( - gh<_i44.IProductRepository>(), - gh<_i502.ICategoryRepository>(), + gh.factory<_i44.IProductRepository>( + () => _i763.ProductRepository( + gh<_i707.ProductRemoteDataProvider>(), + gh<_i464.ProductLocalDataProvider>(), + gh<_i584.UnitRegistry>(), ), ); gh.factory<_i911.CategoryAnalyticLoaderBloc>( @@ -419,6 +430,22 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i413.SalesAnalyticLoaderBloc>( () => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), ); + gh.factory<_i729.SyncSettingBloc>( + () => _i729.SyncSettingBloc( + gh<_i44.IProductRepository>(), + gh<_i502.ICategoryRepository>(), + gh<_i584.UnitRegistry>(), + ), + ); + gh.factory<_i13.ProductLoaderBloc>( + () => _i13.ProductLoaderBloc(gh<_i44.IProductRepository>()), + ); + gh.factory<_i741.SyncBloc>( + () => _i741.SyncBloc( + gh<_i44.IProductRepository>(), + gh<_i502.ICategoryRepository>(), + ), + ); return this; } } diff --git a/lib/main.dart b/lib/main.dart index dc2e5a0..b8c416d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'common/service/fcm_service.dart'; +import 'common/service/unit_registry.dart'; import 'injection.dart'; import 'presentation/app_widget.dart'; @@ -49,6 +50,10 @@ void main() async { // Inisialisasi FCM setelah DI siap await getIt().initialize(); + // Master satuan dimuat dari cache lebih dulu supaya produk timbangan + // langsung punya label satuan, bahkan saat perangkat offline. + unawaited(getIt().load()); + runApp(const AppWidget()); }, (error, stack) { diff --git a/lib/presentation/components/card/order_menu.dart b/lib/presentation/components/card/order_menu.dart index 82fda90..8ed70ff 100644 --- a/lib/presentation/components/card/order_menu.dart +++ b/lib/presentation/components/card/order_menu.dart @@ -5,6 +5,7 @@ import '../../../application/checkout/checkout_form/checkout_form_bloc.dart'; import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../../domain/product/product.dart'; +import '../dialog/dialog.dart'; import '../image/image.dart'; import '../spaces/space.dart'; @@ -26,8 +27,8 @@ class _OrderMenuState extends State { _controller.addListener(() { context.read().add( - CheckoutFormEvent.updateItemNotes( - widget.data.product, + CheckoutFormEvent.updateLineNotes( + widget.data.lineId, _controller.text, ), ); @@ -40,6 +41,29 @@ class _OrderMenuState extends State { super.dispose(); } + void _removeLine() { + context.read().add( + CheckoutFormEvent.removeLine(widget.data.lineId), + ); + } + + /// Koreksi salah timbang: ubah angka pada baris yang sama, bukan menambah + /// baris baru. + void _editWeight() { + final bloc = context.read(); + showDialog( + context: context, + builder: (context) => WeightInputDialog( + product: widget.data.product, + variant: widget.data.variant, + initialWeight: widget.data.weight, + onSubmit: (weight) => bloc.add( + CheckoutFormEvent.updateItemWeight(widget.data.lineId, weight), + ), + ), + ); + } + @override Widget build(BuildContext context) { return Column( @@ -77,9 +101,10 @@ class _OrderMenuState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - (widget.data.product.price + - (widget.data.variant?.priceModifier ?? 0)) - .currencyFormatRp, + widget.data.isWeighted && + widget.data.product.unitLabel.isNotEmpty + ? '${widget.data.unitPrice.currencyFormatRp} / ${widget.data.product.unitLabel}' + : widget.data.unitPrice.currencyFormatRp, ), if (widget.data.variant != null) Text(widget.data.variant?.name ?? ""), @@ -87,60 +112,15 @@ class _OrderMenuState extends State { ), ), ), - Row( - children: [ - GestureDetector( - onTap: () { - context.read().add( - CheckoutFormEvent.removeItem( - widget.data.product, - widget.data.variant, - ), - ); - }, - child: Container( - width: 30, - height: 30, - color: AppColor.white, - child: const Icon( - Icons.remove_circle, - color: AppColor.primary, - ), - ), - ), - SizedBox( - width: 30.0, - child: Center(child: Text(widget.data.quantity.toString())), - ), - GestureDetector( - onTap: () { - context.read().add( - CheckoutFormEvent.addItem( - widget.data.product, - widget.data.variant, - ), - ); - }, - child: Container( - width: 30, - height: 30, - color: AppColor.white, - child: const Icon( - Icons.add_circle, - color: AppColor.primary, - ), - ), - ), - ], - ), + if (widget.data.isWeighted) + _buildWeightControl() + else + _buildQuantityStepper(), const SpaceWidth(8), SizedBox( width: 80.0, child: Text( - ((widget.data.product.price + - (widget.data.variant?.priceModifier ?? 0)) * - widget.data.quantity) - .currencyFormatRp, + widget.data.lineTotal.currencyFormatRp, textAlign: TextAlign.right, style: const TextStyle( color: AppColor.primary, @@ -167,14 +147,7 @@ class _OrderMenuState extends State { ), const SpaceWidth(16.0), GestureDetector( - onTap: () { - context.read().add( - CheckoutFormEvent.removeItem( - widget.data.product, - widget.data.variant, - ), - ); - }, + onTap: _removeLine, child: Container( height: 40, width: 40, @@ -194,4 +167,77 @@ class _OrderMenuState extends State { ], ); } + + /// Baris berbobot tidak punya stepper — nilainya bukan cacah, dan angka 1 + /// yang tersimpan di `quantity` akan terbaca seperti "satu ons". + Widget _buildWeightControl() { + return GestureDetector( + onTap: _editWeight, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.08), + borderRadius: BorderRadius.circular(8.0), + border: Border.all(color: AppColor.primary.withOpacity(0.4)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.data.quantityLabel, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + const SpaceWidth(4), + const Icon(Icons.edit_outlined, size: 14, color: AppColor.primary), + ], + ), + ), + ); + } + + Widget _buildQuantityStepper() { + return Row( + children: [ + GestureDetector( + onTap: () { + context.read().add( + CheckoutFormEvent.removeItem( + widget.data.product, + widget.data.variant, + ), + ); + }, + child: Container( + width: 30, + height: 30, + color: AppColor.white, + child: const Icon(Icons.remove_circle, color: AppColor.primary), + ), + ), + SizedBox( + width: 30.0, + child: Center(child: Text(widget.data.quantity.toString())), + ), + GestureDetector( + onTap: () { + context.read().add( + CheckoutFormEvent.addItem( + widget.data.product, + widget.data.variant, + ), + ); + }, + child: Container( + width: 30, + height: 30, + color: AppColor.white, + child: const Icon(Icons.add_circle, color: AppColor.primary), + ), + ), + ], + ); + } } diff --git a/lib/presentation/components/card/product_card.dart b/lib/presentation/components/card/product_card.dart index de9564b..92e3cc5 100644 --- a/lib/presentation/components/card/product_card.dart +++ b/lib/presentation/components/card/product_card.dart @@ -17,18 +17,35 @@ class ProductCard extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: () { - if (product.isActive == true) { - if (product.variants.isEmpty) { - context.read().add( - CheckoutFormEvent.addItem(product, null), - ); - } else { - showDialog( - context: context, - builder: (context) => VariantDialog(product: product), - ); - } + if (product.isActive != true) return; + + if (product.variants.isNotEmpty) { + showDialog( + context: context, + builder: (context) => VariantDialog(product: product), + ); + return; } + + // Produk timbangan tidak bisa langsung masuk keranjang — kasir harus + // menimbang dulu, dan tiap penimbangan jadi barisnya sendiri. + if (product.isSoldByWeight) { + final bloc = context.read(); + showDialog( + context: context, + builder: (context) => WeightInputDialog( + product: product, + onSubmit: (weight) => bloc.add( + CheckoutFormEvent.addWeightedItem(product, null, weight), + ), + ), + ); + return; + } + + context.read().add( + CheckoutFormEvent.addItem(product, null), + ); }, child: Container( decoration: BoxDecoration( @@ -75,7 +92,11 @@ class ProductCard extends StatelessWidget { Align( alignment: Alignment.center, child: Text( - product.price.currencyFormatRp, + product.isSoldByWeight + ? '${product.price.currencyFormatRp} ${product.priceUnitLabel}' + .trim() + : product.price.currencyFormatRp, + textAlign: TextAlign.center, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 12, @@ -89,31 +110,39 @@ class ProductCard extends StatelessWidget { BlocBuilder( builder: (context, state) { - final totalQuantity = state.items + final lines = state.items .where((item) => item.product.id == product.id) - .map((item) => item.quantity) - .fold(0, (sum, qty) => sum + qty); + .toList(); + + if (lines.isEmpty) return const SizedBox.shrink(); + + // Untuk produk timbangan, jumlah baris tidak berarti apa-apa + // (selalu 1 per penimbangan) — yang informatif adalah total + // berat yang sudah masuk keranjang. + final label = product.isSoldByWeight + ? '${lines.fold(0, (sum, item) => sum + (item.weight ?? 0)).weightFormat} ${product.unitLabel}' + .trim() + : lines + .fold(0, (sum, item) => sum + item.quantity) + .toString(); - if (totalQuantity == 0) { - return const SizedBox.shrink(); - } return Positioned( top: 4, right: 4, child: Container( - width: 40, + constraints: const BoxConstraints(minWidth: 40), height: 40, - padding: const EdgeInsets.all(6), + padding: const EdgeInsets.symmetric(horizontal: 6), decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(9.0)), color: AppColor.primary, ), child: Center( child: Text( - totalQuantity.toString(), - style: const TextStyle( + label, + style: TextStyle( color: Colors.white, - fontSize: 20, + fontSize: product.isSoldByWeight ? 13 : 20, fontWeight: FontWeight.bold, ), ), diff --git a/lib/presentation/components/dialog/dialog.dart b/lib/presentation/components/dialog/dialog.dart index 840bfdb..2d84814 100644 --- a/lib/presentation/components/dialog/dialog.dart +++ b/lib/presentation/components/dialog/dialog.dart @@ -24,6 +24,7 @@ import '../spaces/space.dart'; part 'custom_modal_dialog.dart'; part 'outlet_dialog.dart'; part 'variant_dialog.dart'; +part 'weight_input_dialog.dart'; part 'delivery_dialog.dart'; part 'order_type_dialog.dart'; part 'printer_bluetooth_dialog.dart'; diff --git a/lib/presentation/components/dialog/order/order_save_confirm_dialog.dart b/lib/presentation/components/dialog/order/order_save_confirm_dialog.dart index a583a45..cd4e241 100644 --- a/lib/presentation/components/dialog/order/order_save_confirm_dialog.dart +++ b/lib/presentation/components/dialog/order/order_save_confirm_dialog.dart @@ -163,7 +163,9 @@ class OrderSaveConfirmDialog extends StatelessWidget { overflow: TextOverflow.ellipsis, ), Text( - '${item.quantity} qty', + item.isWeighted + ? item.quantityLabel + : '${item.quantity} qty', style: AppStyle.sm.copyWith( color: AppColor.textSecondary, ), @@ -185,8 +187,7 @@ class OrderSaveConfirmDialog extends StatelessWidget { ), ), child: Text( - ((item.product.price) * item.quantity) - .currencyFormatRpV2, + item.lineTotal.currencyFormatRpV2, style: AppStyle.md.copyWith( fontWeight: FontWeight.bold, color: AppColor.primary, diff --git a/lib/presentation/components/dialog/variant_dialog.dart b/lib/presentation/components/dialog/variant_dialog.dart index 2d70727..7234075 100644 --- a/lib/presentation/components/dialog/variant_dialog.dart +++ b/lib/presentation/components/dialog/variant_dialog.dart @@ -21,10 +21,33 @@ class VariantDialog extends StatelessWidget { return GestureDetector( onTap: () { // Aksi saat varian dipilih - context.maybePop(); - context.read().add( - CheckoutFormEvent.addItem(product, variant), - ); + final bloc = context.read(); + // Context dialog ini mati begitu di-pop, jadi dialog lanjutan + // dibuka lewat context milik Navigator. + final navigator = Navigator.of(context, rootNavigator: true); + navigator.pop(); + + // Produk timbangan lanjut ke papan angka; varian hanya mengubah + // harga per satuannya. + if (product.isSoldByWeight) { + showDialog( + context: navigator.context, + builder: (context) => WeightInputDialog( + product: product, + variant: variant, + onSubmit: (weight) => bloc.add( + CheckoutFormEvent.addWeightedItem( + product, + variant, + weight, + ), + ), + ), + ); + return; + } + + bloc.add(CheckoutFormEvent.addItem(product, variant)); }, child: Container( width: (context.deviceWidth * 0.4 - 12 - 32) / 2 - 6, // 2 per row diff --git a/lib/presentation/components/dialog/weight_input_dialog.dart b/lib/presentation/components/dialog/weight_input_dialog.dart new file mode 100644 index 0000000..63bef8d --- /dev/null +++ b/lib/presentation/components/dialog/weight_input_dialog.dart @@ -0,0 +1,267 @@ +part of 'dialog.dart'; + +/// Papan angka desimal untuk produk yang dijual per timbangan. +/// +/// Satu kali dialog ini ditutup dengan angka = satu penimbangan = satu baris +/// keranjang. Untuk mengoreksi salah timbang, buka lagi baris yang sama lewat +/// [initialWeight] alih-alih menambah baris baru. +class WeightInputDialog extends StatefulWidget { + final Product product; + final ProductVariant? variant; + final double? initialWeight; + final ValueChanged onSubmit; + + const WeightInputDialog({ + super.key, + required this.product, + required this.onSubmit, + this.variant, + this.initialWeight, + }); + + /// Berat terkecil yang diterima backend; di bawah ini membulat ke nol. + static const double minWeight = 0.001; + + /// Backend membulatkan berat ke 3 desimal. + static const int maxDecimals = 3; + + @override + State createState() => _WeightInputDialogState(); +} + +class _WeightInputDialogState extends State { + /// Angka disimpan sebagai teks supaya "4," dan "4,20" bisa diketik utuh + /// tanpa dinormalisasi di tengah pengetikan. + late String _input = _formatInitial(widget.initialWeight); + + static String _formatInitial(double? weight) { + if (weight == null || weight <= 0) return ''; + return weight + .toStringAsFixed(WeightInputDialog.maxDecimals) + .replaceFirst(RegExp(r'\.?0+$'), '') + .replaceAll('.', ','); + } + + double get _weight => double.tryParse(_input.replaceAll(',', '.')) ?? 0; + + bool get _isValid => _weight >= WeightInputDialog.minWeight; + + double get _unitPrice => + widget.product.price + (widget.variant?.priceModifier ?? 0); + + String get _unitLabel => widget.product.unitLabel; + + void _tapDigit(String digit) { + final separatorIndex = _input.indexOf(','); + if (separatorIndex != -1 && + _input.length - separatorIndex - 1 >= WeightInputDialog.maxDecimals) { + return; + } + + setState(() { + // Hindari "007": nol di depan hanya berarti sesuatu sebelum koma. + _input = _input == '0' ? digit : '$_input$digit'; + }); + } + + void _tapSeparator() { + if (_input.contains(',')) return; + setState(() => _input = _input.isEmpty ? '0,' : '$_input,'); + } + + void _tapBackspace() { + if (_input.isEmpty) return; + setState(() => _input = _input.substring(0, _input.length - 1)); + } + + void _tapClear() => setState(() => _input = ''); + + void _submit() { + if (!_isValid) return; + final rounded = double.parse( + _weight.toStringAsFixed(WeightInputDialog.maxDecimals), + ); + context.maybePop(); + widget.onSubmit(rounded); + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: widget.product.name, + subtitle: _unitLabel.isEmpty + ? 'Masukkan berat' + : 'Masukkan berat dalam $_unitLabel', + minWidth: 380, + maxWidth: 420, + minHeight: 0, + contentPadding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildDisplay(), + const SpaceHeight(16), + _buildKeypad(), + const SpaceHeight(16), + AppElevatedButton.filled( + borderRadius: 12, + disabled: !_isValid, + onPressed: _submit, + label: widget.initialWeight == null + ? 'Tambah ke Pesanan' + : 'Simpan Berat', + ), + if (_input.isNotEmpty && !_isValid) ...[ + const SpaceHeight(8), + Text( + 'Berat minimal 0,001${_unitLabel.isEmpty ? '' : ' $_unitLabel'}', + textAlign: TextAlign.center, + style: AppStyle.sm.copyWith(color: AppColor.error), + ), + ], + ], + ), + ); + } + + Widget _buildDisplay() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.06), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.primary.withOpacity(0.3)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Expanded( + child: Text( + _input.isEmpty ? '0' : _input, + textAlign: TextAlign.right, + style: AppStyle.xxl.copyWith( + fontSize: 40, + fontWeight: FontWeight.bold, + color: _input.isEmpty + ? AppColor.textSecondary + : AppColor.black, + ), + ), + ), + if (_unitLabel.isNotEmpty) ...[ + const SpaceWidth(8), + Text( + _unitLabel, + style: AppStyle.lg.copyWith(color: AppColor.textSecondary), + ), + ], + ], + ), + const SpaceHeight(4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${_unitPrice.currencyFormatRpV2}' + '${_unitLabel.isEmpty ? '' : ' / $_unitLabel'}', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + Text( + (_unitPrice * _weight).currencyFormatRpV2, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildKeypad() { + const keys = [ + ['1', '2', '3'], + ['4', '5', '6'], + ['7', '8', '9'], + [',', '0', 'hapus'], + ]; + + return Column( + children: [ + for (final row in keys) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + for (final key in row) + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: _KeypadButton( + label: key, + onTap: () { + switch (key) { + case ',': + _tapSeparator(); + case 'hapus': + _tapBackspace(); + default: + _tapDigit(key); + } + }, + // Tahan lama untuk mengosongkan angka sekaligus. + onLongPress: key == 'hapus' ? _tapClear : null, + ), + ), + ), + ], + ), + ), + ], + ); + } +} + +class _KeypadButton extends StatelessWidget { + final String label; + final VoidCallback onTap; + final VoidCallback? onLongPress; + + const _KeypadButton({ + required this.label, + required this.onTap, + this.onLongPress, + }); + + @override + Widget build(BuildContext context) { + final isBackspace = label == 'hapus'; + + return InkWell( + onTap: onTap, + onLongPress: onLongPress, + borderRadius: BorderRadius.circular(12), + child: Container( + height: 56, + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: isBackspace + ? const Icon(Icons.backspace_outlined, color: AppColor.primary) + : Text( + label, + style: AppStyle.xxl.copyWith(fontWeight: FontWeight.w600), + ), + ), + ); + } +} diff --git a/lib/presentation/components/print/print_ui.dart b/lib/presentation/components/print/print_ui.dart index 7dc58c0..74eeca8 100644 --- a/lib/presentation/components/print/print_ui.dart +++ b/lib/presentation/components/print/print_ui.dart @@ -77,6 +77,7 @@ class PrintUi { bytes += builder.orderItem( productName: item.productName, quantity: item.quantity, + quantityLabel: item.isWeighted ? item.quantityLabel : null, unitPrice: item.unitPrice.currencyFormatRpV2, totalPrice: item.totalPrice.currencyFormatRpV2, variantName: item.productVariantName, @@ -140,7 +141,7 @@ class PrintUi { // Items — qty NAMA (uppercase), variant indent for (final item in order.orderItems.where((e) => e.printToChecker)) { final name = item.productName.toUpperCase(); - bytes += builder.itemText('${item.quantity} $name'); + bytes += builder.itemText('${item.quantityLabel} $name'); if (item.productVariantName.isNotEmpty) { bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false); } @@ -196,7 +197,7 @@ class PrintUi { // Single item final name = item.productName.toUpperCase(); - bytes += builder.itemText('${item.quantity} $name'); + bytes += builder.itemText('${item.quantityLabel} $name'); if (item.productVariantName.isNotEmpty) { bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false); } @@ -252,7 +253,7 @@ class PrintUi { // Single item final name = item.productName.toUpperCase(); - bytes += builder.itemText('${item.quantity} $name'); + bytes += builder.itemText('${item.quantityLabel} $name'); if (item.productVariantName.isNotEmpty) { bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false); } @@ -359,6 +360,7 @@ class PrintUi { bytes += builder.orderItem( productName: item.productName, quantity: item.quantity, + quantityLabel: item.isWeighted ? item.quantityLabel : null, unitPrice: item.unitPrice.currencyFormatRpV2, totalPrice: item.totalPrice.currencyFormatRpV2, variantName: item.productVariantName, @@ -439,6 +441,7 @@ class PrintUi { bytes += builder.orderItem( productName: item.productName, quantity: item.quantity, + quantityLabel: item.isWeighted ? item.quantityLabel : null, unitPrice: item.unitPrice.currencyFormatRpV2, totalPrice: item.totalPrice.currencyFormatRpV2, variantName: item.productVariantName, @@ -530,6 +533,7 @@ class PrintUi { bytes += builder.orderItem( productName: item.productName, quantity: item.quantity, + quantityLabel: item.isWeighted ? item.quantityLabel : null, unitPrice: item.unitPrice.currencyFormatRpV2, totalPrice: item.totalPrice.currencyFormatRpV2, variantName: item.productVariantName, diff --git a/lib/presentation/components/print/receipt_component_builder.dart b/lib/presentation/components/print/receipt_component_builder.dart index a0f3081..3df052a 100644 --- a/lib/presentation/components/print/receipt_component_builder.dart +++ b/lib/presentation/components/print/receipt_component_builder.dart @@ -301,6 +301,10 @@ class ReceiptComponentBuilder { List orderItem({ required String productName, required int quantity, + + /// Untuk baris berbobot, isi dengan "4,2 ons" — angka quantity-nya selalu + /// 1 dan akan salah dibaca di struk. + String? quantityLabel, required String unitPrice, required String totalPrice, String? variantName, @@ -314,7 +318,10 @@ class ReceiptComponentBuilder { ? '$productName ($variantName)' : productName; bytes += textLeft(displayName, bold: true, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); - bytes += row2Columns('${quantity}x $unitPrice', totalPrice, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); + final quantityText = quantityLabel != null && quantityLabel.isNotEmpty + ? '$quantityLabel x $unitPrice' + : '${quantity}x $unitPrice'; + bytes += row2Columns(quantityText, totalPrice, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); if (notes != null && notes.isNotEmpty) { bytes += row2Columns('Note', notes, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth, leftWidth: 4, rightWidth: 8); } diff --git a/lib/presentation/pages/checkout/checkout_page.dart b/lib/presentation/pages/checkout/checkout_page.dart index fffd0a0..614e33c 100644 --- a/lib/presentation/pages/checkout/checkout_page.dart +++ b/lib/presentation/pages/checkout/checkout_page.dart @@ -7,6 +7,7 @@ import '../../../application/order/order_form/order_form_bloc.dart'; import '../../../application/payment_method/payment_method_loader/payment_method_loader_bloc.dart'; import '../../../application/table/table_loader/table_loader_bloc.dart'; import '../../../common/theme/theme.dart'; +import '../../../domain/product/product.dart'; import '../../../injection.dart'; import '../../components/spaces/space.dart'; import '../../components/toast/flushbar.dart'; @@ -86,14 +87,7 @@ class CheckoutPage extends StatelessWidget implements AutoRouteWrapper { tag: 'checkout_screen', child: BlocBuilder( builder: (context, state) { - final price = state.items.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price.toInt() + - (element.variant?.priceModifier.toInt() ?? 0)) * - element.quantity, - ); + final price = state.items.cartTotal.toInt(); return Scaffold( backgroundColor: AppColor.white, diff --git a/lib/presentation/pages/main/pages/home/widgets/home_right_panel.dart b/lib/presentation/pages/main/pages/home/widgets/home_right_panel.dart index 2b042ca..da5996c 100644 --- a/lib/presentation/pages/main/pages/home/widgets/home_right_panel.dart +++ b/lib/presentation/pages/main/pages/home/widgets/home_right_panel.dart @@ -7,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../../../application/checkout/checkout_form/checkout_form_bloc.dart'; import '../../../../../../common/extension/extension.dart'; import '../../../../../../common/theme/theme.dart'; +import '../../../../../../domain/product/product.dart'; import '../../../../../components/button/button.dart'; import '../../../../../components/card/order_menu.dart'; import '../../../../../components/spaces/space.dart'; @@ -21,15 +22,7 @@ class HomeRightPanel extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final price = state.items.isEmpty - ? 0.0 - : state.items - .map( - (e) => - (e.product.price + (e.variant?.priceModifier ?? 0)) * - e.quantity, - ) - .reduce((value, element) => value + element); + final price = state.items.cartTotal; log('🛒 Total Price: $price'); return Align( alignment: Alignment.topCenter, @@ -87,8 +80,12 @@ class HomeRightPanel extends StatelessWidget { : ListView.separated( shrinkWrap: true, padding: const EdgeInsets.symmetric(horizontal: 16), - itemBuilder: (context, index) => - OrderMenu(data: state.items[index]), + itemBuilder: (context, index) => OrderMenu( + // Baris berbobot bisa dihapus satu per satu, jadi + // state tiap baris harus terikat ke identitasnya. + key: ValueKey(state.items[index].lineId), + data: state.items[index], + ), separatorBuilder: (context, index) => const SpaceHeight(1.0), itemCount: state.items.length, diff --git a/lib/presentation/pages/order/pages/success_add_item_order/widgets/success_add_item_order_right_panel.dart b/lib/presentation/pages/order/pages/success_add_item_order/widgets/success_add_item_order_right_panel.dart index 7cbb1b5..003cb77 100644 --- a/lib/presentation/pages/order/pages/success_add_item_order/widgets/success_add_item_order_right_panel.dart +++ b/lib/presentation/pages/order/pages/success_add_item_order/widgets/success_add_item_order_right_panel.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../../../../../application/checkout/checkout_form/checkout_form_bloc.dart'; import '../../../../../../common/extension/extension.dart'; import '../../../../../../common/theme/theme.dart'; +import '../../../../../../domain/product/product.dart'; import '../../../../../components/spaces/space.dart'; class SuccessAddItemOrderRightPanel extends StatelessWidget { @@ -86,14 +87,7 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget { ], ), Text( - (checkoutState.items.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price.toInt() + - (element.variant?.priceModifier.toInt() ?? 0)) * - element.quantity, - )).toString().currencyFormatRpV2, + checkoutState.items.cartTotal.toInt().toString().currencyFormatRpV2, style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), ), ], @@ -165,15 +159,10 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget { ], ), child: Text( - (checkoutState.items.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price.toInt() + - (element.variant?.priceModifier.toInt() ?? - 0)) * - element.quantity, - )).toString().currencyFormatRpV2, + checkoutState.items.cartTotal + .toInt() + .toString() + .currencyFormatRpV2, style: AppStyle.xl.copyWith( fontWeight: FontWeight.bold, color: Colors.white, @@ -270,7 +259,7 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Text( - '${item.quantity}x', + (item.isWeighted ? item.quantityLabel : "${item.quantity}x"), style: AppStyle.md.copyWith( fontWeight: FontWeight.bold, color: Colors.white, @@ -279,11 +268,7 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget { ), const SpaceHeight(8), Text( - ((item.product.price.toInt() + - (item.variant?.priceModifier.toInt() ?? 0)) * - item.quantity) - .toString() - .currencyFormatRpV2, + item.lineTotal.toInt().toString().currencyFormatRpV2, style: AppStyle.lg.copyWith( fontWeight: FontWeight.bold, color: AppColor.primary, diff --git a/lib/presentation/pages/order/pages/success_order/widgets/success_order_right_panel.dart b/lib/presentation/pages/order/pages/success_order/widgets/success_order_right_panel.dart index 1044535..f19a74c 100644 --- a/lib/presentation/pages/order/pages/success_order/widgets/success_order_right_panel.dart +++ b/lib/presentation/pages/order/pages/success_order/widgets/success_order_right_panel.dart @@ -255,7 +255,7 @@ class SuccessOrderRightPanel extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Text( - '${item.quantity}x', + (item.isWeighted ? item.quantityLabel : "${item.quantity}x"), style: AppStyle.md.copyWith( fontWeight: FontWeight.bold, color: Colors.white, diff --git a/lib/presentation/pages/order/widgets/order_list.dart b/lib/presentation/pages/order/widgets/order_list.dart index f513c3f..789c3ce 100644 --- a/lib/presentation/pages/order/widgets/order_list.dart +++ b/lib/presentation/pages/order/widgets/order_list.dart @@ -211,7 +211,9 @@ class OrderList extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), child: Text( - 'x${product.quantity}', + (product.isWeighted + ? product.quantityLabel + : "x${product.quantity}"), style: AppStyle.md.copyWith( fontWeight: FontWeight.w600, color: AppColor.primary, diff --git a/lib/presentation/pages/payment/pages/payment_success/widgets/payment_success_right_panel.dart b/lib/presentation/pages/payment/pages/payment_success/widgets/payment_success_right_panel.dart index 4fe6b5f..ca0c86a 100644 --- a/lib/presentation/pages/payment/pages/payment_success/widgets/payment_success_right_panel.dart +++ b/lib/presentation/pages/payment/pages/payment_success/widgets/payment_success_right_panel.dart @@ -255,7 +255,7 @@ class PaymentSuccessRightPanel extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Text( - '${item.quantity}x', + (item.isWeighted ? item.quantityLabel : "${item.quantity}x"), style: AppStyle.md.copyWith( fontWeight: FontWeight.bold, color: Colors.white, diff --git a/lib/presentation/pages/payment/pages/payment_success_split_bill/widgets/payment_success_split_bill_right_panel.dart b/lib/presentation/pages/payment/pages/payment_success_split_bill/widgets/payment_success_split_bill_right_panel.dart index 1372e06..a288d4c 100644 --- a/lib/presentation/pages/payment/pages/payment_success_split_bill/widgets/payment_success_split_bill_right_panel.dart +++ b/lib/presentation/pages/payment/pages/payment_success_split_bill/widgets/payment_success_split_bill_right_panel.dart @@ -118,7 +118,7 @@ class PaymentSuccessSplitBillRightPanel extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Text( - '${item.quantity}x', + (item.isWeighted ? item.quantityLabel : "${item.quantity}x"), style: AppStyle.md.copyWith( fontWeight: FontWeight.bold, color: Colors.white, diff --git a/lib/presentation/pages/payment/widgets/payment_left_panel.dart b/lib/presentation/pages/payment/widgets/payment_left_panel.dart index 450965f..75b7236 100644 --- a/lib/presentation/pages/payment/widgets/payment_left_panel.dart +++ b/lib/presentation/pages/payment/widgets/payment_left_panel.dart @@ -65,7 +65,9 @@ class PaymentLeftPanel extends StatelessWidget { ), ), subtitle: Text( - 'Qty: ${item.quantity} | ${item.productVariantName}', + item.isWeighted + ? '${item.quantityLabel} | ${item.productVariantName}' + : 'Qty: ${item.quantity} | ${item.productVariantName}', style: AppStyle.sm, ), trailing: Text( diff --git a/lib/presentation/pages/refund/pages/refund_success/widgets/refund_success_right_panel.dart b/lib/presentation/pages/refund/pages/refund_success/widgets/refund_success_right_panel.dart index 6227e48..5fdaef1 100644 --- a/lib/presentation/pages/refund/pages/refund_success/widgets/refund_success_right_panel.dart +++ b/lib/presentation/pages/refund/pages/refund_success/widgets/refund_success_right_panel.dart @@ -255,7 +255,7 @@ class RefundSuccessRightPanel extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Text( - '${item.quantity}x', + (item.isWeighted ? item.quantityLabel : "${item.quantity}x"), style: AppStyle.md.copyWith( fontWeight: FontWeight.bold, color: Colors.white, diff --git a/lib/presentation/pages/refund/widgets/refund_left_panel.dart b/lib/presentation/pages/refund/widgets/refund_left_panel.dart index 0f558ca..6763f78 100644 --- a/lib/presentation/pages/refund/widgets/refund_left_panel.dart +++ b/lib/presentation/pages/refund/widgets/refund_left_panel.dart @@ -62,7 +62,9 @@ class RefundLeftPanel extends StatelessWidget { ), ), Text( - 'Qty: ${item.quantity} x Rp ${item.unitPrice.currencyFormatRpV2} ', + item.isWeighted + ? item.quantityPriceLabel + : 'Qty: ${item.quantity} x ${item.unitPrice.currencyFormatRpV2}', style: AppStyle.md.copyWith( color: AppColor.textSecondary, ), diff --git a/lib/presentation/pages/split_bill/widgets/split_bill_left_panel.dart b/lib/presentation/pages/split_bill/widgets/split_bill_left_panel.dart index 5e5e5da..aeaefa7 100644 --- a/lib/presentation/pages/split_bill/widgets/split_bill_left_panel.dart +++ b/lib/presentation/pages/split_bill/widgets/split_bill_left_panel.dart @@ -160,7 +160,9 @@ class SplitBillLeftPanel extends StatelessWidget { ), ), Text( - 'Qty: ${item.quantity} x Rp ${item.unitPrice.currencyFormatRpV2} ', + item.isWeighted + ? item.quantityPriceLabel + : 'Qty: ${item.quantity} x ${item.unitPrice.currencyFormatRpV2}', style: AppStyle.md.copyWith( color: AppColor.textSecondary, ), diff --git a/lib/presentation/pages/split_bill/widgets/split_bill_right_panel.dart b/lib/presentation/pages/split_bill/widgets/split_bill_right_panel.dart index c71daa6..c9d2600 100644 --- a/lib/presentation/pages/split_bill/widgets/split_bill_right_panel.dart +++ b/lib/presentation/pages/split_bill/widgets/split_bill_right_panel.dart @@ -332,97 +332,148 @@ class _SplitBillRightPanelState extends State { ), ), Text( - '${item.unitPrice.currencyFormatRpV2} per item', + item.isWeighted + ? '${item.unitPrice.currencyFormatRpV2} per ${item.unitLabel}' + : '${item.unitPrice.currencyFormatRpV2} per item', style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), ], ), ), - Row( - children: [ - GestureDetector( - onTap: () { - if (selectedQty > 0) { - setState(() { - context.read().add( - SplitBillFormEvent.itemQuantityChanged( - itemId: item.id, - quantity: selectedQty - 1, - ), - ); - }); - } - }, - child: Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: selectedQty > 0 ? AppColor.primary : AppColor.border, - borderRadius: BorderRadius.circular(4), - ), - child: Icon( - Icons.remove, - size: 16, - color: selectedQty > 0 - ? Colors.white - : AppColor.textSecondary, - ), - ), - ), - Container( - width: 40, - height: 32, - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - border: Border.all(color: AppColor.border), - borderRadius: BorderRadius.circular(4), - ), - child: Center( - child: Text( - '$selectedQty', - style: AppStyle.md.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - ), - ), - GestureDetector( - onTap: () { - if (selectedQty < maxQty) { - context.read().add( - SplitBillFormEvent.itemQuantityChanged( - itemId: item.id, - quantity: selectedQty + 1, - ), - ); - } - }, - child: Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: selectedQty < maxQty - ? AppColor.primary - : AppColor.border, - borderRadius: BorderRadius.circular(4), - ), - child: Icon( - Icons.add, - size: 16, - color: selectedQty < maxQty - ? Colors.white - : AppColor.textSecondary, - ), - ), - ), - ], - ), + // Baris berbobot hanya punya dua keadaan: belum dibayar atau lunas. + // Tidak ada nilai di antaranya, jadi stepper diganti tombol pilih. + if (item.isWeighted) + _buildWeightedLineSelector(item, selectedQty > 0, maxQty) + else + _buildQuantityStepper(item, selectedQty, maxQty), ], ), ); } + Widget _buildWeightedLineSelector(OrderItem item, bool selected, int maxQty) { + final canSelect = selected || maxQty > 0; + + return GestureDetector( + onTap: () { + if (!canSelect) return; + context.read().add( + SplitBillFormEvent.itemQuantityChanged( + itemId: item.id, + quantity: selected ? 0 : 1, + ), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: selected ? AppColor.primary : Colors.transparent, + border: Border.all( + color: canSelect ? AppColor.primary : AppColor.border, + ), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + selected ? Icons.check_circle : Icons.radio_button_unchecked, + size: 16, + color: selected ? Colors.white : AppColor.textSecondary, + ), + const SizedBox(width: 6), + Text( + selected ? 'Dibayar' : 'Bayar baris', + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + color: selected ? Colors.white : AppColor.textSecondary, + ), + ), + ], + ), + ), + ); + } + + Widget _buildQuantityStepper(OrderItem item, int selectedQty, int maxQty) { + return Row( + children: [ + GestureDetector( + onTap: () { + if (selectedQty > 0) { + setState(() { + context.read().add( + SplitBillFormEvent.itemQuantityChanged( + itemId: item.id, + quantity: selectedQty - 1, + ), + ); + }); + } + }, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: selectedQty > 0 ? AppColor.primary : AppColor.border, + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + Icons.remove, + size: 16, + color: selectedQty > 0 ? Colors.white : AppColor.textSecondary, + ), + ), + ), + Container( + width: 40, + height: 32, + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + border: Border.all(color: AppColor.border), + borderRadius: BorderRadius.circular(4), + ), + child: Center( + child: Text( + '$selectedQty', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedQty < maxQty) { + context.read().add( + SplitBillFormEvent.itemQuantityChanged( + itemId: item.id, + quantity: selectedQty + 1, + ), + ); + } + }, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: selectedQty < maxQty ? AppColor.primary : AppColor.border, + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + Icons.add, + size: 16, + color: selectedQty < maxQty + ? Colors.white + : AppColor.textSecondary, + ), + ), + ), + ], + ); + } + Widget _buildRowButtonSplit() { switch (widget.state.order.splitType) { case 'AMOUNT': @@ -510,7 +561,10 @@ class _SplitBillRightPanelState extends State { splitItems.add( originalItem.copyWith( quantity: quantity, - totalPrice: (originalItem.unitPrice) * quantity, + // Baris berbobot dibayar penuh; nilainya sudah weight x harga. + totalPrice: originalItem.isWeighted + ? originalItem.totalPrice + : (originalItem.unitPrice) * quantity, ), ); } diff --git a/lib/presentation/pages/void/widgets/void_product_card.dart b/lib/presentation/pages/void/widgets/void_product_card.dart index 972b54b..0e34cc5 100644 --- a/lib/presentation/pages/void/widgets/void_product_card.dart +++ b/lib/presentation/pages/void/widgets/void_product_card.dart @@ -71,7 +71,9 @@ class VoidProductCard extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Text( - 'Qty: ${item.quantity}', + item.isWeighted + ? item.quantityLabel + : 'Qty: ${item.quantity}', style: AppStyle.sm.copyWith( fontWeight: FontWeight.w500, color: Colors.blue[700], @@ -112,65 +114,13 @@ class VoidProductCard extends StatelessWidget { ), if (canSelect) ...[ - Container( - decoration: BoxDecoration( - border: Border.all(color: AppColor.border), - borderRadius: BorderRadius.circular(6), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // Decrease Button - InkWell( - onTap: onTapDecrease, - child: Container( - padding: const EdgeInsets.all(8), - child: Icon( - Icons.remove, - size: 16, - color: voidQty > 0 - ? AppColor.primary - : Colors.grey[400], - ), - ), - ), - - // Quantity Display - Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - decoration: const BoxDecoration( - border: Border.symmetric( - vertical: BorderSide(color: AppColor.border), - ), - ), - child: Text( - '$voidQty', - style: AppStyle.sm.copyWith( - fontWeight: FontWeight.w600, - ), - ), - ), - - // Increase Button - InkWell( - onTap: onTapIncrease, - child: Container( - padding: const EdgeInsets.all(8), - child: Icon( - Icons.add, - size: 16, - color: voidQty < (item.quantity) - ? AppColor.primary - : Colors.grey[400], - ), - ), - ), - ], - ), - ), + // Baris berbobot bersifat utuh: dibatalkan seluruhnya atau + // tidak sama sekali, jadi yang ditawarkan tombol pilih baris + // — bukan stepper jumlah. + if (item.isWeighted) + _buildLineSelector() + else + _buildQuantityStepper(), const SpaceWidth(20), ], // Price @@ -195,7 +145,7 @@ class VoidProductCard extends StatelessWidget { borderRadius: BorderRadius.circular(4), ), child: Text( - 'Void: ${((item.unitPrice) * voidQty).currencyFormatRpV2}', + 'Void: ${(item.isWeighted ? item.totalPrice : (item.unitPrice) * voidQty).currencyFormatRpV2}', style: AppStyle.sm.copyWith( fontSize: 12, color: AppColor.error, @@ -214,4 +164,97 @@ class VoidProductCard extends StatelessWidget { ), ); } + + /// Pilih atau lepas satu baris berbobot secara utuh. + Widget _buildLineSelector() { + final selected = voidQty > 0; + + return InkWell( + onTap: selected ? onTapDecrease : onTapIncrease, + borderRadius: BorderRadius.circular(6), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: selected ? AppColor.primary : Colors.transparent, + border: Border.all( + color: selected ? AppColor.primary : AppColor.border, + ), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + selected ? Icons.check_circle : Icons.radio_button_unchecked, + size: 16, + color: selected ? AppColor.white : AppColor.textSecondary, + ), + const SpaceWidth(6), + Text( + selected ? 'Dipilih' : 'Pilih baris', + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + color: selected ? AppColor.white : AppColor.textSecondary, + ), + ), + ], + ), + ), + ); + } + + Widget _buildQuantityStepper() { + return Container( + decoration: BoxDecoration( + border: Border.all(color: AppColor.border), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Decrease Button + InkWell( + onTap: onTapDecrease, + child: Container( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.remove, + size: 16, + color: voidQty > 0 ? AppColor.primary : Colors.grey[400], + ), + ), + ), + + // Quantity Display + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: const BoxDecoration( + border: Border.symmetric( + vertical: BorderSide(color: AppColor.border), + ), + ), + child: Text( + '$voidQty', + style: AppStyle.sm.copyWith(fontWeight: FontWeight.w600), + ), + ), + + // Increase Button + InkWell( + onTap: onTapIncrease, + child: Container( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.add, + size: 16, + color: voidQty < (item.quantity) + ? AppColor.primary + : Colors.grey[400], + ), + ), + ), + ], + ), + ); + } } diff --git a/test/weight_based_products_test.dart b/test/weight_based_products_test.dart new file mode 100644 index 0000000..1f6d4df --- /dev/null +++ b/test/weight_based_products_test.dart @@ -0,0 +1,139 @@ +import 'package:apskel_pos_flutter_v2/application/checkout/checkout_form/checkout_form_bloc.dart'; +import 'package:apskel_pos_flutter_v2/common/types/sell_by.dart'; +import 'package:apskel_pos_flutter_v2/domain/order/order.dart'; +import 'package:apskel_pos_flutter_v2/domain/product/product.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Ikan Tude — Rp 4.500 per ons. +final ikanTude = Product.empty().copyWith( + id: 'ikan-tude', + name: 'Ikan Tude', + price: 4500, + isActive: true, + sellBy: SellBy.weight, + unitId: 'unit-ons', + unitName: 'Ons', + unitAbbreviation: 'ons', +); + +final nasiGoreng = Product.empty().copyWith( + id: 'nasi-goreng', + name: 'Nasi Goreng', + price: 25000, + isActive: true, +); + +void main() { + group('keranjang produk timbangan', () { + test('dua penimbangan produk yang sama menjadi dua baris', () async { + final bloc = CheckoutFormBloc(); + + bloc.add(CheckoutFormEvent.addWeightedItem(ikanTude, null, 4.2)); + bloc.add(CheckoutFormEvent.addWeightedItem(ikanTude, null, 5.6)); + await bloc.stream.firstWhere((s) => s.items.length == 2); + + final items = bloc.state.items; + expect(items.length, 2); + expect(items.map((e) => e.weight), [4.2, 5.6]); + expect(items.every((e) => e.quantity == 1), isTrue); + expect(items[0].lineId, isNot(items[1].lineId)); + }); + + test('produk cacah tetap digabung jadi satu baris', () async { + final bloc = CheckoutFormBloc(); + + bloc.add(CheckoutFormEvent.addItem(nasiGoreng, null)); + bloc.add(CheckoutFormEvent.addItem(nasiGoreng, null)); + await bloc.stream.firstWhere((s) => s.items.isNotEmpty && s.items.first.quantity == 2); + + expect(bloc.state.items.length, 1); + expect(bloc.state.items.first.quantity, 2); + }); + + test('produk timbangan tidak bisa masuk tanpa berat', () async { + final bloc = CheckoutFormBloc(); + + bloc.add(CheckoutFormEvent.addItem(ikanTude, null)); + await Future.delayed(Duration.zero); + + expect(bloc.state.items, isEmpty); + }); + + test('total baris dihitung berat x harga satuan', () { + final line = ProductQuantity( + product: ikanTude, + quantity: 1, + notes: '', + weight: 4.2, + ); + + expect(line.lineTotal, closeTo(18900, 0.001)); + expect(line.quantityLabel, '4,2 ons'); + }); + + test('total keranjang mencampur baris berbobot dan baris cacah', () { + final items = [ + ProductQuantity( + product: ikanTude, + quantity: 1, + notes: '', + weight: 4.2, + ), + ProductQuantity(product: nasiGoreng, quantity: 2, notes: ''), + ]; + + expect(items.cartTotal, closeTo(18900 + 50000, 0.001)); + expect(items.cartItemCount, 3); + }); + }); + + group('permintaan ke backend', () { + test('baris berbobot dikirim quantity 1 dan membawa berat', () { + final request = OrderItemRequest.fromProductQuantity( + ProductQuantity( + product: ikanTude, + quantity: 1, + notes: '', + weight: 4.2, + ), + ); + + expect(request.quantity, 1); + expect(request.weight, 4.2); + expect(request.unitPrice, 4500); + }); + + test('produk cacah tidak membawa berat sama sekali', () { + final request = OrderItemRequest.fromProductQuantity( + ProductQuantity(product: nasiGoreng, quantity: 2, notes: ''), + ); + + expect(request.quantity, 2); + expect(request.weight, isNull); + }); + }); + + group('tampilan baris pesanan', () { + test('baris berbobot memakai berat, bukan quantity', () { + final item = OrderItem.empty().copyWith( + quantity: 1, + unitPrice: 4500, + totalPrice: 18900, + weight: 4.2, + unitAbbreviation: 'ons', + ); + + expect(item.isWeighted, isTrue); + expect(item.quantityLabel, '4,2 ons'); + expect(item.quantityPriceLabel, '4,2 ons x Rp 4.500'); + }); + + test('baris biasa tetap memakai quantity', () { + final item = OrderItem.empty().copyWith(quantity: 2, unitPrice: 25000); + + expect(item.isWeighted, isFalse); + expect(item.quantityLabel, '2'); + expect(item.quantityPriceLabel, '2 x Rp 25.000'); + }); + }); +}