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/listForKDS.cfm
John Mizerek a8257b2509 Fix inverted modifier groups in KDS
The inverted group header item isn't always an order line item itself,
so RemovedDefaults was never computed. Now detects inverted groups
via children's ParentIsInvertedGroup flag and attaches RemovedDefaults
to the first child as a proxy. KDS JS handles both patterns.

Also skips showing default modifiers from inverted groups since those
are represented by "NO removed-item" instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:06:18 -07:00

236 lines
No EOL
8.2 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cffunction name="apiAbort" access="public" returntype="void" output="true">
<cfargument name="payload" type="struct" required="true">
<cfcontent type="application/json; charset=utf-8">
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
<cfabort>
</cffunction>
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
<cfset var raw = getHttpRequestData().content>
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
<cfreturn {}>
</cfif>
<cftry>
<cfset var data = deserializeJSON(raw)>
<cfif isStruct(data)>
<cfreturn data>
<cfelse>
<cfreturn {}>
</cfif>
<cfcatch>
<cfreturn {}>
</cfcatch>
</cftry>
</cffunction>
<cfset data = readJsonBody()>
<cfset BusinessID = val( structKeyExists(data,"BusinessID") ? data.BusinessID : 0 )>
<cfset ServicePointID = val( structKeyExists(data,"ServicePointID") ? data.ServicePointID : 0 )>
<cfset StationID = val( structKeyExists(data,"StationID") ? data.StationID : 0 )>
<cfif BusinessID LTE 0>
<cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "BusinessID is required.", "DETAIL": "" })>
</cfif>
<cftry>
<!--- Build WHERE clause for filtering --->
<cfset whereClauses = ["o.BusinessID = ?"]>
<cfset params = [ { value = BusinessID, cfsqltype = "cf_sql_integer" } ]>
<!--- Filter by service point if provided --->
<cfif ServicePointID GT 0>
<cfset arrayAppend(whereClauses, "o.ServicePointID = ?")>
<cfset arrayAppend(params, { value = ServicePointID, cfsqltype = "cf_sql_integer" })>
</cfif>
<!--- Only show submitted orders (StatusID >= 1) --->
<cfset arrayAppend(whereClauses, "o.StatusID >= 1")>
<!--- Don't show completed orders (assuming StatusID 4 is completed) --->
<cfset arrayAppend(whereClauses, "o.StatusID < 4")>
<cfset whereSQL = arrayToList(whereClauses, " AND ")>
<!--- If filtering by station, only get orders that have items for that station --->
<cfif StationID GT 0>
<cfset stationParams = duplicate(params)>
<cfset arrayAppend(stationParams, { value = StationID, cfsqltype = "cf_sql_integer" })>
<cfset qOrders = queryTimed("
SELECT DISTINCT
o.ID,
o.UUID,
o.UserID,
o.BusinessID,
o.OrderTypeID,
o.StatusID,
o.ServicePointID,
o.Remarks,
DATE_FORMAT(o.SubmittedOn, '%Y-%m-%dT%H:%i:%sZ') AS SubmittedOn,
DATE_FORMAT(o.LastEditedOn, '%Y-%m-%dT%H:%i:%sZ') AS LastEditedOn,
sp.Name AS Name,
u.FirstName,
u.LastName
FROM Orders o
LEFT JOIN ServicePoints sp ON sp.ID = o.ServicePointID
LEFT JOIN Users u ON u.ID = o.UserID
INNER JOIN OrderLineItems oli ON oli.OrderID = o.ID
INNER JOIN Items i ON i.ID = oli.ItemID
WHERE #whereSQL#
AND (i.StationID = ? OR i.StationID IS NULL)
AND oli.IsDeleted = b'0'
ORDER BY SubmittedOn ASC, o.ID ASC
", stationParams, { datasource = "payfrit" })>
<cfelse>
<cfset qOrders = queryTimed("
SELECT
o.ID,
o.UUID,
o.UserID,
o.BusinessID,
o.OrderTypeID,
o.StatusID,
o.ServicePointID,
o.Remarks,
DATE_FORMAT(o.SubmittedOn, '%Y-%m-%dT%H:%i:%sZ') AS SubmittedOn,
DATE_FORMAT(o.LastEditedOn, '%Y-%m-%dT%H:%i:%sZ') AS LastEditedOn,
sp.Name AS Name,
u.FirstName,
u.LastName
FROM Orders o
LEFT JOIN ServicePoints sp ON sp.ID = o.ServicePointID
LEFT JOIN Users u ON u.ID = o.UserID
WHERE #whereSQL#
ORDER BY o.SubmittedOn ASC, o.ID ASC
", params, { datasource = "payfrit" })>
</cfif>
<cfset orders = []>
<cfloop query="qOrders">
<!--- Get all line items for this order (frontend handles station filtering) --->
<cfset qLineItems = queryTimed("
SELECT
oli.ID,
oli.ParentOrderLineItemID,
oli.ItemID,
oli.Price,
oli.Quantity,
oli.Remark,
oli.IsDeleted,
oli.StatusID,
i.Name,
i.ParentItemID,
i.IsCheckedByDefault,
i.IsInvertedGroup,
i.StationID,
parent.Name AS ItemParentName,
COALESCE(parent.IsInvertedGroup, 0) AS ParentIsInvertedGroup
FROM OrderLineItems oli
INNER JOIN Items i ON i.ID = oli.ItemID
LEFT JOIN Items parent ON parent.ID = i.ParentItemID
WHERE oli.OrderID = ?
AND oli.IsDeleted = b'0'
ORDER BY oli.ID
", [ { value = qOrders.ID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
<cfset lineItems = []>
<cfloop query="qLineItems">
<cfset arrayAppend(lineItems, {
"OrderLineItemID": qLineItems.ID,
"ParentOrderLineItemID": qLineItems.ParentOrderLineItemID,
"ItemID": qLineItems.ItemID,
"Price": qLineItems.Price,
"Quantity": qLineItems.Quantity,
"Remark": qLineItems.Remark,
"Name": qLineItems.Name,
"ParentItemID": qLineItems.ParentItemID,
"ItemParentName": qLineItems.ItemParentName,
"IsCheckedByDefault": qLineItems.IsCheckedByDefault,
"IsInvertedGroup": qLineItems.IsInvertedGroup,
"ParentIsInvertedGroup": qLineItems.ParentIsInvertedGroup,
"StationID": qLineItems.StationID,
"StatusID": val(qLineItems.StatusID)
})>
</cfloop>
<!--- For inverted modifier groups, compute removed defaults --->
<!--- The inverted group header may not be an order line item itself,
so we detect inverted groups by finding children whose parent item is inverted --->
<cfset invertedGroupsSeen = {}>
<cfloop array="#lineItems#" index="li">
<cfif li.ParentIsInvertedGroup AND NOT structKeyExists(invertedGroupsSeen, li.ParentItemID)>
<!--- First child of this inverted group — compute removed defaults once --->
<cfset invertedGroupsSeen[li.ParentItemID] = true>
<cfset qRemovedDefaults = queryTimed("
SELECT i.Name
FROM Items i
WHERE i.ParentItemID = ?
AND i.IsActive = 1
AND i.IsCheckedByDefault = b'1'
AND i.ID NOT IN (
SELECT oli2.ItemID FROM OrderLineItems oli2
WHERE oli2.OrderID = ? AND oli2.ParentOrderLineItemID = ? AND oli2.IsDeleted = b'0'
)
ORDER BY i.SortOrder
", [
{ value = li.ParentItemID, cfsqltype = "cf_sql_integer" },
{ value = qOrders.ID, cfsqltype = "cf_sql_integer" },
{ value = li.ParentOrderLineItemID, cfsqltype = "cf_sql_integer" }
], { datasource = "payfrit" })>
<cfset removedNames = []>
<cfloop query="qRemovedDefaults">
<cfset arrayAppend(removedNames, qRemovedDefaults.Name)>
</cfloop>
<!--- Attach RemovedDefaults to the first child so the JS can find it --->
<cfset li["RemovedDefaults"] = removedNames>
<cfset li["IsInvertedGroupProxy"] = true>
</cfif>
</cfloop>
<!--- Determine order type name --->
<cfset orderTypeName = "">
<cfswitch expression="#qOrders.OrderTypeID#">
<cfcase value="1"><cfset orderTypeName = "Dine-In"></cfcase>
<cfcase value="2"><cfset orderTypeName = "Takeaway"></cfcase>
<cfcase value="3"><cfset orderTypeName = "Delivery"></cfcase>
<cfdefaultcase><cfset orderTypeName = ""></cfdefaultcase>
</cfswitch>
<cfset arrayAppend(orders, {
"OrderID": qOrders.ID,
"UUID": qOrders.UUID,
"UserID": qOrders.UserID,
"BusinessID": qOrders.BusinessID,
"OrderTypeID": qOrders.OrderTypeID,
"OrderTypeName": orderTypeName,
"StatusID": qOrders.StatusID,
"ServicePointID": qOrders.ServicePointID,
"Remarks": qOrders.Remarks,
"SubmittedOn": qOrders.SubmittedOn,
"LastEditedOn": qOrders.LastEditedOn,
"Name": qOrders.Name,
"FirstName": qOrders.FirstName,
"LastName": qOrders.LastName,
"LineItems": lineItems
})>
</cfloop>
<cfset apiAbort({
"OK": true,
"ERROR": "",
"ORDERS": orders,
"STATION_FILTER": StationID
})>
<cfcatch>
<cfset apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": "DB error loading orders for KDS",
"DETAIL": cfcatch.message
})>
</cfcatch>
</cftry>