diff --git a/api/Application.cfm b/api/Application.cfm index ceff646..240216f 100644 --- a/api/Application.cfm +++ b/api/Application.cfm @@ -95,6 +95,8 @@ if (len(request._api_path)) { if (findNoCase("/api/beacons/list_all.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/beacons/getBusinessFromBeacon.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/menu/items.cfm", request._api_path)) request._api_isPublic = true; + if (findNoCase("/api/menu/clearAllData.cfm", request._api_path)) request._api_isPublic = true; + if (findNoCase("/api/menu/clearOrders.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/addresses/states.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/addresses/debug.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/debug/", request._api_path)) request._api_isPublic = true; diff --git a/api/menu/clearAllData.cfm b/api/menu/clearAllData.cfm new file mode 100644 index 0000000..22649f1 --- /dev/null +++ b/api/menu/clearAllData.cfm @@ -0,0 +1,50 @@ + + + + + + +response = { "OK": false }; + +try { + requestBody = toString(getHttpRequestData().content); + requestData = {}; + if (len(requestBody)) { + requestData = deserializeJSON(requestBody); + } + + confirmDelete = requestData.confirm ?: ""; + + if (confirmDelete != "NUKE_EVERYTHING") { + throw("Must pass confirm: 'NUKE_EVERYTHING' to proceed"); + } + + // Get counts before deletion + qItemCount = queryExecute("SELECT COUNT(*) as cnt FROM Items", {}, { datasource: "payfrit" }); + qCatCount = queryExecute("SELECT COUNT(*) as cnt FROM Categories", {}, { datasource: "payfrit" }); + qLinkCount = queryExecute("SELECT COUNT(*) as cnt FROM ItemTemplateLinks", {}, { datasource: "payfrit" }); + + // Delete in correct order (foreign key constraints) + queryExecute("DELETE FROM ItemTemplateLinks", {}, { datasource: "payfrit" }); + queryExecute("DELETE FROM Items", {}, { datasource: "payfrit" }); + queryExecute("DELETE FROM Categories", {}, { datasource: "payfrit" }); + + response = { + "OK": true, + "deleted": { + "items": qItemCount.cnt, + "categories": qCatCount.cnt, + "templateLinks": qLinkCount.cnt + } + }; + +} catch (any e) { + response = { + "OK": false, + "ERROR": e.message, + "DETAIL": e.detail ?: "" + }; +} + +writeOutput(serializeJSON(response)); + diff --git a/api/menu/clearOrders.cfm b/api/menu/clearOrders.cfm new file mode 100644 index 0000000..c638f4e --- /dev/null +++ b/api/menu/clearOrders.cfm @@ -0,0 +1,53 @@ + + + + + + +response = { "OK": false }; + +try { + requestBody = toString(getHttpRequestData().content); + requestData = {}; + if (len(requestBody)) { + requestData = deserializeJSON(requestBody); + } + + confirmDelete = requestData.confirm ?: ""; + + if (confirmDelete != "NUKE_ORDERS") { + throw("Must pass confirm: 'NUKE_ORDERS' to proceed"); + } + + // Get counts before deletion + qOrderLineItems = queryExecute("SELECT COUNT(*) as cnt FROM OrderLineItems", {}, { datasource: "payfrit" }); + qOrders = queryExecute("SELECT COUNT(*) as cnt FROM Orders", {}, { datasource: "payfrit" }); + qAddresses = queryExecute("SELECT COUNT(*) as cnt FROM Addresses", {}, { datasource: "payfrit" }); + qTasks = queryExecute("SELECT COUNT(*) as cnt FROM Tasks", {}, { datasource: "payfrit" }); + + // Delete in correct order (foreign key constraints) + queryExecute("DELETE FROM Tasks", {}, { datasource: "payfrit" }); + queryExecute("DELETE FROM OrderLineItems", {}, { datasource: "payfrit" }); + queryExecute("DELETE FROM Orders", {}, { datasource: "payfrit" }); + queryExecute("DELETE FROM Addresses", {}, { datasource: "payfrit" }); + + response = { + "OK": true, + "deleted": { + "tasks": qTasks.cnt, + "lineItems": qOrderLineItems.cnt, + "orders": qOrders.cnt, + "addresses": qAddresses.cnt + } + }; + +} catch (any e) { + response = { + "OK": false, + "ERROR": e.message, + "DETAIL": e.detail ?: "" + }; +} + +writeOutput(serializeJSON(response)); + diff --git a/api/menu/getForBuilder.cfm b/api/menu/getForBuilder.cfm index 9f7c43d..a8f5b55 100644 --- a/api/menu/getForBuilder.cfm +++ b/api/menu/getForBuilder.cfm @@ -7,20 +7,38 @@ /** * 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 }; +// Recursive function to build nested options +function buildOptionsTree(allOptions, parentId) { + var result = []; + for (var i = 1; i <= allOptions.recordCount; i++) { + if (allOptions.ParentItemID[i] == parentId) { + var children = buildOptionsTree(allOptions, allOptions.ItemID[i]); + arrayAppend(result, { + "id": "opt_" & allOptions.ItemID[i], + "dbId": allOptions.ItemID[i], + "name": allOptions.ItemName[i], + "price": allOptions.ItemPrice[i], + "isDefault": allOptions.IsDefault[i] == 1 ? true : false, + "sortOrder": allOptions.ItemSortOrder[i], + "requiresSelection": isNull(allOptions.RequiresSelection[i]) ? false : (allOptions.RequiresSelection[i] == 1), + "maxSelections": isNull(allOptions.MaxSelections[i]) ? 0 : allOptions.MaxSelections[i], + "options": children + }); + } + } + if (arrayLen(result) > 1) { + arraySort(result, function(a, b) { + return a.sortOrder - b.sortOrder; + }); + } + return result; +} + try { - // Get request body requestBody = toString(getHttpRequestData().content); requestData = {}; if (len(requestBody)) { @@ -39,113 +57,30 @@ try { abort; } - // Check if new schema is active (ItemBusinessID column exists and has data) - newSchemaActive = false; + // Check if Categories table has data for this business + hasCategoriesData = false; try { - qCheck = queryExecute(" - SELECT COUNT(*) as cnt FROM Items - WHERE ItemBusinessID = :businessID AND ItemBusinessID > 0 + qCatCheck = queryExecute(" + SELECT 1 FROM Categories WHERE CategoryBusinessID = :businessID LIMIT 1 ", { businessID: businessID }, { datasource: "payfrit" }); - newSchemaActive = (qCheck.cnt > 0); + hasCategoriesData = (qCatCheck.recordCount > 0); } catch (any e) { - newSchemaActive = false; + hasCategoriesData = 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 + if (hasCategoriesData) { + // OLD SCHEMA: Use Categories table for categories qCategories = queryExecute(" SELECT CategoryID, CategoryName, - 0 as ItemSortOrder + CategorySortOrder as ItemSortOrder FROM Categories WHERE CategoryBusinessID = :businessID - ORDER BY CategoryName + ORDER BY CategorySortOrder, CategoryName ", { businessID: businessID }, { datasource: "payfrit" }); + // Get menu items - items that belong to categories (not modifiers) qItems = queryExecute(" SELECT i.ItemID, @@ -156,72 +91,135 @@ try { i.ItemSortOrder, i.ItemIsActive FROM Items i - INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID - WHERE c.CategoryBusinessID = :businessID + WHERE i.ItemBusinessID = :businessID AND i.ItemIsActive = 1 - AND i.ItemParentItemID = 0 + AND i.ItemCategoryID > 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 + // Get direct modifiers (items with ParentItemID pointing to menu items, not categories) + qDirectModifiers = queryExecute(" + SELECT + m.ItemID, + m.ItemParentItemID as ParentItemID, + m.ItemName, + m.ItemPrice, + m.ItemIsCheckedByDefault as IsDefault, + m.ItemSortOrder, + m.ItemRequiresChildSelection as RequiresSelection, + m.ItemMaxNumSelectionReq as MaxSelections + FROM Items m + WHERE m.ItemBusinessID = :businessID + AND m.ItemIsActive = 1 + AND m.ItemParentItemID > 0 + AND (m.ItemCategoryID = 0 OR m.ItemCategoryID IS NULL) + ORDER BY m.ItemSortOrder, m.ItemName ", { businessID: businessID }, { datasource: "payfrit" }); + } else { - qTemplates = queryExecute(" + // NEW UNIFIED SCHEMA: Categories are Items at ParentID=0 with children + qCategories = 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 + 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" }); + + 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" }); + + qDirectModifiers = queryExecute(" + SELECT + m.ItemID, + m.ItemParentItemID as ParentItemID, + m.ItemName, + m.ItemPrice, + m.ItemIsCheckedByDefault as IsDefault, + m.ItemSortOrder, + m.ItemRequiresChildSelection as RequiresSelection, + m.ItemMaxNumSelectionReq as MaxSelections + FROM Items m + WHERE m.ItemBusinessID = :businessID + AND m.ItemIsActive = 1 + AND m.ItemParentItemID > 0 + ORDER BY m.ItemSortOrder, m.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) { + // Collect menu item IDs for filtering template links + menuItemIds = []; + for (i = 1; i <= qItems.recordCount; i++) { + arrayAppend(menuItemIds, qItems.ItemID[i]); + } + + // Get template links ONLY for this business's menu items + qTemplateLinks = queryNew("ParentItemID,TemplateItemID,SortOrder"); + if (arrayLen(menuItemIds) > 0) { + qTemplateLinks = queryExecute(" + SELECT + tl.ItemID as ParentItemID, + tl.TemplateItemID, + tl.SortOrder + FROM ItemTemplateLinks tl + WHERE tl.ItemID IN (:itemIds) + ORDER BY tl.ItemID, tl.SortOrder + ", { itemIds: { value: arrayToList(menuItemIds), cfsqltype: "cf_sql_varchar", list: true } }, { datasource: "payfrit" }); + } + + // Get templates for this business only + 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 OR t.ItemCategoryID IS NULL) + AND t.ItemParentItemID = 0 + AND t.ItemIsActive = 1 + ORDER BY t.ItemSortOrder, t.ItemName + ", { businessID: businessID }, { datasource: "payfrit" }); + + // Get template children (options within templates) + templateIds = []; + for (i = 1; i <= qTemplates.recordCount; i++) { + arrayAppend(templateIds, qTemplates.ItemID[i]); + } + + qTemplateChildren = queryNew("ItemID,ParentItemID,ItemName,ItemPrice,IsDefault,ItemSortOrder,RequiresSelection,MaxSelections"); + if (arrayLen(templateIds) > 0) { qTemplateChildren = queryExecute(" - SELECT DISTINCT + SELECT c.ItemID, c.ItemParentItemID as ParentItemID, c.ItemName, @@ -231,173 +229,109 @@ try { 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 + WHERE c.ItemParentItemID IN (:templateIds) + 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" }); + ", { templateIds: { value: arrayToList(templateIds), cfsqltype: "cf_sql_varchar", list: true } }, { 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 + // Build templates lookup with their options templatesById = {}; - for (tmpl in qTemplates) { - templateID = tmpl.ItemID; - children = structKeyExists(childrenByTemplate, templateID) ? childrenByTemplate[templateID] : []; + for (i = 1; i <= qTemplates.recordCount; i++) { + templateID = qTemplates.ItemID[i]; + options = buildOptionsTree(qTemplateChildren, 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, + "id": "mod_" & qTemplates.ItemID[i], + "dbId": qTemplates.ItemID[i], + "name": qTemplates.ItemName[i], + "price": qTemplates.ItemPrice[i], + "isDefault": qTemplates.IsDefault[i] == 1 ? true : false, + "sortOrder": qTemplates.ItemSortOrder[i], "isTemplate": true, - "requiresSelection": isNull(tmpl.RequiresSelection) ? false : (tmpl.RequiresSelection == 1), - "maxSelections": isNull(tmpl.MaxSelections) ? 0 : tmpl.MaxSelections, - "options": children + "requiresSelection": isNull(qTemplates.RequiresSelection[i]) ? false : (qTemplates.RequiresSelection[i] == 1), + "maxSelections": isNull(qTemplates.MaxSelections[i]) ? 0 : qTemplates.MaxSelections[i], + "options": options }; } - // Build modifier lookup by parent ItemID using template links - modifiersByItem = {}; - for (link in qTemplateLinks) { - parentID = link.ParentItemID; - templateID = link.TemplateItemID; + // Build template links lookup by parent ItemID + templateLinksByItem = {}; + for (i = 1; i <= qTemplateLinks.recordCount; i++) { + parentID = qTemplateLinks.ParentItemID[i]; + templateID = qTemplateLinks.TemplateItemID[i]; - if (!structKeyExists(modifiersByItem, parentID)) { - modifiersByItem[parentID] = []; + if (!structKeyExists(templateLinksByItem, parentID)) { + templateLinksByItem[parentID] = []; } if (structKeyExists(templatesById, templateID)) { tmpl = duplicate(templatesById[templateID]); - tmpl["sortOrder"] = link.SortOrder; - arrayAppend(modifiersByItem[parentID], tmpl); + tmpl["sortOrder"] = qTemplateLinks.SortOrder[i]; + arrayAppend(templateLinksByItem[parentID], tmpl); + } + } + + // Build nested direct modifiers for each menu item + directModsByItem = {}; + for (itemId in menuItemIds) { + options = buildOptionsTree(qDirectModifiers, itemId); + if (arrayLen(options) > 0) { + directModsByItem[itemId] = options; } } // Build items lookup by CategoryID itemsByCategory = {}; - for (item in qItems) { - catID = item.CategoryItemID; + for (i = 1; i <= qItems.recordCount; i++) { + catID = qItems.CategoryItemID[i]; if (!structKeyExists(itemsByCategory, catID)) { itemsByCategory[catID] = []; } - itemID = item.ItemID; - itemModifiers = structKeyExists(modifiersByItem, itemID) ? modifiersByItem[itemID] : []; + itemID = qItems.ItemID[i]; + + // Get template-linked modifiers + itemModifiers = structKeyExists(templateLinksByItem, itemID) ? duplicate(templateLinksByItem[itemID]) : []; + + // Add direct modifiers + if (structKeyExists(directModsByItem, itemID)) { + directMods = directModsByItem[itemID]; + for (j = 1; j <= arrayLen(directMods); j++) { + arrayAppend(itemModifiers, directMods[j]); + } + } + + // Sort modifiers by sortOrder + if (arrayLen(itemModifiers) > 1) { + arraySort(itemModifiers, function(a, b) { + return a.sortOrder - b.sortOrder; + }); + } arrayAppend(itemsByCategory[catID], { - "id": "item_" & item.ItemID, - "dbId": item.ItemID, - "name": item.ItemName, - "description": isNull(item.ItemDescription) ? "" : item.ItemDescription, - "price": item.ItemPrice, + "id": "item_" & qItems.ItemID[i], + "dbId": qItems.ItemID[i], + "name": qItems.ItemName[i], + "description": isNull(qItems.ItemDescription[i]) ? "" : qItems.ItemDescription[i], + "price": qItems.ItemPrice[i], "imageUrl": javaCast("null", ""), "photoTaskId": javaCast("null", ""), "modifiers": itemModifiers, - "sortOrder": item.ItemSortOrder + "sortOrder": qItems.ItemSortOrder[i] }); } // Build categories array categories = []; catIndex = 0; - for (cat in qCategories) { - catID = cat.CategoryID; + for (i = 1; i <= qCategories.recordCount; i++) { + catID = qCategories.CategoryID[i]; catItems = structKeyExists(itemsByCategory, catID) ? itemsByCategory[catID] : []; arrayAppend(categories, { - "id": "cat_" & cat.CategoryID, - "dbId": cat.CategoryID, - "name": cat.CategoryName, + "id": "cat_" & qCategories.CategoryID[i], + "dbId": qCategories.CategoryID[i], + "name": qCategories.CategoryName[i], "description": "", "sortOrder": catIndex, "items": catItems @@ -405,7 +339,7 @@ try { catIndex++; } - // Build template library array for the UI + // Build template library array templateLibrary = []; for (templateID in templatesById) { arrayAppend(templateLibrary, templatesById[templateID]); @@ -416,7 +350,7 @@ try { response["TEMPLATES"] = templateLibrary; response["CATEGORY_COUNT"] = arrayLen(categories); response["TEMPLATE_COUNT"] = arrayLen(templateLibrary); - response["SCHEMA"] = newSchemaActive ? "unified" : "legacy"; + response["SCHEMA"] = hasCategoriesData ? "legacy" : "unified"; totalItems = 0; for (cat in categories) { @@ -427,6 +361,7 @@ try { } catch (any e) { response["ERROR"] = "server_error"; response["MESSAGE"] = e.message; + response["DETAIL"] = e.detail ?: ""; } writeOutput(serializeJSON(response)); diff --git a/api/orders/updateStatus.cfm b/api/orders/updateStatus.cfm index 9a8cc03..70d6a60 100644 --- a/api/orders/updateStatus.cfm +++ b/api/orders/updateStatus.cfm @@ -95,62 +95,51 @@ ", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })> - + - - - - - - - - - - - - - - - - - - - + + + + + + + + - - + + + diff --git a/api/setup/saveWizard.cfm b/api/setup/saveWizard.cfm index 121e17c..bc3c197 100644 --- a/api/setup/saveWizard.cfm +++ b/api/setup/saveWizard.cfm @@ -162,6 +162,9 @@ try { required = structKeyExists(tmpl, "required") && tmpl.required == true; options = structKeyExists(tmpl, "options") ? tmpl.options : []; + // Debug: Log options info + response.steps.append("Template '" & tmplName & "' has " & arrayLen(options) & " options (type: " & (isArray(options) ? "array" : "other") & ")"); + // Check if template already exists for this business qTmpl = queryExecute(" SELECT i.ItemID FROM Items i @@ -199,7 +202,8 @@ try { // Create/update template options optionOrder = 1; - for (opt in options) { + for (j = 1; j <= arrayLen(options); j++) { + opt = options[j]; // Safety check: ensure opt is a struct with a name if (!isStruct(opt) || !structKeyExists(opt, "name") || !len(opt.name)) { continue; @@ -240,7 +244,8 @@ try { response.steps.append("Processing " & arrayLen(categories) & " categories..."); catOrder = 1; - for (cat in categories) { + for (c = 1; c <= arrayLen(categories); c++) { + cat = categories[c]; catName = cat.name; // Check if category exists in Categories table @@ -286,7 +291,8 @@ try { // Track item order within each category categoryItemOrder = {}; - for (item in items) { + for (n = 1; n <= arrayLen(items); n++) { + item = items[n]; itemName = item.name; itemDesc = structKeyExists(item, "description") ? item.description : ""; itemPrice = structKeyExists(item, "price") ? val(item.price) : 0; @@ -356,7 +362,8 @@ try { // Link modifier templates to this item modOrder = 1; - for (modName in itemModifiers) { + for (m = 1; m <= arrayLen(itemModifiers); m++) { + modName = itemModifiers[m]; if (structKeyExists(templateMap, modName)) { templateItemID = templateMap[modName]; diff --git a/portal/setup-wizard.html b/portal/setup-wizard.html index 02f46a8..3ca2482 100644 --- a/portal/setup-wizard.html +++ b/portal/setup-wizard.html @@ -841,6 +841,70 @@
+ +
+
+ × + Menu Image +
+
+
+ + +