// Save menu data from the builder UI // Input: BusinessID, Menu (JSON structure) // Output: { OK: true } // // Supports both old schema (Categories table) and new unified schema (Categories as Items) response = { "OK": false }; try { requestBody = toString(getHttpRequestData().content); if (!len(requestBody)) { throw("Request body is required"); } jsonData = deserializeJSON(requestBody); businessID = val(jsonData.BusinessID ?: 0); menu = jsonData.Menu ?: {}; if (businessID == 0) { throw("BusinessID is required"); } if (!structKeyExists(menu, "categories") || !isArray(menu.categories)) { throw("Menu categories are required"); } // 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 }); newSchemaActive = (qCheck.cnt > 0); } catch (any e) { newSchemaActive = false; } // Process each category for (cat in menu.categories) { categoryID = 0; categoryDbId = structKeyExists(cat, "dbId") ? val(cat.dbId) : 0; if (newSchemaActive) { // NEW SCHEMA: Categories are Items with ParentID=0 and Template=0 if (categoryDbId > 0) { categoryID = categoryDbId; // Update existing category Item queryExecute(" UPDATE Items SET ItemName = :name, ItemSortOrder = :sortOrder WHERE ItemID = :categoryID AND ItemBusinessID = :businessID ", { categoryID: categoryID, businessID: businessID, name: cat.name, sortOrder: val(cat.sortOrder ?: 0) }); } else { // Insert new category as Item queryExecute(" INSERT INTO Items ( ItemBusinessID, ItemName, ItemDescription, ItemParentItemID, ItemPrice, ItemIsActive, ItemSortOrder, ItemIsModifierTemplate, ItemAddedOn ) VALUES ( :businessID, :name, '', 0, 0, 1, :sortOrder, 0, NOW() ) ", { businessID: businessID, name: cat.name, sortOrder: val(cat.sortOrder ?: 0) }); result = queryExecute("SELECT LAST_INSERT_ID() as newID"); categoryID = result.newID; } } else { // OLD SCHEMA: Use Categories table if (categoryDbId > 0) { categoryID = categoryDbId; queryExecute(" UPDATE Categories SET CategoryName = :name, CategoryDescription = :description, CategorySortOrder = :sortOrder WHERE CategoryID = :categoryID ", { categoryID: categoryID, name: cat.name, description: cat.description ?: "", sortOrder: val(cat.sortOrder ?: 0) }); } else { queryExecute(" INSERT INTO Categories (CategoryBusinessID, CategoryName, CategoryDescription, CategorySortOrder) VALUES (:businessID, :name, :description, :sortOrder) ", { businessID: businessID, name: cat.name, description: cat.description ?: "", sortOrder: val(cat.sortOrder ?: 0) }); result = queryExecute("SELECT LAST_INSERT_ID() as newID"); categoryID = result.newID; } } // Process items in this category if (structKeyExists(cat, "items") && isArray(cat.items)) { for (item in cat.items) { itemID = 0; itemDbId = structKeyExists(item, "dbId") ? val(item.dbId) : 0; if (itemDbId > 0) { itemID = itemDbId; if (newSchemaActive) { // Update existing item - set parent to category Item queryExecute(" UPDATE Items SET ItemName = :name, ItemDescription = :description, ItemPrice = :price, ItemParentItemID = :categoryID, ItemSortOrder = :sortOrder WHERE ItemID = :itemID ", { itemID: itemID, name: item.name, description: item.description ?: "", price: val(item.price ?: 0), categoryID: categoryID, sortOrder: val(item.sortOrder ?: 0) }); } else { // Update existing item - old schema with CategoryID queryExecute(" UPDATE Items SET ItemName = :name, ItemDescription = :description, ItemPrice = :price, ItemCategoryID = :categoryID, ItemSortOrder = :sortOrder WHERE ItemID = :itemID ", { itemID: itemID, name: item.name, description: item.description ?: "", price: val(item.price ?: 0), categoryID: categoryID, sortOrder: val(item.sortOrder ?: 0) }); } } else { // Insert new item if (newSchemaActive) { queryExecute(" INSERT INTO Items ( ItemBusinessID, ItemParentItemID, ItemName, ItemDescription, ItemPrice, ItemSortOrder, ItemIsActive, ItemAddedOn ) VALUES ( :businessID, :categoryID, :name, :description, :price, :sortOrder, 1, NOW() ) ", { businessID: businessID, categoryID: categoryID, name: item.name, description: item.description ?: "", price: val(item.price ?: 0), sortOrder: val(item.sortOrder ?: 0) }); } else { queryExecute(" INSERT INTO Items ( ItemBusinessID, ItemCategoryID, ItemName, ItemDescription, ItemPrice, ItemSortOrder, ItemIsActive, ItemParentItemID ) VALUES ( :businessID, :categoryID, :name, :description, :price, :sortOrder, 1, 0 ) ", { businessID: businessID, categoryID: categoryID, name: item.name, description: item.description ?: "", price: val(item.price ?: 0), sortOrder: val(item.sortOrder ?: 0) }); } result = queryExecute("SELECT LAST_INSERT_ID() as newID"); itemID = result.newID; } // Handle template links for modifiers if (structKeyExists(item, "modifiers") && isArray(item.modifiers)) { // Clear existing template links for this item queryExecute(" DELETE FROM ItemTemplateLinks WHERE ItemID = :itemID ", { itemID: itemID }); modSortOrder = 0; for (mod in item.modifiers) { modDbId = structKeyExists(mod, "dbId") ? val(mod.dbId) : 0; // Get selection rules (for modifier groups) requiresSelection = (structKeyExists(mod, "requiresSelection") && mod.requiresSelection) ? 1 : 0; maxSelections = structKeyExists(mod, "maxSelections") ? val(mod.maxSelections) : 0; // Check if this is a template reference if (structKeyExists(mod, "isTemplate") && mod.isTemplate && modDbId > 0) { // Create template link queryExecute(" INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder) VALUES (:itemID, :templateID, :sortOrder) ON DUPLICATE KEY UPDATE SortOrder = :sortOrder ", { itemID: itemID, templateID: modDbId, sortOrder: modSortOrder }); // Also update the template's selection rules queryExecute(" UPDATE Items SET ItemRequiresChildSelection = :requiresSelection, ItemMaxNumSelectionReq = :maxSelections WHERE ItemID = :modID ", { modID: modDbId, requiresSelection: requiresSelection, maxSelections: maxSelections }); } else if (modDbId > 0) { // Update existing direct modifier queryExecute(" UPDATE Items SET ItemName = :name, ItemPrice = :price, ItemIsCheckedByDefault = :isDefault, ItemSortOrder = :sortOrder, ItemRequiresChildSelection = :requiresSelection, ItemMaxNumSelectionReq = :maxSelections WHERE ItemID = :modID ", { modID: modDbId, name: mod.name, price: val(mod.price ?: 0), isDefault: (mod.isDefault ?: false) ? 1 : 0, sortOrder: modSortOrder, requiresSelection: requiresSelection, maxSelections: maxSelections }); } else { // Insert new direct modifier (non-template) queryExecute(" INSERT INTO Items ( ItemBusinessID, ItemParentItemID, ItemName, ItemPrice, ItemIsCheckedByDefault, ItemSortOrder, ItemIsActive, ItemAddedOn, ItemRequiresChildSelection, ItemMaxNumSelectionReq ) VALUES ( :businessID, :parentID, :name, :price, :isDefault, :sortOrder, 1, NOW(), :requiresSelection, :maxSelections ) ", { businessID: businessID, parentID: itemID, name: mod.name, price: val(mod.price ?: 0), isDefault: (mod.isDefault ?: false) ? 1 : 0, sortOrder: modSortOrder, requiresSelection: requiresSelection, maxSelections: maxSelections }); } modSortOrder++; } } } } } response = { "OK": true, "SCHEMA": newSchemaActive ? "unified" : "legacy" }; } catch (any e) { response = { "OK": false, "ERROR": e.message, "DETAIL": e.detail ?: "", "TYPE": e.type ?: "" }; } cfheader(name="Content-Type", value="application/json"); writeOutput(serializeJSON(response));