- Fix for...in loops on query results in getForBuilder.cfm (only iterated once) - Remove illegal var keyword from loop variables in saveWizard.cfm - Only create food running tasks for dine-in orders (skip takeaway/delivery) - Add image preview modal for modifier source images in wizard - Add data clearing utilities (clearAllData, clearOrders) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
411 lines
16 KiB
Text
411 lines
16 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
<cfsetting requesttimeout="300">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
|
|
<cfscript>
|
|
/**
|
|
* Save Wizard Data
|
|
*
|
|
* Takes the extracted menu data from the setup wizard and saves it to the database.
|
|
* This transforms the wizard format into the format expected by the import system.
|
|
*
|
|
* POST JSON:
|
|
* {
|
|
* "businessId": "existing-business-id",
|
|
* "data": {
|
|
* "business": { "name": "...", "address": "...", "phone": "...", "hours": "..." },
|
|
* "categories": [ { "name": "...", "itemCount": 0 } ],
|
|
* "modifiers": [ { "name": "...", "required": true, "options": [...] } ],
|
|
* "items": [ { "name": "...", "price": 0, "category": "...", "modifiers": [...] } ]
|
|
* }
|
|
* }
|
|
*/
|
|
|
|
response = { "OK": false, "steps": [], "errors": [] };
|
|
|
|
try {
|
|
requestBody = toString(getHttpRequestData().content);
|
|
if (!len(requestBody)) {
|
|
throw(message="No request body provided");
|
|
}
|
|
|
|
data = deserializeJSON(requestBody);
|
|
|
|
businessId = structKeyExists(data, "businessId") ? val(data.businessId) : 0;
|
|
userId = structKeyExists(data, "userId") ? val(data.userId) : 0;
|
|
wizardData = structKeyExists(data, "data") ? data.data : {};
|
|
biz = structKeyExists(wizardData, "business") ? wizardData.business : {};
|
|
|
|
// If no businessId, create a new business
|
|
if (businessId == 0) {
|
|
response.steps.append("No businessId provided - creating new business");
|
|
|
|
if (!structKeyExists(biz, "name") || !len(biz.name)) {
|
|
throw(message="Business name is required to create new business");
|
|
}
|
|
|
|
if (userId == 0) {
|
|
throw(message="userId is required to create new business");
|
|
}
|
|
|
|
// Create address record first (use extracted address fields)
|
|
addressLine1 = structKeyExists(biz, "addressLine1") ? biz.addressLine1 : "";
|
|
city = structKeyExists(biz, "city") ? biz.city : "";
|
|
state = structKeyExists(biz, "state") ? biz.state : "";
|
|
zip = structKeyExists(biz, "zip") ? biz.zip : "";
|
|
|
|
// Look up state ID from state abbreviation
|
|
stateID = 0;
|
|
if (len(state)) {
|
|
qState = queryExecute("
|
|
SELECT tt_StateID FROM tt_States WHERE tt_StateAbbreviation = :abbr
|
|
", { abbr: uCase(state) }, { datasource: "payfrit" });
|
|
|
|
if (qState.recordCount > 0) {
|
|
stateID = qState.tt_StateID;
|
|
}
|
|
}
|
|
|
|
queryExecute("
|
|
INSERT INTO Addresses (AddressLine1, AddressCity, AddressStateID, AddressZIPCode, AddressUserID, AddressTypeID, AddressAddedOn)
|
|
VALUES (:line1, :city, :stateID, :zip, :userID, :typeID, NOW())
|
|
", {
|
|
line1: len(addressLine1) ? addressLine1 : "Address pending",
|
|
city: len(city) ? city : "",
|
|
stateID: stateID,
|
|
zip: len(zip) ? zip : "",
|
|
userID: userId,
|
|
typeID: 2
|
|
}, { datasource: "payfrit" });
|
|
|
|
qNewAddr = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
|
addressId = qNewAddr.id;
|
|
response.steps.append("Created address record (ID: " & addressId & ")");
|
|
|
|
// Create new business with address link
|
|
queryExecute("
|
|
INSERT INTO Businesses (BusinessName, BusinessUserID, BusinessAddressID, BusinessDeliveryZipCodes, BusinessAddedOn)
|
|
VALUES (:name, :userId, :addressId, :deliveryZips, NOW())
|
|
", {
|
|
name: biz.name,
|
|
userId: userId,
|
|
addressId: addressId,
|
|
deliveryZips: len(zip) ? zip : ""
|
|
}, { datasource: "payfrit" });
|
|
|
|
qNewBiz = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
|
businessId = qNewBiz.id;
|
|
response.steps.append("Created new business: " & biz.name & " (ID: " & businessId & ")");
|
|
|
|
// Update address with business ID link
|
|
queryExecute("
|
|
UPDATE Addresses SET AddressBusinessID = :businessID WHERE AddressID = :addressID
|
|
", {
|
|
businessID: businessId,
|
|
addressID: addressId
|
|
}, { datasource: "payfrit" });
|
|
response.steps.append("Linked address to business");
|
|
|
|
// Save business hours from structured schedule
|
|
if (structKeyExists(biz, "hoursSchedule") && isArray(biz.hoursSchedule)) {
|
|
hoursSchedule = biz.hoursSchedule;
|
|
response.steps.append("Processing " & arrayLen(hoursSchedule) & " days of hours");
|
|
|
|
for (i = 1; i <= arrayLen(hoursSchedule); i++) {
|
|
dayData = hoursSchedule[i];
|
|
dayID = structKeyExists(dayData, "dayId") ? val(dayData.dayId) : 0;
|
|
openTime = structKeyExists(dayData, "open") ? dayData.open : "09:00";
|
|
closeTime = structKeyExists(dayData, "close") ? dayData.close : "17:00";
|
|
|
|
// Convert HH:MM to HH:MM:SS if needed
|
|
if (len(openTime) == 5) openTime = openTime & ":00";
|
|
if (len(closeTime) == 5) closeTime = closeTime & ":00";
|
|
|
|
// Insert hours record for this day
|
|
queryExecute("
|
|
INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime)
|
|
VALUES (:bizID, :dayID, :openTime, :closeTime)
|
|
", {
|
|
bizID: businessId,
|
|
dayID: dayID,
|
|
openTime: openTime,
|
|
closeTime: closeTime
|
|
}, { datasource: "payfrit" });
|
|
}
|
|
|
|
response.steps.append("Created " & arrayLen(hoursSchedule) & " hours records");
|
|
}
|
|
} else {
|
|
// Verify existing business exists
|
|
qBiz = queryExecute("
|
|
SELECT BusinessID, BusinessName FROM Businesses WHERE BusinessID = :id
|
|
", { id: businessId }, { datasource: "payfrit" });
|
|
|
|
if (qBiz.recordCount == 0) {
|
|
throw(message="Business not found: " & businessId);
|
|
}
|
|
|
|
response.steps.append("Found existing business: " & qBiz.BusinessName);
|
|
}
|
|
|
|
// Build modifier template map
|
|
// The wizard format has modifiers as simple objects, we need to create IDs
|
|
modTemplates = structKeyExists(wizardData, "modifiers") ? wizardData.modifiers : [];
|
|
templateMap = {}; // Maps modifier name to database ItemID
|
|
|
|
response.steps.append("Processing " & arrayLen(modTemplates) & " modifier templates...");
|
|
|
|
for (i = 1; i <= arrayLen(modTemplates); i++) {
|
|
tmpl = modTemplates[i];
|
|
tmplName = tmpl.name;
|
|
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
|
|
WHERE i.ItemBusinessID = :bizID
|
|
AND i.ItemName = :name
|
|
AND i.ItemParentItemID = 0
|
|
AND i.ItemCategoryID = 0
|
|
", { bizID: businessId, name: tmplName }, { datasource: "payfrit" });
|
|
|
|
if (qTmpl.recordCount > 0) {
|
|
templateItemID = qTmpl.ItemID;
|
|
response.steps.append("Template exists: " & tmplName & " (ID: " & templateItemID & ")");
|
|
} else {
|
|
// Create template as Item with ItemCategoryID=0 to mark as template
|
|
queryExecute("
|
|
INSERT INTO Items (
|
|
ItemBusinessID, ItemName, ItemParentItemID, ItemCategoryID, ItemPrice,
|
|
ItemIsActive, ItemRequiresChildSelection, ItemMaxNumSelectionReq,
|
|
ItemSortOrder
|
|
) VALUES (
|
|
:bizID, :name, 0, 0, 0, 1, :required, 1, 0
|
|
)
|
|
", {
|
|
bizID: businessId,
|
|
name: tmplName,
|
|
required: required ? 1 : 0
|
|
}, { datasource: "payfrit" });
|
|
|
|
qNewTmpl = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
|
templateItemID = qNewTmpl.id;
|
|
response.steps.append("Created template: " & tmplName & " (ID: " & templateItemID & ")");
|
|
}
|
|
|
|
templateMap[tmplName] = templateItemID;
|
|
|
|
// Create/update template options
|
|
optionOrder = 1;
|
|
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;
|
|
}
|
|
|
|
optName = opt.name;
|
|
optPrice = structKeyExists(opt, "price") ? val(opt.price) : 0;
|
|
|
|
qOpt = queryExecute("
|
|
SELECT ItemID FROM Items
|
|
WHERE ItemBusinessID = :bizID AND ItemName = :name AND ItemParentItemID = :parentID
|
|
", { bizID: businessId, name: optName, parentID: templateItemID }, { datasource: "payfrit" });
|
|
|
|
if (qOpt.recordCount == 0) {
|
|
queryExecute("
|
|
INSERT INTO Items (
|
|
ItemBusinessID, ItemName, ItemParentItemID, ItemCategoryID,
|
|
ItemPrice, ItemIsActive, ItemSortOrder
|
|
) VALUES (
|
|
:bizID, :name, :parentID, 0, :price, 1, :sortOrder
|
|
)
|
|
", {
|
|
bizID: businessId,
|
|
name: optName,
|
|
parentID: templateItemID,
|
|
price: optPrice,
|
|
sortOrder: optionOrder
|
|
}, { datasource: "payfrit" });
|
|
}
|
|
optionOrder++;
|
|
}
|
|
}
|
|
|
|
// Build category map
|
|
categories = structKeyExists(wizardData, "categories") ? wizardData.categories : [];
|
|
categoryMap = {}; // Maps category name to CategoryID
|
|
|
|
response.steps.append("Processing " & arrayLen(categories) & " categories...");
|
|
|
|
catOrder = 1;
|
|
for (c = 1; c <= arrayLen(categories); c++) {
|
|
cat = categories[c];
|
|
catName = cat.name;
|
|
|
|
// Check if category exists in Categories table
|
|
qCat = queryExecute("
|
|
SELECT CategoryID FROM Categories
|
|
WHERE CategoryBusinessID = :bizID AND CategoryName = :name
|
|
", { bizID: businessId, name: catName }, { datasource: "payfrit" });
|
|
|
|
if (qCat.recordCount > 0) {
|
|
categoryID = qCat.CategoryID;
|
|
response.steps.append("Category exists: " & catName & " (ID: " & categoryID & ")");
|
|
} else {
|
|
// Create category in Categories table
|
|
queryExecute("
|
|
INSERT INTO Categories (
|
|
CategoryBusinessID, CategoryName, CategorySortOrder
|
|
) VALUES (
|
|
:bizID, :name, :sortOrder
|
|
)
|
|
", {
|
|
bizID: businessId,
|
|
name: catName,
|
|
sortOrder: catOrder
|
|
}, { datasource: "payfrit" });
|
|
|
|
qNewCat = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
|
categoryID = qNewCat.id;
|
|
response.steps.append("Created category: " & catName & " (ID: " & categoryID & ")");
|
|
}
|
|
|
|
categoryMap[catName] = categoryID;
|
|
catOrder++;
|
|
}
|
|
|
|
// Create menu items
|
|
items = structKeyExists(wizardData, "items") ? wizardData.items : [];
|
|
|
|
response.steps.append("Processing " & arrayLen(items) & " menu items...");
|
|
|
|
totalItems = 0;
|
|
totalLinks = 0;
|
|
|
|
// Track item order within each category
|
|
categoryItemOrder = {};
|
|
|
|
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;
|
|
itemCategory = structKeyExists(item, "category") ? item.category : "";
|
|
itemModifiers = structKeyExists(item, "modifiers") ? item.modifiers : [];
|
|
|
|
// Get category ID
|
|
if (!len(itemCategory) || !structKeyExists(categoryMap, itemCategory)) {
|
|
response.steps.append("Warning: Item '" & itemName & "' has unknown category '" & itemCategory & "' - skipping");
|
|
continue;
|
|
}
|
|
|
|
categoryID = categoryMap[itemCategory];
|
|
|
|
// Track sort order within category
|
|
if (!structKeyExists(categoryItemOrder, itemCategory)) {
|
|
categoryItemOrder[itemCategory] = 1;
|
|
}
|
|
itemOrder = categoryItemOrder[itemCategory];
|
|
categoryItemOrder[itemCategory]++;
|
|
|
|
// Check if item exists
|
|
qItem = queryExecute("
|
|
SELECT ItemID FROM Items
|
|
WHERE ItemBusinessID = :bizID
|
|
AND ItemName = :name
|
|
AND ItemCategoryID = :catID
|
|
", { bizID: businessId, name: itemName, catID: categoryID }, { datasource: "payfrit" });
|
|
|
|
if (qItem.recordCount > 0) {
|
|
menuItemID = qItem.ItemID;
|
|
// Update existing item
|
|
queryExecute("
|
|
UPDATE Items SET
|
|
ItemDescription = :desc,
|
|
ItemPrice = :price,
|
|
ItemSortOrder = :sortOrder
|
|
WHERE ItemID = :id
|
|
", {
|
|
desc: itemDesc,
|
|
price: itemPrice,
|
|
sortOrder: itemOrder,
|
|
id: menuItemID
|
|
}, { datasource: "payfrit" });
|
|
} else {
|
|
queryExecute("
|
|
INSERT INTO Items (
|
|
ItemBusinessID, ItemName, ItemDescription, ItemParentItemID,
|
|
ItemCategoryID, ItemPrice, ItemIsActive, ItemSortOrder
|
|
) VALUES (
|
|
:bizID, :name, :desc, 0, :catID, :price, 1, :sortOrder
|
|
)
|
|
", {
|
|
bizID: businessId,
|
|
name: itemName,
|
|
desc: itemDesc,
|
|
catID: categoryID,
|
|
price: itemPrice,
|
|
sortOrder: itemOrder
|
|
}, { datasource: "payfrit" });
|
|
|
|
qNewItem = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
|
menuItemID = qNewItem.id;
|
|
}
|
|
|
|
totalItems++;
|
|
|
|
// Link modifier templates to this item
|
|
modOrder = 1;
|
|
for (m = 1; m <= arrayLen(itemModifiers); m++) {
|
|
modName = itemModifiers[m];
|
|
if (structKeyExists(templateMap, modName)) {
|
|
templateItemID = templateMap[modName];
|
|
|
|
// Check if link exists
|
|
qLink = queryExecute("
|
|
SELECT 1 FROM ItemTemplateLinks
|
|
WHERE ItemID = :itemID AND TemplateItemID = :templateID
|
|
", { itemID: menuItemID, templateID: templateItemID }, { datasource: "payfrit" });
|
|
|
|
if (qLink.recordCount == 0) {
|
|
queryExecute("
|
|
INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
|
|
VALUES (:itemID, :templateID, :sortOrder)
|
|
", {
|
|
itemID: menuItemID,
|
|
templateID: templateItemID,
|
|
sortOrder: modOrder
|
|
}, { datasource: "payfrit" });
|
|
totalLinks++;
|
|
}
|
|
modOrder++;
|
|
}
|
|
}
|
|
}
|
|
|
|
response.steps.append("Created/updated " & totalItems & " items with " & totalLinks & " modifier links");
|
|
|
|
response.OK = true;
|
|
response.summary = {
|
|
"businessId": businessId,
|
|
"categoriesProcessed": arrayLen(categories),
|
|
"templatesProcessed": arrayLen(modTemplates),
|
|
"itemsProcessed": totalItems,
|
|
"linksCreated": totalLinks
|
|
};
|
|
|
|
} catch (any e) {
|
|
response.errors.append(e.message);
|
|
if (len(e.detail)) {
|
|
response.errors.append(e.detail);
|
|
}
|
|
}
|
|
|
|
writeOutput(serializeJSON(response));
|
|
</cfscript>
|