payfrit-app/lib/models/cart.dart
John Mizerek 821eae4170 Fix cart model string casting to use ?.toString()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:17:43 -08:00

329 lines
11 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 List<int> businessOrderTypes; // Which order types the business offers (1=dine-in, 2=takeaway, 3=delivery)
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.businessOrderTypes,
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,
});
// Helper methods for checking available order types
bool get offersDineIn => businessOrderTypes.contains(1);
bool get offersTakeaway => businessOrderTypes.contains(2);
bool get offersDelivery => businessOrderTypes.contains(3);
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? ?? [];
// Parse business order types - can be array of ints or strings
List<int> orderTypes = [1, 2, 3]; // Default to all types
final rawOrderTypes = order["BusinessOrderTypes"];
if (rawOrderTypes != null && rawOrderTypes is List) {
orderTypes = rawOrderTypes
.map((e) => e is int ? e : int.tryParse(e.toString()) ?? 0)
.where((e) => e > 0)
.toList();
if (orderTypes.isEmpty) orderTypes = [1, 2, 3];
}
return Cart(
orderId: _parseInt(order["OrderID"]) ?? 0,
orderUuid: order["OrderUUID"]?.toString() ?? "",
userId: _parseInt(order["OrderUserID"]) ?? 0,
businessId: _parseInt(order["OrderBusinessID"]) ?? 0,
businessDeliveryMultiplier: _parseDouble(order["OrderBusinessDeliveryMultiplier"]) ?? 0.0,
businessDeliveryFee: _parseDouble(order["BusinessDeliveryFee"]) ?? 0.0,
businessOrderTypes: orderTypes,
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"]?.toString(),
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"]?.toString(),
isDeleted: _parseBool(json["OrderLineItemIsDeleted"]),
addedOn: _parseDateTime(json["OrderLineItemAddedOn"]),
itemName: json["ItemName"]?.toString(),
itemParentItemId: _parseInt(json["ItemParentItemID"]),
itemParentName: json["ItemParentName"]?.toString(),
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;
}
/// Lightweight cart info returned by getActiveCart API
/// Used at app startup to check if user has an existing order
class ActiveCartInfo {
final int orderId;
final String orderUuid;
final int businessId;
final String businessName;
final int orderTypeId;
final String orderTypeName;
final int servicePointId;
final String servicePointName;
final int itemCount;
const ActiveCartInfo({
required this.orderId,
required this.orderUuid,
required this.businessId,
required this.businessName,
required this.orderTypeId,
required this.orderTypeName,
required this.servicePointId,
required this.servicePointName,
required this.itemCount,
});
factory ActiveCartInfo.fromJson(Map<String, dynamic> json) {
return ActiveCartInfo(
orderId: _parseInt(json["OrderID"]) ?? 0,
orderUuid: json["OrderUUID"]?.toString() ?? "",
businessId: _parseInt(json["BusinessID"]) ?? 0,
businessName: json["BusinessName"]?.toString() ?? "",
orderTypeId: _parseInt(json["OrderTypeID"]) ?? 0,
orderTypeName: json["OrderTypeName"]?.toString() ?? "",
servicePointId: _parseInt(json["ServicePointID"]) ?? 0,
servicePointName: json["ServicePointName"]?.toString() ?? "",
itemCount: _parseInt(json["ItemCount"]) ?? 0,
);
}
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;
}
bool get hasItems => itemCount > 0;
bool get isDineIn => orderTypeId == 1;
bool get isTakeaway => orderTypeId == 2;
bool get isDelivery => orderTypeId == 3;
bool get isUndecided => orderTypeId == 0;
}