- getOrCreateCart: only require ServicePointID for dine-in (OrderTypeID=1) - get.cfm + items.cfm: return OrderTypes from Businesses table - saveOrderTypes.cfm: new endpoint to save business order type config - KDS: add PICKUP/DELIVERY badges on order cards - Portal: add Order Types toggle card in settings (Dine-In always on, Takeaway toggle) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
613 lines
22 KiB
Text
613 lines
22 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
|
|
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
|
|
<cfset var raw = getHttpRequestData().content>
|
|
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
|
|
<cfreturn {}>
|
|
</cfif>
|
|
<cftry>
|
|
<cfset var data = deserializeJSON(raw)>
|
|
<cfif isStruct(data)>
|
|
<cfreturn data>
|
|
<cfelse>
|
|
<cfreturn {}>
|
|
</cfif>
|
|
<cfcatch>
|
|
<cfreturn {}>
|
|
</cfcatch>
|
|
</cftry>
|
|
</cffunction>
|
|
|
|
<cffunction name="apiAbort" access="public" returntype="void" output="true">
|
|
<cfargument name="payload" type="struct" required="true">
|
|
<cfcontent type="application/json; charset=utf-8">
|
|
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
|
|
<cfabort>
|
|
</cffunction>
|
|
|
|
<cfset data = readJsonBody()>
|
|
<cfset BusinessID = 0>
|
|
<cfif structKeyExists(data, "BusinessID")>
|
|
<cfset BusinessID = val(data.BusinessID)>
|
|
</cfif>
|
|
|
|
<!--- Optional OrderTypeID for channel filtering (1=Dine-In, 2=Takeaway, 3=Delivery) --->
|
|
<cfset OrderTypeID = 0>
|
|
<cfif structKeyExists(data, "OrderTypeID")>
|
|
<cfset OrderTypeID = val(data.OrderTypeID)>
|
|
</cfif>
|
|
|
|
<!--- Optional MenuID for menu filtering (0 = all active menus) --->
|
|
<cfset requestedMenuID = 0>
|
|
<cfif structKeyExists(data, "MenuID")>
|
|
<cfset requestedMenuID = val(data.MenuID)>
|
|
</cfif>
|
|
|
|
<cfif BusinessID LTE 0>
|
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_businessid", "MESSAGE": "BusinessID is required.", "DETAIL": "" })>
|
|
</cfif>
|
|
|
|
<!--- Get business timezone for schedule filtering --->
|
|
<cfset businessTimezone = "America/Los_Angeles">
|
|
<cftry>
|
|
<cfset qTz = queryExecute(
|
|
"SELECT Timezone FROM Businesses WHERE ID = ?",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
<cfif qTz.recordCount GT 0 AND len(trim(qTz.Timezone))>
|
|
<cfset businessTimezone = qTz.Timezone>
|
|
</cfif>
|
|
<cfcatch><!--- Column might not exist yet, use default ---></cfcatch>
|
|
</cftry>
|
|
|
|
<!--- Get current time and day in business timezone for schedule filtering --->
|
|
<cfset currentTime = getTimeInZone(businessTimezone)>
|
|
<cfset currentDayID = getDayInZone(businessTimezone)>
|
|
|
|
<cfset menuList = []>
|
|
<cftry>
|
|
<!--- Check if new schema is active (BusinessID column exists and has data) --->
|
|
<cfset newSchemaActive = false>
|
|
<cftry>
|
|
<cfset qCheck = queryTimed(
|
|
"SELECT COUNT(*) as cnt FROM Items WHERE BusinessID = ? AND BusinessID > 0",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
<cfset newSchemaActive = (qCheck.cnt GT 0)>
|
|
<cfcatch>
|
|
<!--- Column doesn't exist yet, use old schema --->
|
|
<cfset newSchemaActive = false>
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<cfif newSchemaActive>
|
|
<!--- NEW SCHEMA: Items have BusinessID, try Categories table first, fallback to parent Items --->
|
|
<!--- Check if Categories table has data for this business --->
|
|
<cfset hasCategoriesData = false>
|
|
<cftry>
|
|
<cfset qCatCheck = queryTimed(
|
|
"SELECT COUNT(*) as cnt FROM Categories WHERE BusinessID = ?",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
<cfset hasCategoriesData = (qCatCheck.cnt GT 0)>
|
|
<cfcatch>
|
|
<cfset hasCategoriesData = false>
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<cfif hasCategoriesData>
|
|
<!--- Use Categories table with CategoryID --->
|
|
<!--- Get all active menus for this business (for chip selector) --->
|
|
<cfset activeMenuIds = "">
|
|
<cfset menuList = []>
|
|
<cftry>
|
|
<cfset qAllMenus = queryTimed(
|
|
"
|
|
SELECT ID, Name FROM Menus
|
|
WHERE BusinessID = :bizId
|
|
AND IsActive = 1
|
|
ORDER BY SortOrder, Name
|
|
",
|
|
{
|
|
bizId: { value = BusinessID, cfsqltype = "cf_sql_integer" }
|
|
},
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
<cfif requestedMenuID GT 0>
|
|
<!--- User selected a specific menu --->
|
|
<cfset activeMenuIds = requestedMenuID>
|
|
<cfelse>
|
|
<!--- No specific menu selected — show all items from all menus --->
|
|
<cfset activeMenuIds = valueList(qAllMenus.ID)>
|
|
</cfif>
|
|
<!--- Build menu list for the response (all active menus, always) --->
|
|
<cfloop query="qAllMenus">
|
|
<cfset arrayAppend(menuList, {
|
|
"MenuID": qAllMenus.ID,
|
|
"Name": qAllMenus.Name
|
|
})>
|
|
</cfloop>
|
|
<cfcatch>
|
|
<!--- Menus table might not exist yet --->
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<!--- Get category headers as virtual items --->
|
|
<!--- Apply schedule filtering, order type filtering, and menu filtering --->
|
|
<cfset qCategories = queryTimed(
|
|
"
|
|
SELECT
|
|
ID,
|
|
Name,
|
|
SortOrder,
|
|
OrderTypes,
|
|
ParentCategoryID,
|
|
ScheduleStart,
|
|
ScheduleEnd,
|
|
ScheduleDays,
|
|
MenuID
|
|
FROM Categories
|
|
WHERE BusinessID = :bizId
|
|
AND (
|
|
:orderTypeId = 0
|
|
OR FIND_IN_SET(:orderTypeId, OrderTypes) > 0
|
|
)
|
|
AND (
|
|
ScheduleStart IS NULL
|
|
OR ScheduleEnd IS NULL
|
|
OR (
|
|
TIME(:currentTime) >= ScheduleStart
|
|
AND TIME(:currentTime) <= ScheduleEnd
|
|
)
|
|
)
|
|
AND (
|
|
ScheduleDays IS NULL
|
|
OR ScheduleDays = ''
|
|
OR FIND_IN_SET(:currentDay, ScheduleDays) > 0
|
|
)
|
|
AND (
|
|
MenuID IS NULL
|
|
OR MenuID = 0
|
|
#len(activeMenuIds) ? "OR MenuID IN (#activeMenuIds#)" : ""#
|
|
)
|
|
ORDER BY SortOrder
|
|
",
|
|
{
|
|
bizId: { value = BusinessID, cfsqltype = "cf_sql_integer" },
|
|
orderTypeId: { value = OrderTypeID, cfsqltype = "cf_sql_integer" },
|
|
currentTime: { value = currentTime, cfsqltype = "cf_sql_varchar" },
|
|
currentDay: { value = currentDayID, cfsqltype = "cf_sql_integer" }
|
|
},
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
|
|
<!--- Get menu items --->
|
|
<!--- Exclude old-style category headers (ParentID=0 AND CategoryID=0 AND no children with CategoryID>0) --->
|
|
<!--- These are legacy category headers that should be replaced by Categories table entries --->
|
|
<!--- Only include items from visible categories (after schedule/channel filtering) --->
|
|
<cfset visibleCategoryIds = valueList(qCategories.ID)>
|
|
<cfif len(trim(visibleCategoryIds)) EQ 0>
|
|
<cfset visibleCategoryIds = "0">
|
|
</cfif>
|
|
<cfset q = queryTimed(
|
|
"
|
|
SELECT
|
|
i.ID,
|
|
i.CategoryID,
|
|
c.Name AS CategoryName,
|
|
i.Name AS ItemName,
|
|
i.Description,
|
|
i.ParentItemID,
|
|
i.Price,
|
|
i.IsActive,
|
|
i.IsCheckedByDefault,
|
|
i.RequiresChildSelection,
|
|
i.MaxNumSelectionReq,
|
|
i.IsCollapsible,
|
|
i.SortOrder,
|
|
i.StationID,
|
|
s.Name AS StationName,
|
|
s.Color,
|
|
c.MenuID
|
|
FROM Items i
|
|
LEFT JOIN Categories c ON c.ID = i.CategoryID
|
|
LEFT JOIN Stations s ON s.ID = i.StationID
|
|
WHERE i.BusinessID = :bizId
|
|
AND i.IsActive = 1
|
|
AND (i.CategoryID IN (#visibleCategoryIds#) OR (i.CategoryID = 0 AND i.ParentItemID > 0))
|
|
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID)
|
|
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl2 WHERE tl2.TemplateItemID = i.ParentItemID)
|
|
AND NOT (i.ParentItemID = 0 AND i.CategoryID = 0 AND i.Price = 0)
|
|
ORDER BY COALESCE(c.SortOrder, 999), i.SortOrder, i.ID
|
|
",
|
|
{ bizId: { value = BusinessID, cfsqltype = "cf_sql_integer" } },
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
<cfelse>
|
|
<!--- Fallback: Derive categories from parent Items --->
|
|
<cfset q = queryTimed(
|
|
"
|
|
SELECT
|
|
i.ID,
|
|
CASE
|
|
WHEN i.ParentItemID = 0 AND i.IsCollapsible = 0 THEN i.ID
|
|
ELSE COALESCE(
|
|
(SELECT cat.ID FROM Items cat
|
|
WHERE cat.ID = i.ParentItemID
|
|
AND cat.ParentItemID = 0
|
|
AND cat.IsCollapsible = 0),
|
|
0
|
|
)
|
|
END as CategoryID,
|
|
CASE
|
|
WHEN i.ParentItemID = 0 AND i.IsCollapsible = 0 THEN i.Name
|
|
ELSE COALESCE(
|
|
(SELECT cat.Name FROM Items cat
|
|
WHERE cat.ID = i.ParentItemID
|
|
AND cat.ParentItemID = 0
|
|
AND cat.IsCollapsible = 0),
|
|
''
|
|
)
|
|
END as Name,
|
|
i.Name,
|
|
i.Description,
|
|
i.ParentItemID,
|
|
i.Price,
|
|
i.IsActive,
|
|
i.IsCheckedByDefault,
|
|
i.RequiresChildSelection,
|
|
i.MaxNumSelectionReq,
|
|
i.IsCollapsible,
|
|
i.SortOrder,
|
|
i.StationID,
|
|
s.Name,
|
|
s.Color
|
|
FROM Items i
|
|
LEFT JOIN Stations s ON s.ID = i.StationID
|
|
WHERE i.BusinessID = ?
|
|
AND i.IsActive = 1
|
|
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID)
|
|
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl2 WHERE tl2.TemplateItemID = i.ParentItemID)
|
|
AND (
|
|
i.ParentItemID > 0
|
|
OR (i.ParentItemID = 0 AND i.IsCollapsible = 0)
|
|
)
|
|
ORDER BY i.ParentItemID, i.SortOrder, i.ID
|
|
",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
</cfif>
|
|
<cfelse>
|
|
<!--- OLD SCHEMA: Use Categories table --->
|
|
<cfset q = queryTimed(
|
|
"
|
|
SELECT
|
|
i.ID,
|
|
i.CategoryID,
|
|
c.Name,
|
|
i.Name,
|
|
i.Description,
|
|
i.ParentItemID,
|
|
i.Price,
|
|
i.IsActive,
|
|
i.IsCheckedByDefault,
|
|
i.RequiresChildSelection,
|
|
i.MaxNumSelectionReq,
|
|
i.IsCollapsible,
|
|
i.SortOrder,
|
|
i.StationID,
|
|
s.Name,
|
|
s.Color
|
|
FROM Items i
|
|
INNER JOIN Categories c ON c.ID = i.CategoryID
|
|
LEFT JOIN Stations s ON s.ID = i.StationID
|
|
WHERE c.BusinessID = ?
|
|
ORDER BY i.ParentItemID, i.SortOrder, i.ID
|
|
",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
</cfif>
|
|
|
|
<cfset rows = []>
|
|
|
|
<!--- Build set of category IDs that actually have items --->
|
|
<cfset categoriesWithItems = {}>
|
|
<cfloop query="q">
|
|
<cfif q.CategoryID GT 0>
|
|
<cfset categoriesWithItems[q.CategoryID] = true>
|
|
</cfif>
|
|
</cfloop>
|
|
|
|
<!--- For unified schema with Categories table, add category headers --->
|
|
<!--- Include categories that have items, plus parent categories whose subcategories have items --->
|
|
<cfif newSchemaActive AND isDefined("qCategories")>
|
|
<!--- Second pass: mark parent categories of subcategories that have items --->
|
|
<cfloop query="qCategories">
|
|
<cfif val(qCategories.ParentCategoryID) GT 0
|
|
AND structKeyExists(categoriesWithItems, qCategories.ID)>
|
|
<cfset categoriesWithItems[qCategories.ParentCategoryID] = true>
|
|
</cfif>
|
|
</cfloop>
|
|
<cfloop query="qCategories">
|
|
<cfif structKeyExists(categoriesWithItems, qCategories.ID)>
|
|
<!--- Add category as a virtual parent item --->
|
|
<!--- Include top-level categories even without direct items (subcats may have items) --->
|
|
<cfset arrayAppend(rows, {
|
|
"ItemID": qCategories.ID,
|
|
"CategoryID": qCategories.ID,
|
|
"Name": qCategories.Name,
|
|
"Description": "",
|
|
"ParentItemID": 0,
|
|
"ParentCategoryID": isNull(qCategories.ParentCategoryID) ? 0 : val(qCategories.ParentCategoryID),
|
|
"Price": 0,
|
|
"IsActive": 1,
|
|
"IsCheckedByDefault": 0,
|
|
"RequiresChildSelection": 0,
|
|
"MaxNumSelectionReq": 0,
|
|
"IsCollapsible": 0,
|
|
"SortOrder": qCategories.SortOrder,
|
|
"MenuID": isNull(qCategories.MenuID) ? 0 : val(qCategories.MenuID),
|
|
"StationID": "",
|
|
"ItemName": "",
|
|
"ItemColor": ""
|
|
})>
|
|
</cfif>
|
|
</cfloop>
|
|
</cfif>
|
|
|
|
<!--- Build a set of category IDs for quick lookup --->
|
|
<cfset categoryIdSet = {}>
|
|
<cfif isDefined("qCategories")>
|
|
<cfloop query="qCategories">
|
|
<cfset categoryIdSet[qCategories.ID] = true>
|
|
</cfloop>
|
|
</cfif>
|
|
|
|
<cfloop query="q">
|
|
<!--- For unified schema with Categories: set ParentItemID to CategoryID for top-level items --->
|
|
<!--- Remap old parent IDs to category IDs --->
|
|
<cfset effectiveParentID = q.ParentItemID>
|
|
<cfif newSchemaActive AND isDefined("qCategories") AND q.CategoryID GT 0>
|
|
<cfif q.ParentItemID EQ 0>
|
|
<!--- Item has no parent but has a category - link to category --->
|
|
<cfset effectiveParentID = q.CategoryID>
|
|
<cfelseif structKeyExists(categoryIdSet, q.ParentItemID)>
|
|
<!--- Item's parent IS a category ID - this is correct, keep it --->
|
|
<cfset effectiveParentID = q.ParentItemID>
|
|
<cfelseif NOT structKeyExists(categoryIdSet, q.ParentItemID)>
|
|
<!--- Parent ID is an old-style category header - remap to CategoryID --->
|
|
<cfset effectiveParentID = q.CategoryID>
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<cfset itemMenuID = 0>
|
|
<cftry>
|
|
<cfset itemMenuID = isNull(q.MenuID) ? 0 : val(q.MenuID)>
|
|
<cfcatch><cfset itemMenuID = 0></cfcatch>
|
|
</cftry>
|
|
<!--- Use aliased columns if available, fall back to generic Name --->
|
|
<cfset itemName = "">
|
|
<cfset catName = "">
|
|
<cftry>
|
|
<cfset itemName = q.ItemName>
|
|
<cfset catName = q.CategoryName>
|
|
<cfcatch>
|
|
<!--- Fallback for old schema queries without aliases --->
|
|
<cfset itemName = q.Name>
|
|
<cfset catName = q.Name>
|
|
</cfcatch>
|
|
</cftry>
|
|
<cfset arrayAppend(rows, {
|
|
"ItemID": q.ID,
|
|
"CategoryID": q.CategoryID,
|
|
"Name": len(trim(itemName)) ? itemName : catName,
|
|
"Description": q.Description,
|
|
"ParentItemID": effectiveParentID,
|
|
"Price": q.Price,
|
|
"IsActive": q.IsActive,
|
|
"IsCheckedByDefault": q.IsCheckedByDefault,
|
|
"RequiresChildSelection": q.RequiresChildSelection,
|
|
"MaxNumSelectionReq": q.MaxNumSelectionReq,
|
|
"IsCollapsible": q.IsCollapsible,
|
|
"SortOrder": q.SortOrder,
|
|
"MenuID": itemMenuID,
|
|
"StationID": len(trim(q.StationID)) ? q.StationID : "",
|
|
"ItemName": len(trim(catName)) ? catName : "",
|
|
"ItemColor": len(trim(q.Color)) ? q.Color : ""
|
|
})>
|
|
</cfloop>
|
|
|
|
<!--- For unified schema: Add template-linked modifiers as virtual children of menu items --->
|
|
<cfif newSchemaActive>
|
|
<!--- Get template links: which menu items use which templates --->
|
|
<cfset qTemplateLinks = queryTimed(
|
|
"
|
|
SELECT
|
|
tl.ItemID as MenuItemID,
|
|
tmpl.ID as TemplateItemID,
|
|
tmpl.Name as TemplateName,
|
|
tmpl.Description as TemplateDescription,
|
|
tmpl.RequiresChildSelection as TemplateRequired,
|
|
tmpl.MaxNumSelectionReq as TemplateMaxSelections,
|
|
tmpl.IsCollapsible as TemplateIsCollapsible,
|
|
tl.SortOrder as TemplateSortOrder
|
|
FROM lt_ItemID_TemplateItemID tl
|
|
INNER JOIN Items tmpl ON tmpl.ID = tl.TemplateItemID AND tmpl.IsActive = 1
|
|
INNER JOIN Items menuItem ON menuItem.ID = tl.ItemID
|
|
WHERE menuItem.BusinessID = ?
|
|
AND menuItem.IsActive = 1
|
|
ORDER BY tl.ItemID, tl.SortOrder
|
|
",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
|
|
<!--- Get template options --->
|
|
<cfset qTemplateOptions = queryTimed(
|
|
"
|
|
SELECT DISTINCT
|
|
opt.ID as OptionItemID,
|
|
opt.ParentItemID as TemplateItemID,
|
|
opt.Name as OptionName,
|
|
opt.Description as OptionDescription,
|
|
opt.Price as OptionPrice,
|
|
opt.IsCheckedByDefault as OptionIsDefault,
|
|
opt.SortOrder as OptionSortOrder
|
|
FROM Items opt
|
|
INNER JOIN lt_ItemID_TemplateItemID tl ON tl.TemplateItemID = opt.ParentItemID
|
|
INNER JOIN Items menuItem ON menuItem.ID = tl.ItemID
|
|
WHERE menuItem.BusinessID = ?
|
|
AND menuItem.IsActive = 1
|
|
AND opt.IsActive = 1
|
|
ORDER BY opt.ParentItemID, opt.SortOrder
|
|
",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
|
|
<!--- Build template options map: templateID -> [options] --->
|
|
<cfset templateOptionsMap = {}>
|
|
<cfloop query="qTemplateOptions">
|
|
<cfif NOT structKeyExists(templateOptionsMap, qTemplateOptions.TemplateItemID)>
|
|
<cfset templateOptionsMap[qTemplateOptions.TemplateItemID] = []>
|
|
</cfif>
|
|
<cfset arrayAppend(templateOptionsMap[qTemplateOptions.TemplateItemID], {
|
|
"ItemID": qTemplateOptions.OptionItemID,
|
|
"Name": qTemplateOptions.OptionName,
|
|
"Description": qTemplateOptions.OptionDescription,
|
|
"Price": qTemplateOptions.OptionPrice,
|
|
"IsCheckedByDefault": qTemplateOptions.OptionIsDefault,
|
|
"SortOrder": qTemplateOptions.OptionSortOrder
|
|
})>
|
|
</cfloop>
|
|
|
|
<!--- Add templates and their options as virtual children --->
|
|
<!--- Use virtual IDs to make each template instance unique per menu item --->
|
|
<!--- Virtual ID format: menuItemID * 100000 + templateID for templates --->
|
|
<!--- menuItemID * 100000 + optionID for options --->
|
|
<cfset addedTemplates = {}>
|
|
<cfloop query="qTemplateLinks">
|
|
<cfset menuItemID = qTemplateLinks.MenuItemID>
|
|
<cfset templateID = qTemplateLinks.TemplateItemID>
|
|
<cfset linkKey = menuItemID & "_" & templateID>
|
|
|
|
<!--- Skip duplicates --->
|
|
<cfif structKeyExists(addedTemplates, linkKey)>
|
|
<cfcontinue>
|
|
</cfif>
|
|
<cfset addedTemplates[linkKey] = true>
|
|
|
|
<!--- Generate unique virtual ID for this template instance --->
|
|
<cfset virtualTemplateID = menuItemID * 100000 + templateID>
|
|
|
|
<!--- Add template as modifier group (child of menu item) --->
|
|
<cfset arrayAppend(rows, {
|
|
"ItemID": virtualTemplateID,
|
|
"CategoryID": 0,
|
|
"Name": "",
|
|
"Name": qTemplateLinks.TemplateName,
|
|
"Description": qTemplateLinks.TemplateDescription,
|
|
"ParentItemID": menuItemID,
|
|
"Price": 0,
|
|
"IsActive": 1,
|
|
"IsCheckedByDefault": 0,
|
|
"RequiresChildSelection": qTemplateLinks.TemplateRequired,
|
|
"MaxNumSelectionReq": qTemplateLinks.TemplateMaxSelections,
|
|
"IsCollapsible": qTemplateLinks.TemplateIsCollapsible,
|
|
"SortOrder": qTemplateLinks.TemplateSortOrder,
|
|
"StationID": "",
|
|
"ItemName": "",
|
|
"ItemColor": ""
|
|
})>
|
|
|
|
<!--- Add template options as children of virtual template --->
|
|
<cfif structKeyExists(templateOptionsMap, templateID)>
|
|
<cfloop array="#templateOptionsMap[templateID]#" index="opt">
|
|
<!--- Generate unique virtual ID for this option instance --->
|
|
<cfset virtualOptionID = menuItemID * 100000 + opt.ItemID>
|
|
<cfset arrayAppend(rows, {
|
|
"ItemID": virtualOptionID,
|
|
"CategoryID": 0,
|
|
"Name": "",
|
|
"Name": opt.Name,
|
|
"Description": opt.Description,
|
|
"ParentItemID": virtualTemplateID,
|
|
"Price": opt.Price,
|
|
"IsActive": 1,
|
|
"IsCheckedByDefault": opt.IsCheckedByDefault,
|
|
"RequiresChildSelection": 0,
|
|
"MaxNumSelectionReq": 0,
|
|
"IsCollapsible": 0,
|
|
"SortOrder": opt.SortOrder,
|
|
"StationID": "",
|
|
"ItemName": "",
|
|
"ItemColor": ""
|
|
})>
|
|
</cfloop>
|
|
</cfif>
|
|
</cfloop>
|
|
</cfif>
|
|
|
|
<!--- Get brand color, tax rate, payfrit fee, and header image for this business --->
|
|
<cfset brandColor = "">
|
|
<cfset headerImageUrl = "">
|
|
<cfset qBrand = queryTimed(
|
|
"SELECT BrandColor AS BusinessBrandColor, BrandColorLight AS BusinessBrandColorLight, TaxRate, PayfritFee, HeaderImageExtension, SessionEnabled, OrderTypes FROM Businesses WHERE ID = ?",
|
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
|
{ datasource = "payfrit" }
|
|
)>
|
|
<cfif qBrand.recordCount EQ 0>
|
|
<cfset apiAbort({ "OK": false, "ERROR": "business_not_found" })>
|
|
</cfif>
|
|
<!--- TaxRate and PayfritFee MUST be configured - no fallbacks --->
|
|
<cfif NOT isNumeric(qBrand.TaxRate)>
|
|
<cfset apiAbort({ "OK": false, "ERROR": "business_tax_rate_not_configured" })>
|
|
</cfif>
|
|
<cfif NOT isNumeric(qBrand.PayfritFee) OR qBrand.PayfritFee LTE 0>
|
|
<cfset apiAbort({ "OK": false, "ERROR": "business_payfrit_fee_not_configured" })>
|
|
</cfif>
|
|
<cfset businessTaxRate = qBrand.TaxRate>
|
|
<cfset businessPayfritFee = qBrand.PayfritFee>
|
|
<cfif len(trim(qBrand.BusinessBrandColor))>
|
|
<cfset brandColor = left(qBrand.BusinessBrandColor, 1) EQ chr(35) ? qBrand.BusinessBrandColor : chr(35) & qBrand.BusinessBrandColor>
|
|
</cfif>
|
|
<cfset brandColorLight = "">
|
|
<cfif len(trim(qBrand.BusinessBrandColorLight))>
|
|
<cfset brandColorLight = left(qBrand.BusinessBrandColorLight, 1) EQ chr(35) ? qBrand.BusinessBrandColorLight : chr(35) & qBrand.BusinessBrandColorLight>
|
|
</cfif>
|
|
<cfif len(trim(qBrand.HeaderImageExtension))>
|
|
<cfset headerImageUrl = "/uploads/headers/#BusinessID#.#qBrand.HeaderImageExtension#">
|
|
</cfif>
|
|
|
|
<cfset apiAbort({
|
|
"OK": true,
|
|
"ERROR": "",
|
|
"Items": rows,
|
|
"COUNT": arrayLen(rows),
|
|
"SCHEMA": newSchemaActive ? "unified" : "legacy",
|
|
"BRANDCOLOR": brandColor,
|
|
"BRANDCOLORLIGHT": brandColorLight,
|
|
"HEADERIMAGEURL": headerImageUrl,
|
|
"TAXRATE": val(businessTaxRate),
|
|
"PAYFRITFEE": val(businessPayfritFee),
|
|
"SESSIONENABLED": val(qBrand.SessionEnabled),
|
|
"Menus": menuList,
|
|
"SelectedMenuID": requestedMenuID,
|
|
"ORDERTYPES": len(qBrand.OrderTypes) ? qBrand.OrderTypes : "1"
|
|
})>
|
|
|
|
<cfcatch>
|
|
<cfset apiAbort({
|
|
"OK": false,
|
|
"ERROR": "server_error",
|
|
"MESSAGE": "DB error loading items",
|
|
"DETAIL": cfcatch.message
|
|
})>
|
|
</cfcatch>
|
|
</cftry>
|