Files
apskel-pos-flutter-v2/lib/domain/product/entities/product_entity.dart
T
efrilmandClaude Opus 5 1505a83dd3 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>
2026-09-06 23:03:31 +07:00

108 lines
2.6 KiB
Dart

part of '../product.dart';
@freezed
class ListProduct with _$ListProduct {
const factory ListProduct({
required List<Product> products,
required int totalCount,
required int page,
required int limit,
required int totalPages,
}) = _ListProduct;
factory ListProduct.empty() => const ListProduct(
products: [],
totalCount: 0,
page: 0,
limit: 0,
totalPages: 0,
);
}
@freezed
class Product with _$Product {
const Product._();
const factory Product({
required String id,
required String organizationId,
required String categoryId,
required String sku,
required String name,
required String description,
required double price,
required double cost,
required String businessType,
required String imageUrl,
required String printerType,
required bool printToChecker,
required Map<String, dynamic> metadata,
required bool isActive,
required String createdAt,
required String updatedAt,
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;
factory Product.empty() => const Product(
id: '',
organizationId: '',
categoryId: '',
sku: '',
name: '',
description: '',
price: 0.0,
cost: 0.0,
businessType: '',
imageUrl: '',
printerType: '',
printToChecker: false,
metadata: {},
isActive: false,
createdAt: '',
updatedAt: '',
variants: [],
);
/// Produk yang dijual per timbangan: `quantity` selalu 1, `weight` wajib.
bool get isSoldByWeight => SellBy.isWeight(sellBy);
/// Singkatan satuan untuk ditampilkan — `ons`. Jatuh ke nama panjang bila
/// backend hanya mengirim itu.
String get unitLabel =>
unitAbbreviation.isNotEmpty ? unitAbbreviation : unitName;
/// Label harga mengikuti satuan: "Harga per ons".
String get priceUnitLabel =>
isSoldByWeight && unitLabel.isNotEmpty ? 'per $unitLabel' : '';
}
@freezed
class ProductVariant with _$ProductVariant {
const factory ProductVariant({
required String id,
required String productId,
required String name,
required double priceModifier,
required double cost,
required Map<String, dynamic> metadata,
required String createdAt,
required String updatedAt,
}) = _ProductVariant;
factory ProductVariant.empty() => const ProductVariant(
id: '',
productId: '',
name: '',
priceModifier: 0.0,
cost: 0.0,
metadata: {},
createdAt: '',
updatedAt: '',
);
}