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/setLineItem.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

556 lines
18 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="nextId" access="public" returntype="numeric" output="false">
<cfargument name="tableName" type="string" required="true">
<cfargument name="idField" type="string" required="true">
<cfset var q = queryExecute(
"SELECT IFNULL(MAX(#arguments.idField#),0) + 1 AS NextID FROM #arguments.tableName#",
[],
{ datasource = "payfrit" }
)>
<cfreturn q.NextID>
</cffunction>
<cffunction name="attachDefaultChildren" access="public" returntype="void" output="false">
<cfargument name="OrderID" type="numeric" required="true">
<cfargument name="ParentLineItemID" type="numeric" required="true">
<cfargument name="ParentItemID" type="numeric" required="true">
<!--- Find immediate children where checked by default --->
<cfset var qKids = queryExecute(
"
SELECT ID, Price
FROM Items
WHERE ParentItemID = ?
AND IsCheckedByDefault = 1
AND IsActive = 1
ORDER BY SortOrder, ItemID
",
[ { value = arguments.ParentItemID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<!--- Also find default children from templates linked to this item --->
<cfset var qTemplateKids = queryExecute(
"
SELECT i.ID, i.Price
FROM lt_ItemID_TemplateItemID tl
INNER JOIN Items i ON i.ParentItemID = tl.TemplateItemID
WHERE tl.ItemID = ?
AND i.IsCheckedByDefault = 1
AND i.IsActive = 1
ORDER BY i.SortOrder, i.ID
",
[ { value = arguments.ParentItemID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<!--- Store debug info in request scope for response --->
<cfif NOT structKeyExists(request, "attachDebug")>
<cfset request.attachDebug = []>
</cfif>
<cfset arrayAppend(request.attachDebug, "attachDefaultChildren: OrderID=#arguments.OrderID#, ParentLI=#arguments.ParentLineItemID#, ParentItemID=#arguments.ParentItemID#")>
<cfset arrayAppend(request.attachDebug, " qKids=#qKids.recordCount# rows, qTemplateKids=#qTemplateKids.recordCount# rows")>
<!--- Process direct children --->
<cfloop query="qKids">
<cfset arrayAppend(request.attachDebug, " -> direct child: ItemID=#qKids.ItemID#")>
<cfset processDefaultChild(arguments.OrderID, arguments.ParentLineItemID, qKids.ItemID, qKids.Price)>
</cfloop>
<!--- Process template children --->
<cfloop query="qTemplateKids">
<cfset arrayAppend(request.attachDebug, " -> template child: ItemID=#qTemplateKids.ItemID#")>
<cfset processDefaultChild(arguments.OrderID, arguments.ParentLineItemID, qTemplateKids.ItemID, qTemplateKids.Price)>
</cfloop>
</cffunction>
<cffunction name="processDefaultChild" access="public" returntype="void" output="false">
<cfargument name="OrderID" type="numeric" required="true">
<cfargument name="ParentLineItemID" type="numeric" required="true">
<cfargument name="ItemID" type="numeric" required="true">
<cfargument name="Price" type="numeric" required="true">
<!--- If existing, undelete; else insert new --->
<cfset var qExisting = queryExecute(
"
SELECT ID
FROM OrderLineItems
WHERE OrderID = ?
AND ParentOrderLineItemID = ?
AND ItemID = ?
LIMIT 1
",
[
{ value = arguments.OrderID, cfsqltype = "cf_sql_integer" },
{ value = arguments.ParentLineItemID, cfsqltype = "cf_sql_integer" },
{ value = arguments.ItemID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<cfif qExisting.recordCount GT 0>
<cfset queryExecute(
"
UPDATE OrderLineItems
SET IsDeleted = b'0'
WHERE OrderLineItemID = ?
",
[ { value = qExisting.OrderLineItemID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfset attachDefaultChildren(arguments.OrderID, qExisting.OrderLineItemID, arguments.ItemID)>
<cfelse>
<cfset var NewLIID = nextId("OrderLineItems","OrderLineItemID")>
<cfset queryExecute(
"
INSERT INTO OrderLineItems (
OrderLineItemID,
ParentOrderLineItemID,
OrderID,
ItemID,
StatusID,
Price,
Quantity,
Remark,
IsDeleted,
AddedOn
) VALUES (
?,
?,
?,
?,
0,
?,
1,
NULL,
b'0',
?
)
",
[
{ value = NewLIID, cfsqltype = "cf_sql_integer" },
{ value = arguments.ParentLineItemID, cfsqltype = "cf_sql_integer" },
{ value = arguments.OrderID, cfsqltype = "cf_sql_integer" },
{ value = arguments.ItemID, cfsqltype = "cf_sql_integer" },
{ value = arguments.Price, cfsqltype = "cf_sql_decimal" },
{ value = now(), cfsqltype = "cf_sql_timestamp" }
],
{ datasource = "payfrit" }
)>
<cfset attachDefaultChildren(arguments.OrderID, NewLIID, arguments.ItemID)>
</cfif>
</cffunction>
<cffunction name="loadCartPayload" access="public" returntype="struct" output="false">
<cfargument name="OrderID" type="numeric" required="true">
<cfset var out = {}>
<cfset var qOrder = queryExecute(
"
SELECT
o.ID,
o.UUID,
o.UserID,
o.BusinessID,
o.DeliveryMultiplier,
o.OrderTypeID,
o.DeliveryFee,
o.StatusID,
o.AddressID,
o.PaymentID,
o.Remarks,
o.AddedOn,
o.LastEditedOn,
o.SubmittedOn,
o.ServicePointID,
COALESCE(b.DeliveryFlatFee, 0) AS BusinessDeliveryFee
FROM Orders o
LEFT JOIN Businesses b ON b.ID = o.BusinessID
WHERE o.ID = ?
LIMIT 1
",
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qOrder.recordCount EQ 0>
<cfreturn { "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found", "DETAIL": "" }>
</cfif>
<cfset out.ORDER = {
"OrderID": val(qOrder.ID),
"UUID": qOrder.UUID ?: "",
"UserID": val(qOrder.UserID),
"BusinessID": val(qOrder.BusinessID),
"DeliveryMultiplier": val(qOrder.DeliveryMultiplier),
"OrderTypeID": val(qOrder.OrderTypeID),
"DeliveryFee": val(qOrder.DeliveryFee),
"StatusID": val(qOrder.StatusID),
"AddressID": val(qOrder.AddressID),
"PaymentID": val(qOrder.PaymentID),
"Remarks": qOrder.Remarks ?: "",
"AddedOn": qOrder.AddedOn,
"LastEditedOn": qOrder.LastEditedOn,
"SubmittedOn": qOrder.SubmittedOn,
"ServicePointID": val(qOrder.ServicePointID),
"BusinessDeliveryFee": val(qOrder.BusinessDeliveryFee)
}>
<cfset var qLI = queryExecute(
"
SELECT
oli.ID,
oli.ParentOrderLineItemID,
oli.OrderID,
oli.ItemID,
oli.StatusID,
oli.Price,
oli.Quantity,
oli.Remark,
oli.IsDeleted,
oli.AddedOn,
i.Name,
i.ParentItemID,
i.IsCheckedByDefault,
parent.Name AS ItemParentName
FROM OrderLineItems oli
INNER JOIN Items i ON i.ID = oli.ItemID
LEFT JOIN Items parent ON parent.ItemID = i.ParentItemID
WHERE oli.OrderID = ?
ORDER BY oli.ID
",
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfset var rows = []>
<cfloop query="qLI">
<cfset arrayAppend(rows, {
"OrderLineItemID": val(qLI.OrderLineItemID),
"ParentOrderLineItemID": val(qLI.ParentOrderLineItemID),
"OrderID": val(qLI.OrderID),
"ItemID": val(qLI.ItemID),
"StatusID": val(qLI.StatusID),
"Price": val(qLI.Price),
"Quantity": val(qLI.Quantity),
"Remark": qLI.Remark ?: "",
"IsDeleted": val(qLI.IsDeleted),
"AddedOn": qLI.AddedOn,
"Name": qLI.Name ?: "",
"ParentItemID": val(qLI.ParentItemID),
"ItemParentName": qLI.ItemParentName ?: "",
"IsCheckedByDefault": val(qLI.IsCheckedByDefault)
})>
</cfloop>
<cfset out.ORDERLINEITEMS = rows>
<cfset out.OK = true>
<cfset out.ERROR = "">
<cfreturn out>
</cffunction>
<cfset data = readJsonBody()>
<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>
<cfset ParentLineItemID = val( structKeyExists(data,"ParentOrderLineItemID") ? data.ParentOrderLineItemID : 0 )>
<cfset OriginalItemID = structKeyExists(data,"ItemID") ? data.ItemID : 0>
<cfset ItemID = val(OriginalItemID)>
<!--- Decode virtual IDs from menu API (format: menuItemID * 100000 + realItemID) --->
<!--- If ItemID > 100000, extract the real ItemID --->
<cfset WasDecoded = false>
<cfif ItemID GT 100000>
<cfset WasDecoded = true>
<cfset ItemID = ItemID MOD 100000>
</cfif>
<!--- Store debug info for response --->
<cfset request.itemDebug = {
"OriginalItemID": OriginalItemID,
"ParsedItemID": val(OriginalItemID),
"WasDecoded": WasDecoded,
"FinalItemID": ItemID
}>
<cfset IsSelected = false>
<cfif structKeyExists(data, "IsSelected")>
<cfset IsSelected = (data.IsSelected EQ true OR data.IsSelected EQ 1 OR (isSimpleValue(data.IsSelected) AND lcase(toString(data.IsSelected)) EQ "true"))>
</cfif>
<cfset Quantity = structKeyExists(data,"Quantity") ? val(data.Quantity) : 0>
<cfset Remark = structKeyExists(data,"Remark") ? toString(data.Remark) : "">
<cfset ForceNew = false>
<cfif structKeyExists(data, "ForceNew")>
<cfset ForceNew = (data.ForceNew EQ true OR data.ForceNew EQ 1 OR (isSimpleValue(data.ForceNew) AND lcase(toString(data.ForceNew)) EQ "true"))>
</cfif>
<cfif OrderID LTE 0 OR ItemID LTE 0>
<cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "OrderID and ItemID are required.", "DETAIL": "" })>
</cfif>
<cftry>
<!--- Load item price --->
<cfset qItem = queryExecute(
"
SELECT ID, Price, ParentItemID, IsActive
FROM Items
WHERE ID = ?
LIMIT 1
",
[ { value = ItemID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qItem.recordCount EQ 0 OR qItem.IsActive NEQ 1>
<cfset apiAbort({
"OK": false,
"ERROR": "bad_item",
"MESSAGE": "Item not found or inactive. Original=#OriginalItemID# Decoded=#ItemID# WasDecoded=#WasDecoded#",
"DETAIL": "",
"DEBUG_ITEM": request.itemDebug
})>
</cfif>
<!--- Root vs modifier rules --->
<cfif ParentLineItemID EQ 0>
<!--- Root item quantity required when selecting --->
<cfif IsSelected AND Quantity LTE 0>
<cfset apiAbort({ "OK": false, "ERROR": "bad_quantity", "MESSAGE": "Root line items require Quantity > 0.", "DETAIL": "" })>
</cfif>
<cfelse>
<!--- Modifier quantity is implicitly tied => force 1 when selecting --->
<cfif IsSelected>
<cfset Quantity = 1>
<cfelse>
<cfset Quantity = 1>
</cfif>
<!--- Check if this is an exclusive selection group (max = 1) --->
<!--- If so, deselect siblings when selecting a new item --->
<cfif IsSelected>
<!--- Get the parent line item's ItemID to check maxSel --->
<cfset qParentLI = queryExecute(
"SELECT ItemID FROM OrderLineItems WHERE OrderLineItemID = ? LIMIT 1",
[ { value = ParentLineItemID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qParentLI.recordCount GT 0>
<!--- Check if this parent item has maxSel = 1 (exclusive selection) --->
<cfset qParentItem = queryExecute(
"SELECT MaxNumSelectionReq FROM Items WHERE ID = ? LIMIT 1",
[ { value = qParentLI.ItemID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qParentItem.recordCount GT 0 AND val(qParentItem.MaxNumSelectionReq) EQ 1>
<!--- Exclusive selection: deselect all other siblings under this parent --->
<cfset queryExecute(
"
UPDATE OrderLineItems
SET IsDeleted = b'1'
WHERE OrderID = ?
AND ParentOrderLineItemID = ?
AND ItemID != ?
AND IsDeleted = b'0'
",
[
{ value = OrderID, cfsqltype = "cf_sql_integer" },
{ value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
{ value = ItemID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
</cfif>
</cfif>
</cfif>
</cfif>
<!--- Find existing line item (by order, parent LI, item) - unless ForceNew is set --->
<cfif ForceNew>
<!--- ForceNew: Skip existing lookup, will always create new line item --->
<cfset qExisting = queryNew("OrderLineItemID", "integer")>
<cfelse>
<cfset qExisting = queryExecute(
"
SELECT ID
FROM OrderLineItems
WHERE OrderID = ?
AND ParentOrderLineItemID = ?
AND ItemID = ?
LIMIT 1
",
[
{ value = OrderID, cfsqltype = "cf_sql_integer" },
{ value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
{ value = ItemID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
</cfif>
<!--- Initialize debug array at start of processing --->
<cfset request.attachDebug = ["Flow start: qExisting.recordCount=#qExisting.recordCount#, IsSelected=#IsSelected#"]>
<cfif qExisting.recordCount GT 0>
<!--- Update existing --->
<cfset arrayAppend(request.attachDebug, "Path: update existing")>
<cfif IsSelected>
<cfset queryExecute(
"
UPDATE OrderLineItems
SET
IsDeleted = b'0',
Quantity = ?,
Price = ?,
Remark = ?,
StatusID = 0
WHERE OrderLineItemID = ?
",
[
{ value = Quantity, cfsqltype = "cf_sql_integer" },
{ value = qItem.Price, cfsqltype = "cf_sql_decimal" },
{ value = Remark, cfsqltype = "cf_sql_varchar", null = (len(trim(Remark)) EQ 0) },
{ value = qExisting.OrderLineItemID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<!--- Attach default children for this node (recursively) --->
<cfif NOT structKeyExists(request, "attachDebug")>
<cfset request.attachDebug = []>
</cfif>
<cfset arrayAppend(request.attachDebug, "BEFORE attachDefaultChildren call: OrderID=#OrderID#, LIID=#qExisting.OrderLineItemID#, ItemID=#ItemID#")>
<cfset attachDefaultChildren(OrderID, qExisting.OrderLineItemID, ItemID)>
<cfset arrayAppend(request.attachDebug, "AFTER attachDefaultChildren call")>
<cfelse>
<cfset queryExecute(
"
UPDATE OrderLineItems
SET IsDeleted = b'1'
WHERE OrderLineItemID = ?
",
[ { value = qExisting.OrderLineItemID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
</cfif>
<cfelse>
<!--- Insert new if selecting, otherwise no-op --->
<cfif IsSelected>
<cfset NewLIID = nextId("OrderLineItems","OrderLineItemID")>
<cfset queryExecute(
"
INSERT INTO OrderLineItems (
OrderLineItemID,
ParentOrderLineItemID,
OrderID,
ItemID,
StatusID,
Price,
Quantity,
Remark,
IsDeleted,
AddedOn
) VALUES (
?,
?,
?,
?,
0,
?,
?,
?,
b'0',
?
)
",
[
{ value = NewLIID, cfsqltype = "cf_sql_integer" },
{ value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
{ value = OrderID, cfsqltype = "cf_sql_integer" },
{ value = ItemID, cfsqltype = "cf_sql_integer" },
{ value = qItem.Price, cfsqltype = "cf_sql_decimal" },
{ value = (ParentLineItemID EQ 0 ? Quantity : 1), cfsqltype = "cf_sql_integer" },
{ value = Remark, cfsqltype = "cf_sql_varchar", null = (len(trim(Remark)) EQ 0) },
{ value = now(), cfsqltype = "cf_sql_timestamp" }
],
{ datasource = "payfrit" }
)>
<cfset attachDefaultChildren(OrderID, NewLIID, ItemID)>
</cfif>
</cfif>
<!--- Touch order last edited --->
<cftry>
<cfset queryExecute(
"UPDATE Orders SET LastEditedOn = ? WHERE OrderID = ?",
[
{ value = now(), cfsqltype = "cf_sql_timestamp" },
{ value = OrderID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<cfcatch>
<cfset apiAbort({
"OK": false,
"ERROR": "update_order_error",
"MESSAGE": "Error updating order timestamp: " & cfcatch.message,
"DETAIL": cfcatch.tagContext[1].template & ":" & cfcatch.tagContext[1].line
})>
</cfcatch>
</cftry>
<cftry>
<cfset payload = loadCartPayload(OrderID)>
<!--- Add debug info to response --->
<cfif structKeyExists(request, "attachDebug")>
<cfset payload["DEBUG_ATTACH"] = request.attachDebug>
</cfif>
<cfset apiAbort(payload)>
<cfcatch>
<cfset apiAbort({
"OK": false,
"ERROR": "load_cart_error",
"MESSAGE": "Error loading cart: " & cfcatch.message & " | " & (structKeyExists(cfcatch, "detail") ? cfcatch.detail : ""),
"DETAIL": cfcatch.tagContext[1].template & ":" & cfcatch.tagContext[1].line
})>
</cfcatch>
</cftry>
<cfcatch>
<cfset apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": "DB error setting line item: " & cfcatch.message & " | " & (structKeyExists(cfcatch, "detail") ? cfcatch.detail : "") & " | " & (structKeyExists(cfcatch, "sql") ? cfcatch.sql : ""),
"DETAIL": cfcatch.tagContext[1].template & ":" & cfcatch.tagContext[1].line
})>
</cfcatch>
</cftry>