This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/orders/getDetail.cfm
John Mizerek 1210249f54 Normalize database column and table names across entire codebase
Update all SQL queries, query result references, and ColdFusion code to match
the renamed database schema. Tables use plural CamelCase, PKs are all `ID`,
column prefixes stripped (e.g. BusinessName→Name, UserFirstName→FirstName).

Key changes:
- Strip table-name prefixes from all column references (Businesses, Users,
  Addresses, Hours, Menus, Categories, Items, Stations, Orders,
  OrderLineItems, Tasks, TaskCategories, TaskRatings, QuickTaskTemplates,
  ScheduledTaskDefinitions, ChatMessages, Beacons, ServicePoints, Employees,
  VisitorTrackings, ApiPerfLogs, tt_States, tt_Days, tt_AddressTypes,
  tt_OrderTypes, tt_TaskTypes)
- Rename PK references from {TableName}ID to ID in all queries
- Rewrite 7 admin beacon files to use ServicePoints.BeaconID instead of
  dropped lt_Beacon_Businesses_ServicePoints link table
- Rewrite beacon assignment files (list, save, delete) for new schema
- Fix FK references incorrectly changed to ID (OrderLineItems.OrderID,
  Categories.MenuID, Tasks.CategoryID, ServicePoints.BeaconID)
- Update Addresses: AddressLat→Latitude, AddressLng→Longitude
- Update Users: UserPassword→Password, UserIsEmailVerified→IsEmailVerified,
  UserIsActive→IsActive, UserBalance→Balance, etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:39:12 -08:00

249 lines
7.8 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.ID,
o.BusinessID,
o.UserID,
o.ServicePointID,
o.StatusID,
o.OrderTypeID,
o.Remarks,
o.AddedOn,
o.LastEditedOn,
o.SubmittedOn,
o.OrderTipAmount,
u.FirstName,
u.LastName,
u.ContactNumber,
u.EmailAddress,
sp.Name,
sp.TypeID,
b.Name,
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 = queryExecute("
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 or default 8.25%
taxRate = isNumeric(qOrder.TaxRate) && qOrder.TaxRate > 0 ? qOrder.TaxRate : 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) with pending rating tokens
qStaff = queryExecute("
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"] = "https://biz.payfrit.com/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["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>