Wrap numeric fields with val() to prevent "can't cast empty string to number" errors when database values are null or empty strings. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
239 lines
7.4 KiB
Text
239 lines
7.4 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<cfscript>
|
|
/**
|
|
* Get Order Detail
|
|
* Returns full order info including line items, customer details, and staff who worked on the order
|
|
*
|
|
* GET: ?OrderID=123
|
|
* POST: { OrderID: 123 }
|
|
*/
|
|
|
|
response = { "OK": false };
|
|
|
|
try {
|
|
// Get OrderID from request
|
|
orderID = 0;
|
|
|
|
// Check URL params
|
|
if (structKeyExists(url, "OrderID")) {
|
|
orderID = val(url.OrderID);
|
|
}
|
|
|
|
// Check POST body
|
|
if (orderID == 0) {
|
|
requestBody = toString(getHttpRequestData().content);
|
|
if (len(requestBody)) {
|
|
requestData = deserializeJSON(requestBody);
|
|
if (structKeyExists(requestData, "OrderID")) {
|
|
orderID = val(requestData.OrderID);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (orderID == 0) {
|
|
response["ERROR"] = "missing_order_id";
|
|
response["MESSAGE"] = "OrderID is required";
|
|
writeOutput(serializeJSON(response));
|
|
abort;
|
|
}
|
|
|
|
// Get order details
|
|
qOrder = queryExecute("
|
|
SELECT
|
|
o.OrderID,
|
|
o.OrderBusinessID,
|
|
o.OrderUserID,
|
|
o.OrderServicePointID,
|
|
o.OrderStatusID,
|
|
o.OrderTypeID,
|
|
o.OrderRemarks,
|
|
o.OrderAddedOn,
|
|
o.OrderLastEditedOn,
|
|
o.OrderSubmittedOn,
|
|
o.OrderTipAmount,
|
|
u.UserFirstName,
|
|
u.UserLastName,
|
|
u.UserContactNumber,
|
|
u.UserEmailAddress,
|
|
sp.ServicePointName,
|
|
sp.ServicePointTypeID,
|
|
b.BusinessName,
|
|
b.BusinessTaxRate
|
|
FROM Orders o
|
|
LEFT JOIN Users u ON u.UserID = o.OrderUserID
|
|
LEFT JOIN ServicePoints sp ON sp.ServicePointID = o.OrderServicePointID
|
|
LEFT JOIN Businesses b ON b.BusinessID = o.OrderBusinessID
|
|
WHERE o.OrderID = :orderID
|
|
", { orderID: orderID });
|
|
|
|
if (qOrder.recordCount == 0) {
|
|
response["ERROR"] = "order_not_found";
|
|
response["MESSAGE"] = "Order not found";
|
|
writeOutput(serializeJSON(response));
|
|
abort;
|
|
}
|
|
|
|
// Get line items (excluding deleted items)
|
|
qItems = queryExecute("
|
|
SELECT
|
|
oli.OrderLineItemID,
|
|
oli.OrderLineItemItemID,
|
|
oli.OrderLineItemParentOrderLineItemID,
|
|
oli.OrderLineItemQuantity,
|
|
oli.OrderLineItemPrice,
|
|
oli.OrderLineItemRemark,
|
|
i.ItemName,
|
|
i.ItemPrice,
|
|
i.ItemIsCheckedByDefault
|
|
FROM OrderLineItems oli
|
|
INNER JOIN Items i ON i.ItemID = oli.OrderLineItemItemID
|
|
WHERE oli.OrderLineItemOrderID = :orderID
|
|
AND oli.OrderLineItemIsDeleted = 0
|
|
ORDER BY oli.OrderLineItemID
|
|
", { orderID: orderID });
|
|
|
|
// Build line items array with parent-child structure
|
|
lineItems = [];
|
|
itemsById = {};
|
|
|
|
// First pass: create all items
|
|
for (row in qItems) {
|
|
item = {
|
|
"LineItemID": val(row.OrderLineItemID),
|
|
"ItemID": val(row.OrderLineItemItemID),
|
|
"ParentLineItemID": val(row.OrderLineItemParentOrderLineItemID),
|
|
"ItemName": row.ItemName ?: "",
|
|
"Quantity": val(row.OrderLineItemQuantity),
|
|
"UnitPrice": val(row.OrderLineItemPrice),
|
|
"Remarks": row.OrderLineItemRemark ?: "",
|
|
"IsDefault": (val(row.ItemIsCheckedByDefault) == 1),
|
|
"Modifiers": []
|
|
};
|
|
itemsById[row.OrderLineItemID] = item;
|
|
}
|
|
|
|
// Second pass: build hierarchy
|
|
for (row in qItems) {
|
|
item = itemsById[row.OrderLineItemID];
|
|
parentID = row.OrderLineItemParentOrderLineItemID;
|
|
|
|
if (parentID > 0 && structKeyExists(itemsById, parentID)) {
|
|
// This is a modifier - add to parent
|
|
arrayAppend(itemsById[parentID].Modifiers, item);
|
|
} else {
|
|
// This is a top-level item
|
|
arrayAppend(lineItems, item);
|
|
}
|
|
}
|
|
|
|
// Calculate subtotal from root line items
|
|
subtotal = 0;
|
|
for (item in lineItems) {
|
|
itemTotal = item.UnitPrice * item.Quantity;
|
|
// Add modifier prices
|
|
for (mod in item.Modifiers) {
|
|
itemTotal += mod.UnitPrice * mod.Quantity;
|
|
}
|
|
subtotal += itemTotal;
|
|
}
|
|
|
|
// Calculate tax using business tax rate or default 8.25%
|
|
taxRate = isNumeric(qOrder.BusinessTaxRate) && qOrder.BusinessTaxRate > 0 ? qOrder.BusinessTaxRate : 0.0825;
|
|
tax = subtotal * taxRate;
|
|
|
|
// Get tip from order
|
|
tip = isNumeric(qOrder.OrderTipAmount) ? qOrder.OrderTipAmount : 0;
|
|
|
|
// Calculate total
|
|
total = subtotal + tax + tip;
|
|
|
|
// Get staff who worked on this order (from Tasks table)
|
|
qStaff = queryExecute("
|
|
SELECT DISTINCT u.UserID, u.UserFirstName
|
|
FROM Tasks t
|
|
INNER JOIN Users u ON u.UserID = t.TaskClaimedByUserID
|
|
WHERE t.TaskOrderID = :orderID
|
|
AND t.TaskClaimedByUserID > 0
|
|
", { orderID: orderID });
|
|
|
|
// Build staff array with avatar URLs
|
|
staff = [];
|
|
for (row in qStaff) {
|
|
arrayAppend(staff, {
|
|
"UserID": row.UserID,
|
|
"FirstName": row.UserFirstName,
|
|
"AvatarUrl": "https://biz.payfrit.com/uploads/users/" & row.UserID & ".jpg"
|
|
});
|
|
}
|
|
|
|
// Build response
|
|
order = {
|
|
"OrderID": qOrder.OrderID,
|
|
"BusinessID": qOrder.OrderBusinessID,
|
|
"BusinessName": qOrder.BusinessName ?: "",
|
|
"Status": qOrder.OrderStatusID,
|
|
"StatusText": getStatusText(qOrder.OrderStatusID),
|
|
"OrderTypeID": qOrder.OrderTypeID ?: 0,
|
|
"OrderTypeName": getOrderTypeName(qOrder.OrderTypeID ?: 0),
|
|
"Subtotal": subtotal,
|
|
"Tax": tax,
|
|
"Tip": tip,
|
|
"Total": total,
|
|
"Notes": qOrder.OrderRemarks,
|
|
"CreatedOn": dateTimeFormat(qOrder.OrderAddedOn, "yyyy-mm-dd HH:nn:ss"),
|
|
"SubmittedOn": len(qOrder.OrderSubmittedOn) ? dateTimeFormat(qOrder.OrderSubmittedOn, "yyyy-mm-dd HH:nn:ss") : "",
|
|
"UpdatedOn": len(qOrder.OrderLastEditedOn) ? dateTimeFormat(qOrder.OrderLastEditedOn, "yyyy-mm-dd HH:nn:ss") : "",
|
|
"Customer": {
|
|
"UserID": qOrder.OrderUserID,
|
|
"FirstName": qOrder.UserFirstName,
|
|
"LastName": qOrder.UserLastName,
|
|
"Phone": qOrder.UserContactNumber,
|
|
"Email": qOrder.UserEmailAddress
|
|
},
|
|
"ServicePoint": {
|
|
"ServicePointID": qOrder.OrderServicePointID,
|
|
"Name": qOrder.ServicePointName,
|
|
"TypeID": qOrder.ServicePointTypeID
|
|
},
|
|
"LineItems": lineItems,
|
|
"Staff": staff
|
|
};
|
|
|
|
response["OK"] = true;
|
|
response["ORDER"] = order;
|
|
|
|
} catch (any e) {
|
|
response["ERROR"] = "server_error";
|
|
response["MESSAGE"] = e.message;
|
|
}
|
|
|
|
writeOutput(serializeJSON(response));
|
|
|
|
// Helper functions
|
|
function getStatusText(status) {
|
|
switch (status) {
|
|
case 0: return "Cart";
|
|
case 1: return "Submitted";
|
|
case 2: return "In Progress";
|
|
case 3: return "Ready";
|
|
case 4: return "On the Way";
|
|
case 5: return "Complete";
|
|
case 6: return "Cancelled";
|
|
case 7: return "Deleted";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
function getOrderTypeName(orderType) {
|
|
switch (orderType) {
|
|
case 1: return "Dine-in";
|
|
case 2: return "Takeaway";
|
|
case 3: return "Delivery";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
</cfscript>
|