payfrit-works/api/menu/getForBuilder.cfm
John Mizerek a4fb7e7503 Show all templates in menu builder regardless of links
- Changed template query to find ALL templates for business (ItemCategoryID=0, ItemParentItemID=0)
- Previously only showed templates that were in ItemTemplateLinks
- Now shows all templates so they can be viewed and manually assigned to items
- Template children query also updated to include children of all templates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-15 15:17:08 -08:00

433 lines
16 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>
/**
* Get Menu for Builder
* Returns categories and items in structured format for the menu builder UI
*
* POST: { BusinessID: int }
*
* Unified schema:
* - Categories = Items at ParentID=0 that have menu items as children
* - Templates = Items at ParentID=0 that appear in ItemTemplateLinks
* - Menu items have ItemParentItemID pointing to their category
* - All items have ItemBusinessID for filtering
*/
response = { "OK": false };
try {
// Get request body
requestBody = toString(getHttpRequestData().content);
requestData = {};
if (len(requestBody)) {
requestData = deserializeJSON(requestBody);
}
businessID = 0;
if (structKeyExists(requestData, "BusinessID")) {
businessID = val(requestData.BusinessID);
}
if (businessID == 0) {
response["ERROR"] = "missing_business_id";
response["MESSAGE"] = "BusinessID is required";
writeOutput(serializeJSON(response));
abort;
}
// Check if new schema is active (ItemBusinessID column exists and has data)
newSchemaActive = false;
try {
qCheck = queryExecute("
SELECT COUNT(*) as cnt FROM Items
WHERE ItemBusinessID = :businessID AND ItemBusinessID > 0
", { businessID: businessID }, { datasource: "payfrit" });
newSchemaActive = (qCheck.cnt > 0);
} catch (any e) {
newSchemaActive = false;
}
if (newSchemaActive) {
// NEW SCHEMA: Check if Categories table has data for this business
hasCategoriesData = false;
try {
qCatCheck = queryExecute("
SELECT COUNT(*) as cnt FROM Categories WHERE CategoryBusinessID = :businessID
", { businessID: businessID }, { datasource: "payfrit" });
hasCategoriesData = (qCatCheck.cnt > 0);
} catch (any e) {
hasCategoriesData = false;
}
if (hasCategoriesData) {
// Use Categories table
qCategories = queryExecute("
SELECT
CategoryID,
CategoryName,
CategorySortOrder as ItemSortOrder
FROM Categories
WHERE CategoryBusinessID = :businessID
ORDER BY CategorySortOrder, CategoryName
", { businessID: businessID }, { datasource: "payfrit" });
// Get menu items with CategoryID
qItems = queryExecute("
SELECT
i.ItemID,
i.ItemCategoryID as CategoryItemID,
i.ItemName,
i.ItemDescription,
i.ItemPrice,
i.ItemSortOrder,
i.ItemIsActive
FROM Items i
WHERE i.ItemBusinessID = :businessID
AND i.ItemIsActive = 1
AND i.ItemCategoryID > 0
AND NOT EXISTS (
SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID
)
ORDER BY i.ItemSortOrder, i.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
} else {
// Fallback: Categories are Items at ParentID=0 with children (not in ItemTemplateLinks)
qCategories = queryExecute("
SELECT DISTINCT
p.ItemID as CategoryID,
p.ItemName as CategoryName,
p.ItemSortOrder
FROM Items p
INNER JOIN Items c ON c.ItemParentItemID = p.ItemID
WHERE p.ItemBusinessID = :businessID
AND p.ItemParentItemID = 0
AND p.ItemIsActive = 1
AND NOT EXISTS (
SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = p.ItemID
)
ORDER BY p.ItemSortOrder, p.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
// Get all menu items (children of category Items, not templates)
qItems = queryExecute("
SELECT
i.ItemID,
i.ItemParentItemID as CategoryItemID,
i.ItemName,
i.ItemDescription,
i.ItemPrice,
i.ItemSortOrder,
i.ItemIsActive
FROM Items i
INNER JOIN Items cat ON cat.ItemID = i.ItemParentItemID
WHERE i.ItemBusinessID = :businessID
AND i.ItemIsActive = 1
AND cat.ItemParentItemID = 0
AND NOT EXISTS (
SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = cat.ItemID
)
ORDER BY i.ItemSortOrder, i.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
}
} else {
// OLD SCHEMA: Use Categories table
qCategories = queryExecute("
SELECT
CategoryID,
CategoryName,
0 as ItemSortOrder
FROM Categories
WHERE CategoryBusinessID = :businessID
ORDER BY CategoryName
", { businessID: businessID }, { datasource: "payfrit" });
qItems = queryExecute("
SELECT
i.ItemID,
i.ItemCategoryID as CategoryItemID,
i.ItemName,
i.ItemDescription,
i.ItemPrice,
i.ItemSortOrder,
i.ItemIsActive
FROM Items i
INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID
WHERE c.CategoryBusinessID = :businessID
AND i.ItemIsActive = 1
AND i.ItemParentItemID = 0
ORDER BY i.ItemSortOrder, i.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
}
// Get template links (which templates are linked to which menu items)
qTemplateLinks = queryExecute("
SELECT
tl.ItemID as ParentItemID,
tl.TemplateItemID,
tl.SortOrder,
t.ItemName as TemplateName,
t.ItemPrice as TemplatePrice,
t.ItemIsCheckedByDefault as TemplateIsDefault
FROM ItemTemplateLinks tl
INNER JOIN Items t ON t.ItemID = tl.TemplateItemID
ORDER BY tl.ItemID, tl.SortOrder
", {}, { datasource: "payfrit" });
// Get all templates for this business
// Templates are Items with ItemCategoryID=0 and ItemParentItemID=0
if (newSchemaActive) {
qTemplates = queryExecute("
SELECT DISTINCT
t.ItemID,
t.ItemName,
t.ItemPrice,
t.ItemIsCheckedByDefault as IsDefault,
t.ItemSortOrder,
t.ItemRequiresChildSelection as RequiresSelection,
t.ItemMaxNumSelectionReq as MaxSelections
FROM Items t
WHERE t.ItemBusinessID = :businessID
AND t.ItemCategoryID = 0
AND t.ItemParentItemID = 0
AND t.ItemIsActive = 1
ORDER BY t.ItemSortOrder, t.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
} else {
qTemplates = queryExecute("
SELECT DISTINCT
t.ItemID,
t.ItemName,
t.ItemPrice,
t.ItemIsCheckedByDefault as IsDefault,
t.ItemSortOrder,
t.ItemRequiresChildSelection as RequiresSelection,
t.ItemMaxNumSelectionReq as MaxSelections
FROM Items t
INNER JOIN ItemTemplateLinks tl ON tl.TemplateItemID = t.ItemID
INNER JOIN Items i ON i.ItemID = tl.ItemID
INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID
WHERE c.CategoryBusinessID = :businessID
AND t.ItemIsActive = 1
ORDER BY t.ItemSortOrder, t.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
}
// Get all children of templates (options within modifier groups)
// Get children of ALL templates for this business (not just linked ones)
if (newSchemaActive) {
qTemplateChildren = queryExecute("
SELECT DISTINCT
c.ItemID,
c.ItemParentItemID as ParentItemID,
c.ItemName,
c.ItemPrice,
c.ItemIsCheckedByDefault as IsDefault,
c.ItemSortOrder,
c.ItemRequiresChildSelection as RequiresSelection,
c.ItemMaxNumSelectionReq as MaxSelections
FROM Items c
WHERE c.ItemParentItemID IN (
SELECT t.ItemID
FROM Items t
WHERE t.ItemBusinessID = :businessID
AND t.ItemCategoryID = 0
AND t.ItemParentItemID = 0
)
AND c.ItemIsActive = 1
ORDER BY c.ItemSortOrder, c.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
} else {
qTemplateChildren = queryExecute("
SELECT DISTINCT
c.ItemID,
c.ItemParentItemID as ParentItemID,
c.ItemName,
c.ItemPrice,
c.ItemIsCheckedByDefault as IsDefault,
c.ItemSortOrder,
c.ItemRequiresChildSelection as RequiresSelection,
c.ItemMaxNumSelectionReq as MaxSelections
FROM Items c
WHERE c.ItemParentItemID IN (
SELECT DISTINCT t.ItemID
FROM Items t
INNER JOIN ItemTemplateLinks tl ON tl.TemplateItemID = t.ItemID
INNER JOIN Items i ON i.ItemID = tl.ItemID
WHERE i.ItemBusinessID = :businessID
)
AND c.ItemIsActive = 1
ORDER BY c.ItemSortOrder, c.ItemName
", { businessID: businessID }, { datasource: "payfrit" });
}
// Build lookup of children by parent ID (flat list for now)
childrenByParent = {};
allChildIds = [];
for (child in qTemplateChildren) {
parentID = child.ParentItemID;
if (!structKeyExists(childrenByParent, parentID)) {
childrenByParent[parentID] = [];
}
arrayAppend(childrenByParent[parentID], {
"id": "opt_" & child.ItemID,
"dbId": child.ItemID,
"name": child.ItemName,
"price": child.ItemPrice,
"isDefault": child.IsDefault == 1 ? true : false,
"sortOrder": child.ItemSortOrder,
"requiresSelection": isNull(child.RequiresSelection) ? false : (child.RequiresSelection == 1),
"maxSelections": isNull(child.MaxSelections) ? 0 : child.MaxSelections,
"options": []
});
arrayAppend(allChildIds, child.ItemID);
}
// Now recursively attach nested options to their parents
// We need to build the tree structure from the flat list
function attachNestedOptions(items, childrenByParent) {
for (var item in items) {
var itemDbId = item.dbId;
if (structKeyExists(childrenByParent, itemDbId)) {
item.options = childrenByParent[itemDbId];
// Recursively process children
attachNestedOptions(item.options, childrenByParent);
}
}
}
// Build the childrenByTemplate using only top-level children (those whose parent is a template)
childrenByTemplate = {};
for (child in qTemplateChildren) {
parentID = child.ParentItemID;
// Only add to childrenByTemplate if parent is actually a template (in templatesById later)
// For now, just organize by parentID - we'll filter when building templatesById
if (!structKeyExists(childrenByTemplate, parentID)) {
childrenByTemplate[parentID] = [];
}
arrayAppend(childrenByTemplate[parentID], {
"id": "opt_" & child.ItemID,
"dbId": child.ItemID,
"name": child.ItemName,
"price": child.ItemPrice,
"isDefault": child.IsDefault == 1 ? true : false,
"sortOrder": child.ItemSortOrder,
"requiresSelection": isNull(child.RequiresSelection) ? false : (child.RequiresSelection == 1),
"maxSelections": isNull(child.MaxSelections) ? 0 : child.MaxSelections,
"options": structKeyExists(childrenByParent, child.ItemID) ? childrenByParent[child.ItemID] : []
});
}
// Recursively attach deeper nested options
for (templateID in childrenByTemplate) {
attachNestedOptions(childrenByTemplate[templateID], childrenByParent);
}
// Build template lookup with their children
templatesById = {};
for (tmpl in qTemplates) {
templateID = tmpl.ItemID;
children = structKeyExists(childrenByTemplate, templateID) ? childrenByTemplate[templateID] : [];
templatesById[templateID] = {
"id": "mod_" & tmpl.ItemID,
"dbId": tmpl.ItemID,
"name": tmpl.ItemName,
"price": tmpl.ItemPrice,
"isDefault": tmpl.IsDefault == 1 ? true : false,
"sortOrder": tmpl.ItemSortOrder,
"isTemplate": true,
"requiresSelection": isNull(tmpl.RequiresSelection) ? false : (tmpl.RequiresSelection == 1),
"maxSelections": isNull(tmpl.MaxSelections) ? 0 : tmpl.MaxSelections,
"options": children
};
}
// Build modifier lookup by parent ItemID using template links
modifiersByItem = {};
for (link in qTemplateLinks) {
parentID = link.ParentItemID;
templateID = link.TemplateItemID;
if (!structKeyExists(modifiersByItem, parentID)) {
modifiersByItem[parentID] = [];
}
if (structKeyExists(templatesById, templateID)) {
tmpl = duplicate(templatesById[templateID]);
tmpl["sortOrder"] = link.SortOrder;
arrayAppend(modifiersByItem[parentID], tmpl);
}
}
// Build items lookup by CategoryID
itemsByCategory = {};
for (item in qItems) {
catID = item.CategoryItemID;
if (!structKeyExists(itemsByCategory, catID)) {
itemsByCategory[catID] = [];
}
itemID = item.ItemID;
itemModifiers = structKeyExists(modifiersByItem, itemID) ? modifiersByItem[itemID] : [];
arrayAppend(itemsByCategory[catID], {
"id": "item_" & item.ItemID,
"dbId": item.ItemID,
"name": item.ItemName,
"description": isNull(item.ItemDescription) ? "" : item.ItemDescription,
"price": item.ItemPrice,
"imageUrl": javaCast("null", ""),
"photoTaskId": javaCast("null", ""),
"modifiers": itemModifiers,
"sortOrder": item.ItemSortOrder
});
}
// Build categories array
categories = [];
catIndex = 0;
for (cat in qCategories) {
catID = cat.CategoryID;
catItems = structKeyExists(itemsByCategory, catID) ? itemsByCategory[catID] : [];
arrayAppend(categories, {
"id": "cat_" & cat.CategoryID,
"dbId": cat.CategoryID,
"name": cat.CategoryName,
"description": "",
"sortOrder": catIndex,
"items": catItems
});
catIndex++;
}
// Build template library array for the UI
templateLibrary = [];
for (templateID in templatesById) {
arrayAppend(templateLibrary, templatesById[templateID]);
}
response["OK"] = true;
response["MENU"] = { "categories": categories };
response["TEMPLATES"] = templateLibrary;
response["CATEGORY_COUNT"] = arrayLen(categories);
response["TEMPLATE_COUNT"] = arrayLen(templateLibrary);
response["SCHEMA"] = newSchemaActive ? "unified" : "legacy";
totalItems = 0;
for (cat in categories) {
totalItems += arrayLen(cat.items);
}
response["ITEM_COUNT"] = totalItems;
} catch (any e) {
response["ERROR"] = "server_error";
response["MESSAGE"] = e.message;
}
writeOutput(serializeJSON(response));
</cfscript>