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 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

276 lines
8.4 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 OrderID = ?
ORDER BY OrderLineItemID
",
[ { 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.OrderLineItemID] = {
"id": qLI.OrderLineItemID,
"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.OrderLineItemID)>
<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.Name
FROM Orders o
LEFT JOIN ServicePoints sp ON sp.ID = 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 OrderID = ?
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 OrderID = ?
",
[
{ 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>