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/menu/menus.cfm
John Mizerek 31a89018f5 Launch prep: fix menu builder, payment flow, comment out pre-launch features
- Fix menu builder dropdown showing empty names (return MenuName instead of Name)
- Add default menu selection (setDefault action, DefaultMenuID in getForBuilder)
- Fix createPaymentIntent column names for dev schema (ID, StripeAccountID, etc.)
- Fix menu-builder favicon and remove redundant business label
- Comment out Tabs/Running Checks feature for launch (HTML + JS)
- Comment out Service Point Marketing/Grants feature for launch (HTML + JS)
- Add testMarkPaid.cfm for testing orders without Stripe webhooks
- Task API updates for worker payout ledger integration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:18:33 -08:00

276 lines
12 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfscript>
/**
* Menu CRUD API
*
* GET: List all menus for a business
* POST: Create or update a menu
* DELETE: Soft-delete a menu
*
* Input: BusinessID, and optionally Menu data for POST
*/
response = { "OK": false };
try {
requestBody = toString(getHttpRequestData().content);
requestData = {};
if (len(requestBody)) {
requestData = deserializeJSON(requestBody);
}
businessID = 0;
if (structKeyExists(requestData, "BusinessID")) {
businessID = val(requestData.BusinessID);
}
if (businessID == 0) {
apiAbort({ "OK": false, "ERROR": "missing_business_id", "MESSAGE": "BusinessID is required" });
}
method = cgi.REQUEST_METHOD;
action = structKeyExists(requestData, "action") ? lCase(requestData.action) : "list";
// Handle different actions
switch (action) {
case "list":
// Get all active menus for this business
qMenus = queryTimed("
SELECT
ID,
Name,
Description,
DaysActive,
StartTime,
EndTime,
SortOrder,
IsActive
FROM Menus
WHERE BusinessID = :businessID
AND IsActive = 1
ORDER BY SortOrder, Name
", { businessID: businessID }, { datasource: "payfrit" });
menus = [];
for (i = 1; i <= qMenus.recordCount; i++) {
// Count categories in this menu
qCatCount = queryTimed("
SELECT COUNT(*) as cnt FROM Categories
WHERE BusinessID = :businessID
AND MenuID = :menuID
", { businessID: businessID, menuID: qMenus.ID[i] }, { datasource: "payfrit" });
arrayAppend(menus, {
"MenuID": qMenus.ID[i],
"Name": qMenus.Name[i],
"Description": isNull(qMenus.Description[i]) ? "" : qMenus.Description[i],
"DaysActive": qMenus.DaysActive[i],
"StartTime": isNull(qMenus.StartTime[i]) ? "" : timeFormat(qMenus.StartTime[i], "HH:mm"),
"EndTime": isNull(qMenus.EndTime[i]) ? "" : timeFormat(qMenus.EndTime[i], "HH:mm"),
"SortOrder": qMenus.SortOrder[i],
"CategoryCount": qCatCount.cnt
});
}
response = {
"OK": true,
"MENUS": menus,
"COUNT": arrayLen(menus)
};
break;
case "get":
// Get a single menu by ID
menuID = structKeyExists(requestData, "MenuID") ? val(requestData.MenuID) : 0;
if (menuID == 0) {
apiAbort({ "OK": false, "ERROR": "missing_menu_id", "MESSAGE": "MenuID is required" });
}
qMenu = queryTimed("
SELECT * FROM Menus
WHERE ID = :menuID AND BusinessID = :businessID
", { menuID: menuID, businessID: businessID }, { datasource: "payfrit" });
if (qMenu.recordCount == 0) {
apiAbort({ "OK": false, "ERROR": "menu_not_found", "MESSAGE": "Menu not found" });
}
response = {
"OK": true,
"MENU": {
"MenuID": qMenu.ID,
"Name": qMenu.Name,
"Description": isNull(qMenu.Description) ? "" : qMenu.Description,
"DaysActive": qMenu.DaysActive,
"StartTime": isNull(qMenu.StartTime) ? "" : timeFormat(qMenu.StartTime, "HH:mm"),
"EndTime": isNull(qMenu.EndTime) ? "" : timeFormat(qMenu.EndTime, "HH:mm"),
"SortOrder": qMenu.SortOrder,
"IsActive": qMenu.IsActive
}
};
break;
case "save":
// Create or update a menu
menuID = structKeyExists(requestData, "MenuID") ? val(requestData.MenuID) : 0;
menuName = structKeyExists(requestData, "Name") ? trim(requestData.Name) : "";
menuDescription = structKeyExists(requestData, "Description") ? trim(requestData.Description) : "";
menuDaysActive = structKeyExists(requestData, "DaysActive") ? val(requestData.DaysActive) : 127;
menuStartTime = structKeyExists(requestData, "StartTime") && len(trim(requestData.StartTime)) ? trim(requestData.StartTime) : "";
menuEndTime = structKeyExists(requestData, "EndTime") && len(trim(requestData.EndTime)) ? trim(requestData.EndTime) : "";
menuSortOrder = structKeyExists(requestData, "SortOrder") ? val(requestData.SortOrder) : 0;
if (len(menuName) == 0) {
apiAbort({ "OK": false, "ERROR": "missing_menu_name", "MESSAGE": "Menu name is required" });
}
if (menuID > 0) {
// Update existing menu
queryTimed("
UPDATE Menus SET
Name = :menuName,
Description = :menuDescription,
DaysActive = :menuDaysActive,
StartTime = :menuStartTime,
EndTime = :menuEndTime,
SortOrder = :menuSortOrder
WHERE ID = :menuID AND BusinessID = :businessID
", {
menuID: { value: menuID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
menuName: { value: menuName, cfsqltype: "cf_sql_varchar" },
menuDescription: { value: menuDescription, cfsqltype: "cf_sql_varchar" },
menuDaysActive: { value: menuDaysActive, cfsqltype: "cf_sql_integer" },
menuStartTime: { value: menuStartTime, cfsqltype: "cf_sql_time", null: !len(menuStartTime) },
menuEndTime: { value: menuEndTime, cfsqltype: "cf_sql_time", null: !len(menuEndTime) },
menuSortOrder: { value: menuSortOrder, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
response = { "OK": true, "MenuID": menuID, "ACTION": "updated" };
} else {
// Create new menu
queryTimed("
INSERT INTO Menus (
BusinessID, Name, Description,
DaysActive, StartTime, EndTime,
SortOrder, IsActive, AddedOn
) VALUES (
:businessID, :menuName, :menuDescription,
:menuDaysActive, :menuStartTime, :menuEndTime,
:menuSortOrder, 1, NOW()
)
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
menuName: { value: menuName, cfsqltype: "cf_sql_varchar" },
menuDescription: { value: menuDescription, cfsqltype: "cf_sql_varchar" },
menuDaysActive: { value: menuDaysActive, cfsqltype: "cf_sql_integer" },
menuStartTime: { value: menuStartTime, cfsqltype: "cf_sql_time", null: !len(menuStartTime) },
menuEndTime: { value: menuEndTime, cfsqltype: "cf_sql_time", null: !len(menuEndTime) },
menuSortOrder: { value: menuSortOrder, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
result = queryTimed("SELECT LAST_INSERT_ID() as newID", {}, { datasource: "payfrit" });
response = { "OK": true, "MenuID": result.newID, "ACTION": "created" };
}
break;
case "delete":
// Soft-delete a menu
menuID = structKeyExists(requestData, "MenuID") ? val(requestData.MenuID) : 0;
if (menuID == 0) {
apiAbort({ "OK": false, "ERROR": "missing_menu_id", "MESSAGE": "MenuID is required" });
}
// Check if menu has categories/items for warning
qCatCheck = queryTimed("
SELECT COUNT(*) as cnt FROM Categories
WHERE MenuID = :menuID AND BusinessID = :businessID
", { menuID: menuID, businessID: businessID }, { datasource: "payfrit" });
// Unassign categories from this menu
if (qCatCheck.cnt > 0) {
queryTimed("
UPDATE Categories SET MenuID = 0
WHERE MenuID = :menuID AND BusinessID = :businessID
", { menuID: menuID, businessID: businessID }, { datasource: "payfrit" });
}
// Soft-delete the menu
queryTimed("
UPDATE Menus SET IsActive = 0
WHERE ID = :menuID AND BusinessID = :businessID
", { menuID: menuID, businessID: businessID }, { datasource: "payfrit" });
response = {
"OK": true,
"MenuID": menuID,
"ACTION": "deleted",
"CategoriesUnassigned": qCatCheck.cnt
};
break;
case "reorder":
// Reorder menus
menuOrder = structKeyExists(requestData, "MenuOrder") ? requestData.MenuOrder : [];
if (!isArray(menuOrder) || arrayLen(menuOrder) == 0) {
apiAbort({ "OK": false, "ERROR": "missing_menu_order", "MESSAGE": "MenuOrder array is required" });
}
for (i = 1; i <= arrayLen(menuOrder); i++) {
queryTimed("
UPDATE Menus SET SortOrder = :sortOrder
WHERE ID = :menuID AND BusinessID = :businessID
", {
menuID: val(menuOrder[i]),
businessID: businessID,
sortOrder: i - 1
}, { datasource: "payfrit" });
}
response = { "OK": true, "ACTION": "reordered" };
break;
case "setDefault":
// Set the default menu for this business
menuID = structKeyExists(requestData, "MenuID") ? val(requestData.MenuID) : 0;
// MenuID of 0 means "no default" (clear the default)
if (menuID > 0) {
// Verify the menu exists and belongs to this business
qCheck = queryTimed("
SELECT ID FROM Menus WHERE ID = :menuID AND BusinessID = :businessID AND IsActive = 1
", { menuID: menuID, businessID: businessID }, { datasource: "payfrit" });
if (qCheck.recordCount == 0) {
apiAbort({ "OK": false, "ERROR": "invalid_menu", "MESSAGE": "Menu not found or not active" });
}
}
queryTimed("
UPDATE Businesses SET DefaultMenuID = :menuID WHERE ID = :businessID
", {
menuID: { value: menuID, cfsqltype: "cf_sql_integer", null: menuID == 0 },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
response = { "OK": true, "ACTION": "defaultSet", "DefaultMenuID": menuID };
break;
default:
apiAbort({ "OK": false, "ERROR": "invalid_action", "MESSAGE": "Unknown action: " & action });
}
} catch (any e) {
response["ERROR"] = "server_error";
response["MESSAGE"] = e.message;
response["DETAIL"] = e.detail ?: "";
}
try{logPerf(0);}catch(any e){}
writeOutput(serializeJSON(response));
</cfscript>