- OrderLineItem now includes itemName, itemParentName, and isCheckedByDefault from API response - Cart view displays modifiers as "Category: Selection" format (e.g., "Select Drink: Coke") instead of just item IDs - No longer requires menu item lookup for modifier names Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
253 lines
8.1 KiB
Dart
253 lines
8.1 KiB
Dart
class Cart {
|
|
final int orderId;
|
|
final String orderUuid;
|
|
final int userId;
|
|
final int businessId;
|
|
final double businessDeliveryMultiplier;
|
|
final double businessDeliveryFee; // The business's standard delivery fee (for preview)
|
|
final int orderTypeId;
|
|
final double deliveryFee; // The actual delivery fee on this order (set when order type is confirmed)
|
|
final int statusId;
|
|
final int? addressId;
|
|
final int? paymentId;
|
|
final String? remarks;
|
|
final DateTime addedOn;
|
|
final DateTime lastEditedOn;
|
|
final DateTime? submittedOn;
|
|
final int servicePointId;
|
|
final List<OrderLineItem> lineItems;
|
|
|
|
const Cart({
|
|
required this.orderId,
|
|
required this.orderUuid,
|
|
required this.userId,
|
|
required this.businessId,
|
|
required this.businessDeliveryMultiplier,
|
|
required this.businessDeliveryFee,
|
|
required this.orderTypeId,
|
|
required this.deliveryFee,
|
|
required this.statusId,
|
|
this.addressId,
|
|
this.paymentId,
|
|
this.remarks,
|
|
required this.addedOn,
|
|
required this.lastEditedOn,
|
|
this.submittedOn,
|
|
required this.servicePointId,
|
|
required this.lineItems,
|
|
});
|
|
|
|
factory Cart.fromJson(Map<String, dynamic> json) {
|
|
final order = (json["ORDER"] ?? json["Order"]) as Map<String, dynamic>? ?? {};
|
|
final lineItemsJson = (json["ORDERLINEITEMS"] ?? json["OrderLineItems"]) as List? ?? [];
|
|
|
|
return Cart(
|
|
orderId: _parseInt(order["OrderID"]) ?? 0,
|
|
orderUuid: (order["OrderUUID"] as String?) ?? "",
|
|
userId: _parseInt(order["OrderUserID"]) ?? 0,
|
|
businessId: _parseInt(order["OrderBusinessID"]) ?? 0,
|
|
businessDeliveryMultiplier: _parseDouble(order["OrderBusinessDeliveryMultiplier"]) ?? 0.0,
|
|
businessDeliveryFee: _parseDouble(order["BusinessDeliveryFee"]) ?? 0.0,
|
|
orderTypeId: _parseInt(order["OrderTypeID"]) ?? 0,
|
|
deliveryFee: _parseDouble(order["OrderDeliveryFee"]) ?? 0.0,
|
|
statusId: _parseInt(order["OrderStatusID"]) ?? 0,
|
|
addressId: _parseInt(order["OrderAddressID"]),
|
|
paymentId: _parseInt(order["OrderPaymentID"]),
|
|
remarks: order["OrderRemarks"] as String?,
|
|
addedOn: _parseDateTime(order["OrderAddedOn"]),
|
|
lastEditedOn: _parseDateTime(order["OrderLastEditedOn"]),
|
|
submittedOn: _parseDateTime(order["OrderSubmittedOn"]),
|
|
servicePointId: _parseInt(order["OrderServicePointID"]) ?? 0,
|
|
lineItems: lineItemsJson
|
|
.map((item) => OrderLineItem.fromJson(item as Map<String, dynamic>))
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
static int? _parseInt(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is int) return value;
|
|
if (value is num) return value.toInt();
|
|
if (value is String) {
|
|
if (value.isEmpty) return null;
|
|
return int.tryParse(value);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static double? _parseDouble(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is double) return value;
|
|
if (value is num) return value.toDouble();
|
|
if (value is String) {
|
|
if (value.isEmpty) return null;
|
|
return double.tryParse(value);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static DateTime _parseDateTime(dynamic value) {
|
|
if (value == null) return DateTime.now();
|
|
if (value is DateTime) return value;
|
|
if (value is String) {
|
|
try {
|
|
return DateTime.parse(value);
|
|
} catch (e) {
|
|
return DateTime.now();
|
|
}
|
|
}
|
|
return DateTime.now();
|
|
}
|
|
|
|
double get subtotal {
|
|
// Sum all non-deleted line items (root items and modifiers)
|
|
// Root items have their base price, modifiers have their add-on prices
|
|
// Modifier prices are multiplied by the root item's quantity
|
|
double total = 0.0;
|
|
|
|
// First, get all root items and their prices
|
|
final rootItems = lineItems.where((item) => !item.isDeleted && item.parentOrderLineItemId == 0);
|
|
for (final rootItem in rootItems) {
|
|
total += rootItem.price * rootItem.quantity;
|
|
|
|
// Add all modifier prices for this root item (recursively)
|
|
total += _sumModifierPrices(rootItem.orderLineItemId, rootItem.quantity);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/// Recursively sum modifier prices for a parent line item
|
|
double _sumModifierPrices(int parentOrderLineItemId, int rootQuantity) {
|
|
double total = 0.0;
|
|
final children = lineItems.where(
|
|
(item) => !item.isDeleted && item.parentOrderLineItemId == parentOrderLineItemId
|
|
);
|
|
|
|
for (final child in children) {
|
|
// Modifier price is multiplied by root item quantity
|
|
total += child.price * rootQuantity;
|
|
// Recursively add grandchildren modifier prices
|
|
total += _sumModifierPrices(child.orderLineItemId, rootQuantity);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
// Only include delivery fee for delivery orders (orderTypeId == 3)
|
|
// Sales tax rate (8.25% for California - can be made configurable per business later)
|
|
static const double taxRate = 0.0825;
|
|
|
|
// Calculate sales tax on subtotal
|
|
double get tax => subtotal * taxRate;
|
|
|
|
double get total => subtotal + tax + (orderTypeId == 3 ? deliveryFee : 0);
|
|
|
|
int get itemCount {
|
|
return lineItems
|
|
.where((item) => !item.isDeleted && item.parentOrderLineItemId == 0)
|
|
.fold(0, (sum, item) => sum + item.quantity);
|
|
}
|
|
}
|
|
|
|
class OrderLineItem {
|
|
final int orderLineItemId;
|
|
final int parentOrderLineItemId;
|
|
final int orderId;
|
|
final int itemId;
|
|
final int statusId;
|
|
final double price;
|
|
final int quantity;
|
|
final String? remark;
|
|
final bool isDeleted;
|
|
final DateTime addedOn;
|
|
final String? itemName;
|
|
final int? itemParentItemId;
|
|
final String? itemParentName;
|
|
final bool isCheckedByDefault;
|
|
|
|
const OrderLineItem({
|
|
required this.orderLineItemId,
|
|
required this.parentOrderLineItemId,
|
|
required this.orderId,
|
|
required this.itemId,
|
|
required this.statusId,
|
|
required this.price,
|
|
required this.quantity,
|
|
this.remark,
|
|
required this.isDeleted,
|
|
required this.addedOn,
|
|
this.itemName,
|
|
this.itemParentItemId,
|
|
this.itemParentName,
|
|
this.isCheckedByDefault = false,
|
|
});
|
|
|
|
factory OrderLineItem.fromJson(Map<String, dynamic> json) {
|
|
return OrderLineItem(
|
|
orderLineItemId: _parseInt(json["OrderLineItemID"]) ?? 0,
|
|
parentOrderLineItemId: _parseInt(json["OrderLineItemParentOrderLineItemID"]) ?? 0,
|
|
orderId: _parseInt(json["OrderLineItemOrderID"]) ?? 0,
|
|
itemId: _parseInt(json["OrderLineItemItemID"]) ?? 0,
|
|
statusId: _parseInt(json["OrderLineItemStatusID"]) ?? 0,
|
|
price: _parseDouble(json["OrderLineItemPrice"]) ?? 0.0,
|
|
quantity: _parseInt(json["OrderLineItemQuantity"]) ?? 0,
|
|
remark: json["OrderLineItemRemark"] as String?,
|
|
isDeleted: _parseBool(json["OrderLineItemIsDeleted"]),
|
|
addedOn: _parseDateTime(json["OrderLineItemAddedOn"]),
|
|
itemName: json["ItemName"] as String?,
|
|
itemParentItemId: _parseInt(json["ItemParentItemID"]),
|
|
itemParentName: json["ItemParentName"] as String?,
|
|
isCheckedByDefault: _parseBool(json["ItemIsCheckedByDefault"]),
|
|
);
|
|
}
|
|
|
|
static int? _parseInt(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is int) return value;
|
|
if (value is num) return value.toInt();
|
|
if (value is String) {
|
|
if (value.isEmpty) return null;
|
|
return int.tryParse(value);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static double? _parseDouble(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is double) return value;
|
|
if (value is num) return value.toDouble();
|
|
if (value is String) {
|
|
if (value.isEmpty) return null;
|
|
return double.tryParse(value);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static bool _parseBool(dynamic value) {
|
|
if (value == null) return false;
|
|
if (value is bool) return value;
|
|
if (value is num) return value != 0;
|
|
if (value is String) {
|
|
final lower = value.toLowerCase();
|
|
return lower == "true" || lower == "1";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static DateTime _parseDateTime(dynamic value) {
|
|
if (value == null) return DateTime.now();
|
|
if (value is DateTime) return value;
|
|
if (value is String) {
|
|
try {
|
|
return DateTime.parse(value);
|
|
} catch (e) {
|
|
return DateTime.now();
|
|
}
|
|
}
|
|
return DateTime.now();
|
|
}
|
|
|
|
bool get isRootItem => parentOrderLineItemId == 0;
|
|
bool get isModifier => parentOrderLineItemId != 0;
|
|
}
|