- 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>
276 lines
12 KiB
Text
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>
|