update cart

This commit is contained in:
Phuoc Nguyen
2025-11-14 16:19:25 +07:00
parent 4738553d2e
commit aae3c9d080
30 changed files with 5954 additions and 758 deletions

View File

@@ -22,13 +22,14 @@ class ProductModel extends HiveObject {
this.description,
required this.basePrice,
this.images,
this.thumbnail,
required this.thumbnail,
this.imageCaptions,
this.customLink360,
this.specifications,
this.category,
this.brand,
this.unit,
this.conversionOfSm,
required this.isActive,
required this.isFeatured,
this.erpnextItemCode,
@@ -58,7 +59,7 @@ class ProductModel extends HiveObject {
/// Thumbnail image URL
@HiveField(5)
final String? thumbnail;
final String thumbnail;
/// Image captions (JSON encoded map of image_url -> caption)
@HiveField(6)
@@ -85,6 +86,11 @@ class ProductModel extends HiveObject {
@HiveField(11)
final String? unit;
/// Conversion factor for Square Meter UOM (tiles per m²)
/// Used to calculate: Số viên = Số lượng × conversionOfSm
@HiveField(17)
final double? conversionOfSm;
/// Whether product is active
@HiveField(12)
final bool isActive;
@@ -117,7 +123,7 @@ class ProductModel extends HiveObject {
description: json['description'] as String?,
basePrice: (json['base_price'] as num).toDouble(),
images: json['images'] != null ? jsonEncode(json['images']) : null,
thumbnail: json['thumbnail'] as String?,
thumbnail: json['thumbnail'] as String,
imageCaptions: json['image_captions'] != null
? jsonEncode(json['image_captions'])
: null,
@@ -128,6 +134,9 @@ class ProductModel extends HiveObject {
category: json['category'] as String?,
brand: json['brand'] as String?,
unit: json['unit'] as String?,
conversionOfSm: json['conversion_of_sm'] != null
? (json['conversion_of_sm'] as num).toDouble()
: null,
isActive: json['is_active'] as bool? ?? true,
isFeatured: json['is_featured'] as bool? ?? false,
erpnextItemCode: json['erpnext_item_code'] as String?,
@@ -227,7 +236,7 @@ class ProductModel extends HiveObject {
description: json['description'] as String?,
basePrice: price,
images: imagesList.isNotEmpty ? jsonEncode(imagesList) : null,
thumbnail: thumbnailUrl,
thumbnail: thumbnailUrl ?? '',
imageCaptions: imageCaptionsMap.isNotEmpty
? jsonEncode(imageCaptionsMap)
: null,
@@ -239,6 +248,9 @@ class ProductModel extends HiveObject {
json['item_group'] as String?, // Try item_group_name first, fallback to item_group
brand: json['brand'] as String?,
unit: json['stock_uom'] as String? ?? '',
conversionOfSm: json['conversion_of_sm'] != null
? (json['conversion_of_sm'] as num).toDouble()
: null,
isActive: (json['disabled'] as int?) == 0, // Frappe uses 'disabled' field
isFeatured: false, // Not provided by API, default to false
erpnextItemCode: json['name'] as String, // Store item code for reference
@@ -270,6 +282,7 @@ class ProductModel extends HiveObject {
'category': category,
'brand': brand,
'unit': unit,
'conversion_of_sm': conversionOfSm,
'is_active': isActive,
'is_featured': isFeatured,
'erpnext_item_code': erpnextItemCode,
@@ -349,6 +362,7 @@ class ProductModel extends HiveObject {
String? category,
String? brand,
String? unit,
double? conversionOfSm,
bool? isActive,
bool? isFeatured,
String? erpnextItemCode,
@@ -368,6 +382,7 @@ class ProductModel extends HiveObject {
category: category ?? this.category,
brand: brand ?? this.brand,
unit: unit ?? this.unit,
conversionOfSm: conversionOfSm ?? this.conversionOfSm,
isActive: isActive ?? this.isActive,
isFeatured: isFeatured ?? this.isFeatured,
erpnextItemCode: erpnextItemCode ?? this.erpnextItemCode,
@@ -410,6 +425,7 @@ class ProductModel extends HiveObject {
category: category,
brand: brand,
unit: unit,
conversionOfSm: conversionOfSm,
isActive: isActive,
isFeatured: isFeatured,
erpnextItemCode: erpnextItemCode,

View File

@@ -22,13 +22,14 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
description: fields[2] as String?,
basePrice: (fields[3] as num).toDouble(),
images: fields[4] as String?,
thumbnail: fields[5] as String?,
thumbnail: fields[5] as String,
imageCaptions: fields[6] as String?,
customLink360: fields[7] as String?,
specifications: fields[8] as String?,
category: fields[9] as String?,
brand: fields[10] as String?,
unit: fields[11] as String?,
conversionOfSm: (fields[17] as num?)?.toDouble(),
isActive: fields[12] as bool,
isFeatured: fields[13] as bool,
erpnextItemCode: fields[14] as String?,
@@ -40,7 +41,7 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
@override
void write(BinaryWriter writer, ProductModel obj) {
writer
..writeByte(17)
..writeByte(18)
..writeByte(0)
..write(obj.productId)
..writeByte(1)
@@ -74,7 +75,9 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
..writeByte(15)
..write(obj.createdAt)
..writeByte(16)
..write(obj.updatedAt);
..write(obj.updatedAt)
..writeByte(17)
..write(obj.conversionOfSm);
}
@override

View File

@@ -9,6 +9,27 @@ library;
/// Represents a tile/construction product in the application.
/// Used across all layers but originates in the domain layer.
class Product {
const Product({
required this.productId,
required this.name,
this.description,
required this.basePrice,
required this.images,
required this.thumbnail,
required this.imageCaptions,
this.customLink360,
required this.specifications,
this.category,
this.brand,
this.unit,
this.conversionOfSm,
required this.isActive,
required this.isFeatured,
this.erpnextItemCode,
required this.createdAt,
required this.updatedAt,
});
/// Unique identifier
final String productId;
@@ -25,7 +46,7 @@ class Product {
final List<String> images;
/// Thumbnail image URL
final String? thumbnail;
final String thumbnail;
/// Image captions
final Map<String, String> imageCaptions;
@@ -45,6 +66,10 @@ class Product {
/// Unit of measurement (e.g., "m²", "viên", "hộp")
final String? unit;
/// Conversion factor for Square Meter UOM (tiles per m²)
/// Used to calculate: Số viên = Số lượng × conversionOfSm
final double? conversionOfSm;
/// Product is active
final bool isActive;
@@ -60,26 +85,6 @@ class Product {
/// Last updated date
final DateTime updatedAt;
const Product({
required this.productId,
required this.name,
this.description,
required this.basePrice,
required this.images,
this.thumbnail,
required this.imageCaptions,
this.customLink360,
required this.specifications,
this.category,
this.brand,
this.unit,
required this.isActive,
required this.isFeatured,
this.erpnextItemCode,
required this.createdAt,
required this.updatedAt,
});
/// Get primary image URL
String? get primaryImage => images.isNotEmpty ? images.first : null;
@@ -134,6 +139,7 @@ class Product {
String? category,
String? brand,
String? unit,
double? conversionOfSm,
bool? isActive,
bool? isFeatured,
String? erpnextItemCode,
@@ -153,6 +159,7 @@ class Product {
category: category ?? this.category,
brand: brand ?? this.brand,
unit: unit ?? this.unit,
conversionOfSm: conversionOfSm ?? this.conversionOfSm,
isActive: isActive ?? this.isActive,
isFeatured: isFeatured ?? this.isFeatured,
erpnextItemCode: erpnextItemCode ?? this.erpnextItemCode,

View File

@@ -238,7 +238,7 @@ class ProductCard extends ConsumerWidget {
width: double.infinity,
height: 36.0,
child: ElevatedButton.icon(
onPressed: product.inStock ? onAddToCart : null,
onPressed: !product.inStock ? onAddToCart : null,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
@@ -256,7 +256,7 @@ class ProductCard extends ConsumerWidget {
),
icon: const Icon(Icons.shopping_cart, size: 16.0),
label: Text(
product.inStock ? 'Thêm vào giỏ' : l10n.outOfStock,
!product.inStock ? 'Thêm vào giỏ' : l10n.outOfStock,
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,