- Add About Payfrit screen with app info, features, and contact details - Add Order Detail screen showing line items with non-default modifiers - Add order_detail model with parent-child line item hierarchy - Update order history to navigate to detail screen on tap - Add getOrderDetail API method - Add about route to app router Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
225 lines
6.7 KiB
Dart
225 lines
6.7 KiB
Dart
/// Model for order detail with line items and modifiers
|
|
class OrderDetail {
|
|
final int orderId;
|
|
final int businessId;
|
|
final String businessName;
|
|
final int status;
|
|
final String statusText;
|
|
final int orderTypeId;
|
|
final String orderTypeName;
|
|
final double subtotal;
|
|
final double tax;
|
|
final double tip;
|
|
final double total;
|
|
final String notes;
|
|
final DateTime createdOn;
|
|
final DateTime? submittedOn;
|
|
final DateTime? updatedOn;
|
|
final OrderCustomer customer;
|
|
final OrderServicePoint servicePoint;
|
|
final List<OrderLineItemDetail> lineItems;
|
|
|
|
const OrderDetail({
|
|
required this.orderId,
|
|
required this.businessId,
|
|
required this.businessName,
|
|
required this.status,
|
|
required this.statusText,
|
|
required this.orderTypeId,
|
|
required this.orderTypeName,
|
|
required this.subtotal,
|
|
required this.tax,
|
|
required this.tip,
|
|
required this.total,
|
|
required this.notes,
|
|
required this.createdOn,
|
|
this.submittedOn,
|
|
this.updatedOn,
|
|
required this.customer,
|
|
required this.servicePoint,
|
|
required this.lineItems,
|
|
});
|
|
|
|
factory OrderDetail.fromJson(Map<String, dynamic> json) {
|
|
final lineItemsJson = json['LineItems'] as List<dynamic>? ?? [];
|
|
|
|
return OrderDetail(
|
|
orderId: _parseInt(json['OrderID']) ?? 0,
|
|
businessId: _parseInt(json['BusinessID']) ?? 0,
|
|
businessName: (json['BusinessName'] as String?) ?? '',
|
|
status: _parseInt(json['Status']) ?? 0,
|
|
statusText: (json['StatusText'] as String?) ?? '',
|
|
orderTypeId: _parseInt(json['OrderTypeID']) ?? 0,
|
|
orderTypeName: (json['OrderTypeName'] as String?) ?? '',
|
|
subtotal: _parseDouble(json['Subtotal']) ?? 0.0,
|
|
tax: _parseDouble(json['Tax']) ?? 0.0,
|
|
tip: _parseDouble(json['Tip']) ?? 0.0,
|
|
total: _parseDouble(json['Total']) ?? 0.0,
|
|
notes: (json['Notes'] as String?) ?? '',
|
|
createdOn: _parseDateTime(json['CreatedOn']),
|
|
submittedOn: _parseDateTimeNullable(json['SubmittedOn']),
|
|
updatedOn: _parseDateTimeNullable(json['UpdatedOn']),
|
|
customer: OrderCustomer.fromJson(json['Customer'] as Map<String, dynamic>? ?? {}),
|
|
servicePoint: OrderServicePoint.fromJson(json['ServicePoint'] as Map<String, dynamic>? ?? {}),
|
|
lineItems: lineItemsJson
|
|
.map((e) => OrderLineItemDetail.fromJson(e as Map<String, dynamic>))
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
/// Get only non-default modifiers for display
|
|
List<OrderLineItemDetail> getNonDefaultModifiers(OrderLineItemDetail item) {
|
|
return item.modifiers.where((m) => !m.isDefault).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 && value.isNotEmpty) 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 && value.isNotEmpty) 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 && value.isNotEmpty) {
|
|
try {
|
|
return DateTime.parse(value);
|
|
} catch (_) {}
|
|
}
|
|
return DateTime.now();
|
|
}
|
|
|
|
static DateTime? _parseDateTimeNullable(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is String && value.isEmpty) return null;
|
|
if (value is DateTime) return value;
|
|
if (value is String) {
|
|
try {
|
|
return DateTime.parse(value);
|
|
} catch (_) {}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class OrderCustomer {
|
|
final int userId;
|
|
final String firstName;
|
|
final String lastName;
|
|
final String phone;
|
|
final String email;
|
|
|
|
const OrderCustomer({
|
|
required this.userId,
|
|
required this.firstName,
|
|
required this.lastName,
|
|
required this.phone,
|
|
required this.email,
|
|
});
|
|
|
|
factory OrderCustomer.fromJson(Map<String, dynamic> json) {
|
|
return OrderCustomer(
|
|
userId: (json['UserID'] as num?)?.toInt() ?? 0,
|
|
firstName: (json['FirstName'] as String?) ?? '',
|
|
lastName: (json['LastName'] as String?) ?? '',
|
|
phone: (json['Phone'] as String?) ?? '',
|
|
email: (json['Email'] as String?) ?? '',
|
|
);
|
|
}
|
|
|
|
String get fullName {
|
|
final parts = [firstName, lastName].where((s) => s.isNotEmpty);
|
|
return parts.isEmpty ? 'Guest' : parts.join(' ');
|
|
}
|
|
}
|
|
|
|
class OrderServicePoint {
|
|
final int servicePointId;
|
|
final String name;
|
|
final int typeId;
|
|
|
|
const OrderServicePoint({
|
|
required this.servicePointId,
|
|
required this.name,
|
|
required this.typeId,
|
|
});
|
|
|
|
factory OrderServicePoint.fromJson(Map<String, dynamic> json) {
|
|
return OrderServicePoint(
|
|
servicePointId: (json['ServicePointID'] as num?)?.toInt() ?? 0,
|
|
name: (json['Name'] as String?) ?? '',
|
|
typeId: (json['TypeID'] as num?)?.toInt() ?? 0,
|
|
);
|
|
}
|
|
}
|
|
|
|
class OrderLineItemDetail {
|
|
final int lineItemId;
|
|
final int itemId;
|
|
final int parentLineItemId;
|
|
final String itemName;
|
|
final int quantity;
|
|
final double unitPrice;
|
|
final String remarks;
|
|
final bool isDefault;
|
|
final List<OrderLineItemDetail> modifiers;
|
|
|
|
const OrderLineItemDetail({
|
|
required this.lineItemId,
|
|
required this.itemId,
|
|
required this.parentLineItemId,
|
|
required this.itemName,
|
|
required this.quantity,
|
|
required this.unitPrice,
|
|
required this.remarks,
|
|
required this.isDefault,
|
|
required this.modifiers,
|
|
});
|
|
|
|
factory OrderLineItemDetail.fromJson(Map<String, dynamic> json) {
|
|
final modifiersJson = json['Modifiers'] as List<dynamic>? ?? [];
|
|
|
|
return OrderLineItemDetail(
|
|
lineItemId: (json['LineItemID'] as num?)?.toInt() ?? 0,
|
|
itemId: (json['ItemID'] as num?)?.toInt() ?? 0,
|
|
parentLineItemId: (json['ParentLineItemID'] as num?)?.toInt() ?? 0,
|
|
itemName: (json['ItemName'] as String?) ?? '',
|
|
quantity: (json['Quantity'] as num?)?.toInt() ?? 1,
|
|
unitPrice: (json['UnitPrice'] as num?)?.toDouble() ?? 0.0,
|
|
remarks: (json['Remarks'] as String?) ?? '',
|
|
isDefault: json['IsDefault'] == true,
|
|
modifiers: modifiersJson
|
|
.map((e) => OrderLineItemDetail.fromJson(e as Map<String, dynamic>))
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
/// Calculate total price for this item including modifiers
|
|
double get totalPrice {
|
|
double total = unitPrice * quantity;
|
|
for (final mod in modifiers) {
|
|
total += mod.unitPrice * mod.quantity;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/// Check if this item has any non-default modifiers
|
|
bool get hasNonDefaultModifiers {
|
|
return modifiers.any((m) => !m.isDefault);
|
|
}
|
|
|
|
/// Get only non-default modifiers
|
|
List<OrderLineItemDetail> get nonDefaultModifiers {
|
|
return modifiers.where((m) => !m.isDefault).toList();
|
|
}
|
|
}
|