Restaurant Select Screen: - Horizontal bars with logos (text fallback with first letter) - Tap to expand and preview menu with horizontal item cards - Dark theme with subtle header backgrounds Menu Browse Screen: - Removed redundant business info overlay from header - Sharp gradient bars on top/bottom edges of headers - Accordion categories with animated expand/collapse - Hide checkboxes on container/interim items - Track user-modified selections separately from defaults Beacon Scan Screen: - Rotating status messages during 5 scan cycles - Removed manual selection link for cleaner UX Cart/Checkout: - Only show delivery fee for delivery orders (OrderTypeID=3) - Fixed total calculation to exclude fee for dine-in 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
203 lines
6.1 KiB
Dart
203 lines
6.1 KiB
Dart
class Cart {
|
|
final int orderId;
|
|
final String orderUuid;
|
|
final int userId;
|
|
final int businessId;
|
|
final double businessDeliveryMultiplier;
|
|
final int orderTypeId;
|
|
final double deliveryFee;
|
|
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.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,
|
|
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 {
|
|
return lineItems
|
|
.where((item) => !item.isDeleted && item.parentOrderLineItemId == 0)
|
|
.fold(0.0, (sum, item) => sum + (item.price * item.quantity));
|
|
}
|
|
|
|
// Only include delivery fee for delivery orders (orderTypeId == 3)
|
|
double get total => subtotal + (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;
|
|
|
|
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,
|
|
});
|
|
|
|
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"]),
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|