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/saveFromBuilder.cfm
John 16a3b7c9a3 Replace queryExecute with queryTimed across all endpoints for perf tracking
Converts 200+ endpoint files to use queryTimed() wrapper which tracks
DB query count and execution time. Restores perf dashboard files that
were accidentally moved to _scripts/. Includes portal UI updates.
2026-02-02 00:28:37 -08:00

394 lines
17 KiB
Text

<cfscript>
// Save menu data from the builder UI (OPTIMIZED)
// Input: BusinessID, Menu (JSON structure)
// Output: { OK: true }
response = { "OK": false };
// Track which templates we've already saved options for (to avoid duplicate saves)
savedTemplates = {};
// Recursive function to save options/modifiers at any depth
function saveOptionsRecursive(options, parentID, businessID) {
if (!isArray(options) || arrayLen(options) == 0) return;
var optSortOrder = 0;
for (var opt in options) {
var optDbId = structKeyExists(opt, "dbId") ? val(opt.dbId) : 0;
var requiresSelection = (structKeyExists(opt, "requiresSelection") && opt.requiresSelection) ? 1 : 0;
var maxSelections = structKeyExists(opt, "maxSelections") ? val(opt.maxSelections) : 0;
var isDefault = (structKeyExists(opt, "isDefault") && opt.isDefault) ? 1 : 0;
var optionID = 0;
if (optDbId > 0) {
optionID = optDbId;
queryTimed("
UPDATE Items
SET Name = :name,
Price = :price,
IsCheckedByDefault = :isDefault,
SortOrder = :sortOrder,
RequiresChildSelection = :requiresSelection,
MaxNumSelectionReq = :maxSelections,
ParentItemID = :parentID
WHERE ItemID = :optID
", {
optID: optDbId,
parentID: parentID,
name: opt.name,
price: val(opt.price ?: 0),
isDefault: isDefault,
sortOrder: optSortOrder,
requiresSelection: requiresSelection,
maxSelections: maxSelections
});
} else {
queryTimed("
INSERT INTO Items (
BusinessID, ParentItemID, Name, Price,
IsCheckedByDefault, SortOrder, IsActive, AddedOn,
RequiresChildSelection, MaxNumSelectionReq, CategoryID
) VALUES (
:businessID, :parentID, :name, :price,
:isDefault, :sortOrder, 1, NOW(),
:requiresSelection, :maxSelections, 0
)
", {
businessID: businessID,
parentID: parentID,
name: opt.name,
price: val(opt.price ?: 0),
isDefault: isDefault,
sortOrder: optSortOrder,
requiresSelection: requiresSelection,
maxSelections: maxSelections
});
var result = queryTimed("SELECT LAST_INSERT_ID() as newID");
optionID = result.newID;
}
if (structKeyExists(opt, "options") && isArray(opt.options) && arrayLen(opt.options) > 0) {
saveOptionsRecursive(opt.options, optionID, businessID);
}
optSortOrder++;
}
}
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
newSchemaActive = false;
try {
qCheck = queryTimed("
SELECT 1 FROM Items
WHERE BusinessID = :businessID AND BusinessID > 0
LIMIT 1
", { businessID: businessID });
newSchemaActive = (qCheck.recordCount > 0);
} catch (any e) {
newSchemaActive = false;
}
// Wrap everything in a transaction for speed and consistency
transaction {
catSortOrder = 0;
for (cat in menu.categories) {
categoryID = 0;
categoryDbId = structKeyExists(cat, "dbId") ? val(cat.dbId) : 0;
if (newSchemaActive) {
if (categoryDbId > 0) {
categoryID = categoryDbId;
queryTimed("
UPDATE Items
SET Name = :name,
SortOrder = :sortOrder
WHERE ItemID = :categoryID
AND BusinessID = :businessID
", {
categoryID: categoryID,
businessID: businessID,
name: cat.name,
sortOrder: catSortOrder
});
} else {
queryTimed("
INSERT INTO Items (
BusinessID, Name, Description,
ParentItemID, Price, IsActive,
SortOrder, AddedOn, CategoryID
) VALUES (
:businessID, :name, '',
0, 0, 1,
:sortOrder, NOW(), 0
)
", {
businessID: businessID,
name: cat.name,
sortOrder: catSortOrder
});
result = queryTimed("SELECT LAST_INSERT_ID() as newID");
categoryID = result.newID;
}
} else {
// Get menu ID from category if provided
categoryMenuId = structKeyExists(cat, "menuId") ? val(cat.menuId) : 0;
categoryMenuIdParam = categoryMenuId > 0 ? categoryMenuId : javaCast("null", "");
if (categoryDbId > 0) {
categoryID = categoryDbId;
queryTimed("
UPDATE Categories
SET Name = :name,
SortOrder = :sortOrder,
MenuID = :menuId
WHERE CategoryID = :categoryID
", {
categoryID: categoryID,
name: cat.name,
sortOrder: catSortOrder,
menuId: categoryMenuIdParam
});
} else {
queryTimed("
INSERT INTO Categories (BusinessID, MenuID, Name, SortOrder, AddedOn)
VALUES (:businessID, :menuId, :name, :sortOrder, NOW())
", {
businessID: businessID,
menuId: categoryMenuIdParam,
name: cat.name,
sortOrder: catSortOrder
});
result = queryTimed("SELECT LAST_INSERT_ID() as newID");
categoryID = result.newID;
}
}
// Process items
if (structKeyExists(cat, "items") && isArray(cat.items)) {
itemSortOrder = 0;
for (item in cat.items) {
itemID = 0;
itemDbId = structKeyExists(item, "dbId") ? val(item.dbId) : 0;
if (itemDbId > 0) {
itemID = itemDbId;
if (newSchemaActive) {
queryTimed("
UPDATE Items
SET Name = :name,
Description = :description,
Price = :price,
ParentItemID = :categoryID,
SortOrder = :sortOrder
WHERE ItemID = :itemID
", {
itemID: itemID,
name: item.name,
description: item.description ?: "",
price: val(item.price ?: 0),
categoryID: categoryID,
sortOrder: itemSortOrder
});
} else {
queryTimed("
UPDATE Items
SET Name = :name,
Description = :description,
Price = :price,
CategoryID = :categoryID,
SortOrder = :sortOrder
WHERE ItemID = :itemID
", {
itemID: itemID,
name: item.name,
description: item.description ?: "",
price: val(item.price ?: 0),
categoryID: categoryID,
sortOrder: itemSortOrder
});
}
} else {
if (newSchemaActive) {
queryTimed("
INSERT INTO Items (
BusinessID, ParentItemID, Name, Description,
Price, SortOrder, IsActive, AddedOn, CategoryID
) VALUES (
:businessID, :categoryID, :name, :description,
:price, :sortOrder, 1, NOW(), 0
)
", {
businessID: businessID,
categoryID: categoryID,
name: item.name,
description: item.description ?: "",
price: val(item.price ?: 0),
sortOrder: itemSortOrder
});
} else {
queryTimed("
INSERT INTO Items (
BusinessID, CategoryID, Name, Description,
Price, SortOrder, IsActive, ParentItemID, AddedOn
) VALUES (
:businessID, :categoryID, :name, :description,
:price, :sortOrder, 1, 0, NOW()
)
", {
businessID: businessID,
categoryID: categoryID,
name: item.name,
description: item.description ?: "",
price: val(item.price ?: 0),
sortOrder: itemSortOrder
});
}
result = queryTimed("SELECT LAST_INSERT_ID() as newID");
itemID = result.newID;
}
// Handle modifiers
if (structKeyExists(item, "modifiers") && isArray(item.modifiers) && arrayLen(item.modifiers) > 0) {
// Clear existing template links for this item
queryTimed("DELETE FROM lt_ItemID_TemplateItemID WHERE ItemID = :itemID", { itemID: itemID });
modSortOrder = 0;
for (mod in item.modifiers) {
modDbId = structKeyExists(mod, "dbId") ? val(mod.dbId) : 0;
requiresSelection = (structKeyExists(mod, "requiresSelection") && mod.requiresSelection) ? 1 : 0;
maxSelections = structKeyExists(mod, "maxSelections") ? val(mod.maxSelections) : 0;
if (structKeyExists(mod, "isTemplate") && mod.isTemplate && modDbId > 0) {
// This is a template reference - create link
queryTimed("
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder)
VALUES (:itemID, :templateID, :sortOrder)
ON DUPLICATE KEY UPDATE SortOrder = :sortOrder
", {
itemID: itemID,
templateID: modDbId,
sortOrder: modSortOrder
});
// Update template's selection rules
queryTimed("
UPDATE Items
SET RequiresChildSelection = :requiresSelection,
MaxNumSelectionReq = :maxSelections
WHERE ItemID = :modID
", {
modID: modDbId,
requiresSelection: requiresSelection,
maxSelections: maxSelections
});
// Only save template options ONCE (first time we encounter this template)
if (!structKeyExists(savedTemplates, modDbId)) {
savedTemplates[modDbId] = true;
if (structKeyExists(mod, "options") && isArray(mod.options)) {
saveOptionsRecursive(mod.options, modDbId, businessID);
}
}
} else if (modDbId > 0) {
// Direct modifier (not a template) - update it
queryTimed("
UPDATE Items
SET Name = :name,
Price = :price,
IsCheckedByDefault = :isDefault,
SortOrder = :sortOrder,
RequiresChildSelection = :requiresSelection,
MaxNumSelectionReq = :maxSelections,
ParentItemID = :parentID
WHERE ItemID = :modID
", {
modID: modDbId,
parentID: itemID,
name: mod.name,
price: val(mod.price ?: 0),
isDefault: (mod.isDefault ?: false) ? 1 : 0,
sortOrder: modSortOrder,
requiresSelection: requiresSelection,
maxSelections: maxSelections
});
if (structKeyExists(mod, "options") && isArray(mod.options)) {
saveOptionsRecursive(mod.options, modDbId, businessID);
}
} else {
// New direct modifier - insert it
queryTimed("
INSERT INTO Items (
BusinessID, ParentItemID, Name, Price,
IsCheckedByDefault, SortOrder, IsActive, AddedOn,
RequiresChildSelection, MaxNumSelectionReq, CategoryID
) VALUES (
:businessID, :parentID, :name, :price,
:isDefault, :sortOrder, 1, NOW(),
:requiresSelection, :maxSelections, 0
)
", {
businessID: businessID,
parentID: itemID,
name: mod.name,
price: val(mod.price ?: 0),
isDefault: (mod.isDefault ?: false) ? 1 : 0,
sortOrder: modSortOrder,
requiresSelection: requiresSelection,
maxSelections: maxSelections
});
modResult = queryTimed("SELECT LAST_INSERT_ID() as newModID");
newModID = modResult.newModID;
if (structKeyExists(mod, "options") && isArray(mod.options)) {
saveOptionsRecursive(mod.options, newModID, businessID);
}
}
modSortOrder++;
}
}
itemSortOrder++;
}
}
catSortOrder++;
}
}
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));
</cfscript>