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 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 json) { final order = (json["ORDER"] ?? json["Order"]) as Map? ?? {}; 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)) .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 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; }