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/submit.cfm
John Mizerek 39448c5d91 Fix prefixed column names in auth, orders, portal team, users search, workers APIs
Updated Users (UserID, UserFirstName, UserLastName, UserEmailAddress, UserContactNumber),
ServicePoints (ServicePointID, ServicePointName, ServicePointTypeID), and Businesses
(BusinessID, BusinessName, BusinessTaxRate, BusinessPhone) column references with proper
prefixed names and AS aliases for API compatibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 17:43:33 -08:00

276 lines
8.3 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<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>
<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="buildLineItemsGraph" access="public" returntype="struct" output="false">
<cfargument name="OrderID" type="numeric" required="true">
<cfset var out = {}>
<cfset out.items = {}> <!--- lineItemId -> struct --->
<cfset out.children = {}> <!--- parentLineItemId -> array(lineItemId) --->
<cfset out.itemMeta = {}> <!--- ItemID -> struct(meta) --->
<cfset var qLI = queryExecute(
"
SELECT
ID,
ParentOrderLineItemID,
ItemID,
IsDeleted
FROM OrderLineItems
WHERE ID = ?
ORDER BY ID
",
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qLI.recordCount EQ 0>
<cfreturn out>
</cfif>
<cfset var itemIds = []>
<cfloop query="qLI">
<cfset out.items[qLI.ID] = {
"id": qLI.ID,
"parentId": qLI.ParentOrderLineItemID,
"itemId": qLI.ItemID,
"isDeleted": (qLI.IsDeleted EQ true)
}>
<cfif NOT structKeyExists(out.children, qLI.ParentOrderLineItemID)>
<cfset out.children[qLI.ParentOrderLineItemID] = []>
</cfif>
<cfset arrayAppend(out.children[qLI.ParentOrderLineItemID], qLI.ID)>
<cfset arrayAppend(itemIds, qLI.ItemID)>
</cfloop>
<!--- Load meta for involved items --->
<cfset var uniq = {} >
<cfloop array="#itemIds#" index="iid">
<cfset uniq[iid] = true>
</cfloop>
<cfset var uniqIds = structKeyArray(uniq)>
<cfif arrayLen(uniqIds) GT 0>
<cfset var inList = arrayToList(uniqIds)>
<cfset var qMeta = queryExecute(
"
SELECT
ID,
RequiresChildSelection,
MaxNumSelectionReq
FROM Items
WHERE ID IN (#inList#)
",
[],
{ datasource = "payfrit" }
)>
<cfloop query="qMeta">
<cfset out.itemMeta[qMeta.ItemID] = {
"requires": qMeta.RequiresChildSelection,
"maxSel": qMeta.MaxNumSelectionReq
}>
</cfloop>
</cfif>
<cfreturn out>
</cffunction>
<cffunction name="hasSelectedDescendant" access="public" returntype="boolean" output="false">
<cfargument name="graph" type="struct" required="true">
<cfargument name="lineItemId" type="numeric" required="true">
<cfset var stack = []>
<cfif structKeyExists(arguments.graph.children, arguments.lineItemId)>
<cfset stack = duplicate(arguments.graph.children[arguments.lineItemId])>
</cfif>
<cfloop condition="arrayLen(stack) GT 0">
<cfset var id = stack[arrayLen(stack)]>
<cfset arrayDeleteAt(stack, arrayLen(stack))>
<cfif structKeyExists(arguments.graph.items, id)>
<cfset var node = arguments.graph.items[id]>
<cfif NOT node.isDeleted>
<cfreturn true>
</cfif>
<cfif structKeyExists(arguments.graph.children, id)>
<cfset var kids = arguments.graph.children[id]>
<cfloop array="#kids#" index="kidId">
<cfset arrayAppend(stack, kidId)>
</cfloop>
</cfif>
</cfif>
</cfloop>
<cfreturn false>
</cffunction>
<cfset data = readJsonBody()>
<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>
<cfif OrderID LTE 0>
<cfset apiAbort({ "OK": false, "ERROR": "missing_orderid", "MESSAGE": "OrderID is required.", "DETAIL": "" })>
</cfif>
<cftry>
<cfset qOrder = queryExecute(
"
SELECT o.ID, o.StatusID, o.OrderTypeID, o.BusinessID, o.ServicePointID,
sp.ServicePointName AS Name
FROM Orders o
LEFT JOIN ServicePoints sp ON sp.ServicePointID = o.ServicePointID
WHERE o.ID = ?
LIMIT 1
",
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qOrder.recordCount EQ 0>
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found.", "DETAIL": "" })>
</cfif>
<cfif qOrder.StatusID NEQ 0>
<cfset apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Order is not in cart state.", "DETAIL": "" })>
</cfif>
<!--- OrderTypeID: 1=dine-in, 2=takeaway, 3=delivery (0=undecided is invalid for submit) --->
<cfif qOrder.OrderTypeID LT 1 OR qOrder.OrderTypeID GT 3>
<cfset apiAbort({ "OK": false, "ERROR": "bad_type", "MESSAGE": "Order type must be set before submitting (1=dine-in, 2=takeaway, 3=delivery).", "DETAIL": "" })>
</cfif>
<!--- Delivery orders require an address --->
<cfif qOrder.OrderTypeID EQ 3>
<cfset qAddr = queryExecute(
"SELECT AddressID FROM Orders WHERE ID = ? LIMIT 1",
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qAddr.AddressID LTE 0 OR isNull(qAddr.AddressID)>
<cfset apiAbort({ "OK": false, "ERROR": "missing_address", "MESSAGE": "Delivery orders require a delivery address.", "DETAIL": "" })>
</cfif>
</cfif>
<!--- Must have at least one non-deleted root line item --->
<cfset qRoots = queryExecute(
"
SELECT COUNT(*) AS Cnt
FROM OrderLineItems
WHERE ID = ?
AND ParentOrderLineItemID = 0
AND IsDeleted = b'0'
",
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qRoots.Cnt LTE 0>
<cfset apiAbort({ "OK": false, "ERROR": "empty_order", "MESSAGE": "Order has no items.", "DETAIL": "" })>
</cfif>
<!--- Validate requires-descendant and max immediate selections --->
<cfset graph = buildLineItemsGraph(OrderID)>
<!--- Loop all non-deleted nodes and validate --->
<cfloop collection="#graph.items#" item="k">
<cfset node = graph.items[k]>
<cfif node.isDeleted>
<!--- skip deleted nodes --->
<cfcontinue>
</cfif>
<cfset meta = structKeyExists(graph.itemMeta, node.itemId) ? graph.itemMeta[node.itemId] : { "requires": 0, "maxSel": 0 }>
<!--- max immediate selections --->
<cfset maxSel = val(meta.maxSel)>
<cfif maxSel GT 0>
<cfset selCount = 0>
<cfif structKeyExists(graph.children, node.id)>
<cfset kids = graph.children[node.id]>
<cfloop array="#kids#" index="kidId">
<cfif structKeyExists(graph.items, kidId)>
<cfset kidNode = graph.items[kidId]>
<cfif NOT kidNode.isDeleted>
<cfset selCount = selCount + 1>
</cfif>
</cfif>
</cfloop>
</cfif>
<cfif selCount GT maxSel>
<cfset apiAbort({
"OK": false,
"ERROR": "max_selection_exceeded",
"MESSAGE": "Too many selections under a modifier group.",
"DETAIL": "LineItemID #node.id# has #selCount# immediate children selected; max is #maxSel#."
})>
</cfif>
</cfif>
<!--- requires descendant selection --->
<cfif val(meta.requires) EQ 1>
<cfif NOT hasSelectedDescendant(graph, node.id)>
<cfset apiAbort({
"OK": false,
"ERROR": "required_selection_missing",
"MESSAGE": "A required modifier selection is missing.",
"DETAIL": "LineItemID #node.id# requires at least one descendant selection."
})>
</cfif>
</cfif>
</cfloop>
<!--- Submit: mark submitted + status 1 --->
<cfset queryExecute(
"
UPDATE Orders
SET
StatusID = 1,
SubmittedOn = ?,
LastEditedOn = ?
WHERE ID = ?
",
[
{ value = now(), cfsqltype = "cf_sql_timestamp" },
{ value = now(), cfsqltype = "cf_sql_timestamp" },
{ value = OrderID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<cfset apiAbort({ "OK": true, "ERROR": "", "OrderID": OrderID, "MESSAGE": "submitted" })>
<cfcatch>
<cfset apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": "DB error submitting order",
"DETAIL": cfcatch.message
})>
</cfcatch>
</cftry>