feat: dukung penjualan produk per timbangan
Produk kini punya cara jual `unit` (cacah) atau `weight` (timbangan), mengikuti kontrak backend di docs/integration-weight-based-products.md. - tipe SellBy, domain/infrastructure unit baru, dan endpoint /api/v1/units - dialog input berat saat produk timbangan ditambahkan ke keranjang - tiap penimbangan jadi baris keranjang sendiri; total baris dihitung berat x harga satuan, sedangkan produk cacah tetap digabung - request ke backend mengirim quantity 1 dan membawa berat - migrasi DB v3: kolom sell_by, unit_id, unit_name, unit_abbreviation di tabel products supaya satuan tetap tampil saat offline - tampilan keranjang, struk, split bill, void, dan refund memakai berat - test/weight_based_products_test.dart menutup perilaku di atas (9 test) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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`<br>`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": "<id satuan Ons>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 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": "<ikan-tude>", "quantity": 1, "weight": 4.2 },
|
||||||
|
{ "product_id": "<ikan-tude>", "quantity": 1, "weight": 5.6 },
|
||||||
|
{ "product_id": "<nasi-goreng>", "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": "<baris 4,2 ons>", "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
|
||||||
@@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
import '../../../common/service/device_info_service.dart';
|
import '../../../common/service/device_info_service.dart';
|
||||||
|
import '../../../common/service/unit_registry.dart';
|
||||||
import '../../../common/service/fcm_service.dart';
|
import '../../../common/service/fcm_service.dart';
|
||||||
import '../../../domain/auth/auth.dart';
|
import '../../../domain/auth/auth.dart';
|
||||||
|
|
||||||
@@ -18,11 +19,13 @@ class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
|
|||||||
final IAuthRepository _authRepository;
|
final IAuthRepository _authRepository;
|
||||||
final FcmService _fcmService;
|
final FcmService _fcmService;
|
||||||
final DeviceInfoService _deviceInfoService;
|
final DeviceInfoService _deviceInfoService;
|
||||||
|
final UnitRegistry _unitRegistry;
|
||||||
|
|
||||||
LoginFormBloc(
|
LoginFormBloc(
|
||||||
this._authRepository,
|
this._authRepository,
|
||||||
this._fcmService,
|
this._fcmService,
|
||||||
this._deviceInfoService,
|
this._deviceInfoService,
|
||||||
|
this._unitRegistry,
|
||||||
) : super(LoginFormState.initial()) {
|
) : super(LoginFormState.initial()) {
|
||||||
on<LoginFormEvent>(_onLoginFormEvent);
|
on<LoginFormEvent>(_onLoginFormEvent);
|
||||||
}
|
}
|
||||||
@@ -62,6 +65,12 @@ class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
|
|||||||
devicePayload: devicePayload,
|
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(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:injectable/injectable.dart' hide Order;
|
import 'package:injectable/injectable.dart' hide Order;
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../../common/types/order_type.dart';
|
import '../../../common/types/order_type.dart';
|
||||||
import '../../../domain/delivery/delivery.dart';
|
import '../../../domain/delivery/delivery.dart';
|
||||||
@@ -19,6 +20,33 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
|||||||
CheckoutFormBloc() : super(CheckoutFormState.initial()) {
|
CheckoutFormBloc() : super(CheckoutFormState.initial()) {
|
||||||
on<CheckoutFormEvent>(_onCheckoutFormEvent);
|
on<CheckoutFormEvent>(_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<ProductQuantity> items,
|
||||||
|
) {
|
||||||
|
final totalQuantity = items.fold<int>(
|
||||||
|
0,
|
||||||
|
// Baris berbobot selalu satu penimbangan, jadi dihitung satu.
|
||||||
|
(sum, item) => sum + (item.isWeighted ? 1 : item.quantity),
|
||||||
|
);
|
||||||
|
|
||||||
|
final totalPrice = items.fold<double>(
|
||||||
|
0,
|
||||||
|
(sum, item) => sum + item.lineTotal,
|
||||||
|
);
|
||||||
|
|
||||||
|
return state.copyWith(
|
||||||
|
items: items,
|
||||||
|
totalQuantity: totalQuantity,
|
||||||
|
totalPrice: totalPrice.toInt(),
|
||||||
|
isLoading: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onCheckoutFormEvent(
|
Future<void> _onCheckoutFormEvent(
|
||||||
CheckoutFormEvent event,
|
CheckoutFormEvent event,
|
||||||
Emitter<CheckoutFormState> emit,
|
Emitter<CheckoutFormState> emit,
|
||||||
@@ -26,12 +54,21 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
|||||||
return event.map(
|
return event.map(
|
||||||
addItem: (e) async {
|
addItem: (e) async {
|
||||||
final currentState = state;
|
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));
|
emit(currentState.copyWith(isLoading: true));
|
||||||
|
|
||||||
List<ProductQuantity> items = [...currentState.items];
|
List<ProductQuantity> items = [...currentState.items];
|
||||||
|
|
||||||
final index = items.indexWhere(
|
final index = items.indexWhere(
|
||||||
(element) =>
|
(element) =>
|
||||||
|
!element.isWeighted &&
|
||||||
element.product.id == e.product.id &&
|
element.product.id == e.product.id &&
|
||||||
element.variant?.id == e.variant?.id,
|
element.variant?.id == e.variant?.id,
|
||||||
);
|
);
|
||||||
@@ -49,35 +86,47 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
variant: e.variant,
|
variant: e.variant,
|
||||||
notes: '',
|
notes: '',
|
||||||
|
lineId: const Uuid().v4(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log('🛒 Items updated: ${items.length} items total ${items.toList()}');
|
log('🛒 Items updated: ${items.length} items total ${items.toList()}');
|
||||||
|
|
||||||
final totalQuantity = items.fold<int>(
|
emit(_recalculate(currentState, items));
|
||||||
0,
|
},
|
||||||
(sum, item) => sum + item.quantity,
|
addWeightedItem: (e) async {
|
||||||
);
|
final currentState = state;
|
||||||
|
emit(currentState.copyWith(isLoading: true));
|
||||||
|
|
||||||
final totalPrice = state.items.isEmpty
|
// Sengaja tidak digabung dengan baris lain: dua penimbangan adalah dua
|
||||||
? 0.0
|
// ikan berbeda, masing-masing bisa di-void atau dibayar terpisah.
|
||||||
: state.items
|
final items = [
|
||||||
.map(
|
...currentState.items,
|
||||||
(e) =>
|
ProductQuantity(
|
||||||
(e.product.price * e.quantity) +
|
product: e.product,
|
||||||
(e.variant?.priceModifier ?? 0),
|
variant: e.variant,
|
||||||
)
|
quantity: 1,
|
||||||
.reduce((value, element) => value + element);
|
notes: '',
|
||||||
|
lineId: const Uuid().v4(),
|
||||||
emit(
|
weight: e.weight,
|
||||||
currentState.copyWith(
|
|
||||||
items: items,
|
|
||||||
totalQuantity: totalQuantity,
|
|
||||||
totalPrice: totalPrice.toInt(),
|
|
||||||
isLoading: false,
|
|
||||||
),
|
),
|
||||||
);
|
];
|
||||||
|
|
||||||
|
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 {
|
removeItem: (e) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
@@ -86,6 +135,7 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
|||||||
|
|
||||||
final index = items.indexWhere(
|
final index = items.indexWhere(
|
||||||
(element) =>
|
(element) =>
|
||||||
|
!element.isWeighted &&
|
||||||
element.product.id == e.product.id &&
|
element.product.id == e.product.id &&
|
||||||
element.variant?.id == e.variant?.id,
|
element.variant?.id == e.variant?.id,
|
||||||
);
|
);
|
||||||
@@ -103,32 +153,18 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final totalQuantity = items.fold<int>(
|
log('🗑️ Item removed. Total items: ${items.length}');
|
||||||
0,
|
|
||||||
(sum, item) => sum + item.quantity,
|
|
||||||
);
|
|
||||||
final totalPrice = items.fold<int>(
|
|
||||||
0,
|
|
||||||
(sum, item) =>
|
|
||||||
sum +
|
|
||||||
(item.quantity *
|
|
||||||
(item.variant?.priceModifier.toInt() ??
|
|
||||||
item.product.price.toInt())),
|
|
||||||
);
|
|
||||||
|
|
||||||
log(
|
emit(_recalculate(currentState, items));
|
||||||
'🗑️ Item removed. Total items: ${items.length}, totalQuantity: $totalQuantity, totalPrice: $totalPrice',
|
},
|
||||||
);
|
removeLine: (e) async {
|
||||||
|
final currentState = state;
|
||||||
|
final items = [...currentState.items]
|
||||||
|
..removeWhere((item) => item.lineId == e.lineId);
|
||||||
|
|
||||||
// Emit state baru
|
log('🗑️ Baris dihapus. Sisa ${items.length} baris');
|
||||||
emit(
|
|
||||||
currentState.copyWith(
|
emit(_recalculate(currentState, items));
|
||||||
items: items,
|
|
||||||
totalQuantity: totalQuantity,
|
|
||||||
totalPrice: totalPrice,
|
|
||||||
isLoading: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
started: (e) async {
|
started: (e) async {
|
||||||
emit(CheckoutFormState.initial().copyWith(isLoading: true));
|
emit(CheckoutFormState.initial().copyWith(isLoading: true));
|
||||||
@@ -153,14 +189,13 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateItemNotes: (e) async {
|
updateLineNotes: (e) async {
|
||||||
final currentState = state;
|
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 items = [...currentState.items];
|
||||||
final index = items.indexWhere(
|
final index = items.indexWhere((item) => item.lineId == e.lineId);
|
||||||
(element) => element.product.id == e.product.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
items[index] = items[index].copyWith(notes: e.notes);
|
items[index] = items[index].copyWith(notes: e.notes);
|
||||||
@@ -181,8 +216,8 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
|||||||
emit(state.copyWith(orderAdded: e.order));
|
emit(state.copyWith(orderAdded: e.order));
|
||||||
},
|
},
|
||||||
customerNameChanged: (e) async {
|
customerNameChanged: (e) async {
|
||||||
emit(state.copyWith(customerName: e.name,));
|
emit(state.copyWith(customerName: e.name));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,15 +10,32 @@ class CheckoutFormEvent with _$CheckoutFormEvent {
|
|||||||
ProductVariant? variant,
|
ProductVariant? variant,
|
||||||
) = _AddItem;
|
) = _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(
|
const factory CheckoutFormEvent.removeItem(
|
||||||
Product product,
|
Product product,
|
||||||
ProductVariant? variant,
|
ProductVariant? variant,
|
||||||
) = _RemoveItem;
|
) = _RemoveItem;
|
||||||
|
|
||||||
const factory CheckoutFormEvent.updateItemNotes(
|
/// Hapus satu baris utuh — satu-satunya cara membatalkan baris berbobot.
|
||||||
Product product,
|
const factory CheckoutFormEvent.removeLine(String lineId) = _RemoveLine;
|
||||||
|
|
||||||
|
const factory CheckoutFormEvent.updateLineNotes(
|
||||||
|
String lineId,
|
||||||
String notes,
|
String notes,
|
||||||
) = _UpdateItemNotes;
|
) = _UpdateLineNotes;
|
||||||
|
|
||||||
const factory CheckoutFormEvent.updateOrderType(OrderType orderType) =
|
const factory CheckoutFormEvent.updateOrderType(OrderType orderType) =
|
||||||
_UpdateOrderType;
|
_UpdateOrderType;
|
||||||
@@ -30,6 +47,6 @@ class CheckoutFormEvent with _$CheckoutFormEvent {
|
|||||||
|
|
||||||
const factory CheckoutFormEvent.orderAddedItems(Order? order) =
|
const factory CheckoutFormEvent.orderAddedItems(Order? order) =
|
||||||
_OrderAddedItems;
|
_OrderAddedItems;
|
||||||
const factory CheckoutFormEvent.customerNameChanged(String name) =
|
const factory CheckoutFormEvent.customerNameChanged(String name) =
|
||||||
_CustomerNameChanged;
|
_CustomerNameChanged;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,15 +60,7 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
|
|||||||
notes: '',
|
notes: '',
|
||||||
customerName: state.customerName ?? "",
|
customerName: state.customerName ?? "",
|
||||||
orderItems: e.items
|
orderItems: e.items
|
||||||
.map(
|
.map(OrderItemRequest.fromProductQuantity)
|
||||||
(item) => OrderItemRequest(
|
|
||||||
productId: item.product.id,
|
|
||||||
productVariantId: item.variant?.id ?? "",
|
|
||||||
quantity: item.quantity,
|
|
||||||
unitPrice: item.product.price.toInt(),
|
|
||||||
notes: item.notes,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -100,15 +92,7 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
|
|||||||
notes: '',
|
notes: '',
|
||||||
customerName: state.customerName ?? "",
|
customerName: state.customerName ?? "",
|
||||||
orderItems: e.items
|
orderItems: e.items
|
||||||
.map(
|
.map(OrderItemRequest.fromProductQuantity)
|
||||||
(item) => OrderItemRequest(
|
|
||||||
productId: item.product.id,
|
|
||||||
productVariantId: item.variant?.id ?? "",
|
|
||||||
quantity: item.quantity,
|
|
||||||
unitPrice: item.product.price.toInt(),
|
|
||||||
notes: item.notes,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -137,15 +121,7 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final request = e.items
|
final request = e.items
|
||||||
.map(
|
.map(AddItemOrderRequest.fromProductQuantity)
|
||||||
(item) => AddItemOrderRequest(
|
|
||||||
productId: item.product.id,
|
|
||||||
productVariantId: item.variant?.id ?? '',
|
|
||||||
quantity: item.quantity,
|
|
||||||
unitPrice: item.product.price.toInt(),
|
|
||||||
notes: item.notes,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
failureOrAddItemOrder = await _repository.addItemOrder(
|
failureOrAddItemOrder = await _repository.addItemOrder(
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ class SplitBillFormBloc extends Bloc<SplitBillFormEvent, SplitBillFormState> {
|
|||||||
final item = state.pendingItems.firstWhere(
|
final item = state.pendingItems.firstWhere(
|
||||||
(item) => item.id == entry.key,
|
(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(
|
emit(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import '../../../common/service/unit_registry.dart';
|
||||||
import '../../../domain/category/category.dart';
|
import '../../../domain/category/category.dart';
|
||||||
import '../../../domain/product/product.dart';
|
import '../../../domain/product/product.dart';
|
||||||
|
|
||||||
@@ -14,9 +15,13 @@ part 'sync_setting_bloc.freezed.dart';
|
|||||||
class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
||||||
final IProductRepository _productRepository;
|
final IProductRepository _productRepository;
|
||||||
final ICategoryRepository _categoryRepository;
|
final ICategoryRepository _categoryRepository;
|
||||||
|
final UnitRegistry _unitRegistry;
|
||||||
|
|
||||||
SyncSettingBloc(this._productRepository, this._categoryRepository)
|
SyncSettingBloc(
|
||||||
: super(SyncSettingState.initial()) {
|
this._productRepository,
|
||||||
|
this._categoryRepository,
|
||||||
|
this._unitRegistry,
|
||||||
|
) : super(SyncSettingState.initial()) {
|
||||||
on<SyncSettingEvent>(_onSyncSettingEvent);
|
on<SyncSettingEvent>(_onSyncSettingEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +102,10 @@ class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Satuan lebih dulu: produk timbangan butuh singkatannya untuk bisa
|
||||||
|
// ditulis "4,2 ons" begitu selesai disinkronkan.
|
||||||
|
await _unitRegistry.load(forceRefresh: true);
|
||||||
|
|
||||||
// Sync categories first
|
// Sync categories first
|
||||||
final categoryResult = await _categoryRepository.syncAllCategories();
|
final categoryResult = await _categoryRepository.syncAllCategories();
|
||||||
|
|
||||||
@@ -169,6 +178,8 @@ class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
|||||||
Future<void> _onSyncProducts(Emitter<SyncSettingState> emit) async {
|
Future<void> _onSyncProducts(Emitter<SyncSettingState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, failureOptionSyncProduct: none()));
|
emit(state.copyWith(isLoading: true, failureOptionSyncProduct: none()));
|
||||||
|
|
||||||
|
await _unitRegistry.load(forceRefresh: true);
|
||||||
|
|
||||||
final result = await _productRepository.syncAllProducts();
|
final result = await _productRepository.syncAllProducts();
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
|
|||||||
@@ -55,7 +55,10 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
|
|||||||
(item) => item.id == entry.key,
|
(item) => item.id == entry.key,
|
||||||
);
|
);
|
||||||
if (item == null) continue;
|
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(
|
emit(
|
||||||
@@ -92,7 +95,13 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
|
|||||||
productVariantName: originalItem.productVariantName,
|
productVariantName: originalItem.productVariantName,
|
||||||
quantity: voidQty,
|
quantity: voidQty,
|
||||||
unitPrice: originalItem.unitPrice,
|
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,
|
modifiers: originalItem.modifiers,
|
||||||
notes: originalItem.notes,
|
notes: originalItem.notes,
|
||||||
status: originalItem.status,
|
status: originalItem.status,
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ class LocalStorageKey {
|
|||||||
static const String token = 'token';
|
static const String token = 'token';
|
||||||
static const String user = 'user';
|
static const String user = 'user';
|
||||||
static const String outlet = 'outlet';
|
static const String outlet = 'outlet';
|
||||||
|
static const String units = 'units';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class DatabaseHelper {
|
|||||||
|
|
||||||
return await openDatabase(
|
return await openDatabase(
|
||||||
path,
|
path,
|
||||||
version: 2,
|
version: 3,
|
||||||
onCreate: _onCreate,
|
onCreate: _onCreate,
|
||||||
onUpgrade: _onUpgrade,
|
onUpgrade: _onUpgrade,
|
||||||
);
|
);
|
||||||
@@ -42,7 +42,11 @@ class DatabaseHelper {
|
|||||||
metadata TEXT,
|
metadata TEXT,
|
||||||
is_active INTEGER,
|
is_active INTEGER,
|
||||||
created_at TEXT,
|
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',
|
'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<void> close() async {
|
Future<void> close() async {
|
||||||
|
|||||||
@@ -13,3 +13,10 @@ extension DoubleExt on double {
|
|||||||
decimalDigits: 0,
|
decimalDigits: 0,
|
||||||
).format(this);
|
).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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,3 +75,25 @@ int generateRandomNumber() {
|
|||||||
final random = Random();
|
final random = Random();
|
||||||
return 1 + random.nextInt(10000);
|
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>()
|
||||||
|
.map((e) => (e['cause'] ?? e['message'])?.toString())
|
||||||
|
.where((cause) => cause != null && cause.isNotEmpty)
|
||||||
|
.cast<String>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (causes.isEmpty) return null;
|
||||||
|
return causes.join('\n');
|
||||||
|
}
|
||||||
|
|||||||
@@ -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<String, ProductUnit> _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<void> 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 = {};
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ class ApiPath {
|
|||||||
static const String outlets = '/api/v1/outlets';
|
static const String outlets = '/api/v1/outlets';
|
||||||
static const String categories = '/api/v1/categories';
|
static const String categories = '/api/v1/categories';
|
||||||
static const String products = '/api/v1/products';
|
static const String products = '/api/v1/products';
|
||||||
|
static const String units = '/api/v1/units';
|
||||||
static const String tables = '/api/v1/tables';
|
static const String tables = '/api/v1/tables';
|
||||||
static const String customers = '/api/v1/customers';
|
static const String customers = '/api/v1/customers';
|
||||||
static const String paymentMethods = '/api/v1/payment-methods';
|
static const String paymentMethods = '/api/v1/payment-methods';
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ class AddItemOrderRequest with _$AddItemOrderRequest {
|
|||||||
required int quantity,
|
required int quantity,
|
||||||
required int unitPrice,
|
required int unitPrice,
|
||||||
required String notes,
|
required String notes,
|
||||||
|
|
||||||
|
/// Hanya diisi untuk produk `sell_by: "weight"`.
|
||||||
|
double? weight,
|
||||||
}) = _AddItemOrderRequest;
|
}) = _AddItemOrderRequest;
|
||||||
|
|
||||||
factory AddItemOrderRequest.empty() => const AddItemOrderRequest(
|
factory AddItemOrderRequest.empty() => const AddItemOrderRequest(
|
||||||
@@ -17,4 +20,14 @@ class AddItemOrderRequest with _$AddItemOrderRequest {
|
|||||||
unitPrice: 0,
|
unitPrice: 0,
|
||||||
notes: '',
|
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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,19 +197,19 @@ class Order with _$Order {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// For Add Item Order
|
// For Add Item Order
|
||||||
factory Order.fromLastOrder(Order order, List<ProductQuantity> products) =>
|
factory Order.fromLastOrder(Order order, List<ProductQuantity> products) {
|
||||||
order.copyWith(
|
final subtotal = products.fold<int>(
|
||||||
orderItems: products
|
0,
|
||||||
.map((e) => OrderItem.fromProductQuantity(e))
|
(sum, item) => sum + item.lineTotal.toInt(),
|
||||||
.toList(),
|
);
|
||||||
totalAmount: products
|
|
||||||
.map((e) => e.product.price.toInt() * e.quantity)
|
return order.copyWith(
|
||||||
.reduce((value, element) => value + element),
|
orderItems: products.map(OrderItem.fromProductQuantity).toList(),
|
||||||
subtotal: products
|
totalAmount: subtotal,
|
||||||
.map((e) => e.product.price.toInt() * e.quantity)
|
subtotal: subtotal,
|
||||||
.reduce((value, element) => value + element),
|
metadata: order.metadata,
|
||||||
metadata: order.metadata,
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
List<OrderItem> get orderItemNotCancelled =>
|
List<OrderItem> get orderItemNotCancelled =>
|
||||||
orderItems.where((item) => item.status != 'cancelled').toList();
|
orderItems.where((item) => item.status != 'cancelled').toList();
|
||||||
@@ -217,6 +217,8 @@ class Order with _$Order {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class OrderItem with _$OrderItem {
|
class OrderItem with _$OrderItem {
|
||||||
|
const OrderItem._();
|
||||||
|
|
||||||
const factory OrderItem({
|
const factory OrderItem({
|
||||||
required String id,
|
required String id,
|
||||||
required String orderId,
|
required String orderId,
|
||||||
@@ -237,6 +239,12 @@ class OrderItem with _$OrderItem {
|
|||||||
required int paidQuantity,
|
required int paidQuantity,
|
||||||
required String categoryId,
|
required String categoryId,
|
||||||
required String categoryName,
|
required String categoryName,
|
||||||
|
|
||||||
|
/// Angka timbangan baris ini. `null` untuk produk cacah.
|
||||||
|
double? weight,
|
||||||
|
@Default('') String unitId,
|
||||||
|
@Default('') String unitName,
|
||||||
|
@Default('') String unitAbbreviation,
|
||||||
}) = _OrderItem;
|
}) = _OrderItem;
|
||||||
|
|
||||||
factory OrderItem.empty() => OrderItem(
|
factory OrderItem.empty() => OrderItem(
|
||||||
@@ -269,10 +277,9 @@ class OrderItem with _$OrderItem {
|
|||||||
productName: productQuantity.product.name,
|
productName: productQuantity.product.name,
|
||||||
productVariantId: productQuantity.variant?.id ?? '',
|
productVariantId: productQuantity.variant?.id ?? '',
|
||||||
productVariantName: productQuantity.variant?.name ?? '',
|
productVariantName: productQuantity.variant?.name ?? '',
|
||||||
quantity: productQuantity.quantity,
|
quantity: productQuantity.isWeighted ? 1 : productQuantity.quantity,
|
||||||
unitPrice: productQuantity.product.price.toInt(),
|
unitPrice: productQuantity.product.price.toInt(),
|
||||||
totalPrice: (productQuantity.product.price * productQuantity.quantity)
|
totalPrice: productQuantity.lineTotal.toInt(),
|
||||||
.toInt(),
|
|
||||||
modifiers: [],
|
modifiers: [],
|
||||||
notes: productQuantity.notes,
|
notes: productQuantity.notes,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
@@ -283,5 +290,30 @@ class OrderItem with _$OrderItem {
|
|||||||
paidQuantity: 0,
|
paidQuantity: 0,
|
||||||
categoryId: '',
|
categoryId: '',
|
||||||
categoryName: '',
|
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}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ class OrderItemRequest with _$OrderItemRequest {
|
|||||||
required int quantity,
|
required int quantity,
|
||||||
required int unitPrice,
|
required int unitPrice,
|
||||||
required String notes,
|
required String notes,
|
||||||
|
|
||||||
|
/// Hanya diisi untuk produk `sell_by: "weight"`; produk cacah tidak boleh
|
||||||
|
/// mengirim berat sama sekali.
|
||||||
|
double? weight,
|
||||||
}) = _OrderItemRequest;
|
}) = _OrderItemRequest;
|
||||||
|
|
||||||
factory OrderItemRequest.empty() => const OrderItemRequest(
|
factory OrderItemRequest.empty() => const OrderItemRequest(
|
||||||
@@ -42,4 +46,16 @@ class OrderItemRequest with _$OrderItemRequest {
|
|||||||
unitPrice: 0,
|
unitPrice: 0,
|
||||||
notes: '',
|
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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
|
|
||||||
import '../../common/api/api_failure.dart';
|
import '../../common/api/api_failure.dart';
|
||||||
import '../../common/types/split_type.dart';
|
import '../../common/types/split_type.dart';
|
||||||
|
import '../../common/extension/extension.dart';
|
||||||
import '../product/product.dart';
|
import '../product/product.dart';
|
||||||
|
|
||||||
part 'order.freezed.dart';
|
part 'order.freezed.dart';
|
||||||
|
|||||||
@@ -1007,6 +1007,12 @@ mixin _$OrderItem {
|
|||||||
String get categoryId => throw _privateConstructorUsedError;
|
String get categoryId => throw _privateConstructorUsedError;
|
||||||
String get categoryName => 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
|
/// Create a copy of OrderItem
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -1039,6 +1045,10 @@ abstract class $OrderItemCopyWith<$Res> {
|
|||||||
int paidQuantity,
|
int paidQuantity,
|
||||||
String categoryId,
|
String categoryId,
|
||||||
String categoryName,
|
String categoryName,
|
||||||
|
double? weight,
|
||||||
|
String unitId,
|
||||||
|
String unitName,
|
||||||
|
String unitAbbreviation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,6 +1086,10 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem>
|
|||||||
Object? paidQuantity = null,
|
Object? paidQuantity = null,
|
||||||
Object? categoryId = null,
|
Object? categoryId = null,
|
||||||
Object? categoryName = null,
|
Object? categoryName = null,
|
||||||
|
Object? weight = freezed,
|
||||||
|
Object? unitId = null,
|
||||||
|
Object? unitName = null,
|
||||||
|
Object? unitAbbreviation = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -1155,6 +1169,22 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem>
|
|||||||
? _value.categoryName
|
? _value.categoryName
|
||||||
: categoryName // ignore: cast_nullable_to_non_nullable
|
: categoryName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
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,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -1190,6 +1220,10 @@ abstract class _$$OrderItemImplCopyWith<$Res>
|
|||||||
int paidQuantity,
|
int paidQuantity,
|
||||||
String categoryId,
|
String categoryId,
|
||||||
String categoryName,
|
String categoryName,
|
||||||
|
double? weight,
|
||||||
|
String unitId,
|
||||||
|
String unitName,
|
||||||
|
String unitAbbreviation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1226,6 +1260,10 @@ class __$$OrderItemImplCopyWithImpl<$Res>
|
|||||||
Object? paidQuantity = null,
|
Object? paidQuantity = null,
|
||||||
Object? categoryId = null,
|
Object? categoryId = null,
|
||||||
Object? categoryName = null,
|
Object? categoryName = null,
|
||||||
|
Object? weight = freezed,
|
||||||
|
Object? unitId = null,
|
||||||
|
Object? unitName = null,
|
||||||
|
Object? unitAbbreviation = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$OrderItemImpl(
|
_$OrderItemImpl(
|
||||||
@@ -1305,6 +1343,22 @@ class __$$OrderItemImplCopyWithImpl<$Res>
|
|||||||
? _value.categoryName
|
? _value.categoryName
|
||||||
: categoryName // ignore: cast_nullable_to_non_nullable
|
: categoryName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
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
|
/// @nodoc
|
||||||
|
|
||||||
class _$OrderItemImpl implements _OrderItem {
|
class _$OrderItemImpl extends _OrderItem {
|
||||||
const _$OrderItemImpl({
|
const _$OrderItemImpl({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.orderId,
|
required this.orderId,
|
||||||
@@ -1333,7 +1387,12 @@ class _$OrderItemImpl implements _OrderItem {
|
|||||||
required this.paidQuantity,
|
required this.paidQuantity,
|
||||||
required this.categoryId,
|
required this.categoryId,
|
||||||
required this.categoryName,
|
required this.categoryName,
|
||||||
}) : _modifiers = modifiers;
|
this.weight,
|
||||||
|
this.unitId = '',
|
||||||
|
this.unitName = '',
|
||||||
|
this.unitAbbreviation = '',
|
||||||
|
}) : _modifiers = modifiers,
|
||||||
|
super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String id;
|
final String id;
|
||||||
@@ -1380,9 +1439,22 @@ class _$OrderItemImpl implements _OrderItem {
|
|||||||
@override
|
@override
|
||||||
final String categoryName;
|
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
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -1425,7 +1497,13 @@ class _$OrderItemImpl implements _OrderItem {
|
|||||||
(identical(other.categoryId, categoryId) ||
|
(identical(other.categoryId, categoryId) ||
|
||||||
other.categoryId == categoryId) &&
|
other.categoryId == categoryId) &&
|
||||||
(identical(other.categoryName, categoryName) ||
|
(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
|
@override
|
||||||
@@ -1450,6 +1528,10 @@ class _$OrderItemImpl implements _OrderItem {
|
|||||||
paidQuantity,
|
paidQuantity,
|
||||||
categoryId,
|
categoryId,
|
||||||
categoryName,
|
categoryName,
|
||||||
|
weight,
|
||||||
|
unitId,
|
||||||
|
unitName,
|
||||||
|
unitAbbreviation,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// Create a copy of OrderItem
|
/// Create a copy of OrderItem
|
||||||
@@ -1461,7 +1543,7 @@ class _$OrderItemImpl implements _OrderItem {
|
|||||||
__$$OrderItemImplCopyWithImpl<_$OrderItemImpl>(this, _$identity);
|
__$$OrderItemImplCopyWithImpl<_$OrderItemImpl>(this, _$identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _OrderItem implements OrderItem {
|
abstract class _OrderItem extends OrderItem {
|
||||||
const factory _OrderItem({
|
const factory _OrderItem({
|
||||||
required final String id,
|
required final String id,
|
||||||
required final String orderId,
|
required final String orderId,
|
||||||
@@ -1482,7 +1564,12 @@ abstract class _OrderItem implements OrderItem {
|
|||||||
required final int paidQuantity,
|
required final int paidQuantity,
|
||||||
required final String categoryId,
|
required final String categoryId,
|
||||||
required final String categoryName,
|
required final String categoryName,
|
||||||
|
final double? weight,
|
||||||
|
final String unitId,
|
||||||
|
final String unitName,
|
||||||
|
final String unitAbbreviation,
|
||||||
}) = _$OrderItemImpl;
|
}) = _$OrderItemImpl;
|
||||||
|
const _OrderItem._() : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id;
|
String get id;
|
||||||
@@ -1523,6 +1610,16 @@ abstract class _OrderItem implements OrderItem {
|
|||||||
@override
|
@override
|
||||||
String get categoryName;
|
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
|
/// Create a copy of OrderItem
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@@ -1847,6 +1944,10 @@ mixin _$OrderItemRequest {
|
|||||||
int get unitPrice => throw _privateConstructorUsedError;
|
int get unitPrice => throw _privateConstructorUsedError;
|
||||||
String get notes => 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
|
/// Create a copy of OrderItemRequest
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -1867,6 +1968,7 @@ abstract class $OrderItemRequestCopyWith<$Res> {
|
|||||||
int quantity,
|
int quantity,
|
||||||
int unitPrice,
|
int unitPrice,
|
||||||
String notes,
|
String notes,
|
||||||
|
double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1890,6 +1992,7 @@ class _$OrderItemRequestCopyWithImpl<$Res, $Val extends OrderItemRequest>
|
|||||||
Object? quantity = null,
|
Object? quantity = null,
|
||||||
Object? unitPrice = null,
|
Object? unitPrice = null,
|
||||||
Object? notes = null,
|
Object? notes = null,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -1913,6 +2016,10 @@ class _$OrderItemRequestCopyWithImpl<$Res, $Val extends OrderItemRequest>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
weight: freezed == weight
|
||||||
|
? _value.weight
|
||||||
|
: weight // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -1934,6 +2041,7 @@ abstract class _$$OrderItemRequestImplCopyWith<$Res>
|
|||||||
int quantity,
|
int quantity,
|
||||||
int unitPrice,
|
int unitPrice,
|
||||||
String notes,
|
String notes,
|
||||||
|
double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1956,6 +2064,7 @@ class __$$OrderItemRequestImplCopyWithImpl<$Res>
|
|||||||
Object? quantity = null,
|
Object? quantity = null,
|
||||||
Object? unitPrice = null,
|
Object? unitPrice = null,
|
||||||
Object? notes = null,
|
Object? notes = null,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$OrderItemRequestImpl(
|
_$OrderItemRequestImpl(
|
||||||
@@ -1979,6 +2088,10 @@ class __$$OrderItemRequestImplCopyWithImpl<$Res>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
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.quantity,
|
||||||
required this.unitPrice,
|
required this.unitPrice,
|
||||||
required this.notes,
|
required this.notes,
|
||||||
|
this.weight,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -2006,9 +2120,14 @@ class _$OrderItemRequestImpl implements _OrderItemRequest {
|
|||||||
@override
|
@override
|
||||||
final String notes;
|
final String notes;
|
||||||
|
|
||||||
|
/// Hanya diisi untuk produk `sell_by: "weight"`; produk cacah tidak boleh
|
||||||
|
/// mengirim berat sama sekali.
|
||||||
|
@override
|
||||||
|
final double? weight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -2024,7 +2143,8 @@ class _$OrderItemRequestImpl implements _OrderItemRequest {
|
|||||||
other.quantity == quantity) &&
|
other.quantity == quantity) &&
|
||||||
(identical(other.unitPrice, unitPrice) ||
|
(identical(other.unitPrice, unitPrice) ||
|
||||||
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
|
@override
|
||||||
@@ -2035,6 +2155,7 @@ class _$OrderItemRequestImpl implements _OrderItemRequest {
|
|||||||
quantity,
|
quantity,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
notes,
|
notes,
|
||||||
|
weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of OrderItemRequest
|
/// Create a copy of OrderItemRequest
|
||||||
@@ -2056,6 +2177,7 @@ abstract class _OrderItemRequest implements OrderItemRequest {
|
|||||||
required final int quantity,
|
required final int quantity,
|
||||||
required final int unitPrice,
|
required final int unitPrice,
|
||||||
required final String notes,
|
required final String notes,
|
||||||
|
final double? weight,
|
||||||
}) = _$OrderItemRequestImpl;
|
}) = _$OrderItemRequestImpl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -2069,6 +2191,11 @@ abstract class _OrderItemRequest implements OrderItemRequest {
|
|||||||
@override
|
@override
|
||||||
String get notes;
|
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
|
/// Create a copy of OrderItemRequest
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@@ -3448,6 +3575,9 @@ mixin _$AddItemOrderRequest {
|
|||||||
int get unitPrice => throw _privateConstructorUsedError;
|
int get unitPrice => throw _privateConstructorUsedError;
|
||||||
String get notes => throw _privateConstructorUsedError;
|
String get notes => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Hanya diisi untuk produk `sell_by: "weight"`.
|
||||||
|
double? get weight => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of AddItemOrderRequest
|
/// Create a copy of AddItemOrderRequest
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -3468,6 +3598,7 @@ abstract class $AddItemOrderRequestCopyWith<$Res> {
|
|||||||
int quantity,
|
int quantity,
|
||||||
int unitPrice,
|
int unitPrice,
|
||||||
String notes,
|
String notes,
|
||||||
|
double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3491,6 +3622,7 @@ class _$AddItemOrderRequestCopyWithImpl<$Res, $Val extends AddItemOrderRequest>
|
|||||||
Object? quantity = null,
|
Object? quantity = null,
|
||||||
Object? unitPrice = null,
|
Object? unitPrice = null,
|
||||||
Object? notes = null,
|
Object? notes = null,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -3514,6 +3646,10 @@ class _$AddItemOrderRequestCopyWithImpl<$Res, $Val extends AddItemOrderRequest>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
weight: freezed == weight
|
||||||
|
? _value.weight
|
||||||
|
: weight // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -3535,6 +3671,7 @@ abstract class _$$AddItemOrderRequestImplCopyWith<$Res>
|
|||||||
int quantity,
|
int quantity,
|
||||||
int unitPrice,
|
int unitPrice,
|
||||||
String notes,
|
String notes,
|
||||||
|
double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3557,6 +3694,7 @@ class __$$AddItemOrderRequestImplCopyWithImpl<$Res>
|
|||||||
Object? quantity = null,
|
Object? quantity = null,
|
||||||
Object? unitPrice = null,
|
Object? unitPrice = null,
|
||||||
Object? notes = null,
|
Object? notes = null,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$AddItemOrderRequestImpl(
|
_$AddItemOrderRequestImpl(
|
||||||
@@ -3580,6 +3718,10 @@ class __$$AddItemOrderRequestImplCopyWithImpl<$Res>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
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.quantity,
|
||||||
required this.unitPrice,
|
required this.unitPrice,
|
||||||
required this.notes,
|
required this.notes,
|
||||||
|
this.weight,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -3607,9 +3750,13 @@ class _$AddItemOrderRequestImpl implements _AddItemOrderRequest {
|
|||||||
@override
|
@override
|
||||||
final String notes;
|
final String notes;
|
||||||
|
|
||||||
|
/// Hanya diisi untuk produk `sell_by: "weight"`.
|
||||||
|
@override
|
||||||
|
final double? weight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -3625,7 +3772,8 @@ class _$AddItemOrderRequestImpl implements _AddItemOrderRequest {
|
|||||||
other.quantity == quantity) &&
|
other.quantity == quantity) &&
|
||||||
(identical(other.unitPrice, unitPrice) ||
|
(identical(other.unitPrice, unitPrice) ||
|
||||||
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
|
@override
|
||||||
@@ -3636,6 +3784,7 @@ class _$AddItemOrderRequestImpl implements _AddItemOrderRequest {
|
|||||||
quantity,
|
quantity,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
notes,
|
notes,
|
||||||
|
weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of AddItemOrderRequest
|
/// Create a copy of AddItemOrderRequest
|
||||||
@@ -3657,6 +3806,7 @@ abstract class _AddItemOrderRequest implements AddItemOrderRequest {
|
|||||||
required final int quantity,
|
required final int quantity,
|
||||||
required final int unitPrice,
|
required final int unitPrice,
|
||||||
required final String notes,
|
required final String notes,
|
||||||
|
final double? weight,
|
||||||
}) = _$AddItemOrderRequestImpl;
|
}) = _$AddItemOrderRequestImpl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -3670,6 +3820,10 @@ abstract class _AddItemOrderRequest implements AddItemOrderRequest {
|
|||||||
@override
|
@override
|
||||||
String get notes;
|
String get notes;
|
||||||
|
|
||||||
|
/// Hanya diisi untuk produk `sell_by: "weight"`.
|
||||||
|
@override
|
||||||
|
double? get weight;
|
||||||
|
|
||||||
/// Create a copy of AddItemOrderRequest
|
/// Create a copy of AddItemOrderRequest
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ class ListProduct with _$ListProduct {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class Product with _$Product {
|
class Product with _$Product {
|
||||||
|
const Product._();
|
||||||
|
|
||||||
const factory Product({
|
const factory Product({
|
||||||
required String id,
|
required String id,
|
||||||
required String organizationId,
|
required String organizationId,
|
||||||
@@ -39,6 +41,11 @@ class Product with _$Product {
|
|||||||
required String createdAt,
|
required String createdAt,
|
||||||
required String updatedAt,
|
required String updatedAt,
|
||||||
required List<ProductVariant> variants,
|
required List<ProductVariant> variants,
|
||||||
|
/// `unit` (default) atau `weight`. Lihat [SellBy].
|
||||||
|
@Default(SellBy.unit) String sellBy,
|
||||||
|
@Default('') String unitId,
|
||||||
|
@Default('') String unitName,
|
||||||
|
@Default('') String unitAbbreviation,
|
||||||
}) = _Product;
|
}) = _Product;
|
||||||
|
|
||||||
factory Product.empty() => const Product(
|
factory Product.empty() => const Product(
|
||||||
@@ -60,6 +67,18 @@ class Product with _$Product {
|
|||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
variants: [],
|
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
|
@freezed
|
||||||
|
|||||||
@@ -1,14 +1,53 @@
|
|||||||
part of '../product.dart';
|
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
|
@freezed
|
||||||
class ProductQuantity with _$ProductQuantity {
|
class ProductQuantity with _$ProductQuantity {
|
||||||
|
const ProductQuantity._();
|
||||||
|
|
||||||
const factory ProductQuantity({
|
const factory ProductQuantity({
|
||||||
required Product product,
|
required Product product,
|
||||||
ProductVariant? variant,
|
ProductVariant? variant,
|
||||||
required int quantity,
|
required int quantity,
|
||||||
required String notes,
|
required String notes,
|
||||||
|
@Default('') String lineId,
|
||||||
|
|
||||||
|
/// Angka timbangan dalam satuan produk. `null` untuk produk cacah.
|
||||||
|
double? weight,
|
||||||
}) = _ProductQuantity;
|
}) = _ProductQuantity;
|
||||||
|
|
||||||
factory ProductQuantity.empty() =>
|
factory ProductQuantity.empty() =>
|
||||||
ProductQuantity(product: Product.empty(), quantity: 0, notes: '');
|
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<ProductQuantity> {
|
||||||
|
/// Total keranjang: baris berbobot pakai `weight × harga`, baris cacah pakai
|
||||||
|
/// `quantity × harga`.
|
||||||
|
double get cartTotal => fold<double>(0, (sum, item) => sum + item.lineTotal);
|
||||||
|
|
||||||
|
/// Baris berbobot dihitung satu — satu penimbangan, satu barang.
|
||||||
|
int get cartItemCount =>
|
||||||
|
fold<int>(0, (sum, item) => sum + (item.isWeighted ? 1 : item.quantity));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import '../../common/api/api_failure.dart';
|
import '../../common/api/api_failure.dart';
|
||||||
|
import '../../common/extension/extension.dart';
|
||||||
|
import '../../common/types/sell_by.dart';
|
||||||
|
|
||||||
part 'product.freezed.dart';
|
part 'product.freezed.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -286,6 +286,12 @@ mixin _$Product {
|
|||||||
String get updatedAt => throw _privateConstructorUsedError;
|
String get updatedAt => throw _privateConstructorUsedError;
|
||||||
List<ProductVariant> get variants => throw _privateConstructorUsedError;
|
List<ProductVariant> 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
|
/// Create a copy of Product
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -315,6 +321,10 @@ abstract class $ProductCopyWith<$Res> {
|
|||||||
String createdAt,
|
String createdAt,
|
||||||
String updatedAt,
|
String updatedAt,
|
||||||
List<ProductVariant> variants,
|
List<ProductVariant> variants,
|
||||||
|
String sellBy,
|
||||||
|
String unitId,
|
||||||
|
String unitName,
|
||||||
|
String unitAbbreviation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +360,10 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product>
|
|||||||
Object? createdAt = null,
|
Object? createdAt = null,
|
||||||
Object? updatedAt = null,
|
Object? updatedAt = null,
|
||||||
Object? variants = null,
|
Object? variants = null,
|
||||||
|
Object? sellBy = null,
|
||||||
|
Object? unitId = null,
|
||||||
|
Object? unitName = null,
|
||||||
|
Object? unitAbbreviation = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -421,6 +435,22 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product>
|
|||||||
? _value.variants
|
? _value.variants
|
||||||
: variants // ignore: cast_nullable_to_non_nullable
|
: variants // ignore: cast_nullable_to_non_nullable
|
||||||
as List<ProductVariant>,
|
as List<ProductVariant>,
|
||||||
|
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,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -453,6 +483,10 @@ abstract class _$$ProductImplCopyWith<$Res> implements $ProductCopyWith<$Res> {
|
|||||||
String createdAt,
|
String createdAt,
|
||||||
String updatedAt,
|
String updatedAt,
|
||||||
List<ProductVariant> variants,
|
List<ProductVariant> variants,
|
||||||
|
String sellBy,
|
||||||
|
String unitId,
|
||||||
|
String unitName,
|
||||||
|
String unitAbbreviation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,6 +521,10 @@ class __$$ProductImplCopyWithImpl<$Res>
|
|||||||
Object? createdAt = null,
|
Object? createdAt = null,
|
||||||
Object? updatedAt = null,
|
Object? updatedAt = null,
|
||||||
Object? variants = null,
|
Object? variants = null,
|
||||||
|
Object? sellBy = null,
|
||||||
|
Object? unitId = null,
|
||||||
|
Object? unitName = null,
|
||||||
|
Object? unitAbbreviation = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$ProductImpl(
|
_$ProductImpl(
|
||||||
@@ -558,6 +596,22 @@ class __$$ProductImplCopyWithImpl<$Res>
|
|||||||
? _value._variants
|
? _value._variants
|
||||||
: variants // ignore: cast_nullable_to_non_nullable
|
: variants // ignore: cast_nullable_to_non_nullable
|
||||||
as List<ProductVariant>,
|
as List<ProductVariant>,
|
||||||
|
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
|
/// @nodoc
|
||||||
|
|
||||||
class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
|
class _$ProductImpl extends _Product with DiagnosticableTreeMixin {
|
||||||
const _$ProductImpl({
|
const _$ProductImpl({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.organizationId,
|
required this.organizationId,
|
||||||
@@ -584,8 +638,13 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
|
|||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
required final List<ProductVariant> variants,
|
required final List<ProductVariant> variants,
|
||||||
|
this.sellBy = SellBy.unit,
|
||||||
|
this.unitId = '',
|
||||||
|
this.unitName = '',
|
||||||
|
this.unitAbbreviation = '',
|
||||||
}) : _metadata = metadata,
|
}) : _metadata = metadata,
|
||||||
_variants = variants;
|
_variants = variants,
|
||||||
|
super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String id;
|
final String id;
|
||||||
@@ -633,9 +692,23 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
|
|||||||
return EqualUnmodifiableListView(_variants);
|
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
|
@override
|
||||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
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
|
@override
|
||||||
@@ -659,7 +732,11 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
|
|||||||
..add(DiagnosticsProperty('isActive', isActive))
|
..add(DiagnosticsProperty('isActive', isActive))
|
||||||
..add(DiagnosticsProperty('createdAt', createdAt))
|
..add(DiagnosticsProperty('createdAt', createdAt))
|
||||||
..add(DiagnosticsProperty('updatedAt', updatedAt))
|
..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
|
@override
|
||||||
@@ -693,11 +770,17 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
|
|||||||
other.createdAt == createdAt) &&
|
other.createdAt == createdAt) &&
|
||||||
(identical(other.updatedAt, updatedAt) ||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
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
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hashAll([
|
||||||
runtimeType,
|
runtimeType,
|
||||||
id,
|
id,
|
||||||
organizationId,
|
organizationId,
|
||||||
@@ -716,7 +799,11 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
|
|||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
const DeepCollectionEquality().hash(_variants),
|
const DeepCollectionEquality().hash(_variants),
|
||||||
);
|
sellBy,
|
||||||
|
unitId,
|
||||||
|
unitName,
|
||||||
|
unitAbbreviation,
|
||||||
|
]);
|
||||||
|
|
||||||
/// Create a copy of Product
|
/// Create a copy of Product
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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);
|
__$$ProductImplCopyWithImpl<_$ProductImpl>(this, _$identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Product implements Product {
|
abstract class _Product extends Product {
|
||||||
const factory _Product({
|
const factory _Product({
|
||||||
required final String id,
|
required final String id,
|
||||||
required final String organizationId,
|
required final String organizationId,
|
||||||
@@ -746,7 +833,12 @@ abstract class _Product implements Product {
|
|||||||
required final String createdAt,
|
required final String createdAt,
|
||||||
required final String updatedAt,
|
required final String updatedAt,
|
||||||
required final List<ProductVariant> variants,
|
required final List<ProductVariant> variants,
|
||||||
|
final String sellBy,
|
||||||
|
final String unitId,
|
||||||
|
final String unitName,
|
||||||
|
final String unitAbbreviation,
|
||||||
}) = _$ProductImpl;
|
}) = _$ProductImpl;
|
||||||
|
const _Product._() : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id;
|
String get id;
|
||||||
@@ -783,6 +875,16 @@ abstract class _Product implements Product {
|
|||||||
@override
|
@override
|
||||||
List<ProductVariant> get variants;
|
List<ProductVariant> 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
|
/// Create a copy of Product
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@@ -1121,6 +1223,10 @@ mixin _$ProductQuantity {
|
|||||||
ProductVariant? get variant => throw _privateConstructorUsedError;
|
ProductVariant? get variant => throw _privateConstructorUsedError;
|
||||||
int get quantity => throw _privateConstructorUsedError;
|
int get quantity => throw _privateConstructorUsedError;
|
||||||
String get notes => 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
|
/// Create a copy of ProductQuantity
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -1141,6 +1247,8 @@ abstract class $ProductQuantityCopyWith<$Res> {
|
|||||||
ProductVariant? variant,
|
ProductVariant? variant,
|
||||||
int quantity,
|
int quantity,
|
||||||
String notes,
|
String notes,
|
||||||
|
String lineId,
|
||||||
|
double? weight,
|
||||||
});
|
});
|
||||||
|
|
||||||
$ProductCopyWith<$Res> get product;
|
$ProductCopyWith<$Res> get product;
|
||||||
@@ -1166,6 +1274,8 @@ class _$ProductQuantityCopyWithImpl<$Res, $Val extends ProductQuantity>
|
|||||||
Object? variant = freezed,
|
Object? variant = freezed,
|
||||||
Object? quantity = null,
|
Object? quantity = null,
|
||||||
Object? notes = null,
|
Object? notes = null,
|
||||||
|
Object? lineId = null,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -1185,6 +1295,14 @@ class _$ProductQuantityCopyWithImpl<$Res, $Val extends ProductQuantity>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
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,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -1229,6 +1347,8 @@ abstract class _$$ProductQuantityImplCopyWith<$Res>
|
|||||||
ProductVariant? variant,
|
ProductVariant? variant,
|
||||||
int quantity,
|
int quantity,
|
||||||
String notes,
|
String notes,
|
||||||
|
String lineId,
|
||||||
|
double? weight,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1255,6 +1375,8 @@ class __$$ProductQuantityImplCopyWithImpl<$Res>
|
|||||||
Object? variant = freezed,
|
Object? variant = freezed,
|
||||||
Object? quantity = null,
|
Object? quantity = null,
|
||||||
Object? notes = null,
|
Object? notes = null,
|
||||||
|
Object? lineId = null,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$ProductQuantityImpl(
|
_$ProductQuantityImpl(
|
||||||
@@ -1274,6 +1396,14 @@ class __$$ProductQuantityImplCopyWithImpl<$Res>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
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
|
/// @nodoc
|
||||||
|
|
||||||
class _$ProductQuantityImpl
|
class _$ProductQuantityImpl extends _ProductQuantity
|
||||||
with DiagnosticableTreeMixin
|
with DiagnosticableTreeMixin {
|
||||||
implements _ProductQuantity {
|
|
||||||
const _$ProductQuantityImpl({
|
const _$ProductQuantityImpl({
|
||||||
required this.product,
|
required this.product,
|
||||||
this.variant,
|
this.variant,
|
||||||
required this.quantity,
|
required this.quantity,
|
||||||
required this.notes,
|
required this.notes,
|
||||||
});
|
this.lineId = '',
|
||||||
|
this.weight,
|
||||||
|
}) : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final Product product;
|
final Product product;
|
||||||
@@ -1299,10 +1430,17 @@ class _$ProductQuantityImpl
|
|||||||
final int quantity;
|
final int quantity;
|
||||||
@override
|
@override
|
||||||
final String notes;
|
final String notes;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String lineId;
|
||||||
|
|
||||||
|
/// Angka timbangan dalam satuan produk. `null` untuk produk cacah.
|
||||||
|
@override
|
||||||
|
final double? weight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
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
|
@override
|
||||||
@@ -1313,7 +1451,9 @@ class _$ProductQuantityImpl
|
|||||||
..add(DiagnosticsProperty('product', product))
|
..add(DiagnosticsProperty('product', product))
|
||||||
..add(DiagnosticsProperty('variant', variant))
|
..add(DiagnosticsProperty('variant', variant))
|
||||||
..add(DiagnosticsProperty('quantity', quantity))
|
..add(DiagnosticsProperty('quantity', quantity))
|
||||||
..add(DiagnosticsProperty('notes', notes));
|
..add(DiagnosticsProperty('notes', notes))
|
||||||
|
..add(DiagnosticsProperty('lineId', lineId))
|
||||||
|
..add(DiagnosticsProperty('weight', weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1325,12 +1465,21 @@ class _$ProductQuantityImpl
|
|||||||
(identical(other.variant, variant) || other.variant == variant) &&
|
(identical(other.variant, variant) || other.variant == variant) &&
|
||||||
(identical(other.quantity, quantity) ||
|
(identical(other.quantity, quantity) ||
|
||||||
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
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(
|
||||||
Object.hash(runtimeType, product, variant, quantity, notes);
|
runtimeType,
|
||||||
|
product,
|
||||||
|
variant,
|
||||||
|
quantity,
|
||||||
|
notes,
|
||||||
|
lineId,
|
||||||
|
weight,
|
||||||
|
);
|
||||||
|
|
||||||
/// Create a copy of ProductQuantity
|
/// Create a copy of ProductQuantity
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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({
|
const factory _ProductQuantity({
|
||||||
required final Product product,
|
required final Product product,
|
||||||
final ProductVariant? variant,
|
final ProductVariant? variant,
|
||||||
required final int quantity,
|
required final int quantity,
|
||||||
required final String notes,
|
required final String notes,
|
||||||
|
final String lineId,
|
||||||
|
final double? weight,
|
||||||
}) = _$ProductQuantityImpl;
|
}) = _$ProductQuantityImpl;
|
||||||
|
const _ProductQuantity._() : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Product get product;
|
Product get product;
|
||||||
@@ -1360,6 +1512,12 @@ abstract class _ProductQuantity implements ProductQuantity {
|
|||||||
int get quantity;
|
int get quantity;
|
||||||
@override
|
@override
|
||||||
String get notes;
|
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
|
/// Create a copy of ProductQuantity
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<Either<UnitFailure, List<ProductUnit>>> getUnits({
|
||||||
|
bool forceRefresh = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<ProductUnit>> cachedUnits();
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +83,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +114,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +146,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
} else {
|
} else {
|
||||||
final order = OrderDto.fromJson(
|
final order = OrderDto.fromJson(
|
||||||
@@ -171,6 +195,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +231,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +275,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,6 +304,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +338,12 @@ class OrderRemoteDataProvider {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
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());
|
return DC.error(const OrderFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto {
|
|||||||
@JsonKey(name: 'quantity') int? quantity,
|
@JsonKey(name: 'quantity') int? quantity,
|
||||||
@JsonKey(name: 'unit_price') int? unitPrice,
|
@JsonKey(name: 'unit_price') int? unitPrice,
|
||||||
@JsonKey(name: 'notes') String? notes,
|
@JsonKey(name: 'notes') String? notes,
|
||||||
|
@JsonKey(name: 'weight') double? weight,
|
||||||
}) = _AddItemOrderRequestDto;
|
}) = _AddItemOrderRequestDto;
|
||||||
|
|
||||||
factory AddItemOrderRequestDto.fromJson(Map<String, dynamic> json) =>
|
factory AddItemOrderRequestDto.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -21,6 +22,7 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto {
|
|||||||
quantity: quantity ?? 0,
|
quantity: quantity ?? 0,
|
||||||
unitPrice: unitPrice ?? 0,
|
unitPrice: unitPrice ?? 0,
|
||||||
notes: notes ?? '',
|
notes: notes ?? '',
|
||||||
|
weight: weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
factory AddItemOrderRequestDto.fromDomain(
|
factory AddItemOrderRequestDto.fromDomain(
|
||||||
@@ -31,6 +33,7 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto {
|
|||||||
quantity: addItemOrderRequest.quantity,
|
quantity: addItemOrderRequest.quantity,
|
||||||
unitPrice: addItemOrderRequest.unitPrice,
|
unitPrice: addItemOrderRequest.unitPrice,
|
||||||
notes: addItemOrderRequest.notes,
|
notes: addItemOrderRequest.notes,
|
||||||
|
weight: addItemOrderRequest.weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toRequest() {
|
Map<String, dynamic> toRequest() {
|
||||||
@@ -45,6 +48,11 @@ class AddItemOrderRequestDto with _$AddItemOrderRequestDto {
|
|||||||
data['product_variant_id'] = productvariantId;
|
data['product_variant_id'] = productvariantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Produk satuan ditolak backend bila membawa berat.
|
||||||
|
if (weight != null) {
|
||||||
|
data['weight'] = weight;
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ class OrderItemDto with _$OrderItemDto {
|
|||||||
@JsonKey(name: "paid_quantity") int? paidQuantity,
|
@JsonKey(name: "paid_quantity") int? paidQuantity,
|
||||||
@JsonKey(name: "category_id") String? categoryId,
|
@JsonKey(name: "category_id") String? categoryId,
|
||||||
@JsonKey(name: "category_name") String? categoryName,
|
@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;
|
}) = _OrderItemDto;
|
||||||
|
|
||||||
factory OrderItemDto.fromJson(Map<String, dynamic> json) =>
|
factory OrderItemDto.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -144,6 +148,10 @@ class OrderItemDto with _$OrderItemDto {
|
|||||||
paidQuantity: paidQuantity ?? 0,
|
paidQuantity: paidQuantity ?? 0,
|
||||||
categoryId: categoryId ?? '',
|
categoryId: categoryId ?? '',
|
||||||
categoryName: categoryName ?? '',
|
categoryName: categoryName ?? '',
|
||||||
|
weight: weight,
|
||||||
|
unitId: unitId ?? '',
|
||||||
|
unitName: unitName ?? '',
|
||||||
|
unitAbbreviation: unitAbbreviation ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
factory OrderItemDto.fromDomain(OrderItem orderItem) => OrderItemDto(
|
factory OrderItemDto.fromDomain(OrderItem orderItem) => OrderItemDto(
|
||||||
@@ -166,5 +174,9 @@ class OrderItemDto with _$OrderItemDto {
|
|||||||
paidQuantity: orderItem.paidQuantity,
|
paidQuantity: orderItem.paidQuantity,
|
||||||
categoryId: orderItem.categoryId,
|
categoryId: orderItem.categoryId,
|
||||||
categoryName: orderItem.categoryName,
|
categoryName: orderItem.categoryName,
|
||||||
|
weight: orderItem.weight,
|
||||||
|
unitId: orderItem.unitId,
|
||||||
|
unitName: orderItem.unitName,
|
||||||
|
unitAbbreviation: orderItem.unitAbbreviation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class OrderItemRequestDto with _$OrderItemRequestDto {
|
|||||||
@JsonKey(name: "quantity") int? quantity,
|
@JsonKey(name: "quantity") int? quantity,
|
||||||
@JsonKey(name: "unit_price") int? unitPrice,
|
@JsonKey(name: "unit_price") int? unitPrice,
|
||||||
@JsonKey(name: "notes") String? notes,
|
@JsonKey(name: "notes") String? notes,
|
||||||
|
@JsonKey(name: "weight") double? weight,
|
||||||
}) = _OrderItemRequestDto;
|
}) = _OrderItemRequestDto;
|
||||||
|
|
||||||
factory OrderItemRequestDto.fromJson(Map<String, dynamic> json) =>
|
factory OrderItemRequestDto.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -87,6 +88,7 @@ class OrderItemRequestDto with _$OrderItemRequestDto {
|
|||||||
quantity: quantity ?? 0,
|
quantity: quantity ?? 0,
|
||||||
unitPrice: unitPrice ?? 0,
|
unitPrice: unitPrice ?? 0,
|
||||||
notes: notes ?? '',
|
notes: notes ?? '',
|
||||||
|
weight: weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
factory OrderItemRequestDto.fromDomain(OrderItemRequest request) =>
|
factory OrderItemRequestDto.fromDomain(OrderItemRequest request) =>
|
||||||
@@ -96,6 +98,7 @@ class OrderItemRequestDto with _$OrderItemRequestDto {
|
|||||||
quantity: request.quantity,
|
quantity: request.quantity,
|
||||||
unitPrice: request.unitPrice,
|
unitPrice: request.unitPrice,
|
||||||
notes: request.notes,
|
notes: request.notes,
|
||||||
|
weight: request.weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toRequest() {
|
Map<String, dynamic> toRequest() {
|
||||||
@@ -110,6 +113,12 @@ class OrderItemRequestDto with _$OrderItemRequestDto {
|
|||||||
data["product_variant_id"] = productVariantId;
|
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;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1177,6 +1177,14 @@ mixin _$OrderItemDto {
|
|||||||
String? get categoryId => throw _privateConstructorUsedError;
|
String? get categoryId => throw _privateConstructorUsedError;
|
||||||
@JsonKey(name: "category_name")
|
@JsonKey(name: "category_name")
|
||||||
String? get categoryName => throw _privateConstructorUsedError;
|
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.
|
/// Serializes this OrderItemDto to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@@ -1215,6 +1223,10 @@ abstract class $OrderItemDtoCopyWith<$Res> {
|
|||||||
@JsonKey(name: "paid_quantity") int? paidQuantity,
|
@JsonKey(name: "paid_quantity") int? paidQuantity,
|
||||||
@JsonKey(name: "category_id") String? categoryId,
|
@JsonKey(name: "category_id") String? categoryId,
|
||||||
@JsonKey(name: "category_name") String? categoryName,
|
@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? paidQuantity = freezed,
|
||||||
Object? categoryId = freezed,
|
Object? categoryId = freezed,
|
||||||
Object? categoryName = freezed,
|
Object? categoryName = freezed,
|
||||||
|
Object? weight = freezed,
|
||||||
|
Object? unitId = freezed,
|
||||||
|
Object? unitName = freezed,
|
||||||
|
Object? unitAbbreviation = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -1331,6 +1347,22 @@ class _$OrderItemDtoCopyWithImpl<$Res, $Val extends OrderItemDto>
|
|||||||
? _value.categoryName
|
? _value.categoryName
|
||||||
: categoryName // ignore: cast_nullable_to_non_nullable
|
: categoryName // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
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,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -1366,6 +1398,10 @@ abstract class _$$OrderItemDtoImplCopyWith<$Res>
|
|||||||
@JsonKey(name: "paid_quantity") int? paidQuantity,
|
@JsonKey(name: "paid_quantity") int? paidQuantity,
|
||||||
@JsonKey(name: "category_id") String? categoryId,
|
@JsonKey(name: "category_id") String? categoryId,
|
||||||
@JsonKey(name: "category_name") String? categoryName,
|
@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? paidQuantity = freezed,
|
||||||
Object? categoryId = freezed,
|
Object? categoryId = freezed,
|
||||||
Object? categoryName = freezed,
|
Object? categoryName = freezed,
|
||||||
|
Object? weight = freezed,
|
||||||
|
Object? unitId = freezed,
|
||||||
|
Object? unitName = freezed,
|
||||||
|
Object? unitAbbreviation = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$OrderItemDtoImpl(
|
_$OrderItemDtoImpl(
|
||||||
@@ -1481,6 +1521,22 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res>
|
|||||||
? _value.categoryName
|
? _value.categoryName
|
||||||
: categoryName // ignore: cast_nullable_to_non_nullable
|
: categoryName // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
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: "paid_quantity") this.paidQuantity,
|
||||||
@JsonKey(name: "category_id") this.categoryId,
|
@JsonKey(name: "category_id") this.categoryId,
|
||||||
@JsonKey(name: "category_name") this.categoryName,
|
@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,
|
}) : _modifiers = modifiers,
|
||||||
super._();
|
super._();
|
||||||
|
|
||||||
@@ -1580,10 +1640,22 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
|
|||||||
@override
|
@override
|
||||||
@JsonKey(name: "category_name")
|
@JsonKey(name: "category_name")
|
||||||
final String? categoryName;
|
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
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -1626,7 +1698,13 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
|
|||||||
(identical(other.categoryId, categoryId) ||
|
(identical(other.categoryId, categoryId) ||
|
||||||
other.categoryId == categoryId) &&
|
other.categoryId == categoryId) &&
|
||||||
(identical(other.categoryName, categoryName) ||
|
(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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -1652,6 +1730,10 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
|
|||||||
paidQuantity,
|
paidQuantity,
|
||||||
categoryId,
|
categoryId,
|
||||||
categoryName,
|
categoryName,
|
||||||
|
weight,
|
||||||
|
unitId,
|
||||||
|
unitName,
|
||||||
|
unitAbbreviation,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// Create a copy of OrderItemDto
|
/// Create a copy of OrderItemDto
|
||||||
@@ -1689,6 +1771,10 @@ abstract class _OrderItemDto extends OrderItemDto {
|
|||||||
@JsonKey(name: "paid_quantity") final int? paidQuantity,
|
@JsonKey(name: "paid_quantity") final int? paidQuantity,
|
||||||
@JsonKey(name: "category_id") final String? categoryId,
|
@JsonKey(name: "category_id") final String? categoryId,
|
||||||
@JsonKey(name: "category_name") final String? categoryName,
|
@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;
|
}) = _$OrderItemDtoImpl;
|
||||||
const _OrderItemDto._() : super._();
|
const _OrderItemDto._() : super._();
|
||||||
|
|
||||||
@@ -1752,6 +1838,18 @@ abstract class _OrderItemDto extends OrderItemDto {
|
|||||||
@override
|
@override
|
||||||
@JsonKey(name: "category_name")
|
@JsonKey(name: "category_name")
|
||||||
String? get categoryName;
|
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
|
/// Create a copy of OrderItemDto
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -2136,6 +2234,8 @@ mixin _$OrderItemRequestDto {
|
|||||||
int? get unitPrice => throw _privateConstructorUsedError;
|
int? get unitPrice => throw _privateConstructorUsedError;
|
||||||
@JsonKey(name: "notes")
|
@JsonKey(name: "notes")
|
||||||
String? get notes => throw _privateConstructorUsedError;
|
String? get notes => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(name: "weight")
|
||||||
|
double? get weight => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this OrderItemRequestDto to a JSON map.
|
/// Serializes this OrderItemRequestDto to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@@ -2160,6 +2260,7 @@ abstract class $OrderItemRequestDtoCopyWith<$Res> {
|
|||||||
@JsonKey(name: "quantity") int? quantity,
|
@JsonKey(name: "quantity") int? quantity,
|
||||||
@JsonKey(name: "unit_price") int? unitPrice,
|
@JsonKey(name: "unit_price") int? unitPrice,
|
||||||
@JsonKey(name: "notes") String? notes,
|
@JsonKey(name: "notes") String? notes,
|
||||||
|
@JsonKey(name: "weight") double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2183,6 +2284,7 @@ class _$OrderItemRequestDtoCopyWithImpl<$Res, $Val extends OrderItemRequestDto>
|
|||||||
Object? quantity = freezed,
|
Object? quantity = freezed,
|
||||||
Object? unitPrice = freezed,
|
Object? unitPrice = freezed,
|
||||||
Object? notes = freezed,
|
Object? notes = freezed,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -2206,6 +2308,10 @@ class _$OrderItemRequestDtoCopyWithImpl<$Res, $Val extends OrderItemRequestDto>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String?,
|
||||||
|
weight: freezed == weight
|
||||||
|
? _value.weight
|
||||||
|
: weight // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -2227,6 +2333,7 @@ abstract class _$$OrderItemRequestDtoImplCopyWith<$Res>
|
|||||||
@JsonKey(name: "quantity") int? quantity,
|
@JsonKey(name: "quantity") int? quantity,
|
||||||
@JsonKey(name: "unit_price") int? unitPrice,
|
@JsonKey(name: "unit_price") int? unitPrice,
|
||||||
@JsonKey(name: "notes") String? notes,
|
@JsonKey(name: "notes") String? notes,
|
||||||
|
@JsonKey(name: "weight") double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2249,6 +2356,7 @@ class __$$OrderItemRequestDtoImplCopyWithImpl<$Res>
|
|||||||
Object? quantity = freezed,
|
Object? quantity = freezed,
|
||||||
Object? unitPrice = freezed,
|
Object? unitPrice = freezed,
|
||||||
Object? notes = freezed,
|
Object? notes = freezed,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$OrderItemRequestDtoImpl(
|
_$OrderItemRequestDtoImpl(
|
||||||
@@ -2272,6 +2380,10 @@ class __$$OrderItemRequestDtoImplCopyWithImpl<$Res>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
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: "quantity") this.quantity,
|
||||||
@JsonKey(name: "unit_price") this.unitPrice,
|
@JsonKey(name: "unit_price") this.unitPrice,
|
||||||
@JsonKey(name: "notes") this.notes,
|
@JsonKey(name: "notes") this.notes,
|
||||||
|
@JsonKey(name: "weight") this.weight,
|
||||||
}) : super._();
|
}) : super._();
|
||||||
|
|
||||||
factory _$OrderItemRequestDtoImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$OrderItemRequestDtoImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -2306,10 +2419,13 @@ class _$OrderItemRequestDtoImpl extends _OrderItemRequestDto {
|
|||||||
@override
|
@override
|
||||||
@JsonKey(name: "notes")
|
@JsonKey(name: "notes")
|
||||||
final String? notes;
|
final String? notes;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: "weight")
|
||||||
|
final double? weight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -2325,7 +2441,8 @@ class _$OrderItemRequestDtoImpl extends _OrderItemRequestDto {
|
|||||||
other.quantity == quantity) &&
|
other.quantity == quantity) &&
|
||||||
(identical(other.unitPrice, unitPrice) ||
|
(identical(other.unitPrice, unitPrice) ||
|
||||||
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -2337,6 +2454,7 @@ class _$OrderItemRequestDtoImpl extends _OrderItemRequestDto {
|
|||||||
quantity,
|
quantity,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
notes,
|
notes,
|
||||||
|
weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of OrderItemRequestDto
|
/// Create a copy of OrderItemRequestDto
|
||||||
@@ -2363,6 +2481,7 @@ abstract class _OrderItemRequestDto extends OrderItemRequestDto {
|
|||||||
@JsonKey(name: "quantity") final int? quantity,
|
@JsonKey(name: "quantity") final int? quantity,
|
||||||
@JsonKey(name: "unit_price") final int? unitPrice,
|
@JsonKey(name: "unit_price") final int? unitPrice,
|
||||||
@JsonKey(name: "notes") final String? notes,
|
@JsonKey(name: "notes") final String? notes,
|
||||||
|
@JsonKey(name: "weight") final double? weight,
|
||||||
}) = _$OrderItemRequestDtoImpl;
|
}) = _$OrderItemRequestDtoImpl;
|
||||||
const _OrderItemRequestDto._() : super._();
|
const _OrderItemRequestDto._() : super._();
|
||||||
|
|
||||||
@@ -2384,6 +2503,9 @@ abstract class _OrderItemRequestDto extends OrderItemRequestDto {
|
|||||||
@override
|
@override
|
||||||
@JsonKey(name: "notes")
|
@JsonKey(name: "notes")
|
||||||
String? get notes;
|
String? get notes;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: "weight")
|
||||||
|
double? get weight;
|
||||||
|
|
||||||
/// Create a copy of OrderItemRequestDto
|
/// Create a copy of OrderItemRequestDto
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -4025,6 +4147,8 @@ mixin _$AddItemOrderRequestDto {
|
|||||||
int? get unitPrice => throw _privateConstructorUsedError;
|
int? get unitPrice => throw _privateConstructorUsedError;
|
||||||
@JsonKey(name: 'notes')
|
@JsonKey(name: 'notes')
|
||||||
String? get notes => throw _privateConstructorUsedError;
|
String? get notes => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(name: 'weight')
|
||||||
|
double? get weight => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this AddItemOrderRequestDto to a JSON map.
|
/// Serializes this AddItemOrderRequestDto to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@@ -4049,6 +4173,7 @@ abstract class $AddItemOrderRequestDtoCopyWith<$Res> {
|
|||||||
@JsonKey(name: 'quantity') int? quantity,
|
@JsonKey(name: 'quantity') int? quantity,
|
||||||
@JsonKey(name: 'unit_price') int? unitPrice,
|
@JsonKey(name: 'unit_price') int? unitPrice,
|
||||||
@JsonKey(name: 'notes') String? notes,
|
@JsonKey(name: 'notes') String? notes,
|
||||||
|
@JsonKey(name: 'weight') double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4075,6 +4200,7 @@ class _$AddItemOrderRequestDtoCopyWithImpl<
|
|||||||
Object? quantity = freezed,
|
Object? quantity = freezed,
|
||||||
Object? unitPrice = freezed,
|
Object? unitPrice = freezed,
|
||||||
Object? notes = freezed,
|
Object? notes = freezed,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -4098,6 +4224,10 @@ class _$AddItemOrderRequestDtoCopyWithImpl<
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String?,
|
||||||
|
weight: freezed == weight
|
||||||
|
? _value.weight
|
||||||
|
: weight // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@@ -4119,6 +4249,7 @@ abstract class _$$AddItemOrderRequestDtoImplCopyWith<$Res>
|
|||||||
@JsonKey(name: 'quantity') int? quantity,
|
@JsonKey(name: 'quantity') int? quantity,
|
||||||
@JsonKey(name: 'unit_price') int? unitPrice,
|
@JsonKey(name: 'unit_price') int? unitPrice,
|
||||||
@JsonKey(name: 'notes') String? notes,
|
@JsonKey(name: 'notes') String? notes,
|
||||||
|
@JsonKey(name: 'weight') double? weight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4142,6 +4273,7 @@ class __$$AddItemOrderRequestDtoImplCopyWithImpl<$Res>
|
|||||||
Object? quantity = freezed,
|
Object? quantity = freezed,
|
||||||
Object? unitPrice = freezed,
|
Object? unitPrice = freezed,
|
||||||
Object? notes = freezed,
|
Object? notes = freezed,
|
||||||
|
Object? weight = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$AddItemOrderRequestDtoImpl(
|
_$AddItemOrderRequestDtoImpl(
|
||||||
@@ -4165,6 +4297,10 @@ class __$$AddItemOrderRequestDtoImplCopyWithImpl<$Res>
|
|||||||
? _value.notes
|
? _value.notes
|
||||||
: notes // ignore: cast_nullable_to_non_nullable
|
: notes // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
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: 'quantity') this.quantity,
|
||||||
@JsonKey(name: 'unit_price') this.unitPrice,
|
@JsonKey(name: 'unit_price') this.unitPrice,
|
||||||
@JsonKey(name: 'notes') this.notes,
|
@JsonKey(name: 'notes') this.notes,
|
||||||
|
@JsonKey(name: 'weight') this.weight,
|
||||||
}) : super._();
|
}) : super._();
|
||||||
|
|
||||||
factory _$AddItemOrderRequestDtoImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$AddItemOrderRequestDtoImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -4199,10 +4336,13 @@ class _$AddItemOrderRequestDtoImpl extends _AddItemOrderRequestDto {
|
|||||||
@override
|
@override
|
||||||
@JsonKey(name: 'notes')
|
@JsonKey(name: 'notes')
|
||||||
final String? notes;
|
final String? notes;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'weight')
|
||||||
|
final double? weight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -4218,7 +4358,8 @@ class _$AddItemOrderRequestDtoImpl extends _AddItemOrderRequestDto {
|
|||||||
other.quantity == quantity) &&
|
other.quantity == quantity) &&
|
||||||
(identical(other.unitPrice, unitPrice) ||
|
(identical(other.unitPrice, unitPrice) ||
|
||||||
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -4230,6 +4371,7 @@ class _$AddItemOrderRequestDtoImpl extends _AddItemOrderRequestDto {
|
|||||||
quantity,
|
quantity,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
notes,
|
notes,
|
||||||
|
weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of AddItemOrderRequestDto
|
/// Create a copy of AddItemOrderRequestDto
|
||||||
@@ -4257,6 +4399,7 @@ abstract class _AddItemOrderRequestDto extends AddItemOrderRequestDto {
|
|||||||
@JsonKey(name: 'quantity') final int? quantity,
|
@JsonKey(name: 'quantity') final int? quantity,
|
||||||
@JsonKey(name: 'unit_price') final int? unitPrice,
|
@JsonKey(name: 'unit_price') final int? unitPrice,
|
||||||
@JsonKey(name: 'notes') final String? notes,
|
@JsonKey(name: 'notes') final String? notes,
|
||||||
|
@JsonKey(name: 'weight') final double? weight,
|
||||||
}) = _$AddItemOrderRequestDtoImpl;
|
}) = _$AddItemOrderRequestDtoImpl;
|
||||||
const _AddItemOrderRequestDto._() : super._();
|
const _AddItemOrderRequestDto._() : super._();
|
||||||
|
|
||||||
@@ -4278,6 +4421,9 @@ abstract class _AddItemOrderRequestDto extends AddItemOrderRequestDto {
|
|||||||
@override
|
@override
|
||||||
@JsonKey(name: 'notes')
|
@JsonKey(name: 'notes')
|
||||||
String? get notes;
|
String? get notes;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'weight')
|
||||||
|
double? get weight;
|
||||||
|
|
||||||
/// Create a copy of AddItemOrderRequestDto
|
/// Create a copy of AddItemOrderRequestDto
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ _$OrderItemDtoImpl _$$OrderItemDtoImplFromJson(Map<String, dynamic> json) =>
|
|||||||
paidQuantity: (json['paid_quantity'] as num?)?.toInt(),
|
paidQuantity: (json['paid_quantity'] as num?)?.toInt(),
|
||||||
categoryId: json['category_id'] as String?,
|
categoryId: json['category_id'] as String?,
|
||||||
categoryName: json['category_name'] 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<String, dynamic> _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) =>
|
Map<String, dynamic> _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) =>
|
||||||
@@ -136,6 +140,10 @@ Map<String, dynamic> _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) =>
|
|||||||
'paid_quantity': instance.paidQuantity,
|
'paid_quantity': instance.paidQuantity,
|
||||||
'category_id': instance.categoryId,
|
'category_id': instance.categoryId,
|
||||||
'category_name': instance.categoryName,
|
'category_name': instance.categoryName,
|
||||||
|
'weight': instance.weight,
|
||||||
|
'unit_id': instance.unitId,
|
||||||
|
'unit_name': instance.unitName,
|
||||||
|
'unit_abbreviation': instance.unitAbbreviation,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$OrderRequestDtoImpl _$$OrderRequestDtoImplFromJson(
|
_$OrderRequestDtoImpl _$$OrderRequestDtoImplFromJson(
|
||||||
@@ -174,6 +182,7 @@ _$OrderItemRequestDtoImpl _$$OrderItemRequestDtoImplFromJson(
|
|||||||
quantity: (json['quantity'] as num?)?.toInt(),
|
quantity: (json['quantity'] as num?)?.toInt(),
|
||||||
unitPrice: (json['unit_price'] as num?)?.toInt(),
|
unitPrice: (json['unit_price'] as num?)?.toInt(),
|
||||||
notes: json['notes'] as String?,
|
notes: json['notes'] as String?,
|
||||||
|
weight: (json['weight'] as num?)?.toDouble(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$OrderItemRequestDtoImplToJson(
|
Map<String, dynamic> _$$OrderItemRequestDtoImplToJson(
|
||||||
@@ -184,6 +193,7 @@ Map<String, dynamic> _$$OrderItemRequestDtoImplToJson(
|
|||||||
'quantity': instance.quantity,
|
'quantity': instance.quantity,
|
||||||
'unit_price': instance.unitPrice,
|
'unit_price': instance.unitPrice,
|
||||||
'notes': instance.notes,
|
'notes': instance.notes,
|
||||||
|
'weight': instance.weight,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$PaymentRequestDtoImpl _$$PaymentRequestDtoImplFromJson(
|
_$PaymentRequestDtoImpl _$$PaymentRequestDtoImplFromJson(
|
||||||
@@ -317,6 +327,7 @@ _$AddItemOrderRequestDtoImpl _$$AddItemOrderRequestDtoImplFromJson(
|
|||||||
quantity: (json['quantity'] as num?)?.toInt(),
|
quantity: (json['quantity'] as num?)?.toInt(),
|
||||||
unitPrice: (json['unit_price'] as num?)?.toInt(),
|
unitPrice: (json['unit_price'] as num?)?.toInt(),
|
||||||
notes: json['notes'] as String?,
|
notes: json['notes'] as String?,
|
||||||
|
weight: (json['weight'] as num?)?.toDouble(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$AddItemOrderRequestDtoImplToJson(
|
Map<String, dynamic> _$$AddItemOrderRequestDtoImplToJson(
|
||||||
@@ -327,4 +338,5 @@ Map<String, dynamic> _$$AddItemOrderRequestDtoImplToJson(
|
|||||||
'quantity': instance.quantity,
|
'quantity': instance.quantity,
|
||||||
'unit_price': instance.unitPrice,
|
'unit_price': instance.unitPrice,
|
||||||
'notes': instance.notes,
|
'notes': instance.notes,
|
||||||
|
'weight': instance.weight,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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<String, dynamic> json) =>
|
||||||
|
_$ProductUnitDtoFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class ProductDto with _$ProductDto {
|
class ProductDto with _$ProductDto {
|
||||||
const ProductDto._();
|
const ProductDto._();
|
||||||
@@ -46,11 +65,28 @@ class ProductDto with _$ProductDto {
|
|||||||
@JsonKey(name: "created_at") String? createdAt,
|
@JsonKey(name: "created_at") String? createdAt,
|
||||||
@JsonKey(name: "updated_at") String? updatedAt,
|
@JsonKey(name: "updated_at") String? updatedAt,
|
||||||
@JsonKey(name: "variants") List<ProductVariantDto>? variants,
|
@JsonKey(name: "variants") List<ProductVariantDto>? 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;
|
}) = _ProductDto;
|
||||||
|
|
||||||
factory ProductDto.fromJson(Map<String, dynamic> json) =>
|
factory ProductDto.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ProductDtoFromJson(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
|
/// Mapping ke domain
|
||||||
Product toDomain() => Product(
|
Product toDomain() => Product(
|
||||||
id: id ?? '',
|
id: id ?? '',
|
||||||
@@ -70,6 +106,10 @@ class ProductDto with _$ProductDto {
|
|||||||
createdAt: createdAt ?? '',
|
createdAt: createdAt ?? '',
|
||||||
updatedAt: updatedAt ?? '',
|
updatedAt: updatedAt ?? '',
|
||||||
variants: variants?.map((v) => v.toDomain()).toList() ?? [],
|
variants: variants?.map((v) => v.toDomain()).toList() ?? [],
|
||||||
|
sellBy: SellBy.normalize(sellBy),
|
||||||
|
unitId: _resolvedUnitId,
|
||||||
|
unitName: _resolvedUnitName,
|
||||||
|
unitAbbreviation: _resolvedUnitAbbreviation,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
@@ -89,6 +129,10 @@ class ProductDto with _$ProductDto {
|
|||||||
'is_active': isActive == true ? 1 : 0,
|
'is_active': isActive == true ? 1 : 0,
|
||||||
'created_at': createdAt,
|
'created_at': createdAt,
|
||||||
'updated_at': updatedAt,
|
'updated_at': updatedAt,
|
||||||
|
'sell_by': SellBy.normalize(sellBy),
|
||||||
|
'unit_id': _resolvedUnitId,
|
||||||
|
'unit_name': _resolvedUnitName,
|
||||||
|
'unit_abbreviation': _resolvedUnitAbbreviation,
|
||||||
};
|
};
|
||||||
|
|
||||||
factory ProductDto.fromMap(
|
factory ProductDto.fromMap(
|
||||||
@@ -117,6 +161,10 @@ class ProductDto with _$ProductDto {
|
|||||||
createdAt: map['created_at'] as String?,
|
createdAt: map['created_at'] as String?,
|
||||||
updatedAt: map['updated_at'] as String?,
|
updatedAt: map['updated_at'] as String?,
|
||||||
variants: variants,
|
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(
|
factory ProductDto.fromDomain(Product product) => ProductDto(
|
||||||
@@ -139,6 +187,10 @@ class ProductDto with _$ProductDto {
|
|||||||
variants: product.variants
|
variants: product.variants
|
||||||
.map((v) => ProductVariantDto.fromDomain(v))
|
.map((v) => ProductVariantDto.fromDomain(v))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
sellBy: product.sellBy,
|
||||||
|
unitId: product.unitId,
|
||||||
|
unitName: product.unitName,
|
||||||
|
unitAbbreviation: product.unitAbbreviation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import '../../common/types/sell_by.dart';
|
||||||
import '../../domain/product/product.dart';
|
import '../../domain/product/product.dart';
|
||||||
|
|
||||||
part 'product_dtos.freezed.dart';
|
part 'product_dtos.freezed.dart';
|
||||||
|
|||||||
@@ -293,6 +293,223 @@ abstract class _ListProductDto extends ListProductDto {
|
|||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProductUnitDto _$ProductUnitDtoFromJson(Map<String, dynamic> 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<String, dynamic> 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<ProductUnitDto> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> json) {
|
ProductDto _$ProductDtoFromJson(Map<String, dynamic> json) {
|
||||||
return _ProductDto.fromJson(json);
|
return _ProductDto.fromJson(json);
|
||||||
}
|
}
|
||||||
@@ -333,6 +550,16 @@ mixin _$ProductDto {
|
|||||||
String? get updatedAt => throw _privateConstructorUsedError;
|
String? get updatedAt => throw _privateConstructorUsedError;
|
||||||
@JsonKey(name: "variants")
|
@JsonKey(name: "variants")
|
||||||
List<ProductVariantDto>? get variants => throw _privateConstructorUsedError;
|
List<ProductVariantDto>? 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.
|
/// Serializes this ProductDto to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@@ -369,7 +596,14 @@ abstract class $ProductDtoCopyWith<$Res> {
|
|||||||
@JsonKey(name: "created_at") String? createdAt,
|
@JsonKey(name: "created_at") String? createdAt,
|
||||||
@JsonKey(name: "updated_at") String? updatedAt,
|
@JsonKey(name: "updated_at") String? updatedAt,
|
||||||
@JsonKey(name: "variants") List<ProductVariantDto>? variants,
|
@JsonKey(name: "variants") List<ProductVariantDto>? 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
|
/// @nodoc
|
||||||
@@ -404,6 +638,11 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto>
|
|||||||
Object? createdAt = freezed,
|
Object? createdAt = freezed,
|
||||||
Object? updatedAt = freezed,
|
Object? updatedAt = freezed,
|
||||||
Object? variants = freezed,
|
Object? variants = freezed,
|
||||||
|
Object? sellBy = freezed,
|
||||||
|
Object? unitId = freezed,
|
||||||
|
Object? unitName = freezed,
|
||||||
|
Object? unitAbbreviation = freezed,
|
||||||
|
Object? unit = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@@ -475,10 +714,44 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto>
|
|||||||
? _value.variants
|
? _value.variants
|
||||||
: variants // ignore: cast_nullable_to_non_nullable
|
: variants // ignore: cast_nullable_to_non_nullable
|
||||||
as List<ProductVariantDto>?,
|
as List<ProductVariantDto>?,
|
||||||
|
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,
|
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
|
/// @nodoc
|
||||||
@@ -508,7 +781,15 @@ abstract class _$$ProductDtoImplCopyWith<$Res>
|
|||||||
@JsonKey(name: "created_at") String? createdAt,
|
@JsonKey(name: "created_at") String? createdAt,
|
||||||
@JsonKey(name: "updated_at") String? updatedAt,
|
@JsonKey(name: "updated_at") String? updatedAt,
|
||||||
@JsonKey(name: "variants") List<ProductVariantDto>? variants,
|
@JsonKey(name: "variants") List<ProductVariantDto>? 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
|
/// @nodoc
|
||||||
@@ -542,6 +823,11 @@ class __$$ProductDtoImplCopyWithImpl<$Res>
|
|||||||
Object? createdAt = freezed,
|
Object? createdAt = freezed,
|
||||||
Object? updatedAt = freezed,
|
Object? updatedAt = freezed,
|
||||||
Object? variants = freezed,
|
Object? variants = freezed,
|
||||||
|
Object? sellBy = freezed,
|
||||||
|
Object? unitId = freezed,
|
||||||
|
Object? unitName = freezed,
|
||||||
|
Object? unitAbbreviation = freezed,
|
||||||
|
Object? unit = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$ProductDtoImpl(
|
_$ProductDtoImpl(
|
||||||
@@ -613,6 +899,26 @@ class __$$ProductDtoImplCopyWithImpl<$Res>
|
|||||||
? _value._variants
|
? _value._variants
|
||||||
: variants // ignore: cast_nullable_to_non_nullable
|
: variants // ignore: cast_nullable_to_non_nullable
|
||||||
as List<ProductVariantDto>?,
|
as List<ProductVariantDto>?,
|
||||||
|
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: "created_at") this.createdAt,
|
||||||
@JsonKey(name: "updated_at") this.updatedAt,
|
@JsonKey(name: "updated_at") this.updatedAt,
|
||||||
@JsonKey(name: "variants") final List<ProductVariantDto>? variants,
|
@JsonKey(name: "variants") final List<ProductVariantDto>? 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,
|
}) : _metadata = metadata,
|
||||||
_variants = variants,
|
_variants = variants,
|
||||||
super._();
|
super._();
|
||||||
@@ -713,9 +1024,25 @@ class _$ProductDtoImpl extends _ProductDto {
|
|||||||
return EqualUnmodifiableListView(value);
|
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
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -749,12 +1076,19 @@ class _$ProductDtoImpl extends _ProductDto {
|
|||||||
other.createdAt == createdAt) &&
|
other.createdAt == createdAt) &&
|
||||||
(identical(other.updatedAt, updatedAt) ||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hashAll([
|
||||||
runtimeType,
|
runtimeType,
|
||||||
id,
|
id,
|
||||||
organizationId,
|
organizationId,
|
||||||
@@ -773,7 +1107,12 @@ class _$ProductDtoImpl extends _ProductDto {
|
|||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
const DeepCollectionEquality().hash(_variants),
|
const DeepCollectionEquality().hash(_variants),
|
||||||
);
|
sellBy,
|
||||||
|
unitId,
|
||||||
|
unitName,
|
||||||
|
unitAbbreviation,
|
||||||
|
unit,
|
||||||
|
]);
|
||||||
|
|
||||||
/// Create a copy of ProductDto
|
/// Create a copy of ProductDto
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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: "created_at") final String? createdAt,
|
||||||
@JsonKey(name: "updated_at") final String? updatedAt,
|
@JsonKey(name: "updated_at") final String? updatedAt,
|
||||||
@JsonKey(name: "variants") final List<ProductVariantDto>? variants,
|
@JsonKey(name: "variants") final List<ProductVariantDto>? 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;
|
}) = _$ProductDtoImpl;
|
||||||
const _ProductDto._() : super._();
|
const _ProductDto._() : super._();
|
||||||
|
|
||||||
@@ -865,6 +1209,21 @@ abstract class _ProductDto extends ProductDto {
|
|||||||
@override
|
@override
|
||||||
@JsonKey(name: "variants")
|
@JsonKey(name: "variants")
|
||||||
List<ProductVariantDto>? get variants;
|
List<ProductVariantDto>? 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
|
/// Create a copy of ProductDto
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|||||||
@@ -27,6 +27,21 @@ Map<String, dynamic> _$$ListProductDtoImplToJson(
|
|||||||
'total_pages': instance.totalPages,
|
'total_pages': instance.totalPages,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_$ProductUnitDtoImpl _$$ProductUnitDtoImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ProductUnitDtoImpl(
|
||||||
|
id: json['id'] as String?,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
abbreviation: json['abbreviation'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ProductUnitDtoImplToJson(
|
||||||
|
_$ProductUnitDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'abbreviation': instance.abbreviation,
|
||||||
|
};
|
||||||
|
|
||||||
_$ProductDtoImpl _$$ProductDtoImplFromJson(Map<String, dynamic> json) =>
|
_$ProductDtoImpl _$$ProductDtoImplFromJson(Map<String, dynamic> json) =>
|
||||||
_$ProductDtoImpl(
|
_$ProductDtoImpl(
|
||||||
id: json['id'] as String?,
|
id: json['id'] as String?,
|
||||||
@@ -48,6 +63,13 @@ _$ProductDtoImpl _$$ProductDtoImplFromJson(Map<String, dynamic> json) =>
|
|||||||
variants: (json['variants'] as List<dynamic>?)
|
variants: (json['variants'] as List<dynamic>?)
|
||||||
?.map((e) => ProductVariantDto.fromJson(e as Map<String, dynamic>))
|
?.map((e) => ProductVariantDto.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.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<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$ProductDtoImplToJson(_$ProductDtoImpl instance) =>
|
Map<String, dynamic> _$$ProductDtoImplToJson(_$ProductDtoImpl instance) =>
|
||||||
@@ -69,6 +91,11 @@ Map<String, dynamic> _$$ProductDtoImplToJson(_$ProductDtoImpl instance) =>
|
|||||||
'created_at': instance.createdAt,
|
'created_at': instance.createdAt,
|
||||||
'updated_at': instance.updatedAt,
|
'updated_at': instance.updatedAt,
|
||||||
'variants': instance.variants,
|
'variants': instance.variants,
|
||||||
|
'sell_by': instance.sellBy,
|
||||||
|
'unit_id': instance.unitId,
|
||||||
|
'unit_name': instance.unitName,
|
||||||
|
'unit_abbreviation': instance.unitAbbreviation,
|
||||||
|
'unit': instance.unit,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$ProductVariantDtoImpl _$$ProductVariantDtoImplFromJson(
|
_$ProductVariantDtoImpl _$$ProductVariantDtoImplFromJson(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
|||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import '../../../common/service/unit_registry.dart';
|
||||||
import '../../../domain/product/product.dart';
|
import '../../../domain/product/product.dart';
|
||||||
import '../datasources/local_data_provider.dart';
|
import '../datasources/local_data_provider.dart';
|
||||||
import '../datasources/remote_data_provider.dart';
|
import '../datasources/remote_data_provider.dart';
|
||||||
@@ -12,10 +13,38 @@ import '../product_dtos.dart';
|
|||||||
class ProductRepository implements IProductRepository {
|
class ProductRepository implements IProductRepository {
|
||||||
final ProductRemoteDataProvider _remoteDataProvider;
|
final ProductRemoteDataProvider _remoteDataProvider;
|
||||||
final ProductLocalDataProvider _localDataProvider;
|
final ProductLocalDataProvider _localDataProvider;
|
||||||
|
final UnitRegistry _unitRegistry;
|
||||||
|
|
||||||
final _logName = 'ProductRepository';
|
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<Product> _withUnits(List<Product> products) =>
|
||||||
|
products.map(_withUnit).toList();
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<ProductFailure, Product>> getProductById(String id) async {
|
Future<Either<ProductFailure, Product>> 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);
|
log('✅ Product loaded: ${productDomain.name}', name: _logName);
|
||||||
return Right(productDomain);
|
return Right(productDomain);
|
||||||
@@ -85,7 +114,9 @@ class ProductRepository implements IProductRepository {
|
|||||||
|
|
||||||
// 🧱 Bangun entity domain ListProduct
|
// 🧱 Bangun entity domain ListProduct
|
||||||
final result = ListProduct(
|
final result = ListProduct(
|
||||||
products: cachedProducts.data!.map((p) => p.toDomain()).toList(),
|
products: _withUnits(
|
||||||
|
cachedProducts.data!.map((p) => p.toDomain()).toList(),
|
||||||
|
),
|
||||||
totalCount: totalCount,
|
totalCount: totalCount,
|
||||||
page: page,
|
page: page,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
@@ -132,7 +163,9 @@ class ProductRepository implements IProductRepository {
|
|||||||
return left(cachedProducts.error!);
|
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(
|
final totalCount = await _localDataProvider.getTotalCount(
|
||||||
categoryId: categoryId,
|
categoryId: categoryId,
|
||||||
@@ -180,7 +213,9 @@ class ProductRepository implements IProductRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Mapping ke domain entity (kalau hasilnya masih berupa DTO)
|
// ✅ 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(
|
log(
|
||||||
'✅ Local search completed: ${products.length} results',
|
'✅ Local search completed: ${products.length} results',
|
||||||
@@ -369,7 +404,7 @@ class ProductRepository implements IProductRepository {
|
|||||||
|
|
||||||
final products = result.data!.toDomain();
|
final products = result.data!.toDomain();
|
||||||
|
|
||||||
return right(products);
|
return right(products.copyWith(products: _withUnits(products.products)));
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log('getProducts', name: _logName, error: e, stackTrace: s);
|
log('getProducts', name: _logName, error: e, stackTrace: s);
|
||||||
return left(const ProductFailure.unexpectedError());
|
return left(const ProductFailure.unexpectedError());
|
||||||
|
|||||||
@@ -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<void> saveUnits(List<UnitDto> units) async {
|
||||||
|
final jsonString = jsonEncode(units.map((e) => e.toJson()).toList());
|
||||||
|
await _sharedPreferences.setString(LocalStorageKey.units, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UnitDto> 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<String, dynamic>>()
|
||||||
|
.map(UnitDto.fromJson)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearUnits() async {
|
||||||
|
await _sharedPreferences.remove(LocalStorageKey.units);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DC<UnitFailure, List<UnitDto>>> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, dynamic> 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<UnitDto> listFromResponse(dynamic data) {
|
||||||
|
if (data is List) {
|
||||||
|
return data
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(UnitDto.fromJson)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data is Map<String, dynamic>) {
|
||||||
|
for (final key in const ['units', 'items', 'data']) {
|
||||||
|
final value = data[key];
|
||||||
|
if (value is List) {
|
||||||
|
return value
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(UnitDto.fromJson)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Either<UnitFailure, List<ProductUnit>>> 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<List<ProductUnit>> cachedUnits() async =>
|
||||||
|
_localDataProvider.getUnits().map((e) => e.toDomain()).toList();
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
@@ -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>(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<String, dynamic> 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<String, dynamic> 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<UnitDto> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'unit_dtos.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$UnitDtoImpl _$$UnitDtoImplFromJson(Map<String, dynamic> 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<String, dynamic> _$$UnitDtoImplToJson(_$UnitDtoImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'organization_id': instance.organizationId,
|
||||||
|
'name': instance.name,
|
||||||
|
'abbreviation': instance.abbreviation,
|
||||||
|
'is_active': instance.isActive,
|
||||||
|
};
|
||||||
+51
-24
@@ -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'
|
import 'package:apskel_pos_flutter_v2/common/service/device_info_service.dart'
|
||||||
as _i288;
|
as _i288;
|
||||||
import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart' as _i312;
|
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/analytic/analytic.dart' as _i346;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
|
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
|
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/printer/printer.dart' as _i104;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/product/product.dart' as _i44;
|
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/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/env.dart' as _i923;
|
||||||
import 'package:apskel_pos_flutter_v2/infrastructure/analytic/datasources/remote_data_provider.dart'
|
import 'package:apskel_pos_flutter_v2/infrastructure/analytic/datasources/remote_data_provider.dart'
|
||||||
as _i708;
|
as _i708;
|
||||||
@@ -148,6 +151,12 @@ import 'package:apskel_pos_flutter_v2/infrastructure/table/datasources/remote_da
|
|||||||
as _i95;
|
as _i95;
|
||||||
import 'package:apskel_pos_flutter_v2/infrastructure/table/repositories/table_repository.dart'
|
import 'package:apskel_pos_flutter_v2/infrastructure/table/repositories/table_repository.dart'
|
||||||
as _i824;
|
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'
|
import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
|
||||||
as _i800;
|
as _i800;
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
|
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
|
||||||
@@ -229,6 +238,9 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i693.OutletLocalDatasource>(
|
gh.factory<_i693.OutletLocalDatasource>(
|
||||||
() => _i693.OutletLocalDatasource(gh<_i460.SharedPreferences>()),
|
() => _i693.OutletLocalDatasource(gh<_i460.SharedPreferences>()),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i393.UnitLocalDataProvider>(
|
||||||
|
() => _i393.UnitLocalDataProvider(gh<_i460.SharedPreferences>()),
|
||||||
|
);
|
||||||
gh.lazySingleton<_i457.ApiClient>(
|
gh.lazySingleton<_i457.ApiClient>(
|
||||||
() => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()),
|
() => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()),
|
||||||
);
|
);
|
||||||
@@ -279,6 +291,9 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i95.TableRemoteDataProvider>(
|
gh.factory<_i95.TableRemoteDataProvider>(
|
||||||
() => _i95.TableRemoteDataProvider(gh<_i457.ApiClient>()),
|
() => _i95.TableRemoteDataProvider(gh<_i457.ApiClient>()),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i1016.UnitRemoteDataProvider>(
|
||||||
|
() => _i1016.UnitRemoteDataProvider(gh<_i457.ApiClient>()),
|
||||||
|
);
|
||||||
gh.factory<_i776.IAuthRepository>(
|
gh.factory<_i776.IAuthRepository>(
|
||||||
() => _i941.AuthRepository(
|
() => _i941.AuthRepository(
|
||||||
gh<_i370.AuthRemoteDataProvider>(),
|
gh<_i370.AuthRemoteDataProvider>(),
|
||||||
@@ -291,13 +306,6 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh<_i708.CategoryLocalDataProvider>(),
|
gh<_i708.CategoryLocalDataProvider>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.factory<_i46.LoginFormBloc>(
|
|
||||||
() => _i46.LoginFormBloc(
|
|
||||||
gh<_i776.IAuthRepository>(),
|
|
||||||
gh<_i312.FcmService>(),
|
|
||||||
gh<_i288.DeviceInfoService>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.factory<_i983.ITableRepository>(
|
gh.factory<_i983.ITableRepository>(
|
||||||
() => _i824.TableRepository(
|
() => _i824.TableRepository(
|
||||||
gh<_i95.TableRemoteDataProvider>(),
|
gh<_i95.TableRemoteDataProvider>(),
|
||||||
@@ -313,12 +321,6 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i424.TableLoaderBloc>(
|
gh.factory<_i424.TableLoaderBloc>(
|
||||||
() => _i424.TableLoaderBloc(gh<_i983.ITableRepository>()),
|
() => _i424.TableLoaderBloc(gh<_i983.ITableRepository>()),
|
||||||
);
|
);
|
||||||
gh.factory<_i44.IProductRepository>(
|
|
||||||
() => _i763.ProductRepository(
|
|
||||||
gh<_i707.ProductRemoteDataProvider>(),
|
|
||||||
gh<_i464.ProductLocalDataProvider>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.factory<_i552.IOutletRepository>(
|
gh.factory<_i552.IOutletRepository>(
|
||||||
() => _i845.OutletRepository(
|
() => _i845.OutletRepository(
|
||||||
gh<_i132.OutletRemoteDataProvider>(),
|
gh<_i132.OutletRemoteDataProvider>(),
|
||||||
@@ -342,6 +344,12 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh<_i552.IOutletRepository>(),
|
gh<_i552.IOutletRepository>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i320.IUnitRepository>(
|
||||||
|
() => _i228.UnitRepository(
|
||||||
|
gh<_i1016.UnitRemoteDataProvider>(),
|
||||||
|
gh<_i393.UnitLocalDataProvider>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
gh.factory<_i143.ICustomerRepository>(
|
gh.factory<_i143.ICustomerRepository>(
|
||||||
() => _i385.CustomerRepository(gh<_i841.CustomerRemoteDataProvider>()),
|
() => _i385.CustomerRepository(gh<_i841.CustomerRemoteDataProvider>()),
|
||||||
);
|
);
|
||||||
@@ -363,6 +371,9 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i822.VoidFormBloc>(
|
gh.factory<_i822.VoidFormBloc>(
|
||||||
() => _i822.VoidFormBloc(gh<_i299.IOrderRepository>()),
|
() => _i822.VoidFormBloc(gh<_i299.IOrderRepository>()),
|
||||||
);
|
);
|
||||||
|
gh.lazySingleton<_i584.UnitRegistry>(
|
||||||
|
() => _i584.UnitRegistry(gh<_i320.IUnitRepository>()),
|
||||||
|
);
|
||||||
gh.factory<_i683.CustomerLoaderBloc>(
|
gh.factory<_i683.CustomerLoaderBloc>(
|
||||||
() => _i683.CustomerLoaderBloc(gh<_i143.ICustomerRepository>()),
|
() => _i683.CustomerLoaderBloc(gh<_i143.ICustomerRepository>()),
|
||||||
);
|
);
|
||||||
@@ -381,19 +392,19 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i76.OutletLoaderBloc>(
|
gh.factory<_i76.OutletLoaderBloc>(
|
||||||
() => _i76.OutletLoaderBloc(gh<_i552.IOutletRepository>()),
|
() => _i76.OutletLoaderBloc(gh<_i552.IOutletRepository>()),
|
||||||
);
|
);
|
||||||
gh.factory<_i13.ProductLoaderBloc>(
|
gh.factory<_i46.LoginFormBloc>(
|
||||||
() => _i13.ProductLoaderBloc(gh<_i44.IProductRepository>()),
|
() => _i46.LoginFormBloc(
|
||||||
);
|
gh<_i776.IAuthRepository>(),
|
||||||
gh.factory<_i741.SyncBloc>(
|
gh<_i312.FcmService>(),
|
||||||
() => _i741.SyncBloc(
|
gh<_i288.DeviceInfoService>(),
|
||||||
gh<_i44.IProductRepository>(),
|
gh<_i584.UnitRegistry>(),
|
||||||
gh<_i502.ICategoryRepository>(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.factory<_i729.SyncSettingBloc>(
|
gh.factory<_i44.IProductRepository>(
|
||||||
() => _i729.SyncSettingBloc(
|
() => _i763.ProductRepository(
|
||||||
gh<_i44.IProductRepository>(),
|
gh<_i707.ProductRemoteDataProvider>(),
|
||||||
gh<_i502.ICategoryRepository>(),
|
gh<_i464.ProductLocalDataProvider>(),
|
||||||
|
gh<_i584.UnitRegistry>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.factory<_i911.CategoryAnalyticLoaderBloc>(
|
gh.factory<_i911.CategoryAnalyticLoaderBloc>(
|
||||||
@@ -419,6 +430,22 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i413.SalesAnalyticLoaderBloc>(
|
gh.factory<_i413.SalesAnalyticLoaderBloc>(
|
||||||
() => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
|
() => _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;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'common/service/fcm_service.dart';
|
import 'common/service/fcm_service.dart';
|
||||||
|
import 'common/service/unit_registry.dart';
|
||||||
import 'injection.dart';
|
import 'injection.dart';
|
||||||
import 'presentation/app_widget.dart';
|
import 'presentation/app_widget.dart';
|
||||||
|
|
||||||
@@ -49,6 +50,10 @@ void main() async {
|
|||||||
// Inisialisasi FCM setelah DI siap
|
// Inisialisasi FCM setelah DI siap
|
||||||
await getIt<FcmService>().initialize();
|
await getIt<FcmService>().initialize();
|
||||||
|
|
||||||
|
// Master satuan dimuat dari cache lebih dulu supaya produk timbangan
|
||||||
|
// langsung punya label satuan, bahkan saat perangkat offline.
|
||||||
|
unawaited(getIt<UnitRegistry>().load());
|
||||||
|
|
||||||
runApp(const AppWidget());
|
runApp(const AppWidget());
|
||||||
},
|
},
|
||||||
(error, stack) {
|
(error, stack) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import '../../../application/checkout/checkout_form/checkout_form_bloc.dart';
|
|||||||
import '../../../common/extension/extension.dart';
|
import '../../../common/extension/extension.dart';
|
||||||
import '../../../common/theme/theme.dart';
|
import '../../../common/theme/theme.dart';
|
||||||
import '../../../domain/product/product.dart';
|
import '../../../domain/product/product.dart';
|
||||||
|
import '../dialog/dialog.dart';
|
||||||
import '../image/image.dart';
|
import '../image/image.dart';
|
||||||
import '../spaces/space.dart';
|
import '../spaces/space.dart';
|
||||||
|
|
||||||
@@ -26,8 +27,8 @@ class _OrderMenuState extends State<OrderMenu> {
|
|||||||
|
|
||||||
_controller.addListener(() {
|
_controller.addListener(() {
|
||||||
context.read<CheckoutFormBloc>().add(
|
context.read<CheckoutFormBloc>().add(
|
||||||
CheckoutFormEvent.updateItemNotes(
|
CheckoutFormEvent.updateLineNotes(
|
||||||
widget.data.product,
|
widget.data.lineId,
|
||||||
_controller.text,
|
_controller.text,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -40,6 +41,29 @@ class _OrderMenuState extends State<OrderMenu> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _removeLine() {
|
||||||
|
context.read<CheckoutFormBloc>().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<CheckoutFormBloc>();
|
||||||
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
@@ -77,9 +101,10 @@ class _OrderMenuState extends State<OrderMenu> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
(widget.data.product.price +
|
widget.data.isWeighted &&
|
||||||
(widget.data.variant?.priceModifier ?? 0))
|
widget.data.product.unitLabel.isNotEmpty
|
||||||
.currencyFormatRp,
|
? '${widget.data.unitPrice.currencyFormatRp} / ${widget.data.product.unitLabel}'
|
||||||
|
: widget.data.unitPrice.currencyFormatRp,
|
||||||
),
|
),
|
||||||
if (widget.data.variant != null)
|
if (widget.data.variant != null)
|
||||||
Text(widget.data.variant?.name ?? ""),
|
Text(widget.data.variant?.name ?? ""),
|
||||||
@@ -87,60 +112,15 @@ class _OrderMenuState extends State<OrderMenu> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
if (widget.data.isWeighted)
|
||||||
children: [
|
_buildWeightControl()
|
||||||
GestureDetector(
|
else
|
||||||
onTap: () {
|
_buildQuantityStepper(),
|
||||||
context.read<CheckoutFormBloc>().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<CheckoutFormBloc>().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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SpaceWidth(8),
|
const SpaceWidth(8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 80.0,
|
width: 80.0,
|
||||||
child: Text(
|
child: Text(
|
||||||
((widget.data.product.price +
|
widget.data.lineTotal.currencyFormatRp,
|
||||||
(widget.data.variant?.priceModifier ?? 0)) *
|
|
||||||
widget.data.quantity)
|
|
||||||
.currencyFormatRp,
|
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColor.primary,
|
color: AppColor.primary,
|
||||||
@@ -167,14 +147,7 @@ class _OrderMenuState extends State<OrderMenu> {
|
|||||||
),
|
),
|
||||||
const SpaceWidth(16.0),
|
const SpaceWidth(16.0),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: _removeLine,
|
||||||
context.read<CheckoutFormBloc>().add(
|
|
||||||
CheckoutFormEvent.removeItem(
|
|
||||||
widget.data.product,
|
|
||||||
widget.data.variant,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
@@ -194,4 +167,77 @@ class _OrderMenuState extends State<OrderMenu> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<CheckoutFormBloc>().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<CheckoutFormBloc>().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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,18 +17,35 @@ class ProductCard extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (product.isActive == true) {
|
if (product.isActive != true) return;
|
||||||
if (product.variants.isEmpty) {
|
|
||||||
context.read<CheckoutFormBloc>().add(
|
if (product.variants.isNotEmpty) {
|
||||||
CheckoutFormEvent.addItem(product, null),
|
showDialog(
|
||||||
);
|
context: context,
|
||||||
} else {
|
builder: (context) => VariantDialog(product: product),
|
||||||
showDialog(
|
);
|
||||||
context: context,
|
return;
|
||||||
builder: (context) => VariantDialog(product: product),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Produk timbangan tidak bisa langsung masuk keranjang — kasir harus
|
||||||
|
// menimbang dulu, dan tiap penimbangan jadi barisnya sendiri.
|
||||||
|
if (product.isSoldByWeight) {
|
||||||
|
final bloc = context.read<CheckoutFormBloc>();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => WeightInputDialog(
|
||||||
|
product: product,
|
||||||
|
onSubmit: (weight) => bloc.add(
|
||||||
|
CheckoutFormEvent.addWeightedItem(product, null, weight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.read<CheckoutFormBloc>().add(
|
||||||
|
CheckoutFormEvent.addItem(product, null),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -75,7 +92,11 @@ class ProductCard extends StatelessWidget {
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
product.price.currencyFormatRp,
|
product.isSoldByWeight
|
||||||
|
? '${product.price.currencyFormatRp} ${product.priceUnitLabel}'
|
||||||
|
.trim()
|
||||||
|
: product.price.currencyFormatRp,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -89,31 +110,39 @@ class ProductCard extends StatelessWidget {
|
|||||||
|
|
||||||
BlocBuilder<CheckoutFormBloc, CheckoutFormState>(
|
BlocBuilder<CheckoutFormBloc, CheckoutFormState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final totalQuantity = state.items
|
final lines = state.items
|
||||||
.where((item) => item.product.id == product.id)
|
.where((item) => item.product.id == product.id)
|
||||||
.map((item) => item.quantity)
|
.toList();
|
||||||
.fold(0, (sum, qty) => sum + qty);
|
|
||||||
|
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<double>(0, (sum, item) => sum + (item.weight ?? 0)).weightFormat} ${product.unitLabel}'
|
||||||
|
.trim()
|
||||||
|
: lines
|
||||||
|
.fold<int>(0, (sum, item) => sum + item.quantity)
|
||||||
|
.toString();
|
||||||
|
|
||||||
if (totalQuantity == 0) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return Positioned(
|
return Positioned(
|
||||||
top: 4,
|
top: 4,
|
||||||
right: 4,
|
right: 4,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 40,
|
constraints: const BoxConstraints(minWidth: 40),
|
||||||
height: 40,
|
height: 40,
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
||||||
color: AppColor.primary,
|
color: AppColor.primary,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
totalQuantity.toString(),
|
label,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 20,
|
fontSize: product.isSoldByWeight ? 13 : 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import '../spaces/space.dart';
|
|||||||
part 'custom_modal_dialog.dart';
|
part 'custom_modal_dialog.dart';
|
||||||
part 'outlet_dialog.dart';
|
part 'outlet_dialog.dart';
|
||||||
part 'variant_dialog.dart';
|
part 'variant_dialog.dart';
|
||||||
|
part 'weight_input_dialog.dart';
|
||||||
part 'delivery_dialog.dart';
|
part 'delivery_dialog.dart';
|
||||||
part 'order_type_dialog.dart';
|
part 'order_type_dialog.dart';
|
||||||
part 'printer_bluetooth_dialog.dart';
|
part 'printer_bluetooth_dialog.dart';
|
||||||
|
|||||||
@@ -163,7 +163,9 @@ class OrderSaveConfirmDialog extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${item.quantity} qty',
|
item.isWeighted
|
||||||
|
? item.quantityLabel
|
||||||
|
: '${item.quantity} qty',
|
||||||
style: AppStyle.sm.copyWith(
|
style: AppStyle.sm.copyWith(
|
||||||
color: AppColor.textSecondary,
|
color: AppColor.textSecondary,
|
||||||
),
|
),
|
||||||
@@ -185,8 +187,7 @@ class OrderSaveConfirmDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
((item.product.price) * item.quantity)
|
item.lineTotal.currencyFormatRpV2,
|
||||||
.currencyFormatRpV2,
|
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: AppColor.primary,
|
color: AppColor.primary,
|
||||||
|
|||||||
@@ -21,10 +21,33 @@ class VariantDialog extends StatelessWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Aksi saat varian dipilih
|
// Aksi saat varian dipilih
|
||||||
context.maybePop();
|
final bloc = context.read<CheckoutFormBloc>();
|
||||||
context.read<CheckoutFormBloc>().add(
|
// Context dialog ini mati begitu di-pop, jadi dialog lanjutan
|
||||||
CheckoutFormEvent.addItem(product, variant),
|
// 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(
|
child: Container(
|
||||||
width: (context.deviceWidth * 0.4 - 12 - 32) / 2 - 6, // 2 per row
|
width: (context.deviceWidth * 0.4 - 12 - 32) / 2 - 6, // 2 per row
|
||||||
|
|||||||
@@ -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<double> 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<WeightInputDialog> createState() => _WeightInputDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WeightInputDialogState extends State<WeightInputDialog> {
|
||||||
|
/// 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,7 @@ class PrintUi {
|
|||||||
bytes += builder.orderItem(
|
bytes += builder.orderItem(
|
||||||
productName: item.productName,
|
productName: item.productName,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
|
quantityLabel: item.isWeighted ? item.quantityLabel : null,
|
||||||
unitPrice: item.unitPrice.currencyFormatRpV2,
|
unitPrice: item.unitPrice.currencyFormatRpV2,
|
||||||
totalPrice: item.totalPrice.currencyFormatRpV2,
|
totalPrice: item.totalPrice.currencyFormatRpV2,
|
||||||
variantName: item.productVariantName,
|
variantName: item.productVariantName,
|
||||||
@@ -140,7 +141,7 @@ class PrintUi {
|
|||||||
// Items — qty NAMA (uppercase), variant indent
|
// Items — qty NAMA (uppercase), variant indent
|
||||||
for (final item in order.orderItems.where((e) => e.printToChecker)) {
|
for (final item in order.orderItems.where((e) => e.printToChecker)) {
|
||||||
final name = item.productName.toUpperCase();
|
final name = item.productName.toUpperCase();
|
||||||
bytes += builder.itemText('${item.quantity} $name');
|
bytes += builder.itemText('${item.quantityLabel} $name');
|
||||||
if (item.productVariantName.isNotEmpty) {
|
if (item.productVariantName.isNotEmpty) {
|
||||||
bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
|
bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
|
||||||
}
|
}
|
||||||
@@ -196,7 +197,7 @@ class PrintUi {
|
|||||||
|
|
||||||
// Single item
|
// Single item
|
||||||
final name = item.productName.toUpperCase();
|
final name = item.productName.toUpperCase();
|
||||||
bytes += builder.itemText('${item.quantity} $name');
|
bytes += builder.itemText('${item.quantityLabel} $name');
|
||||||
if (item.productVariantName.isNotEmpty) {
|
if (item.productVariantName.isNotEmpty) {
|
||||||
bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
|
bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
|
||||||
}
|
}
|
||||||
@@ -252,7 +253,7 @@ class PrintUi {
|
|||||||
|
|
||||||
// Single item
|
// Single item
|
||||||
final name = item.productName.toUpperCase();
|
final name = item.productName.toUpperCase();
|
||||||
bytes += builder.itemText('${item.quantity} $name');
|
bytes += builder.itemText('${item.quantityLabel} $name');
|
||||||
if (item.productVariantName.isNotEmpty) {
|
if (item.productVariantName.isNotEmpty) {
|
||||||
bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
|
bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
|
||||||
}
|
}
|
||||||
@@ -359,6 +360,7 @@ class PrintUi {
|
|||||||
bytes += builder.orderItem(
|
bytes += builder.orderItem(
|
||||||
productName: item.productName,
|
productName: item.productName,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
|
quantityLabel: item.isWeighted ? item.quantityLabel : null,
|
||||||
unitPrice: item.unitPrice.currencyFormatRpV2,
|
unitPrice: item.unitPrice.currencyFormatRpV2,
|
||||||
totalPrice: item.totalPrice.currencyFormatRpV2,
|
totalPrice: item.totalPrice.currencyFormatRpV2,
|
||||||
variantName: item.productVariantName,
|
variantName: item.productVariantName,
|
||||||
@@ -439,6 +441,7 @@ class PrintUi {
|
|||||||
bytes += builder.orderItem(
|
bytes += builder.orderItem(
|
||||||
productName: item.productName,
|
productName: item.productName,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
|
quantityLabel: item.isWeighted ? item.quantityLabel : null,
|
||||||
unitPrice: item.unitPrice.currencyFormatRpV2,
|
unitPrice: item.unitPrice.currencyFormatRpV2,
|
||||||
totalPrice: item.totalPrice.currencyFormatRpV2,
|
totalPrice: item.totalPrice.currencyFormatRpV2,
|
||||||
variantName: item.productVariantName,
|
variantName: item.productVariantName,
|
||||||
@@ -530,6 +533,7 @@ class PrintUi {
|
|||||||
bytes += builder.orderItem(
|
bytes += builder.orderItem(
|
||||||
productName: item.productName,
|
productName: item.productName,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
|
quantityLabel: item.isWeighted ? item.quantityLabel : null,
|
||||||
unitPrice: item.unitPrice.currencyFormatRpV2,
|
unitPrice: item.unitPrice.currencyFormatRpV2,
|
||||||
totalPrice: item.totalPrice.currencyFormatRpV2,
|
totalPrice: item.totalPrice.currencyFormatRpV2,
|
||||||
variantName: item.productVariantName,
|
variantName: item.productVariantName,
|
||||||
|
|||||||
@@ -301,6 +301,10 @@ class ReceiptComponentBuilder {
|
|||||||
List<int> orderItem({
|
List<int> orderItem({
|
||||||
required String productName,
|
required String productName,
|
||||||
required int quantity,
|
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 unitPrice,
|
||||||
required String totalPrice,
|
required String totalPrice,
|
||||||
String? variantName,
|
String? variantName,
|
||||||
@@ -314,7 +318,10 @@ class ReceiptComponentBuilder {
|
|||||||
? '$productName ($variantName)'
|
? '$productName ($variantName)'
|
||||||
: productName;
|
: productName;
|
||||||
bytes += textLeft(displayName, bold: true, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth);
|
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) {
|
if (notes != null && notes.isNotEmpty) {
|
||||||
bytes += row2Columns('Note', notes, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth, leftWidth: 4, rightWidth: 8);
|
bytes += row2Columns('Note', notes, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth, leftWidth: 4, rightWidth: 8);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/payment_method/payment_method_loader/payment_method_loader_bloc.dart';
|
||||||
import '../../../application/table/table_loader/table_loader_bloc.dart';
|
import '../../../application/table/table_loader/table_loader_bloc.dart';
|
||||||
import '../../../common/theme/theme.dart';
|
import '../../../common/theme/theme.dart';
|
||||||
|
import '../../../domain/product/product.dart';
|
||||||
import '../../../injection.dart';
|
import '../../../injection.dart';
|
||||||
import '../../components/spaces/space.dart';
|
import '../../components/spaces/space.dart';
|
||||||
import '../../components/toast/flushbar.dart';
|
import '../../components/toast/flushbar.dart';
|
||||||
@@ -86,14 +87,7 @@ class CheckoutPage extends StatelessWidget implements AutoRouteWrapper {
|
|||||||
tag: 'checkout_screen',
|
tag: 'checkout_screen',
|
||||||
child: BlocBuilder<CheckoutFormBloc, CheckoutFormState>(
|
child: BlocBuilder<CheckoutFormBloc, CheckoutFormState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final price = state.items.fold(
|
final price = state.items.cartTotal.toInt();
|
||||||
0,
|
|
||||||
(previousValue, element) =>
|
|
||||||
previousValue +
|
|
||||||
(element.product.price.toInt() +
|
|
||||||
(element.variant?.priceModifier.toInt() ?? 0)) *
|
|
||||||
element.quantity,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColor.white,
|
backgroundColor: AppColor.white,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import '../../../../../../application/checkout/checkout_form/checkout_form_bloc.dart';
|
import '../../../../../../application/checkout/checkout_form/checkout_form_bloc.dart';
|
||||||
import '../../../../../../common/extension/extension.dart';
|
import '../../../../../../common/extension/extension.dart';
|
||||||
import '../../../../../../common/theme/theme.dart';
|
import '../../../../../../common/theme/theme.dart';
|
||||||
|
import '../../../../../../domain/product/product.dart';
|
||||||
import '../../../../../components/button/button.dart';
|
import '../../../../../components/button/button.dart';
|
||||||
import '../../../../../components/card/order_menu.dart';
|
import '../../../../../components/card/order_menu.dart';
|
||||||
import '../../../../../components/spaces/space.dart';
|
import '../../../../../components/spaces/space.dart';
|
||||||
@@ -21,15 +22,7 @@ class HomeRightPanel extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<CheckoutFormBloc, CheckoutFormState>(
|
return BlocBuilder<CheckoutFormBloc, CheckoutFormState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final price = state.items.isEmpty
|
final price = state.items.cartTotal;
|
||||||
? 0.0
|
|
||||||
: state.items
|
|
||||||
.map(
|
|
||||||
(e) =>
|
|
||||||
(e.product.price + (e.variant?.priceModifier ?? 0)) *
|
|
||||||
e.quantity,
|
|
||||||
)
|
|
||||||
.reduce((value, element) => value + element);
|
|
||||||
log('🛒 Total Price: $price');
|
log('🛒 Total Price: $price');
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
@@ -87,8 +80,12 @@ class HomeRightPanel extends StatelessWidget {
|
|||||||
: ListView.separated(
|
: ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
itemBuilder: (context, index) =>
|
itemBuilder: (context, index) => OrderMenu(
|
||||||
OrderMenu(data: state.items[index]),
|
// 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) =>
|
separatorBuilder: (context, index) =>
|
||||||
const SpaceHeight(1.0),
|
const SpaceHeight(1.0),
|
||||||
itemCount: state.items.length,
|
itemCount: state.items.length,
|
||||||
|
|||||||
+8
-23
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import '../../../../../../application/checkout/checkout_form/checkout_form_bloc.dart';
|
import '../../../../../../application/checkout/checkout_form/checkout_form_bloc.dart';
|
||||||
import '../../../../../../common/extension/extension.dart';
|
import '../../../../../../common/extension/extension.dart';
|
||||||
import '../../../../../../common/theme/theme.dart';
|
import '../../../../../../common/theme/theme.dart';
|
||||||
|
import '../../../../../../domain/product/product.dart';
|
||||||
import '../../../../../components/spaces/space.dart';
|
import '../../../../../components/spaces/space.dart';
|
||||||
|
|
||||||
class SuccessAddItemOrderRightPanel extends StatelessWidget {
|
class SuccessAddItemOrderRightPanel extends StatelessWidget {
|
||||||
@@ -86,14 +87,7 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
(checkoutState.items.fold(
|
checkoutState.items.cartTotal.toInt().toString().currencyFormatRpV2,
|
||||||
0,
|
|
||||||
(previousValue, element) =>
|
|
||||||
previousValue +
|
|
||||||
(element.product.price.toInt() +
|
|
||||||
(element.variant?.priceModifier.toInt() ?? 0)) *
|
|
||||||
element.quantity,
|
|
||||||
)).toString().currencyFormatRpV2,
|
|
||||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.w600),
|
style: AppStyle.md.copyWith(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -165,15 +159,10 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
(checkoutState.items.fold(
|
checkoutState.items.cartTotal
|
||||||
0,
|
.toInt()
|
||||||
(previousValue, element) =>
|
.toString()
|
||||||
previousValue +
|
.currencyFormatRpV2,
|
||||||
(element.product.price.toInt() +
|
|
||||||
(element.variant?.priceModifier.toInt() ??
|
|
||||||
0)) *
|
|
||||||
element.quantity,
|
|
||||||
)).toString().currencyFormatRpV2,
|
|
||||||
style: AppStyle.xl.copyWith(
|
style: AppStyle.xl.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -270,7 +259,7 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${item.quantity}x',
|
(item.isWeighted ? item.quantityLabel : "${item.quantity}x"),
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -279,11 +268,7 @@ class SuccessAddItemOrderRightPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SpaceHeight(8),
|
const SpaceHeight(8),
|
||||||
Text(
|
Text(
|
||||||
((item.product.price.toInt() +
|
item.lineTotal.toInt().toString().currencyFormatRpV2,
|
||||||
(item.variant?.priceModifier.toInt() ?? 0)) *
|
|
||||||
item.quantity)
|
|
||||||
.toString()
|
|
||||||
.currencyFormatRpV2,
|
|
||||||
style: AppStyle.lg.copyWith(
|
style: AppStyle.lg.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: AppColor.primary,
|
color: AppColor.primary,
|
||||||
|
|||||||
+1
-1
@@ -255,7 +255,7 @@ class SuccessOrderRightPanel extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${item.quantity}x',
|
(item.isWeighted ? item.quantityLabel : "${item.quantity}x"),
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@@ -211,7 +211,9 @@ class OrderList extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'x${product.quantity}',
|
(product.isWeighted
|
||||||
|
? product.quantityLabel
|
||||||
|
: "x${product.quantity}"),
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: AppColor.primary,
|
color: AppColor.primary,
|
||||||
|
|||||||
+1
-1
@@ -255,7 +255,7 @@ class PaymentSuccessRightPanel extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${item.quantity}x',
|
(item.isWeighted ? item.quantityLabel : "${item.quantity}x"),
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ class PaymentSuccessSplitBillRightPanel extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${item.quantity}x',
|
(item.isWeighted ? item.quantityLabel : "${item.quantity}x"),
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ class PaymentLeftPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'Qty: ${item.quantity} | ${item.productVariantName}',
|
item.isWeighted
|
||||||
|
? '${item.quantityLabel} | ${item.productVariantName}'
|
||||||
|
: 'Qty: ${item.quantity} | ${item.productVariantName}',
|
||||||
style: AppStyle.sm,
|
style: AppStyle.sm,
|
||||||
),
|
),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
|
|||||||
+1
-1
@@ -255,7 +255,7 @@ class RefundSuccessRightPanel extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${item.quantity}x',
|
(item.isWeighted ? item.quantityLabel : "${item.quantity}x"),
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ class RefundLeftPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Qty: ${item.quantity} x Rp ${item.unitPrice.currencyFormatRpV2} ',
|
item.isWeighted
|
||||||
|
? item.quantityPriceLabel
|
||||||
|
: 'Qty: ${item.quantity} x ${item.unitPrice.currencyFormatRpV2}',
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
color: AppColor.textSecondary,
|
color: AppColor.textSecondary,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -160,7 +160,9 @@ class SplitBillLeftPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Qty: ${item.quantity} x Rp ${item.unitPrice.currencyFormatRpV2} ',
|
item.isWeighted
|
||||||
|
? item.quantityPriceLabel
|
||||||
|
: 'Qty: ${item.quantity} x ${item.unitPrice.currencyFormatRpV2}',
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
color: AppColor.textSecondary,
|
color: AppColor.textSecondary,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -332,97 +332,148 @@ class _SplitBillRightPanelState extends State<SplitBillRightPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
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),
|
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
// Baris berbobot hanya punya dua keadaan: belum dibayar atau lunas.
|
||||||
children: [
|
// Tidak ada nilai di antaranya, jadi stepper diganti tombol pilih.
|
||||||
GestureDetector(
|
if (item.isWeighted)
|
||||||
onTap: () {
|
_buildWeightedLineSelector(item, selectedQty > 0, maxQty)
|
||||||
if (selectedQty > 0) {
|
else
|
||||||
setState(() {
|
_buildQuantityStepper(item, selectedQty, maxQty),
|
||||||
context.read<SplitBillFormBloc>().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<SplitBillFormBloc>().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 _buildWeightedLineSelector(OrderItem item, bool selected, int maxQty) {
|
||||||
|
final canSelect = selected || maxQty > 0;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (!canSelect) return;
|
||||||
|
context.read<SplitBillFormBloc>().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<SplitBillFormBloc>().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<SplitBillFormBloc>().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() {
|
Widget _buildRowButtonSplit() {
|
||||||
switch (widget.state.order.splitType) {
|
switch (widget.state.order.splitType) {
|
||||||
case 'AMOUNT':
|
case 'AMOUNT':
|
||||||
@@ -510,7 +561,10 @@ class _SplitBillRightPanelState extends State<SplitBillRightPanel> {
|
|||||||
splitItems.add(
|
splitItems.add(
|
||||||
originalItem.copyWith(
|
originalItem.copyWith(
|
||||||
quantity: quantity,
|
quantity: quantity,
|
||||||
totalPrice: (originalItem.unitPrice) * quantity,
|
// Baris berbobot dibayar penuh; nilainya sudah weight x harga.
|
||||||
|
totalPrice: originalItem.isWeighted
|
||||||
|
? originalItem.totalPrice
|
||||||
|
: (originalItem.unitPrice) * quantity,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,9 @@ class VoidProductCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Qty: ${item.quantity}',
|
item.isWeighted
|
||||||
|
? item.quantityLabel
|
||||||
|
: 'Qty: ${item.quantity}',
|
||||||
style: AppStyle.sm.copyWith(
|
style: AppStyle.sm.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.blue[700],
|
color: Colors.blue[700],
|
||||||
@@ -112,65 +114,13 @@ class VoidProductCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
if (canSelect) ...[
|
if (canSelect) ...[
|
||||||
Container(
|
// Baris berbobot bersifat utuh: dibatalkan seluruhnya atau
|
||||||
decoration: BoxDecoration(
|
// tidak sama sekali, jadi yang ditawarkan tombol pilih baris
|
||||||
border: Border.all(color: AppColor.border),
|
// — bukan stepper jumlah.
|
||||||
borderRadius: BorderRadius.circular(6),
|
if (item.isWeighted)
|
||||||
),
|
_buildLineSelector()
|
||||||
child: Row(
|
else
|
||||||
mainAxisSize: MainAxisSize.min,
|
_buildQuantityStepper(),
|
||||||
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],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SpaceWidth(20),
|
const SpaceWidth(20),
|
||||||
],
|
],
|
||||||
// Price
|
// Price
|
||||||
@@ -195,7 +145,7 @@ class VoidProductCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Void: ${((item.unitPrice) * voidQty).currencyFormatRpV2}',
|
'Void: ${(item.isWeighted ? item.totalPrice : (item.unitPrice) * voidQty).currencyFormatRpV2}',
|
||||||
style: AppStyle.sm.copyWith(
|
style: AppStyle.sm.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: AppColor.error,
|
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],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<void>.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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user