payfrit-works/api/menu/saveFromBuilder.cfm
John Mizerek 1f4d06edba Add Payfrit Works (WDS) support and task completion flow
Task System:
- Tasks auto-created when KDS marks order Ready (status 3)
- Duplicate task prevention via TaskOrderID check
- Task completion now marks associated order as Completed (status 4)
- Fixed isNull() check for TaskCompletedOn (use len() instead)
- Added TaskOrderID to task queries for order linking

Worker APIs:
- api/workers/myBusinesses.cfm with GROUP BY to prevent duplicates
- api/tasks/listMine.cfm for worker's claimed tasks with filters
- api/tasks/complete.cfm updates both task and order status
- api/tasks/accept.cfm for claiming tasks

KDS/Portal:
- KDS only shows orders with status < 4
- Portal dashboard improvements

Admin/Debug:
- Debug endpoints for tasks and businesses
- Test data reset endpoint

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 14:52:04 -08:00

158 lines
6.2 KiB
Text

<cfscript>
// Save menu data from the builder UI
// Input: BusinessID, Menu (JSON structure)
// Output: { OK: true }
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");
}
// Process each category
for (cat in menu.categories) {
categoryID = 0;
// Check if it's an existing category (numeric ID) or new (temp_ prefix)
if (isNumeric(cat.id)) {
categoryID = val(cat.id);
// Update existing category
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 {
// Insert new category
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)
});
// Get the new category ID
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;
if (isNumeric(item.id)) {
itemID = val(item.id);
// Update existing item (without ImageURL which may not exist)
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 (without ImageURL which may not exist)
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;
}
// Process modifiers for this item
if (structKeyExists(item, "modifiers") && isArray(item.modifiers)) {
for (mod in item.modifiers) {
if (isNumeric(mod.id)) {
// Update existing modifier
queryExecute("
UPDATE Items
SET ItemName = :name,
ItemPrice = :price,
ItemIsCheckedByDefault = :isDefault,
ItemSortOrder = :sortOrder
WHERE ItemID = :modID
", {
modID: val(mod.id),
name: mod.name,
price: val(mod.price ?: 0),
isDefault: (mod.isDefault ?: false) ? 1 : 0,
sortOrder: val(mod.sortOrder ?: 0)
});
} else {
// Insert new modifier
queryExecute("
INSERT INTO Items (ItemBusinessID, ItemCategoryID, ItemParentItemID, ItemName, ItemPrice, ItemIsCheckedByDefault, ItemSortOrder, ItemIsActive)
VALUES (:businessID, :categoryID, :parentID, :name, :price, :isDefault, :sortOrder, 1)
", {
businessID: businessID,
categoryID: categoryID,
parentID: itemID,
name: mod.name,
price: val(mod.price ?: 0),
isDefault: (mod.isDefault ?: false) ? 1 : 0,
sortOrder: val(mod.sortOrder ?: 0)
});
}
}
}
}
}
}
response = { "OK": true };
} 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>