/** * Import Business from Scraped Data * * Creates a complete business with: * - Business record with contact info * - Categories as Items (ParentID=0) * - Menu items as children of categories * - Modifier templates linked via ItemTemplateLinks * - Modifier options as children of templates * * POST JSON structure: * { * "business": { * "name": "Big Dean's Ocean Front Cafe", * "address": "1615 Ocean Front Walk", * "city": "Santa Monica", * "state": "CA", * "zip": "90401", * "phone": "(310) 393-2666", * "email": "owner@bigdeans.com", * "ownerPhone": "3101234567", * "website": "https://bigdeansoceanfrontcafe.com", * "logoUrl": "https://...", * "headerUrl": "https://...", * "hours": { * "mon": "12:00 PM - 7:00 PM", * "tue": "11:00 AM - 8:00 PM", * ... * } * }, * "categories": [ * { * "name": "World Famous Burgers", * "sortOrder": 1, * "items": [ * { * "name": "Big Dean's Burger", * "description": "The burger that made Santa Monica famous!", * "price": 12.99, * "modifiers": ["onion_style", "add_cheese"] // template references * } * ] * } * ], * "modifierTemplates": [ * { * "id": "onion_style", * "name": "Onions", * "required": true, * "maxSelections": 1, * "options": [ * { "name": "No Onions", "price": 0, "isDefault": false }, * { "name": "Grilled Onions", "price": 0, "isDefault": true }, * { "name": "Raw Onions", "price": 0, "isDefault": false } * ] * } * ], * "ownerUserID": 2, // optional, defaults to 1 * "dryRun": false // optional, if true just validates without inserting * } */ response = { "OK": false, "steps": [], "errors": [], "warnings": [] }; try { requestBody = toString(getHttpRequestData().content); if (!len(requestBody)) { throw(message="No request body provided"); } data = deserializeJSON(requestBody); dryRun = structKeyExists(data, "dryRun") && data.dryRun == true; if (dryRun) { response.steps.append("DRY RUN MODE - no changes will be made"); } // Validate required fields if (!structKeyExists(data, "business") || !structKeyExists(data.business, "name")) { throw(message="business.name is required"); } biz = data.business; ownerUserID = structKeyExists(data, "ownerUserID") ? val(data.ownerUserID) : 1; // Step 1: Create or find business response.steps.append("Step 1: Creating business record..."); qCheck = queryExecute(" SELECT BusinessID FROM Businesses WHERE BusinessName = :name ", { name: biz.name }, { datasource: "payfrit" }); if (qCheck.recordCount > 0) { BusinessID = qCheck.BusinessID; response.steps.append("Business already exists with ID: " & BusinessID); response.warnings.append("Existing business found - will add to existing menu"); } else if (!dryRun) { queryExecute(" INSERT INTO Businesses (BusinessName, BusinessUserID, BusinessAddressID, BusinessDeliveryZipCodes, BusinessAddedOn) VALUES (:name, :ownerID, 0, '', NOW()) ", { name: biz.name, ownerID: ownerUserID }, { datasource: "payfrit" }); qNew = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); BusinessID = qNew.id; response.steps.append("Created business with ID: " & BusinessID); } else { BusinessID = 0; response.steps.append("Would create business: " & biz.name); } // Step 2: Create modifier templates first (they need to exist before items reference them) response.steps.append("Step 2: Creating modifier templates..."); templateMap = {}; // Maps template string ID to database ItemID templates = structKeyExists(data, "modifierTemplates") ? data.modifierTemplates : []; for (tmpl in templates) { templateStringID = tmpl.id; templateName = tmpl.name; required = structKeyExists(tmpl, "required") && tmpl.required == true; maxSelections = structKeyExists(tmpl, "maxSelections") ? val(tmpl.maxSelections) : 0; if (!dryRun) { // Check if template already exists for this business qTmpl = queryExecute(" SELECT ItemID FROM Items WHERE ItemBusinessID = :bizID AND ItemName = :name AND ItemParentItemID = 0 AND ItemID IN (SELECT TemplateItemID FROM ItemTemplateLinks) ", { bizID: BusinessID, name: templateName }, { datasource: "payfrit" }); if (qTmpl.recordCount > 0) { templateItemID = qTmpl.ItemID; response.steps.append("Template exists: " & templateName & " (ID: " & templateItemID & ")"); } else { // Create template as Item at ParentID=0, with ItemIsCollapsible=1 to mark it as a template // This ensures the API filter excludes it from categories queryExecute(" INSERT INTO Items ( ItemBusinessID, ItemName, ItemParentItemID, ItemPrice, ItemIsActive, ItemRequiresChildSelection, ItemMaxNumSelectionReq, ItemSortOrder, ItemIsCollapsible ) VALUES ( :bizID, :name, 0, 0, 1, :required, :maxSelect, 0, 1 ) ", { bizID: BusinessID, name: templateName, required: required ? 1 : 0, maxSelect: maxSelections }, { datasource: "payfrit" }); qNewTmpl = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); templateItemID = qNewTmpl.id; response.steps.append("Created template: " & templateName & " (ID: " & templateItemID & ")"); } templateMap[templateStringID] = templateItemID; // Create template options options = structKeyExists(tmpl, "options") ? tmpl.options : []; optionOrder = 1; for (opt in options) { optName = opt.name; optPrice = structKeyExists(opt, "price") ? val(opt.price) : 0; optDefault = structKeyExists(opt, "isDefault") && opt.isDefault == true; 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, ItemPrice, ItemIsActive, ItemIsCheckedByDefault, ItemSortOrder ) VALUES ( :bizID, :name, :parentID, :price, 1, :isDefault, :sortOrder ) ", { bizID: BusinessID, name: optName, parentID: templateItemID, price: optPrice, isDefault: optDefault ? 1 : 0, sortOrder: optionOrder }, { datasource: "payfrit" }); } optionOrder++; } } else { response.steps.append("Would create template: " & templateName & " with " & arrayLen(tmpl.options) & " options"); templateMap[templateStringID] = 0; } } // Step 3: Create categories (as Items at ParentID=0) response.steps.append("Step 3: Creating categories..."); categoryMap = {}; // Maps category name to ItemID categories = structKeyExists(data, "categories") ? data.categories : []; catOrder = 1; for (cat in categories) { catName = cat.name; if (!dryRun) { // Check if category exists (Item at ParentID=0, not a template) qCat = queryExecute(" SELECT ItemID FROM Items WHERE ItemBusinessID = :bizID AND ItemName = :name AND ItemParentItemID = 0 AND ItemID NOT IN (SELECT TemplateItemID FROM ItemTemplateLinks) ", { bizID: BusinessID, name: catName }, { datasource: "payfrit" }); if (qCat.recordCount > 0) { categoryItemID = qCat.ItemID; response.steps.append("Category exists: " & catName); } else { queryExecute(" INSERT INTO Items ( ItemBusinessID, ItemName, ItemParentItemID, ItemPrice, ItemIsActive, ItemSortOrder ) VALUES ( :bizID, :name, 0, 0, 1, :sortOrder ) ", { bizID: BusinessID, name: catName, sortOrder: catOrder }, { datasource: "payfrit" }); qNewCat = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); categoryItemID = qNewCat.id; response.steps.append("Created category: " & catName & " (ID: " & categoryItemID & ")"); } categoryMap[catName] = categoryItemID; } else { response.steps.append("Would create category: " & catName); categoryMap[catName] = 0; } catOrder++; } // Step 4: Create menu items as children of categories response.steps.append("Step 4: Creating menu items..."); totalItems = 0; totalLinks = 0; for (cat in categories) { catName = cat.name; categoryItemID = categoryMap[catName]; items = structKeyExists(cat, "items") ? cat.items : []; itemOrder = 1; for (item in items) { itemName = item.name; itemDesc = structKeyExists(item, "description") ? item.description : ""; itemPrice = structKeyExists(item, "price") ? val(item.price) : 0; if (!dryRun) { // Check if item exists qItem = queryExecute(" SELECT ItemID FROM Items WHERE ItemBusinessID = :bizID AND ItemName = :name AND ItemParentItemID = :parentID ", { bizID: BusinessID, name: itemName, parentID: categoryItemID }, { datasource: "payfrit" }); if (qItem.recordCount > 0) { menuItemID = qItem.ItemID; } else { queryExecute(" INSERT INTO Items ( ItemBusinessID, ItemName, ItemDescription, ItemParentItemID, ItemPrice, ItemIsActive, ItemSortOrder ) VALUES ( :bizID, :name, :desc, :parentID, :price, 1, :sortOrder ) ", { bizID: BusinessID, name: itemName, desc: itemDesc, parentID: categoryItemID, price: itemPrice, sortOrder: itemOrder }, { datasource: "payfrit" }); qNewItem = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); menuItemID = qNewItem.id; } // Link modifier templates to this item modifiers = structKeyExists(item, "modifiers") ? item.modifiers : []; modOrder = 1; for (modRef in modifiers) { if (structKeyExists(templateMap, modRef)) { templateItemID = templateMap[modRef]; // 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++; } else { response.warnings.append("Unknown modifier reference: " & modRef & " on item: " & itemName); } } } totalItems++; itemOrder++; } } response.steps.append("Created " & totalItems & " menu items with " & totalLinks & " template links"); // Summary response.OK = true; response.summary = { "businessID": BusinessID, "businessName": biz.name, "categoriesCreated": arrayLen(categories), "templatesCreated": arrayLen(templates), "itemsCreated": totalItems, "templateLinksCreated": totalLinks, "dryRun": dryRun }; if (dryRun) { response.steps.append("DRY RUN COMPLETE - no changes were made"); } else { response.steps.append("IMPORT COMPLETE!"); } } catch (any e) { response.errors.append(e.message); if (len(e.detail)) { response.errors.append(e.detail); } } writeOutput(serializeJSON(response));