Fix type casting in order history and order detail models

Handle string/int/null values safely in JSON parsing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-01-23 21:08:39 -08:00
parent 28e41a445e
commit b47c68b63a
4 changed files with 53 additions and 36 deletions

View file

@ -43,22 +43,23 @@ class OrderDetail {
}); });
factory OrderDetail.fromJson(Map<String, dynamic> json) { factory OrderDetail.fromJson(Map<String, dynamic> json) {
String safeStr(dynamic v) => v?.toString() ?? '';
final lineItemsJson = json['LineItems'] as List<dynamic>? ?? []; final lineItemsJson = json['LineItems'] as List<dynamic>? ?? [];
final staffJson = json['Staff'] as List<dynamic>? ?? []; final staffJson = json['Staff'] as List<dynamic>? ?? [];
return OrderDetail( return OrderDetail(
orderId: _parseInt(json['OrderID']) ?? 0, orderId: _parseInt(json['OrderID']) ?? 0,
businessId: _parseInt(json['BusinessID']) ?? 0, businessId: _parseInt(json['BusinessID']) ?? 0,
businessName: (json['BusinessName'] as String?) ?? '', businessName: safeStr(json['BusinessName']),
status: _parseInt(json['Status']) ?? 0, status: _parseInt(json['Status']) ?? 0,
statusText: (json['StatusText'] as String?) ?? '', statusText: safeStr(json['StatusText']),
orderTypeId: _parseInt(json['OrderTypeID']) ?? 0, orderTypeId: _parseInt(json['OrderTypeID']) ?? 0,
orderTypeName: (json['OrderTypeName'] as String?) ?? '', orderTypeName: safeStr(json['OrderTypeName']),
subtotal: _parseDouble(json['Subtotal']) ?? 0.0, subtotal: _parseDouble(json['Subtotal']) ?? 0.0,
tax: _parseDouble(json['Tax']) ?? 0.0, tax: _parseDouble(json['Tax']) ?? 0.0,
tip: _parseDouble(json['Tip']) ?? 0.0, tip: _parseDouble(json['Tip']) ?? 0.0,
total: _parseDouble(json['Total']) ?? 0.0, total: _parseDouble(json['Total']) ?? 0.0,
notes: (json['Notes'] as String?) ?? '', notes: safeStr(json['Notes']),
createdOn: _parseDateTime(json['CreatedOn']), createdOn: _parseDateTime(json['CreatedOn']),
submittedOn: _parseDateTimeNullable(json['SubmittedOn']), submittedOn: _parseDateTimeNullable(json['SubmittedOn']),
updatedOn: _parseDateTimeNullable(json['UpdatedOn']), updatedOn: _parseDateTimeNullable(json['UpdatedOn']),
@ -135,14 +136,17 @@ class OrderCustomer {
factory OrderCustomer.fromJson(Map<String, dynamic> json) { factory OrderCustomer.fromJson(Map<String, dynamic> json) {
return OrderCustomer( return OrderCustomer(
userId: (json['UserID'] as num?)?.toInt() ?? 0, userId: _safeInt(json['UserID']),
firstName: (json['FirstName'] as String?) ?? '', firstName: _safeStr(json['FirstName']),
lastName: (json['LastName'] as String?) ?? '', lastName: _safeStr(json['LastName']),
phone: (json['Phone'] as String?) ?? '', phone: _safeStr(json['Phone']),
email: (json['Email'] as String?) ?? '', email: _safeStr(json['Email']),
); );
} }
static int _safeInt(dynamic v) => v is int ? v : int.tryParse(v?.toString() ?? '') ?? 0;
static String _safeStr(dynamic v) => v?.toString() ?? '';
String get fullName { String get fullName {
final parts = [firstName, lastName].where((s) => s.isNotEmpty); final parts = [firstName, lastName].where((s) => s.isNotEmpty);
return parts.isEmpty ? 'Guest' : parts.join(' '); return parts.isEmpty ? 'Guest' : parts.join(' ');
@ -161,10 +165,12 @@ class OrderServicePoint {
}); });
factory OrderServicePoint.fromJson(Map<String, dynamic> json) { factory OrderServicePoint.fromJson(Map<String, dynamic> json) {
int safeInt(dynamic v) => v is int ? v : int.tryParse(v?.toString() ?? '') ?? 0;
String safeStr(dynamic v) => v?.toString() ?? '';
return OrderServicePoint( return OrderServicePoint(
servicePointId: (json['ServicePointID'] as num?)?.toInt() ?? 0, servicePointId: safeInt(json['ServicePointID']),
name: (json['Name'] as String?) ?? '', name: safeStr(json['Name']),
typeId: (json['TypeID'] as num?)?.toInt() ?? 0, typeId: safeInt(json['TypeID']),
); );
} }
} }
@ -181,10 +187,12 @@ class OrderStaff {
}); });
factory OrderStaff.fromJson(Map<String, dynamic> json) { factory OrderStaff.fromJson(Map<String, dynamic> json) {
int safeInt(dynamic v) => v is int ? v : int.tryParse(v?.toString() ?? '') ?? 0;
String safeStr(dynamic v) => v?.toString() ?? '';
return OrderStaff( return OrderStaff(
userId: (json['UserID'] as num?)?.toInt() ?? 0, userId: safeInt(json['UserID']),
firstName: (json['FirstName'] as String?) ?? '', firstName: safeStr(json['FirstName']),
avatarUrl: (json['AvatarUrl'] as String?) ?? '', avatarUrl: safeStr(json['AvatarUrl']),
); );
} }
} }
@ -213,16 +221,19 @@ class OrderLineItemDetail {
}); });
factory OrderLineItemDetail.fromJson(Map<String, dynamic> json) { factory OrderLineItemDetail.fromJson(Map<String, dynamic> json) {
int safeInt(dynamic v) => v is int ? v : int.tryParse(v?.toString() ?? '') ?? 0;
double safeDouble(dynamic v) => v is num ? v.toDouble() : double.tryParse(v?.toString() ?? '') ?? 0.0;
String safeStr(dynamic v) => v?.toString() ?? '';
final modifiersJson = json['Modifiers'] as List<dynamic>? ?? []; final modifiersJson = json['Modifiers'] as List<dynamic>? ?? [];
return OrderLineItemDetail( return OrderLineItemDetail(
lineItemId: (json['LineItemID'] as num?)?.toInt() ?? 0, lineItemId: safeInt(json['LineItemID']),
itemId: (json['ItemID'] as num?)?.toInt() ?? 0, itemId: safeInt(json['ItemID']),
parentLineItemId: (json['ParentLineItemID'] as num?)?.toInt() ?? 0, parentLineItemId: safeInt(json['ParentLineItemID']),
itemName: (json['ItemName'] as String?) ?? '', itemName: safeStr(json['ItemName']),
quantity: (json['Quantity'] as num?)?.toInt() ?? 1, quantity: safeInt(json['Quantity']),
unitPrice: (json['UnitPrice'] as num?)?.toDouble() ?? 0.0, unitPrice: safeDouble(json['UnitPrice']),
remarks: (json['Remarks'] as String?) ?? '', remarks: safeStr(json['Remarks']),
isDefault: json['IsDefault'] == true, isDefault: json['IsDefault'] == true,
modifiers: modifiersJson modifiers: modifiersJson
.map((e) => OrderLineItemDetail.fromJson(e as Map<String, dynamic>)) .map((e) => OrderLineItemDetail.fromJson(e as Map<String, dynamic>))

View file

@ -28,20 +28,24 @@ class OrderHistoryItem {
}); });
factory OrderHistoryItem.fromJson(Map<String, dynamic> json) { factory OrderHistoryItem.fromJson(Map<String, dynamic> json) {
int parseId(dynamic val) => val is int ? val : int.tryParse(val.toString()) ?? 0;
double parseDouble(dynamic val) => val is num ? val.toDouble() : double.tryParse(val.toString()) ?? 0.0;
String parseStr(dynamic val) => val?.toString() ?? "";
return OrderHistoryItem( return OrderHistoryItem(
orderId: (json["OrderID"] as num).toInt(), orderId: parseId(json["OrderID"]),
orderUuid: json["OrderUUID"] as String? ?? "", orderUuid: parseStr(json["OrderUUID"]),
businessId: (json["BusinessID"] as num).toInt(), businessId: parseId(json["BusinessID"]),
businessName: json["BusinessName"] as String? ?? "Unknown", businessName: parseStr(json["BusinessName"]).isEmpty ? "Unknown" : parseStr(json["BusinessName"]),
total: (json["OrderTotal"] as num?)?.toDouble() ?? 0.0, total: parseDouble(json["OrderTotal"]),
statusId: (json["OrderStatusID"] as num).toInt(), statusId: parseId(json["OrderStatusID"]),
statusName: json["StatusName"] as String? ?? "Unknown", statusName: parseStr(json["StatusName"]).isEmpty ? "Unknown" : parseStr(json["StatusName"]),
orderTypeId: (json["OrderTypeID"] as num?)?.toInt() ?? 0, orderTypeId: parseId(json["OrderTypeID"]),
typeName: json["TypeName"] as String? ?? "Unknown", typeName: parseStr(json["TypeName"]).isEmpty ? "Unknown" : parseStr(json["TypeName"]),
itemCount: (json["ItemCount"] as num?)?.toInt() ?? 0, itemCount: parseId(json["ItemCount"]),
createdAt: DateTime.tryParse(json["CreatedAt"] as String? ?? "") ?? DateTime.now(), createdAt: DateTime.tryParse(parseStr(json["CreatedAt"])) ?? DateTime.now(),
completedAt: json["CompletedAt"] != null && (json["CompletedAt"] as String).isNotEmpty completedAt: parseStr(json["CompletedAt"]).isNotEmpty
? DateTime.tryParse(json["CompletedAt"] as String) ? DateTime.tryParse(parseStr(json["CompletedAt"]))
: null, : null,
); );
} }

View file

@ -183,6 +183,8 @@ class _AddressListScreenState extends State<AddressListScreen> {
_error!, _error!,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
maxLines: 3,
overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
FilledButton.icon( FilledButton.icon(

View file

@ -561,7 +561,7 @@ class _CartViewScreenState extends State<CartViewScreen> {
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(_error!, textAlign: TextAlign.center), Text(_error!, textAlign: TextAlign.center, maxLines: 3, overflow: TextOverflow.ellipsis),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton( ElevatedButton(
onPressed: _loadCart, onPressed: _loadCart,