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

332 lines
9.7 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="loadCartPayload" access="public" returntype="struct" output="false">
<cfargument name="OrderID" type="numeric" required="true">
<cfset var out = {}>
<cfset var qOrder = queryExecute(
"
SELECT
ID,
UUID,
UserID,
BusinessID,
DeliveryMultiplier,
OrderTypeID,
DeliveryFee,
StatusID,
AddressID,
PaymentID,
Remarks,
AddedOn,
LastEditedOn,
SubmittedOn,
ServicePointID
FROM Orders
WHERE 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>
<!--- Get business delivery fee for display in cart --->
<cfset var qBusiness = queryExecute(
"SELECT DeliveryFlatFee FROM Businesses WHERE ID = ? LIMIT 1",
[ { value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfset var businessDeliveryFee = qBusiness.recordCount GT 0 ? qBusiness.DeliveryFlatFee : 0>
<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),
"BusinessDeliveryFee": val(businessDeliveryFee),
"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)
}>
<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.ID = 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.ID),
"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 BusinessID = val( structKeyExists(data,"BusinessID") ? data.BusinessID : 0 )>
<cfset ServicePointID = val( structKeyExists(data,"ServicePointID") ? data.ServicePointID : 0 )>
<cfset OrderTypeID = val( structKeyExists(data,"OrderTypeID") ? data.OrderTypeID : 0 )>
<cfset UserID = val( structKeyExists(data,"UserID") ? data.UserID : 0 )>
<!--- OrderTypeID: 0=undecided, 1=dine-in, 2=takeaway, 3=delivery --->
<cfif BusinessID LTE 0 OR UserID LTE 0>
<cfset apiAbort({
"OK": false,
"ERROR": "missing_params",
"MESSAGE": "BusinessID and UserID are required.",
"DETAIL": ""
})>
</cfif>
<!--- OrderTypeID can be 0 (undecided) for delivery/takeaway flow, or 1 for dine-in --->
<cfif OrderTypeID LT 0 OR OrderTypeID GT 3>
<cfset apiAbort({
"OK": false,
"ERROR": "invalid_order_type",
"MESSAGE": "OrderTypeID must be 0-3 (0=undecided, 1=dine-in, 2=takeaway, 3=delivery).",
"DETAIL": ""
})>
</cfif>
<cftry>
<!--- Find existing cart (StatusID=0 assumed cart) --->
<!--- Look for any active cart for this user/business - order type can be changed later --->
<cfset qFind = queryExecute(
"
SELECT ID, OrderTypeID
FROM Orders
WHERE UserID = ?
AND BusinessID = ?
AND StatusID = 0
ORDER BY ID DESC
LIMIT 1
",
[
{ value = UserID, cfsqltype = "cf_sql_integer" },
{ value = BusinessID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<cfif qFind.recordCount GT 0>
<!--- Always update the service point to match the current table/beacon --->
<cfif ServicePointID GT 0>
<cfset queryExecute(
"UPDATE Orders SET ServicePointID = ?, LastEditedOn = ? WHERE ID = ?",
[
{ value = ServicePointID, cfsqltype = "cf_sql_integer" },
{ value = now(), cfsqltype = "cf_sql_timestamp" },
{ value = qFind.ID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
</cfif>
<!--- Check if cart order type differs from requested and cart is empty --->
<!--- If so, update the cart's order type to match the new flow --->
<cfif qFind.OrderTypeID NEQ OrderTypeID>
<cfset qLineItems = queryExecute(
"SELECT COUNT(*) AS ItemCount FROM OrderLineItems WHERE OrderID = ? AND IsDeleted = 0",
[ { value = qFind.ID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<!--- Only update order type if cart is empty (allows switching flows) --->
<cfif qLineItems.ItemCount EQ 0>
<cfset queryExecute(
"UPDATE Orders SET OrderTypeID = ?, LastEditedOn = ? WHERE ID = ?",
[
{ value = OrderTypeID, cfsqltype = "cf_sql_integer" },
{ value = now(), cfsqltype = "cf_sql_timestamp" },
{ value = qFind.ID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
</cfif>
</cfif>
<cfset payload = loadCartPayload(qFind.ID)>
<cfset apiAbort(payload)>
</cfif>
<!--- Create new cart order --->
<cfset qBiz = queryExecute(
"
SELECT DeliveryMultiplier, DeliveryFlatFee
FROM Businesses
WHERE ID = ?
LIMIT 1
",
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qBiz.recordCount EQ 0>
<cfset apiAbort({ "OK": false, "ERROR": "bad_business", "MESSAGE": "Business not found", "DETAIL": "" })>
</cfif>
<cfset nowDt = now()>
<cfset newUUID = createUUID()>
<!--- Calculate delivery fee: only for delivery orders (OrderTypeID = 3)
OrderTypeID: 0=undecided, 1=dine-in, 2=takeaway, 3=delivery
Only delivery (3) should have delivery fee.
Note: For undecided orders (0), fee is set later via setOrderType.cfm --->
<cfset deliveryFee = (OrderTypeID EQ 3) ? qBiz.DeliveryFlatFee : 0>
<!--- Generate new OrderID (table is not auto-inc in SSOT) --->
<cfset qNext = queryExecute(
"SELECT IFNULL(MAX(ID),0) + 1 AS NextID FROM Orders",
[],
{ datasource = "payfrit" }
)>
<cfset NewOrderID = qNext.NextID>
<cfset queryExecute(
"
INSERT INTO Orders (
ID,
UUID,
UserID,
BusinessID,
DeliveryMultiplier,
OrderTypeID,
DeliveryFee,
StatusID,
AddressID,
PaymentID,
Remarks,
AddedOn,
LastEditedOn,
SubmittedOn,
ServicePointID
) VALUES (
?,
?,
?,
?,
?,
?,
?,
0,
NULL,
NULL,
NULL,
?,
?,
NULL,
?
)
",
[
{ value = NewOrderID, cfsqltype = "cf_sql_integer" },
{ value = newUUID, cfsqltype = "cf_sql_varchar" },
{ value = UserID, cfsqltype = "cf_sql_integer" },
{ value = BusinessID, cfsqltype = "cf_sql_integer" },
{ value = qBiz.DeliveryMultiplier, cfsqltype = "cf_sql_decimal" },
{ value = OrderTypeID, cfsqltype = "cf_sql_integer" },
{ value = deliveryFee, cfsqltype = "cf_sql_decimal" },
{ value = nowDt, cfsqltype = "cf_sql_timestamp" },
{ value = nowDt, cfsqltype = "cf_sql_timestamp" },
{ value = ServicePointID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<!--- Per your rule: OrderID is determined by selecting highest after creation --->
<cfset qLatest = queryExecute(
"SELECT MAX(ID) AS NextID FROM Orders",
[],
{ datasource = "payfrit" }
)>
<cfset FinalOrderID = qLatest.NextID>
<cfset payload = loadCartPayload(FinalOrderID)>
<cfset apiAbort(payload)>
<cfcatch>
<cfset apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": "DB error creating cart",
"DETAIL": cfcatch.message
})>
</cfcatch>
</cftry>