Categories Migration: - Add ItemCategoryID column to Items table (api/admin/addItemCategoryColumn.cfm) - Migration script to populate Categories from unified schema (api/admin/migrateToCategories.cfm) - Updated items.cfm and getForBuilder.cfm to use Categories table with fallback KDS Station Selection: - KDS now prompts for station selection on load (Kitchen, Bar, or All Stations) - Station filter persists in localStorage - Updated listForKDS.cfm to filter orders by station - Simplified KDS UI with station badge in header Portal Improvements: - Fixed drag-and-drop in station assignment (proper event propagation) - Fixed Back button links to use BASE_PATH for local development - Added console logging for debugging station assignment - Order detail API now calculates Subtotal, Tax, Tip, Total properly Admin Tools: - setupBigDeansStations.cfm - Create Kitchen and Bar stations for Big Dean's 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
208 lines
6 KiB
Text
208 lines
6 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 and customer details
|
|
*
|
|
* 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.OrderRemarks,
|
|
o.OrderAddedOn,
|
|
o.OrderLastEditedOn,
|
|
u.UserFirstName,
|
|
u.UserLastName,
|
|
u.UserContactNumber,
|
|
u.UserEmailAddress,
|
|
sp.ServicePointName,
|
|
sp.ServicePointTypeID
|
|
FROM Orders o
|
|
LEFT JOIN Users u ON u.UserID = o.OrderUserID
|
|
LEFT JOIN ServicePoints sp ON sp.ServicePointID = o.OrderServicePointID
|
|
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
|
|
qItems = queryExecute("
|
|
SELECT
|
|
oli.OrderLineItemID,
|
|
oli.OrderLineItemItemID,
|
|
oli.OrderLineItemParentOrderLineItemID,
|
|
oli.OrderLineItemQuantity,
|
|
oli.OrderLineItemPrice,
|
|
oli.OrderLineItemRemark,
|
|
i.ItemName,
|
|
i.ItemPrice
|
|
FROM OrderLineItems oli
|
|
INNER JOIN Items i ON i.ItemID = oli.OrderLineItemItemID
|
|
WHERE oli.OrderLineItemOrderID = :orderID
|
|
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": row.OrderLineItemID,
|
|
"ItemID": row.OrderLineItemItemID,
|
|
"ParentLineItemID": row.OrderLineItemParentOrderLineItemID,
|
|
"ItemName": row.ItemName,
|
|
"Quantity": row.OrderLineItemQuantity,
|
|
"UnitPrice": row.OrderLineItemPrice,
|
|
"Remarks": row.OrderLineItemRemark,
|
|
"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 (assume 8.75% if not stored)
|
|
taxRate = 0.0875;
|
|
tax = subtotal * taxRate;
|
|
|
|
// Look up tip from Payments table if exists
|
|
tip = 0;
|
|
try {
|
|
qPayment = queryExecute("
|
|
SELECT PaymentTipAmount
|
|
FROM Payments
|
|
WHERE PaymentOrderID = :orderID
|
|
LIMIT 1
|
|
", { orderID: orderID });
|
|
if (qPayment.recordCount > 0 && !isNull(qPayment.PaymentTipAmount)) {
|
|
tip = qPayment.PaymentTipAmount;
|
|
}
|
|
} catch (any e) {
|
|
// Payments table may not exist or have this column, ignore
|
|
}
|
|
|
|
// Calculate total
|
|
total = subtotal + tax + tip;
|
|
|
|
// Build response
|
|
order = {
|
|
"OrderID": qOrder.OrderID,
|
|
"BusinessID": qOrder.OrderBusinessID,
|
|
"Status": qOrder.OrderStatusID,
|
|
"StatusText": getStatusText(qOrder.OrderStatusID),
|
|
"Subtotal": subtotal,
|
|
"Tax": tax,
|
|
"Tip": tip,
|
|
"Total": total,
|
|
"Notes": qOrder.OrderRemarks,
|
|
"CreatedOn": dateTimeFormat(qOrder.OrderAddedOn, "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
|
|
};
|
|
|
|
response["OK"] = true;
|
|
response["ORDER"] = order;
|
|
|
|
} catch (any e) {
|
|
response["ERROR"] = "server_error";
|
|
response["MESSAGE"] = e.message;
|
|
}
|
|
|
|
writeOutput(serializeJSON(response));
|
|
|
|
// Helper function
|
|
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 "Completed";
|
|
case 5: return "Cancelled";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
</cfscript>
|