251 lines
7.9 KiB
Text
251 lines
7.9 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 = queryTimed("
|
|
SELECT
|
|
o.ID,
|
|
o.UUID,
|
|
o.BusinessID,
|
|
o.UserID,
|
|
o.ServicePointID,
|
|
o.StatusID,
|
|
o.OrderTypeID,
|
|
o.Remarks,
|
|
o.AddedOn,
|
|
o.LastEditedOn,
|
|
o.SubmittedOn,
|
|
o.TipAmount,
|
|
u.FirstName,
|
|
u.LastName,
|
|
u.ContactNumber,
|
|
u.EmailAddress,
|
|
sp.Name AS Name,
|
|
sp.TypeID AS TypeID,
|
|
b.Name AS BizName,
|
|
b.TaxRate
|
|
FROM Orders o
|
|
LEFT JOIN Users u ON u.ID = o.UserID
|
|
LEFT JOIN ServicePoints sp ON sp.ID = o.ServicePointID
|
|
LEFT JOIN Businesses b ON b.ID = o.BusinessID
|
|
WHERE o.ID = :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 = queryTimed("
|
|
SELECT
|
|
oli.ID,
|
|
oli.ItemID,
|
|
oli.ParentOrderLineItemID,
|
|
oli.Quantity,
|
|
oli.Price,
|
|
oli.Remark,
|
|
i.Name,
|
|
i.Price,
|
|
i.IsCheckedByDefault
|
|
FROM OrderLineItems oli
|
|
INNER JOIN Items i ON i.ID = oli.ItemID
|
|
WHERE oli.OrderID = :orderID
|
|
AND oli.IsDeleted = 0
|
|
ORDER BY oli.ID
|
|
", { orderID: orderID });
|
|
|
|
// Build line items array with parent-child structure
|
|
lineItems = [];
|
|
itemsById = {};
|
|
|
|
// First pass: create all items (use bracket notation to preserve key casing)
|
|
for (row in qItems) {
|
|
item = structNew("ordered");
|
|
item["LineItemID"] = val(row.ID);
|
|
item["ItemID"] = val(row.ID);
|
|
item["ParentLineItemID"] = val(row.ParentOrderLineItemID);
|
|
item["Name"] = row.Name ?: "";
|
|
item["Quantity"] = val(row.Quantity);
|
|
item["UnitPrice"] = val(row.Price);
|
|
item["Remarks"] = row.Remark ?: "";
|
|
item["IsDefault"] = (val(row.IsCheckedByDefault) == 1);
|
|
item["Modifiers"] = [];
|
|
itemsById[row.ID] = item;
|
|
}
|
|
|
|
// Second pass: build hierarchy
|
|
for (row in qItems) {
|
|
item = itemsById[row.ID];
|
|
parentID = row.ParentOrderLineItemID;
|
|
|
|
if (parentID > 0 && structKeyExists(itemsById, parentID)) {
|
|
// This is a modifier - add to parent (use bracket notation)
|
|
arrayAppend(itemsById[parentID]["Modifiers"], item);
|
|
} else {
|
|
// This is a top-level item
|
|
arrayAppend(lineItems, item);
|
|
}
|
|
}
|
|
|
|
// Calculate subtotal from root line items (use bracket notation)
|
|
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 (no default - business must configure)
|
|
taxRate = isNumeric(qOrder.TaxRate) && qOrder.TaxRate > 0 ? qOrder.TaxRate : 0;
|
|
tax = subtotal * taxRate;
|
|
|
|
// Get tip from order
|
|
tip = isNumeric(qOrder.TipAmount) ? qOrder.TipAmount : 0;
|
|
|
|
// Calculate total
|
|
total = subtotal + tax + tip;
|
|
|
|
// Get staff who worked on this order (from Tasks table) with pending rating tokens
|
|
qStaff = queryTimed("
|
|
SELECT DISTINCT u.ID, u.FirstName,
|
|
(SELECT r.AccessToken
|
|
FROM TaskRatings r
|
|
INNER JOIN Tasks t2 ON t2.ID = r.TaskID
|
|
WHERE t2.OrderID = :orderID
|
|
AND r.ForUserID = u.ID
|
|
AND r.Direction = 'customer_rates_worker'
|
|
AND r.CompletedOn IS NULL
|
|
AND r.ExpiresOn > NOW()
|
|
LIMIT 1) AS RatingToken
|
|
FROM Tasks t
|
|
INNER JOIN Users u ON u.ID = t.ClaimedByUserID
|
|
WHERE t.OrderID = :orderID
|
|
AND t.ClaimedByUserID > 0
|
|
", { orderID: orderID });
|
|
|
|
// Build staff array with avatar URLs and rating tokens (use ordered structs)
|
|
staff = [];
|
|
for (row in qStaff) {
|
|
staffMember = structNew("ordered");
|
|
staffMember["UserID"] = row.ID;
|
|
staffMember["FirstName"] = row.FirstName;
|
|
staffMember["AvatarUrl"] = application.baseUrl & "/uploads/users/" & row.ID & ".jpg";
|
|
staffMember["RatingToken"] = row.RatingToken ?: "";
|
|
arrayAppend(staff, staffMember);
|
|
}
|
|
|
|
// Build response (use ordered structs to preserve key casing)
|
|
customer = structNew("ordered");
|
|
customer["UserID"] = qOrder.UserID;
|
|
customer["FirstName"] = qOrder.FirstName;
|
|
customer["LastName"] = qOrder.LastName;
|
|
customer["Phone"] = qOrder.ContactNumber;
|
|
customer["Email"] = qOrder.EmailAddress;
|
|
|
|
servicePoint = structNew("ordered");
|
|
servicePoint["ServicePointID"] = qOrder.ServicePointID;
|
|
servicePoint["Name"] = qOrder.Name;
|
|
servicePoint["TypeID"] = qOrder.TypeID;
|
|
|
|
order = structNew("ordered");
|
|
order["OrderID"] = qOrder.ID;
|
|
order["UUID"] = qOrder.UUID ?: "";
|
|
order["BusinessID"] = qOrder.BusinessID;
|
|
order["Name"] = qOrder.Name ?: "";
|
|
order["Status"] = qOrder.StatusID;
|
|
order["StatusText"] = getStatusText(qOrder.StatusID);
|
|
order["OrderTypeID"] = qOrder.OrderTypeID ?: 0;
|
|
order["OrderTypeName"] = getOrderTypeName(qOrder.OrderTypeID ?: 0);
|
|
order["Subtotal"] = subtotal;
|
|
order["Tax"] = tax;
|
|
order["Tip"] = tip;
|
|
order["Total"] = total;
|
|
order["Notes"] = qOrder.Remarks;
|
|
order["CreatedOn"] = dateTimeFormat(qOrder.AddedOn, "yyyy-mm-dd HH:nn:ss");
|
|
order["SubmittedOn"] = len(qOrder.SubmittedOn) ? dateTimeFormat(qOrder.SubmittedOn, "yyyy-mm-dd HH:nn:ss") : "";
|
|
order["UpdatedOn"] = len(qOrder.LastEditedOn) ? dateTimeFormat(qOrder.LastEditedOn, "yyyy-mm-dd HH:nn:ss") : "";
|
|
order["Customer"] = customer;
|
|
order["ServicePoint"] = servicePoint;
|
|
order["LineItems"] = lineItems;
|
|
order["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>
|