Portal local development: - Add BASE_PATH detection to all portal files (login, portal.js, menu-builder, station-assignment) - Allows portal to work at /biz.payfrit.com/ path locally Menu Builder fixes: - Fix duplicate template options in getForBuilder.cfm query - Filter template children by business ID with DISTINCT New APIs: - api/portal/myBusinesses.cfm - List businesses for logged-in user - api/stations/list.cfm - List KDS stations - api/menu/updateStations.cfm - Update item station assignments - api/setup/reimportBigDeans.cfm - Full Big Dean's menu import script Admin utilities: - Various debug and migration scripts for menu/template management - Beacon switching, category cleanup, modifier template setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
186 lines
6.9 KiB
Text
186 lines
6.9 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
|
|
<!--- Only allow from localhost --->
|
|
<cfif NOT (cgi.remote_addr EQ "127.0.0.1" OR cgi.remote_addr EQ "::1" OR findNoCase("localhost", cgi.server_name))>
|
|
<cfoutput>#serializeJSON({"OK": false, "ERROR": "admin_only"})#</cfoutput>
|
|
<cfabort>
|
|
</cfif>
|
|
|
|
<cfscript>
|
|
/**
|
|
* Migrate Modifier Templates for In and Out Burger (BusinessID=1)
|
|
*
|
|
* This script:
|
|
* 1. Identifies unique modifier trees (children of parent items)
|
|
* 2. Keeps ONE instance of each unique modifier name as the template
|
|
* 3. Links all parent items to these templates
|
|
* 4. Reports which duplicate items can be deleted
|
|
*/
|
|
|
|
response = { "OK": false, "steps": [], "templates": [], "links": [], "orphans": [] };
|
|
|
|
try {
|
|
businessID = 17; // In and Out Burger
|
|
|
|
// Step 1: Get all parent items (burgers, combos, etc.)
|
|
qParentItems = queryExecute("
|
|
SELECT i.ItemID, i.ItemName
|
|
FROM Items i
|
|
INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID
|
|
WHERE c.CategoryBusinessID = :businessID
|
|
AND i.ItemParentItemID = 0
|
|
AND i.ItemIsActive = 1
|
|
ORDER BY i.ItemName
|
|
", { businessID: businessID }, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.steps, "Found " & qParentItems.recordCount & " parent items");
|
|
|
|
// Step 2: Get all direct children (level 1 modifiers) of parent items
|
|
qModifiers = queryExecute("
|
|
SELECT
|
|
m.ItemID,
|
|
m.ItemName,
|
|
m.ItemParentItemID,
|
|
m.ItemPrice,
|
|
m.ItemIsCheckedByDefault,
|
|
m.ItemSortOrder,
|
|
p.ItemName as ParentName
|
|
FROM Items m
|
|
INNER JOIN Items p ON p.ItemID = m.ItemParentItemID
|
|
INNER JOIN Categories c ON c.CategoryID = p.ItemCategoryID
|
|
WHERE c.CategoryBusinessID = :businessID
|
|
AND m.ItemParentItemID > 0
|
|
AND m.ItemIsActive = 1
|
|
AND p.ItemParentItemID = 0
|
|
ORDER BY m.ItemName, m.ItemID
|
|
", { businessID: businessID }, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.steps, "Found " & qModifiers.recordCount & " level-1 modifiers");
|
|
|
|
// Step 3: Group modifiers by name to find duplicates
|
|
modifiersByName = {};
|
|
for (mod in qModifiers) {
|
|
modName = mod.ItemName;
|
|
if (!structKeyExists(modifiersByName, modName)) {
|
|
modifiersByName[modName] = [];
|
|
}
|
|
arrayAppend(modifiersByName[modName], {
|
|
"ItemID": mod.ItemID,
|
|
"ParentItemID": mod.ItemParentItemID,
|
|
"ParentName": mod.ParentName,
|
|
"Price": mod.ItemPrice,
|
|
"IsDefault": mod.ItemIsCheckedByDefault,
|
|
"SortOrder": mod.ItemSortOrder
|
|
});
|
|
}
|
|
|
|
// Step 4: For each unique modifier name, pick ONE as the template
|
|
// Keep the first occurrence (usually oldest/original)
|
|
templateMap = {}; // modifierName -> templateItemID
|
|
orphanItems = []; // Items to delete later
|
|
|
|
for (modName in modifiersByName) {
|
|
instances = modifiersByName[modName];
|
|
|
|
if (arrayLen(instances) > 1) {
|
|
// Multiple instances - first one becomes template
|
|
templateItem = instances[1];
|
|
templateItemID = templateItem.ItemID;
|
|
|
|
// Mark as template
|
|
queryExecute("
|
|
UPDATE Items SET ItemIsModifierTemplate = 1 WHERE ItemID = :itemID
|
|
", { itemID: templateItemID }, { datasource: "payfrit" });
|
|
|
|
templateMap[modName] = templateItemID;
|
|
|
|
arrayAppend(response.templates, {
|
|
"name": modName,
|
|
"templateItemID": templateItemID,
|
|
"originalParent": templateItem.ParentName,
|
|
"duplicateCount": arrayLen(instances) - 1
|
|
});
|
|
|
|
// Create links for ALL parent items that had this modifier
|
|
for (i = 1; i <= arrayLen(instances); i++) {
|
|
inst = instances[i];
|
|
parentItemID = inst.ParentItemID;
|
|
|
|
// Insert link (ignore duplicates)
|
|
try {
|
|
queryExecute("
|
|
INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
|
|
VALUES (:itemID, :templateID, :sortOrder)
|
|
ON DUPLICATE KEY UPDATE SortOrder = :sortOrder
|
|
", {
|
|
itemID: parentItemID,
|
|
templateID: templateItemID,
|
|
sortOrder: inst.SortOrder
|
|
}, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.links, {
|
|
"parentItemID": parentItemID,
|
|
"parentName": inst.ParentName,
|
|
"templateItemID": templateItemID,
|
|
"templateName": modName
|
|
});
|
|
} catch (any linkErr) {
|
|
// Link already exists, ignore
|
|
}
|
|
|
|
// Mark duplicates (not the template) as orphans
|
|
if (i > 1) {
|
|
arrayAppend(orphanItems, {
|
|
"ItemID": inst.ItemID,
|
|
"ItemName": modName,
|
|
"WasUnder": inst.ParentName
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
// Single instance - still mark as template for consistency
|
|
singleItem = instances[1];
|
|
queryExecute("
|
|
UPDATE Items SET ItemIsModifierTemplate = 1 WHERE ItemID = :itemID
|
|
", { itemID: singleItem.ItemID }, { datasource: "payfrit" });
|
|
|
|
// Create link
|
|
queryExecute("
|
|
INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
|
|
VALUES (:itemID, :templateID, :sortOrder)
|
|
ON DUPLICATE KEY UPDATE SortOrder = :sortOrder
|
|
", {
|
|
itemID: singleItem.ParentItemID,
|
|
templateID: singleItem.ItemID,
|
|
sortOrder: singleItem.SortOrder
|
|
}, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.templates, {
|
|
"name": modName,
|
|
"templateItemID": singleItem.ItemID,
|
|
"originalParent": singleItem.ParentName,
|
|
"duplicateCount": 0
|
|
});
|
|
}
|
|
}
|
|
|
|
response["orphans"] = orphanItems;
|
|
response["orphanCount"] = arrayLen(orphanItems);
|
|
response["templateCount"] = arrayLen(response.templates);
|
|
response["linkCount"] = arrayLen(response.links);
|
|
|
|
arrayAppend(response.steps, "Created " & arrayLen(response.templates) & " templates");
|
|
arrayAppend(response.steps, "Created " & arrayLen(response.links) & " links");
|
|
arrayAppend(response.steps, "Identified " & arrayLen(orphanItems) & " orphan items for deletion");
|
|
|
|
response["OK"] = true;
|
|
|
|
} catch (any e) {
|
|
response["ERROR"] = e.message;
|
|
response["DETAIL"] = e.detail;
|
|
}
|
|
|
|
writeOutput(serializeJSON(response));
|
|
</cfscript>
|