Implement complete cart management system

Add full cart functionality with API integration:
- Created Cart and OrderLineItem models with robust JSON parsing
- Implemented cart API methods (getOrCreateCart, setLineItem, getCart, submitOrder)
- Added cart state management to AppState with item count tracking
- Built cart view screen with item display, quantity editing, and removal
- Added cart badge to menu screen showing item count
- Implemented real add-to-cart logic with recursive modifier handling
- Added category name display in menu browsing
- Fixed API response case sensitivity (ORDER/ORDERLINEITEMS)
- Enhanced MenuItem model with categoryName field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2025-12-29 11:14:19 -08:00
parent 9995eb2ff7
commit f505eeb722
7 changed files with 870 additions and 23 deletions

View file

@ -1,5 +1,6 @@
import "package:flutter/material.dart";
import "../screens/cart_view_screen.dart";
import "../screens/login_screen.dart";
import "../screens/menu_browse_screen.dart";
import "../screens/restaurant_select_screen.dart";
@ -12,6 +13,7 @@ class AppRoutes {
static const String restaurantSelect = "/restaurants";
static const String servicePointSelect = "/service-points";
static const String menuBrowse = "/menu";
static const String cartView = "/cart";
static Map<String, WidgetBuilder> get routes => {
splash: (_) => const SplashScreen(),
@ -19,5 +21,6 @@ class AppRoutes {
restaurantSelect: (_) => const RestaurantSelectScreen(),
servicePointSelect: (_) => const ServicePointSelectScreen(),
menuBrowse: (_) => const MenuBrowseScreen(),
cartView: (_) => const CartViewScreen(),
};
}

View file

@ -8,6 +8,7 @@ class AppState extends ChangeNotifier {
int? _cartOrderId;
String? _cartOrderUuid;
int _cartItemCount = 0;
int? get selectedBusinessId => _selectedBusinessId;
int? get selectedServicePointId => _selectedServicePointId;
@ -17,6 +18,7 @@ class AppState extends ChangeNotifier {
int? get cartOrderId => _cartOrderId;
String? get cartOrderUuid => _cartOrderUuid;
int get cartItemCount => _cartItemCount;
bool get hasLocationSelection =>
_selectedBusinessId != null && _selectedServicePointId != null;
@ -50,15 +52,22 @@ class AppState extends ChangeNotifier {
notifyListeners();
}
void setCartOrder({required int orderId, required String orderUuid}) {
void setCartOrder({required int orderId, required String orderUuid, int itemCount = 0}) {
_cartOrderId = orderId;
_cartOrderUuid = orderUuid;
_cartItemCount = itemCount;
notifyListeners();
}
void updateCartItemCount(int count) {
_cartItemCount = count;
notifyListeners();
}
void clearCart() {
_cartOrderId = null;
_cartOrderUuid = null;
_cartItemCount = 0;
notifyListeners();
}

202
lib/models/cart.dart Normal file
View file

@ -0,0 +1,202 @@
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));
}
double get total => subtotal + deliveryFee;
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;
}

View file

@ -1,6 +1,7 @@
class MenuItem {
final int itemId;
final int categoryId;
final String categoryName;
final String name;
final String description;
final int parentItemId;
@ -15,6 +16,7 @@ class MenuItem {
const MenuItem({
required this.itemId,
required this.categoryId,
required this.categoryName,
required this.name,
required this.description,
required this.parentItemId,
@ -31,6 +33,7 @@ class MenuItem {
return MenuItem(
itemId: (json["ItemID"] as num).toInt(),
categoryId: (json["ItemCategoryID"] as num).toInt(),
categoryName: (json["ItemCategoryName"] as String?) ?? "",
name: (json["ItemName"] as String?) ?? "",
description: (json["ItemDescription"] as String?) ?? "",
parentItemId: (json["ItemParentItemID"] as num?)?.toInt() ?? 0,

View file

@ -0,0 +1,462 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../app/app_state.dart';
import '../models/cart.dart';
import '../models/menu_item.dart';
import '../services/api.dart';
class CartViewScreen extends StatefulWidget {
const CartViewScreen({super.key});
@override
State<CartViewScreen> createState() => _CartViewScreenState();
}
class _CartViewScreenState extends State<CartViewScreen> {
Cart? _cart;
bool _isLoading = true;
String? _error;
Map<int, MenuItem> _menuItemsById = {};
@override
void initState() {
super.initState();
_loadCart();
}
Future<void> _loadCart() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final appState = context.read<AppState>();
final cartOrderId = appState.cartOrderId;
if (cartOrderId == null) {
setState(() {
_isLoading = false;
_cart = null;
});
return;
}
// Load cart
final cart = await Api.getCart(orderId: cartOrderId);
// Load menu items to get names and prices
final businessId = appState.selectedBusinessId;
if (businessId != null) {
final menuItems = await Api.listMenuItems(businessId: businessId);
_menuItemsById = {for (var item in menuItems) item.itemId: item};
}
setState(() {
_cart = cart;
_isLoading = false;
});
// Update item count in app state
appState.updateCartItemCount(cart.itemCount);
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
Future<void> _removeLineItem(OrderLineItem lineItem) async {
try {
final appState = context.read<AppState>();
final cartOrderId = appState.cartOrderId;
if (cartOrderId == null) return;
setState(() => _isLoading = true);
// Set IsSelected=false to remove the item
await Api.setLineItem(
orderId: cartOrderId,
parentOrderLineItemId: lineItem.parentOrderLineItemId,
itemId: lineItem.itemId,
isSelected: false,
);
// Reload cart
await _loadCart();
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
Future<void> _updateQuantity(OrderLineItem lineItem, int newQuantity) async {
if (newQuantity < 1) return;
try {
final appState = context.read<AppState>();
final cartOrderId = appState.cartOrderId;
if (cartOrderId == null) return;
setState(() => _isLoading = true);
await Api.setLineItem(
orderId: cartOrderId,
parentOrderLineItemId: lineItem.parentOrderLineItemId,
itemId: lineItem.itemId,
isSelected: true,
quantity: newQuantity,
remark: lineItem.remark,
);
// Reload cart
await _loadCart();
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
Future<void> _submitOrder() async {
try {
final appState = context.read<AppState>();
final cartOrderId = appState.cartOrderId;
if (cartOrderId == null) return;
setState(() => _isLoading = true);
await Api.submitOrder(orderId: cartOrderId);
// Clear cart state
appState.clearCart();
if (!mounted) return;
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Order submitted successfully!"),
backgroundColor: Colors.green,
),
);
// Navigate back
Navigator.of(context).pop();
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Cart"),
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _error != null
? Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 48),
const SizedBox(height: 16),
Text(
"Error loading cart",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(_error!, textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadCart,
child: const Text("Retry"),
),
],
),
),
)
: _cart == null || _cart!.itemCount == 0
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.shopping_cart_outlined,
size: 80,
color: Colors.grey,
),
const SizedBox(height: 16),
Text(
"Your cart is empty",
style: Theme.of(context).textTheme.titleLarge,
),
],
),
)
: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: _buildCartItems(),
),
),
_buildCartSummary(),
],
),
);
}
List<Widget> _buildCartItems() {
if (_cart == null) return [];
// Group line items by root items
final rootItems = _cart!.lineItems
.where((item) => item.parentOrderLineItemId == 0 && !item.isDeleted)
.toList();
final widgets = <Widget>[];
for (final rootItem in rootItems) {
widgets.add(_buildRootItemCard(rootItem));
widgets.add(const SizedBox(height: 12));
}
return widgets;
}
Widget _buildRootItemCard(OrderLineItem rootItem) {
final menuItem = _menuItemsById[rootItem.itemId];
final itemName = menuItem?.name ?? "Item #${rootItem.itemId}";
// Find all modifiers for this root item
final modifiers = _cart!.lineItems
.where((item) =>
item.parentOrderLineItemId == rootItem.orderLineItemId &&
!item.isDeleted)
.toList();
return Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
itemName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _confirmRemoveItem(rootItem, itemName),
),
],
),
if (modifiers.isNotEmpty) ...[
const SizedBox(height: 8),
...modifiers.map((mod) => _buildModifierRow(mod)),
],
const SizedBox(height: 8),
Row(
children: [
IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: rootItem.quantity > 1
? () => _updateQuantity(rootItem, rootItem.quantity - 1)
: null,
),
Text(
"${rootItem.quantity}",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: const Icon(Icons.add_circle_outline),
onPressed: () =>
_updateQuantity(rootItem, rootItem.quantity + 1),
),
const Spacer(),
Text(
"\$${rootItem.price.toStringAsFixed(2)}",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
],
),
),
);
}
Widget _buildModifierRow(OrderLineItem modifier) {
final menuItem = _menuItemsById[modifier.itemId];
final modName = menuItem?.name ?? "Modifier #${modifier.itemId}";
return Padding(
padding: const EdgeInsets.only(left: 16, top: 4),
child: Row(
children: [
const Icon(Icons.add, size: 12, color: Colors.grey),
const SizedBox(width: 4),
Expanded(
child: Text(
modName,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
),
if (modifier.price > 0)
Text(
"+\$${modifier.price.toStringAsFixed(2)}",
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
);
}
Widget _buildCartSummary() {
if (_cart == null) return const SizedBox.shrink();
return Container(
decoration: BoxDecoration(
color: Colors.grey[100],
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, -2),
),
],
),
padding: const EdgeInsets.all(16),
child: SafeArea(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Subtotal",
style: TextStyle(fontSize: 16),
),
Text(
"\$${_cart!.subtotal.toStringAsFixed(2)}",
style: const TextStyle(fontSize: 16),
),
],
),
if (_cart!.deliveryFee > 0) ...[
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Delivery Fee",
style: TextStyle(fontSize: 16),
),
Text(
"\$${_cart!.deliveryFee.toStringAsFixed(2)}",
style: const TextStyle(fontSize: 16),
),
],
),
],
const Divider(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Total",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
"\$${_cart!.total.toStringAsFixed(2)}",
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _cart!.itemCount > 0 ? _submitOrder : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: const Text(
"Submit Order",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
),
],
),
),
);
}
void _confirmRemoveItem(OrderLineItem item, String itemName) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Remove Item"),
content: Text("Remove $itemName from cart?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("Cancel"),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
_removeLineItem(item);
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text("Remove"),
),
],
),
);
}
}

View file

@ -1,5 +1,9 @@
import "package:flutter/material.dart";
import "package:provider/provider.dart";
import "../app/app_router.dart";
import "../app/app_state.dart";
import "../models/cart.dart";
import "../models/menu_item.dart";
import "../services/api.dart";
@ -98,16 +102,20 @@ class _MenuBrowseScreenState extends State<MenuBrowseScreen> {
@override
Widget build(BuildContext context) {
final appState = context.watch<AppState>();
return Scaffold(
appBar: AppBar(
title: const Text("Menu"),
actions: [
IconButton(
icon: const Icon(Icons.shopping_cart),
icon: Badge(
label: Text("${appState.cartItemCount}"),
isLabelVisible: appState.cartItemCount > 0,
child: const Icon(Icons.shopping_cart),
),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Cart view coming soon")),
);
Navigator.of(context).pushNamed(AppRoutes.cartView);
},
),
],
@ -154,6 +162,9 @@ class _MenuBrowseScreenState extends State<MenuBrowseScreen> {
itemBuilder: (context, index) {
final categoryId = categoryIds[index];
final items = _itemsByCategory[categoryId] ?? [];
final categoryName = items.isNotEmpty
? items.first.categoryName
: "Category $categoryId";
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -161,7 +172,7 @@ class _MenuBrowseScreenState extends State<MenuBrowseScreen> {
Padding(
padding: const EdgeInsets.all(16),
child: Text(
"Category $categoryId",
categoryName,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
@ -222,7 +233,68 @@ class _MenuBrowseScreenState extends State<MenuBrowseScreen> {
);
}
void _addToCart(MenuItem item, Set<int> selectedModifierIds) {
Future<void> _addToCart(MenuItem item, Set<int> selectedModifierIds) async {
if (_userId == null || _businessId == null || _servicePointId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Missing required information")),
);
return;
}
try {
final appState = context.read<AppState>();
// Get or create cart
Cart cart;
if (appState.cartOrderId == null) {
cart = await Api.getOrCreateCart(
userId: _userId!,
businessId: _businessId!,
servicePointId: _servicePointId!,
orderTypeId: 1, // Dine-in
);
// ignore: avoid_print
print("DEBUG: Created cart with orderId=${cart.orderId}");
appState.setCartOrder(
orderId: cart.orderId,
orderUuid: cart.orderUuid,
itemCount: cart.itemCount,
);
} else {
// We have an existing cart ID
cart = await Api.getCart(orderId: appState.cartOrderId!);
// ignore: avoid_print
print("DEBUG: Loaded existing cart with orderId=${cart.orderId}");
}
// ignore: avoid_print
print("DEBUG: About to add root item ${item.itemId} to cart ${cart.orderId}");
// Add root item
cart = await Api.setLineItem(
orderId: cart.orderId,
parentOrderLineItemId: 0,
itemId: item.itemId,
isSelected: true,
quantity: 1,
);
// ignore: avoid_print
print("DEBUG: Added root item, cart now has ${cart.lineItems.length} line items");
// Add all selected modifiers recursively
await _addModifiersRecursively(
cart.orderId,
item.itemId,
selectedModifierIds,
);
// Refresh cart to get final state
cart = await Api.getCart(orderId: cart.orderId);
appState.updateCartItemCount(cart.itemCount);
if (!mounted) return;
final message = selectedModifierIds.isEmpty
? "Added ${item.name} to cart"
: "Added ${item.name} with ${selectedModifierIds.length} customizations";
@ -230,6 +302,37 @@ class _MenuBrowseScreenState extends State<MenuBrowseScreen> {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error adding to cart: $e")),
);
}
}
Future<void> _addModifiersRecursively(
int orderId,
int parentItemId,
Set<int> selectedItemIds,
) async {
final children = _itemsByParent[parentItemId] ?? [];
for (final child in children) {
final isSelected = selectedItemIds.contains(child.itemId);
await Api.setLineItem(
orderId: orderId,
parentOrderLineItemId: 0, // Will be handled by backend
itemId: child.itemId,
isSelected: isSelected,
);
// Recursively add grandchildren
if (isSelected) {
await _addModifiersRecursively(orderId, child.itemId, selectedItemIds);
}
}
}
}

View file

@ -1,6 +1,7 @@
import "dart:convert";
import "package:http/http.dart" as http;
import "../models/cart.dart";
import "../models/menu_item.dart";
import "../models/restaurant.dart";
import "../models/service_point.dart";
@ -307,33 +308,97 @@ class Api {
}
// -------------------------
// Ordering API (stubs referenced by OrderHomeScreen)
// Cart & Orders
// -------------------------
static Future<dynamic> getOrCreateCart({
static Future<Cart> getOrCreateCart({
required int userId,
required int businessId,
required int servicePointId,
required int orderTypeId,
}) async {
throw StateError("endpoint_not_implemented: Api.getOrCreateCart");
final raw = await _postRaw(
"/orders/getOrCreateCart.cfm",
{
"OrderUserID": userId,
"BusinessID": businessId,
"OrderServicePointID": servicePointId,
"OrderTypeID": orderTypeId,
},
businessIdOverride: businessId,
);
final j = _requireJson(raw, "GetOrCreateCart");
if (!_ok(j)) {
throw StateError(
"GetOrCreateCart API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["MESSAGE"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
);
}
static Future<dynamic> getCart({required int orderId}) async {
throw StateError("endpoint_not_implemented: Api.getCart");
return Cart.fromJson(j);
}
static Future<void> setLineItem({
static Future<Cart> getCart({required int orderId}) async {
final raw = await _postRaw(
"/orders/getCart.cfm",
{"OrderID": orderId},
);
final j = _requireJson(raw, "GetCart");
if (!_ok(j)) {
throw StateError(
"GetCart API returned OK=false\nERROR: ${_err(j)}\nHTTP Status: ${raw.statusCode}",
);
}
return Cart.fromJson(j);
}
static Future<Cart> setLineItem({
required int orderId,
required int parentOrderLineItemId,
required int itemId,
required int qty,
required List<int> selectedChildItemIds,
required bool isSelected,
int quantity = 1,
String? remark,
}) async {
throw StateError("endpoint_not_implemented: Api.setLineItem");
final raw = await _postRaw(
"/orders/setLineItem.cfm",
{
"OrderID": orderId,
"ParentOrderLineItemID": parentOrderLineItemId,
"ItemID": itemId,
"IsSelected": isSelected,
"Quantity": quantity,
if (remark != null && remark.isNotEmpty) "Remark": remark,
},
);
final j = _requireJson(raw, "SetLineItem");
if (!_ok(j)) {
throw StateError(
"SetLineItem API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["MESSAGE"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
);
}
return Cart.fromJson(j);
}
static Future<void> submitOrder({required int orderId}) async {
throw StateError("endpoint_not_implemented: Api.submitOrder");
final raw = await _postRaw(
"/orders/submit.cfm",
{"OrderID": orderId},
);
final j = _requireJson(raw, "SubmitOrder");
if (!_ok(j)) {
throw StateError(
"SubmitOrder API returned OK=false\nERROR: ${_err(j)}\nHTTP Status: ${raw.statusCode}",
);
}
}
}