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>
This commit is contained in:
parent
0765dc1e27
commit
1f4d06edba
22 changed files with 3866 additions and 53 deletions
|
|
@ -82,10 +82,26 @@ if (len(request._api_path)) {
|
||||||
|
|
||||||
if (findNoCase("/api/tasks/listPending.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/tasks/listPending.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
if (findNoCase("/api/tasks/accept.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/tasks/accept.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/tasks/listMine.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/tasks/complete.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
|
// Worker app endpoints
|
||||||
|
if (findNoCase("/api/workers/myBusinesses.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
// Portal endpoints
|
// Portal endpoints
|
||||||
if (findNoCase("/api/portal/stats.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/portal/stats.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
|
// Menu builder endpoints
|
||||||
|
if (findNoCase("/api/menu/getForBuilder.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/menu/saveFromBuilder.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/tasks/create.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
|
// Admin endpoints
|
||||||
|
if (findNoCase("/api/admin/resetTestData.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/admin/debugTasks.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/admin/testTaskInsert.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/admin/debugBusinesses.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
// Stripe endpoints
|
// Stripe endpoints
|
||||||
if (findNoCase("/api/stripe/onboard.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/stripe/onboard.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
if (findNoCase("/api/stripe/status.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/stripe/status.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
|
||||||
76
api/admin/debugBusinesses.cfm
Normal file
76
api/admin/debugBusinesses.cfm
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
<cfset UserID = val( structKeyExists(data,"UserID") ? data.UserID : 0 )>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<!--- Get raw employee records --- >
|
||||||
|
<cfset qEmployees = queryExecute("
|
||||||
|
SELECT e.*, b.BusinessName
|
||||||
|
FROM lt_Users_Businesses_Employees e
|
||||||
|
INNER JOIN Businesses b ON b.BusinessID = e.BusinessID
|
||||||
|
WHERE e.UserID = ?
|
||||||
|
ORDER BY b.BusinessName ASC
|
||||||
|
", [ { value = UserID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset employees = []>
|
||||||
|
<cfloop query="qEmployees">
|
||||||
|
<cfset arrayAppend(employees, {
|
||||||
|
"EmployeeID": qEmployees.EmployeeID,
|
||||||
|
"UserID": qEmployees.UserID,
|
||||||
|
"BusinessID": qEmployees.BusinessID,
|
||||||
|
"BusinessName": qEmployees.BusinessName,
|
||||||
|
"EmployeeIsActive": qEmployees.EmployeeIsActive
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- Check for duplicate businesses --- >
|
||||||
|
<cfset qDuplicates = queryExecute("
|
||||||
|
SELECT BusinessName, COUNT(*) AS cnt
|
||||||
|
FROM Businesses
|
||||||
|
GROUP BY BusinessName
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset duplicates = []>
|
||||||
|
<cfloop query="qDuplicates">
|
||||||
|
<cfset arrayAppend(duplicates, {
|
||||||
|
"BusinessName": qDuplicates.BusinessName,
|
||||||
|
"Count": qDuplicates.cnt
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfoutput>#serializeJSON({
|
||||||
|
"OK": true,
|
||||||
|
"EMPLOYEE_COUNT": arrayLen(employees),
|
||||||
|
"EMPLOYEES": employees,
|
||||||
|
"DUPLICATE_BUSINESSES": duplicates
|
||||||
|
})#</cfoutput>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfoutput>#serializeJSON({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": cfcatch.message
|
||||||
|
})#</cfoutput>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
38
api/admin/debugTasks.cfm
Normal file
38
api/admin/debugTasks.cfm
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset qDesc = queryExecute("DESCRIBE Tasks", [], { datasource = "payfrit" })>
|
||||||
|
<cfset cols = []>
|
||||||
|
<cfloop query="qDesc">
|
||||||
|
<cfset arrayAppend(cols, { "Field": qDesc.Field, "Type": qDesc.Type })>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset qCount = queryExecute("SELECT COUNT(*) AS cnt FROM Tasks", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset qAll = queryExecute("SELECT * FROM Tasks LIMIT 5", [], { datasource = "payfrit" })>
|
||||||
|
<cfset tasks = []>
|
||||||
|
<cfloop query="qAll">
|
||||||
|
<cfset row = {}>
|
||||||
|
<cfloop list="#qAll.columnList#" index="col">
|
||||||
|
<cfset row[col] = qAll[col]>
|
||||||
|
</cfloop>
|
||||||
|
<cfset arrayAppend(tasks, row)>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfoutput>#serializeJSON({
|
||||||
|
"OK": true,
|
||||||
|
"COLUMNS": cols,
|
||||||
|
"TASK_COUNT": qCount.cnt,
|
||||||
|
"SAMPLE_TASKS": tasks
|
||||||
|
})#</cfoutput>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfoutput>#serializeJSON({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": cfcatch.message,
|
||||||
|
"DETAIL": cfcatch.detail
|
||||||
|
})#</cfoutput>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
35
api/admin/debugTasksRaw.cfm
Normal file
35
api/admin/debugTasksRaw.cfm
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset qAll = queryExecute("
|
||||||
|
SELECT TaskID, TaskClaimedByUserID, TaskCompletedOn, TaskOrderID,
|
||||||
|
CASE WHEN TaskCompletedOn IS NULL THEN 'YES_NULL' ELSE 'NOT_NULL' END AS IsNull
|
||||||
|
FROM Tasks
|
||||||
|
ORDER BY TaskID DESC
|
||||||
|
", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset tasks = []>
|
||||||
|
<cfloop query="qAll">
|
||||||
|
<cfset arrayAppend(tasks, {
|
||||||
|
"TaskID": qAll.TaskID,
|
||||||
|
"TaskClaimedByUserID": qAll.TaskClaimedByUserID,
|
||||||
|
"TaskOrderID": qAll.TaskOrderID,
|
||||||
|
"TaskCompletedOn": len(trim(qAll.TaskCompletedOn)) ? toString(qAll.TaskCompletedOn) : "",
|
||||||
|
"IsNull": qAll.IsNull
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfoutput>#serializeJSON({
|
||||||
|
"OK": true,
|
||||||
|
"TASKS": tasks
|
||||||
|
})#</cfoutput>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfoutput>#serializeJSON({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": cfcatch.message
|
||||||
|
})#</cfoutput>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
57
api/admin/resetTestData.cfm
Normal file
57
api/admin/resetTestData.cfm
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<!--- Disable foreign key checks temporarily --->
|
||||||
|
<cfset queryExecute("SET FOREIGN_KEY_CHECKS = 0", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- Clear Tasks --->
|
||||||
|
<cfset queryExecute("DELETE FROM Tasks", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- Clear Payments --->
|
||||||
|
<cftry>
|
||||||
|
<cfset queryExecute("DELETE FROM Payments", [], { datasource = "payfrit" })>
|
||||||
|
<cfcatch></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
|
<!--- Clear OrderLineItemModifiers if exists --->
|
||||||
|
<cftry>
|
||||||
|
<cfset queryExecute("DELETE FROM OrderLineItemModifiers", [], { datasource = "payfrit" })>
|
||||||
|
<cfcatch></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
|
<!--- Clear OrderLineItems --->
|
||||||
|
<cfset queryExecute("DELETE FROM OrderLineItems", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- Clear Orders --->
|
||||||
|
<cfset queryExecute("DELETE FROM Orders", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- Re-enable foreign key checks --->
|
||||||
|
<cfset queryExecute("SET FOREIGN_KEY_CHECKS = 1", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- Reset auto-increment counters --->
|
||||||
|
<cfset queryExecute("ALTER TABLE Tasks AUTO_INCREMENT = 1", [], { datasource = "payfrit" })>
|
||||||
|
<cfset queryExecute("ALTER TABLE Orders AUTO_INCREMENT = 1", [], { datasource = "payfrit" })>
|
||||||
|
<cfset queryExecute("ALTER TABLE OrderLineItems AUTO_INCREMENT = 1", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": true,
|
||||||
|
"MESSAGE": "All test data cleared successfully. Orders, OrderLineItems, and Tasks tables have been reset."
|
||||||
|
})>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "reset_failed",
|
||||||
|
"MESSAGE": cfcatch.message,
|
||||||
|
"DETAIL": cfcatch.detail
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
43
api/admin/testTaskInsert.cfm
Normal file
43
api/admin/testTaskInsert.cfm
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
|
||||||
|
<cfset result = {}>
|
||||||
|
<cftry>
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
INSERT INTO Tasks (
|
||||||
|
TaskBusinessID,
|
||||||
|
TaskOrderID,
|
||||||
|
TaskClaimedByUserID,
|
||||||
|
TaskAddedOn
|
||||||
|
) VALUES (
|
||||||
|
1,
|
||||||
|
999,
|
||||||
|
0,
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
",
|
||||||
|
[],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset qCount = queryExecute("SELECT COUNT(*) AS cnt FROM Tasks", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset result = {
|
||||||
|
"OK": true,
|
||||||
|
"MESSAGE": "Task inserted successfully",
|
||||||
|
"TASK_COUNT": qCount.cnt
|
||||||
|
}>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset result = {
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": cfcatch.message,
|
||||||
|
"DETAIL": cfcatch.detail,
|
||||||
|
"TYPE": cfcatch.type
|
||||||
|
}>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
|
<cfoutput>#serializeJSON(result)#</cfoutput>
|
||||||
508
api/import/crimson_menu.cfm
Normal file
508
api/import/crimson_menu.cfm
Normal file
|
|
@ -0,0 +1,508 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
// Import Crimson Mediterranean Cookhouse menu
|
||||||
|
// This script creates the business, categories, and all items
|
||||||
|
|
||||||
|
response = { "OK": false, "steps": [], "errors": [] };
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Step 1: Create the business
|
||||||
|
response.steps.append("Creating business record...");
|
||||||
|
|
||||||
|
// Check if business already exists by name
|
||||||
|
qCheck = queryExecute("
|
||||||
|
SELECT BusinessID FROM Businesses WHERE BusinessName = :name
|
||||||
|
", { name: "Crimson Mediterranean Cookhouse" }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qCheck.recordCount > 0) {
|
||||||
|
BusinessID = qCheck.BusinessID;
|
||||||
|
response.steps.append("Business already exists with ID: " & BusinessID);
|
||||||
|
} else {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Businesses (BusinessName, BusinessOwnerUserID)
|
||||||
|
VALUES (:name, :ownerID)
|
||||||
|
", {
|
||||||
|
name: "Crimson Mediterranean Cookhouse",
|
||||||
|
ownerID: 2 // UserID 2 (John)
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
qNew = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
||||||
|
BusinessID = qNew.id;
|
||||||
|
response.steps.append("Created business with ID: " & BusinessID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Create categories
|
||||||
|
response.steps.append("Creating categories...");
|
||||||
|
|
||||||
|
categories = [
|
||||||
|
{ id: "grill", name: "From the Grill", order: 1 },
|
||||||
|
{ id: "wraps", name: "Wraps", order: 2 },
|
||||||
|
{ id: "salads", name: "Salads", order: 3 },
|
||||||
|
{ id: "sides", name: "Sides", order: 4 },
|
||||||
|
{ id: "hot_coffee", name: "Hot Coffee", order: 5 },
|
||||||
|
{ id: "iced_coffee", name: "Iced Coffee", order: 6 },
|
||||||
|
{ id: "hot_tea", name: "Hot Teas", order: 7 },
|
||||||
|
{ id: "iced_tea", name: "Iced Teas", order: 8 },
|
||||||
|
{ id: "beverages", name: "Beverages", order: 9 }
|
||||||
|
];
|
||||||
|
|
||||||
|
categoryMap = {}; // Maps category string ID to CategoryID
|
||||||
|
|
||||||
|
for (cat in categories) {
|
||||||
|
// Check if exists
|
||||||
|
qCat = queryExecute("
|
||||||
|
SELECT CategoryID FROM Categories
|
||||||
|
WHERE CategoryBusinessID = :bizID AND CategoryName = :name
|
||||||
|
", { bizID: BusinessID, name: cat.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qCat.recordCount > 0) {
|
||||||
|
categoryMap[cat.id] = qCat.CategoryID;
|
||||||
|
} else {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Categories (CategoryBusinessID, CategoryName, CategorySortOrder)
|
||||||
|
VALUES (:bizID, :name, :sortOrder)
|
||||||
|
", {
|
||||||
|
bizID: BusinessID,
|
||||||
|
name: cat.name,
|
||||||
|
sortOrder: cat.order
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
qNewCat = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
||||||
|
categoryMap[cat.id] = qNewCat.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.steps.append("Categories created/found: " & structKeyList(categoryMap));
|
||||||
|
|
||||||
|
// Step 3: Create modifier groups and their options
|
||||||
|
response.steps.append("Creating modifier groups...");
|
||||||
|
|
||||||
|
// We'll store modifier IDs for linking
|
||||||
|
modifierMap = {}; // Maps modifier string ID to ItemID
|
||||||
|
|
||||||
|
// Helper function to create a modifier group
|
||||||
|
function createModifierGroup(categoryID, parentItemID, groupName, groupID, options, required, maxSelect) {
|
||||||
|
// Check if group exists
|
||||||
|
var qMod = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = :parentID
|
||||||
|
", { catID: categoryID, name: groupName, parentID: parentItemID }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
var groupItemID = 0;
|
||||||
|
if (qMod.recordCount > 0) {
|
||||||
|
groupItemID = qMod.ItemID;
|
||||||
|
} else {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemParentItemID, ItemPrice, ItemIsActive,
|
||||||
|
ItemRequiresChildSelection, ItemMaxNumSelectionReq, ItemIsCollapsible, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :parentID, 0, 1, :required, :maxSelect, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: categoryID,
|
||||||
|
name: groupName,
|
||||||
|
parentID: parentItemID,
|
||||||
|
required: required ? 1 : 0,
|
||||||
|
maxSelect: maxSelect,
|
||||||
|
sortOrder: 100
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
var qNewMod = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
|
||||||
|
groupItemID = qNewMod.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create options
|
||||||
|
var optionOrder = 1;
|
||||||
|
for (var opt in options) {
|
||||||
|
var qOpt = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = :parentID
|
||||||
|
", { catID: categoryID, name: opt.name, parentID: groupItemID }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qOpt.recordCount == 0) {
|
||||||
|
var isDefault = (optionOrder == 1 && required) ? 1 : 0;
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemParentItemID, ItemPrice, ItemIsActive,
|
||||||
|
ItemIsCheckedByDefault, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :parentID, :price, 1, :isDefault, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: categoryID,
|
||||||
|
name: opt.name,
|
||||||
|
parentID: groupItemID,
|
||||||
|
price: opt.price_adjustment ?: 0,
|
||||||
|
isDefault: isDefault,
|
||||||
|
sortOrder: optionOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
optionOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupItemID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Create menu items
|
||||||
|
response.steps.append("Creating menu items...");
|
||||||
|
|
||||||
|
// === FROM THE GRILL ===
|
||||||
|
grillCatID = categoryMap["grill"];
|
||||||
|
grillItems = [
|
||||||
|
{ name: "Chicken Kabob (White Meat)", desc: "Served with rice & choice of sides", price: 22.00, hasSides: true },
|
||||||
|
{ name: "Chicken Kabob (Dark Meat)", desc: "Served with rice & choice of sides", price: 22.00, hasSides: true },
|
||||||
|
{ name: "Filet Mignon Kabob", desc: "Served with rice & choice of sides", price: 23.00, hasSides: true },
|
||||||
|
{ name: "Ground Sirloin Kabob", desc: "Two skewers of charbroiled seasoned ground sirloin with rice & choice of sides", price: 21.00, hasSides: true },
|
||||||
|
{ name: "Grilled Salmon", desc: "Served with rice & choice of sides", price: 23.00, hasSides: true },
|
||||||
|
{ name: "Falafel Veggie Plate", desc: "3 falafels drizzled with tahini, served with rice, choice of salad & sides", price: 20.00, hasSides: true },
|
||||||
|
{ name: "Chicken Shawarma Plate", desc: "Dark meat served with rice, lebanese turnips, tahini & choice of sides", price: 22.00, hasSides: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in grillItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: grillCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemRequiresChildSelection, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :reqChild, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: grillCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
reqChild: item.hasSides ? 1 : 0,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === WRAPS ===
|
||||||
|
wrapsCatID = categoryMap["wraps"];
|
||||||
|
wrapItems = [
|
||||||
|
{ name: "Chicken Wrap", desc: "White or dark meat wrapped with lettuce, cucumbers, tomatoes, hummus, garlic sauce & rose sauce", price: 19.50 },
|
||||||
|
{ name: "BBQ Chicken Wrap", desc: "White meat wrapped with lettuce, corn, black beans, tomatoes, scallions, tortilla strips, monterey jack cheese with herb ranch & bbq sauce", price: 19.50 },
|
||||||
|
{ name: "Filet Mignon Wrap", desc: "Wrapped with romaine lettuce, cucumbers, tomatoes, hummus, garlic sauce & rose sauce", price: 19.50 },
|
||||||
|
{ name: "Ground Sirloin Wrap", desc: "Wrapped with romaine lettuce, cucumbers, tomatoes, hummus, garlic sauce & rose sauce", price: 18.50 },
|
||||||
|
{ name: "Hummus Veggie Wrap", desc: "Wrapped with rice, lettuce, cucumbers, tomatoes, lebanese turnip, garlic sauce & rose sauce", price: 16.50 },
|
||||||
|
{ name: "Falafel Wrap", desc: "Wrapped with lettuce, cucumbers, tomatoes, lebanese turnips, tahini & garlic sauce", price: 17.50 },
|
||||||
|
{ name: "Spicy Falafel Wrap", desc: "Wrapped with lettuce, cucumbers, tomatoes, lebanese turnips, spicy tahini & garlic sauce", price: 17.50 },
|
||||||
|
{ name: "Sesame Falafel Wrap", desc: "Wrapped with romaine lettuce, broccoli, corn, red onions, cucumbers & rose sauce with sesame dressing", price: 17.50 },
|
||||||
|
{ name: "Crimson Chicken Wrap", desc: "White meat wrapped with lettuce, monterey jack cheese & tortilla strips with spicy herb ranch dressing", price: 19.50 },
|
||||||
|
{ name: "Kale Chicken Caesar Wrap", desc: "White meat with kale, romaine lettuce & shaved parmesan cheese with caesar dressing", price: 19.50 },
|
||||||
|
{ name: "Chicken Shawarma Wrap", desc: "Dark meat wrapped with lettuce, hummus, cucumbers, tomatoes, lebanese turnips, tahini & garlic sauce", price: 19.50 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in wrapItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: wrapsCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: wrapsCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === SALADS ===
|
||||||
|
saladsCatID = categoryMap["salads"];
|
||||||
|
saladItems = [
|
||||||
|
{ name: "Mediterranean Salad", desc: "Romaine lettuce, cucumbers, tomatoes, red onions, kalamata olives, garbanzo beans topped with feta cheese with balsamic vinaigrette", price: 19.50 },
|
||||||
|
{ name: "Cran-Ginger Salad", desc: "Mixed greens, tomatoes, broccoli, cucumbers, topped with strawberries, sliced almonds & dried cranberries in ginger dressing", price: 19.50 },
|
||||||
|
{ name: "Kale Salad", desc: "Red cabbage & carrots tossed in balsamic vinaigrette dressing", price: 19.50 },
|
||||||
|
{ name: "Asian Salad", desc: "Romaine lettuce, red cabbage, mandarin oranges, green onions, shredded carrots & asian noodles topped with sliced almonds in sesame dressing", price: 19.50 },
|
||||||
|
{ name: "BBQ Ranch Salad", desc: "Romaine lettuce, corn, black beans, tomatoes, scallions, fried onions & tortilla strips with monterey jack cheese in herb ranch & bbq sauce", price: 19.50 },
|
||||||
|
{ name: "Kale Caesar Salad", desc: "Kale, romaine lettuce & shaved parmesan cheese tossed in caesar dressing", price: 19.50 },
|
||||||
|
{ name: "Topolino Salad", desc: "Romaine lettuce, red onions, feta cheese, cranberries, sliced almonds & raisins in ranch vinaigrette dressing", price: 19.50 },
|
||||||
|
{ name: "Crimson Salad", desc: "Kale, romaine lettuce, avocado, corn, black beans, tomatoes, scallions, fried onions & tortilla strips with monterey jack cheese in spicy herb ranch", price: 19.50 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in saladItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: saladsCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: saladsCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === SIDES ===
|
||||||
|
sidesCatID = categoryMap["sides"];
|
||||||
|
sideItems = [
|
||||||
|
{ name: "Tomato Soup", desc: "Made fresh daily", price: 5.00 },
|
||||||
|
{ name: "Lentil Soup", desc: "Red lentils, onions, jalapeños & ginger", price: 5.00 },
|
||||||
|
{ name: "Tzatziki", desc: "Yogurt tossed with cucumbers & dried mint", price: 5.00 },
|
||||||
|
{ name: "Dolma", desc: "Grape leaves stuffed with rice & rose sauce", price: 5.00 },
|
||||||
|
{ name: "Kale Salad Side", desc: "Red cabbage & carrots in balsamic vinaigrette", price: 5.00 },
|
||||||
|
{ name: "Mini Greek Salad", desc: "Cucumbers, tomatoes, red onions & feta cheese with lemon vinaigrette", price: 5.00 },
|
||||||
|
{ name: "Mixed Green Salad", desc: "Cucumbers, tomatoes & shredded carrots with balsamic vinaigrette", price: 5.00 },
|
||||||
|
{ name: "Mini Broccoli Salad", desc: "Cucumbers, corn & red cabbage with sesame dressing", price: 5.00 },
|
||||||
|
{ name: "Shirazi Salad", desc: "Cucumbers, tomatoes, parsley with lemon vinaigrette", price: 5.00 },
|
||||||
|
{ name: "Babaganoush", desc: "Grilled eggplant, garlic, tahini, lemon juice, olive oil & sumac", price: 5.00 },
|
||||||
|
{ name: "Hummus", desc: "Chickpeas, garlic, lemon juice, & tahini topped with olive oil & paprika", price: 5.00 },
|
||||||
|
{ name: "Grilled Vegetables", desc: "Seasoned vegetables grilled to order", price: 5.00 },
|
||||||
|
{ name: "Sweet Potato Fries", desc: "Fried in non-GMO cooking oil", price: 5.00 },
|
||||||
|
{ name: "French Fries", desc: "Fried in non-GMO cooking oil", price: 5.00 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in sideItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: sidesCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: sidesCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === HOT COFFEE ===
|
||||||
|
hotCoffeeCatID = categoryMap["hot_coffee"];
|
||||||
|
hotCoffeeItems = [
|
||||||
|
{ name: "Coffee", desc: "Fresh brewed Intelligentsia coffee", price: 4.00 },
|
||||||
|
{ name: "Espresso", desc: "Single shot espresso", price: 3.50 },
|
||||||
|
{ name: "Americano", desc: "Espresso with hot water", price: 4.50 },
|
||||||
|
{ name: "Cappuccino", desc: "Espresso with steamed milk and foam", price: 5.00 },
|
||||||
|
{ name: "Latte", desc: "Espresso with steamed milk", price: 5.25 },
|
||||||
|
{ name: "Macchiato", desc: "Espresso marked with foam", price: 4.50 },
|
||||||
|
{ name: "Vanilla Latte", desc: "Espresso with steamed milk and vanilla", price: 5.50 },
|
||||||
|
{ name: "Caramel Latte", desc: "Espresso with steamed milk and caramel", price: 5.50 },
|
||||||
|
{ name: "Hazelnut Latte", desc: "Espresso with steamed milk and hazelnut", price: 5.50 },
|
||||||
|
{ name: "Honey Vanilla Latte", desc: "Espresso with steamed milk, honey and vanilla", price: 5.75 },
|
||||||
|
{ name: "Mocha Latte", desc: "Espresso with steamed milk and chocolate", price: 5.50 },
|
||||||
|
{ name: "Mocha Mint", desc: "Espresso with steamed milk, chocolate and mint", price: 5.75 },
|
||||||
|
{ name: "Hot Chocolate", desc: "Rich hot chocolate", price: 4.50 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in hotCoffeeItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: hotCoffeeCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: hotCoffeeCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ICED COFFEE ===
|
||||||
|
icedCoffeeCatID = categoryMap["iced_coffee"];
|
||||||
|
icedCoffeeItems = [
|
||||||
|
{ name: "Iced Coffee", desc: "Cold brewed Intelligentsia coffee over ice", price: 4.25 },
|
||||||
|
{ name: "Iced Espresso", desc: "Espresso over ice", price: 3.50 },
|
||||||
|
{ name: "Iced Americano", desc: "Espresso with cold water over ice", price: 4.75 },
|
||||||
|
{ name: "Iced Latte", desc: "Espresso with cold milk over ice", price: 5.25 },
|
||||||
|
{ name: "Iced Vanilla Latte", desc: "Espresso with cold milk and vanilla over ice", price: 5.75 },
|
||||||
|
{ name: "Iced Caramel Macchiato", desc: "Espresso with cold milk and caramel over ice", price: 5.75 },
|
||||||
|
{ name: "Iced Honey Vanilla Latte", desc: "Espresso with cold milk, honey and vanilla over ice", price: 6.00 },
|
||||||
|
{ name: "Iced Mocha", desc: "Espresso with cold milk and chocolate over ice", price: 5.75 },
|
||||||
|
{ name: "Iced Mocha Mint", desc: "Espresso with cold milk, chocolate and mint over ice", price: 6.00 },
|
||||||
|
{ name: "Angeleno", desc: "House specialty iced coffee drink", price: 6.00 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in icedCoffeeItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: icedCoffeeCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: icedCoffeeCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === HOT TEAS ===
|
||||||
|
hotTeaCatID = categoryMap["hot_tea"];
|
||||||
|
hotTeaItems = [
|
||||||
|
{ name: "Organic Chamomile", desc: "Soothing organic chamomile tea", price: 4.00 },
|
||||||
|
{ name: "Organic Earl Grey", desc: "Classic bergamot-infused black tea", price: 4.00 },
|
||||||
|
{ name: "Organic Hot Chai Tea", desc: "Spiced organic chai tea", price: 4.00 },
|
||||||
|
{ name: "Organic House Chai Tea Latte", desc: "Spiced chai with steamed milk", price: 5.00 },
|
||||||
|
{ name: "Organic Jasmine Green", desc: "Delicate jasmine-scented green tea", price: 4.00 },
|
||||||
|
{ name: "Organic Mint Melange", desc: "Refreshing mint tea blend", price: 4.00 },
|
||||||
|
{ name: "Matcha Green Tea Latte", desc: "Japanese matcha with steamed milk", price: 5.75 },
|
||||||
|
{ name: "Persian Latte", desc: "Traditional Persian-style tea latte", price: 5.50 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in hotTeaItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: hotTeaCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: hotTeaCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ICED TEAS ===
|
||||||
|
icedTeaCatID = categoryMap["iced_tea"];
|
||||||
|
icedTeaItems = [
|
||||||
|
{ name: "Organic Black Iced Tea", desc: "Fresh brewed organic black tea over ice", price: 4.00 },
|
||||||
|
{ name: "Organic Tropical Green Iced Tea", desc: "Tropical-infused green tea over ice", price: 4.00 },
|
||||||
|
{ name: "Organic Hibiscus Berry Iced Tea", desc: "Hibiscus and berry tea blend over ice", price: 4.00 },
|
||||||
|
{ name: "Organic Hibiscus Berry Tea Palmer", desc: "Hibiscus berry tea with lemonade", price: 4.00 },
|
||||||
|
{ name: "Organic Black Tea Palmer", desc: "Black tea with lemonade", price: 4.00 },
|
||||||
|
{ name: "Organic Tropical Green Tea Palmer", desc: "Tropical green tea with lemonade", price: 4.00 },
|
||||||
|
{ name: "Iced Organic House Chai Tea Latte", desc: "Spiced chai with cold milk over ice", price: 5.25 },
|
||||||
|
{ name: "Iced Persian Latte", desc: "Persian-style tea latte over ice", price: 5.75 },
|
||||||
|
{ name: "Iced Matcha Green Tea Latte", desc: "Japanese matcha with cold milk over ice", price: 6.00 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in icedTeaItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: icedTeaCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: icedTeaCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === BEVERAGES ===
|
||||||
|
bevCatID = categoryMap["beverages"];
|
||||||
|
bevItems = [
|
||||||
|
{ name: "Coke", desc: "Coca-Cola", price: 3.00 },
|
||||||
|
{ name: "Diet Coke", desc: "Diet Coca-Cola", price: 3.00 },
|
||||||
|
{ name: "Sprite", desc: "Lemon-lime soda", price: 3.00 },
|
||||||
|
{ name: "Sparkling Water", desc: "Carbonated mineral water", price: 3.00 },
|
||||||
|
{ name: "Orange Pellegrino", desc: "San Pellegrino Aranciata", price: 2.50 },
|
||||||
|
{ name: "Bottled Water", desc: "Still bottled water", price: 3.00 },
|
||||||
|
{ name: "Lemonade", desc: "Fresh lemonade", price: 3.00 },
|
||||||
|
{ name: "Strawberry Lemonade", desc: "Fresh lemonade with strawberry", price: 3.00 },
|
||||||
|
{ name: "Milk", desc: "Cold milk", price: 3.00 },
|
||||||
|
{ name: "Orange Juice", desc: "Fresh orange juice", price: 3.50 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemOrder = 1;
|
||||||
|
for (item in bevItems) {
|
||||||
|
qItem = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemCategoryID = :catID AND ItemName = :name AND ItemParentItemID = 0
|
||||||
|
", { catID: bevCatID, name: item.name }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qItem.recordCount == 0) {
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (ItemCategoryID, ItemName, ItemDescription, ItemParentItemID, ItemPrice,
|
||||||
|
ItemIsActive, ItemSortOrder)
|
||||||
|
VALUES (:catID, :name, :desc, 0, :price, 1, :sortOrder)
|
||||||
|
", {
|
||||||
|
catID: bevCatID,
|
||||||
|
name: item.name,
|
||||||
|
desc: item.desc,
|
||||||
|
price: item.price,
|
||||||
|
sortOrder: itemOrder
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
}
|
||||||
|
itemOrder++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total items
|
||||||
|
qCount = queryExecute("
|
||||||
|
SELECT COUNT(*) as cnt FROM Items i
|
||||||
|
INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID
|
||||||
|
WHERE c.CategoryBusinessID = :bizID
|
||||||
|
", { bizID: BusinessID }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
response.OK = true;
|
||||||
|
response.BusinessID = BusinessID;
|
||||||
|
response.totalItems = qCount.cnt;
|
||||||
|
response.steps.append("Import complete! BusinessID: " & BusinessID & ", Total items: " & qCount.cnt);
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
|
response.errors.append(e.message);
|
||||||
|
response.errors.append(e.detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
</cfscript>
|
||||||
108
api/menu/getForBuilder.cfm
Normal file
108
api/menu/getForBuilder.cfm
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
<cfscript>
|
||||||
|
// Get menu data formatted for the builder UI
|
||||||
|
// Input: BusinessID
|
||||||
|
// Output: { OK: true, MENU: { categories: [...] } }
|
||||||
|
|
||||||
|
param name="form.BusinessID" default="0";
|
||||||
|
param name="url.BusinessID" default="#form.BusinessID#";
|
||||||
|
|
||||||
|
businessID = val(url.BusinessID);
|
||||||
|
|
||||||
|
response = { "OK": false };
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (businessID == 0) {
|
||||||
|
// Try to get from request body
|
||||||
|
requestBody = toString(getHttpRequestData().content);
|
||||||
|
if (len(requestBody)) {
|
||||||
|
jsonData = deserializeJSON(requestBody);
|
||||||
|
businessID = val(jsonData.BusinessID ?: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (businessID == 0) {
|
||||||
|
throw("BusinessID is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get categories
|
||||||
|
categories = queryExecute("
|
||||||
|
SELECT CategoryID, CategoryName, CategoryDescription, CategorySortOrder
|
||||||
|
FROM Categories
|
||||||
|
WHERE CategoryBusinessID = :businessID
|
||||||
|
ORDER BY CategorySortOrder, CategoryName
|
||||||
|
", { businessID: businessID });
|
||||||
|
|
||||||
|
menuCategories = [];
|
||||||
|
|
||||||
|
for (cat in categories) {
|
||||||
|
// Get items for this category (without Tasks join which may not exist)
|
||||||
|
items = queryExecute("
|
||||||
|
SELECT i.ItemID, i.ItemName, i.ItemDescription, i.ItemPrice,
|
||||||
|
i.ItemIsActive, i.ItemSortOrder
|
||||||
|
FROM Items i
|
||||||
|
WHERE i.ItemCategoryID = :categoryID
|
||||||
|
AND i.ItemParentItemID = 0
|
||||||
|
ORDER BY i.ItemSortOrder, i.ItemName
|
||||||
|
", { categoryID: cat.CategoryID });
|
||||||
|
|
||||||
|
categoryItems = [];
|
||||||
|
|
||||||
|
for (item in items) {
|
||||||
|
// Get modifiers for this item
|
||||||
|
modifiers = queryExecute("
|
||||||
|
SELECT ItemID, ItemName, ItemPrice, ItemIsCheckedByDefault, ItemSortOrder
|
||||||
|
FROM Items
|
||||||
|
WHERE ItemParentItemID = :itemID
|
||||||
|
ORDER BY ItemSortOrder, ItemName
|
||||||
|
", { itemID: item.ItemID });
|
||||||
|
|
||||||
|
itemModifiers = [];
|
||||||
|
for (mod in modifiers) {
|
||||||
|
arrayAppend(itemModifiers, {
|
||||||
|
"id": mod.ItemID,
|
||||||
|
"name": mod.ItemName,
|
||||||
|
"price": mod.ItemPrice,
|
||||||
|
"isDefault": mod.ItemIsCheckedByDefault == 1,
|
||||||
|
"sortOrder": mod.ItemSortOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayAppend(categoryItems, {
|
||||||
|
"id": item.ItemID,
|
||||||
|
"name": item.ItemName,
|
||||||
|
"description": item.ItemDescription ?: "",
|
||||||
|
"price": item.ItemPrice,
|
||||||
|
"imageUrl": "",
|
||||||
|
"photoTaskId": "",
|
||||||
|
"modifiers": itemModifiers,
|
||||||
|
"sortOrder": item.ItemSortOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayAppend(menuCategories, {
|
||||||
|
"id": cat.CategoryID,
|
||||||
|
"name": cat.CategoryName,
|
||||||
|
"description": cat.CategoryDescription ?: "",
|
||||||
|
"sortOrder": cat.CategorySortOrder,
|
||||||
|
"items": categoryItems
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"OK": true,
|
||||||
|
"MENU": {
|
||||||
|
"categories": menuCategories
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
|
response = {
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": e.message,
|
||||||
|
"DETAIL": e.detail ?: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
cfheader(name="Content-Type", value="application/json");
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
</cfscript>
|
||||||
158
api/menu/saveFromBuilder.cfm
Normal file
158
api/menu/saveFromBuilder.cfm
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
<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>
|
||||||
|
|
@ -89,7 +89,8 @@
|
||||||
oli.OrderLineItemRemark,
|
oli.OrderLineItemRemark,
|
||||||
oli.OrderLineItemIsDeleted,
|
oli.OrderLineItemIsDeleted,
|
||||||
i.ItemName,
|
i.ItemName,
|
||||||
i.ItemParentItemID
|
i.ItemParentItemID,
|
||||||
|
i.ItemIsCheckedByDefault
|
||||||
FROM OrderLineItems oli
|
FROM OrderLineItems oli
|
||||||
INNER JOIN Items i ON i.ItemID = oli.OrderLineItemItemID
|
INNER JOIN Items i ON i.ItemID = oli.OrderLineItemItemID
|
||||||
WHERE oli.OrderLineItemOrderID = ?
|
WHERE oli.OrderLineItemOrderID = ?
|
||||||
|
|
@ -107,7 +108,8 @@
|
||||||
"OrderLineItemQuantity": qLineItems.OrderLineItemQuantity,
|
"OrderLineItemQuantity": qLineItems.OrderLineItemQuantity,
|
||||||
"OrderLineItemRemark": qLineItems.OrderLineItemRemark,
|
"OrderLineItemRemark": qLineItems.OrderLineItemRemark,
|
||||||
"ItemName": qLineItems.ItemName,
|
"ItemName": qLineItems.ItemName,
|
||||||
"ItemParentItemID": qLineItems.ItemParentItemID
|
"ItemParentItemID": qLineItems.ItemParentItemID,
|
||||||
|
"ItemIsCheckedByDefault": qLineItems.ItemIsCheckedByDefault
|
||||||
})>
|
})>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,11 @@
|
||||||
<cftry>
|
<cftry>
|
||||||
<cfset qOrder = queryExecute(
|
<cfset qOrder = queryExecute(
|
||||||
"
|
"
|
||||||
SELECT OrderID, OrderStatusID, OrderTypeID
|
SELECT o.OrderID, o.OrderStatusID, o.OrderTypeID, o.OrderBusinessID, o.OrderServicePointID,
|
||||||
FROM Orders
|
sp.ServicePointName
|
||||||
WHERE OrderID = ?
|
FROM Orders o
|
||||||
|
LEFT JOIN ServicePoints sp ON sp.ServicePointID = o.OrderServicePointID
|
||||||
|
WHERE o.OrderID = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
",
|
",
|
||||||
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
|
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,13 @@
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cftry>
|
<cftry>
|
||||||
<!--- Verify order exists --->
|
<!--- Verify order exists and get details --->
|
||||||
<cfset qOrder = queryExecute("
|
<cfset qOrder = queryExecute("
|
||||||
SELECT OrderID, OrderStatusID
|
SELECT o.OrderID, o.OrderStatusID, o.OrderBusinessID, o.OrderServicePointID,
|
||||||
FROM Orders
|
sp.ServicePointName
|
||||||
WHERE OrderID = ?
|
FROM Orders o
|
||||||
|
LEFT JOIN ServicePoints sp ON sp.ServicePointID = o.OrderServicePointID
|
||||||
|
WHERE o.OrderID = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
|
@ -47,6 +49,8 @@
|
||||||
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found.", "DETAIL": "" })>
|
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found.", "DETAIL": "" })>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
|
<cfset oldStatusID = qOrder.OrderStatusID>
|
||||||
|
|
||||||
<!--- Update status --->
|
<!--- Update status --->
|
||||||
<cfset queryExecute("
|
<cfset queryExecute("
|
||||||
UPDATE Orders
|
UPDATE Orders
|
||||||
|
|
@ -59,12 +63,49 @@
|
||||||
{ value = OrderID, cfsqltype = "cf_sql_integer" }
|
{ value = OrderID, cfsqltype = "cf_sql_integer" }
|
||||||
], { datasource = "payfrit" })>
|
], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- Create delivery task when order is marked as Ready (status 3) --->
|
||||||
|
<cfset taskCreated = false>
|
||||||
|
<cfif NewStatusID EQ 3 AND oldStatusID NEQ 3>
|
||||||
|
<cftry>
|
||||||
|
<!--- Check if task already exists for this order to prevent duplicates --->
|
||||||
|
<cfset qExisting = queryExecute("
|
||||||
|
SELECT TaskID FROM Tasks WHERE TaskOrderID = ? LIMIT 1
|
||||||
|
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfif qExisting.recordCount EQ 0>
|
||||||
|
<cfset queryExecute("
|
||||||
|
INSERT INTO Tasks (
|
||||||
|
TaskBusinessID,
|
||||||
|
TaskOrderID,
|
||||||
|
TaskTypeID,
|
||||||
|
TaskClaimedByUserID,
|
||||||
|
TaskAddedOn
|
||||||
|
) VALUES (
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
", [
|
||||||
|
{ value = qOrder.OrderBusinessID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = OrderID, cfsqltype = "cf_sql_integer" }
|
||||||
|
], { datasource = "payfrit" })>
|
||||||
|
<cfset taskCreated = true>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch>
|
||||||
|
<!--- Task creation failed, but don't fail the status update --->
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
<cfset apiAbort({
|
<cfset apiAbort({
|
||||||
"OK": true,
|
"OK": true,
|
||||||
"ERROR": "",
|
"ERROR": "",
|
||||||
"MESSAGE": "Order status updated successfully.",
|
"MESSAGE": "Order status updated successfully.",
|
||||||
"OrderID": OrderID,
|
"OrderID": OrderID,
|
||||||
"StatusID": NewStatusID
|
"StatusID": NewStatusID,
|
||||||
|
"TaskCreated": taskCreated
|
||||||
})>
|
})>
|
||||||
|
|
||||||
<cfcatch>
|
<cfcatch>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
|
|
||||||
<cfset data = readJsonBody()>
|
<cfset data = readJsonBody()>
|
||||||
<cfset TaskID = val( structKeyExists(data,"TaskID") ? data.TaskID : 0 )>
|
<cfset TaskID = val( structKeyExists(data,"TaskID") ? data.TaskID : 0 )>
|
||||||
<cfset BusinessID = val( structKeyExists(data,"BusinessID") ? data.BusinessID : 0 )>
|
|
||||||
<cfset UserID = val( structKeyExists(request,"UserID") ? request.UserID : 0 )>
|
<cfset UserID = val( structKeyExists(request,"UserID") ? request.UserID : 0 )>
|
||||||
|
|
||||||
<cfif TaskID LTE 0>
|
<cfif TaskID LTE 0>
|
||||||
|
|
@ -36,9 +35,9 @@
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cftry>
|
<cftry>
|
||||||
<!--- Verify task exists and is pending --->
|
<!--- Verify task exists and is unclaimed --->
|
||||||
<cfset qTask = queryExecute("
|
<cfset qTask = queryExecute("
|
||||||
SELECT TaskID, TaskStatusID, TaskBusinessID
|
SELECT TaskID, TaskClaimedByUserID, TaskBusinessID
|
||||||
FROM Tasks
|
FROM Tasks
|
||||||
WHERE TaskID = ?
|
WHERE TaskID = ?
|
||||||
", [ { value = TaskID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
", [ { value = TaskID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
@ -47,27 +46,26 @@
|
||||||
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Task not found." })>
|
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Task not found." })>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cfif qTask.TaskStatusID NEQ 0>
|
<cfif qTask.TaskClaimedByUserID GT 0>
|
||||||
<cfset apiAbort({ "OK": false, "ERROR": "already_accepted", "MESSAGE": "Task has already been accepted." })>
|
<cfset apiAbort({ "OK": false, "ERROR": "already_accepted", "MESSAGE": "Task has already been claimed." })>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Update task to accepted status --->
|
<!--- Update task to claimed --->
|
||||||
<cfset queryExecute("
|
<cfset queryExecute("
|
||||||
UPDATE Tasks
|
UPDATE Tasks
|
||||||
SET TaskStatusID = 1,
|
SET TaskClaimedByUserID = ?,
|
||||||
TaskAcceptedOn = NOW(),
|
TaskClaimedOn = NOW()
|
||||||
TaskAcceptedByUserID = ?
|
|
||||||
WHERE TaskID = ?
|
WHERE TaskID = ?
|
||||||
AND TaskStatusID = 0
|
AND TaskClaimedByUserID = 0
|
||||||
", [
|
", [
|
||||||
{ value = UserID, cfsqltype = "cf_sql_integer", null = (UserID LTE 0) },
|
{ value = UserID GT 0 ? UserID : 1, cfsqltype = "cf_sql_integer" },
|
||||||
{ value = TaskID, cfsqltype = "cf_sql_integer" }
|
{ value = TaskID, cfsqltype = "cf_sql_integer" }
|
||||||
], { datasource = "payfrit" })>
|
], { datasource = "payfrit" })>
|
||||||
|
|
||||||
<cfset apiAbort({
|
<cfset apiAbort({
|
||||||
"OK": true,
|
"OK": true,
|
||||||
"ERROR": "",
|
"ERROR": "",
|
||||||
"MESSAGE": "Task accepted successfully.",
|
"MESSAGE": "Task claimed successfully.",
|
||||||
"TaskID": TaskID
|
"TaskID": TaskID
|
||||||
})>
|
})>
|
||||||
|
|
||||||
|
|
@ -75,7 +73,7 @@
|
||||||
<cfset apiAbort({
|
<cfset apiAbort({
|
||||||
"OK": false,
|
"OK": false,
|
||||||
"ERROR": "server_error",
|
"ERROR": "server_error",
|
||||||
"MESSAGE": "Error accepting task",
|
"MESSAGE": "Error claiming task",
|
||||||
"DETAIL": cfcatch.message
|
"DETAIL": cfcatch.message
|
||||||
})>
|
})>
|
||||||
</cfcatch>
|
</cfcatch>
|
||||||
|
|
|
||||||
98
api/tasks/complete.cfm
Normal file
98
api/tasks/complete.cfm
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
<cfset TaskID = val( structKeyExists(data,"TaskID") ? data.TaskID : 0 )>
|
||||||
|
<!--- Get UserID from request (auth header) or from JSON body as fallback --->
|
||||||
|
<cfset UserID = val( structKeyExists(request,"UserID") ? request.UserID : (structKeyExists(data,"UserID") ? data.UserID : 0) )>
|
||||||
|
|
||||||
|
<cfif TaskID LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "TaskID is required." })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<!--- Verify task exists and is claimed by this user --->
|
||||||
|
<cfset qTask = queryExecute("
|
||||||
|
SELECT TaskID, TaskClaimedByUserID, TaskCompletedOn, TaskOrderID
|
||||||
|
FROM Tasks
|
||||||
|
WHERE TaskID = ?
|
||||||
|
", [ { value = TaskID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfif qTask.recordCount EQ 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Task not found." })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif qTask.TaskClaimedByUserID EQ 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "not_claimed", "MESSAGE": "Task has not been claimed yet." })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif UserID GT 0 AND qTask.TaskClaimedByUserID NEQ UserID>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "not_yours", "MESSAGE": "This task was claimed by someone else." })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Check if already completed - use len() to handle both NULL and empty string --->
|
||||||
|
<cfif len(trim(qTask.TaskCompletedOn)) GT 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "already_completed", "MESSAGE": "Task has already been completed." })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Mark task as completed --->
|
||||||
|
<cfset queryExecute("
|
||||||
|
UPDATE Tasks
|
||||||
|
SET TaskCompletedOn = NOW()
|
||||||
|
WHERE TaskID = ?
|
||||||
|
", [ { value = TaskID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- If task has an associated order, mark it as Completed (status 4) --->
|
||||||
|
<cfset orderUpdated = false>
|
||||||
|
<cfif qTask.TaskOrderID GT 0>
|
||||||
|
<cfset queryExecute("
|
||||||
|
UPDATE Orders
|
||||||
|
SET OrderStatusID = 4,
|
||||||
|
OrderLastEditedOn = NOW()
|
||||||
|
WHERE OrderID = ?
|
||||||
|
", [ { value = qTask.TaskOrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
<cfset orderUpdated = true>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"MESSAGE": "Task completed successfully.",
|
||||||
|
"TaskID": TaskID,
|
||||||
|
"OrderUpdated": orderUpdated
|
||||||
|
})>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "Error completing task",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
98
api/tasks/create.cfm
Normal file
98
api/tasks/create.cfm
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<cfscript>
|
||||||
|
// Create a task (e.g., photo task for menu item)
|
||||||
|
// Input: BusinessID, ItemID, TaskType, Instructions, PYTReward
|
||||||
|
// Output: { OK: true, TASK_ID: ... }
|
||||||
|
|
||||||
|
response = { "OK": false };
|
||||||
|
|
||||||
|
try {
|
||||||
|
requestBody = toString(getHttpRequestData().content);
|
||||||
|
if (!len(requestBody)) {
|
||||||
|
throw("Request body is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData = deserializeJSON(requestBody);
|
||||||
|
businessID = val(jsonData.BusinessID ?: 0);
|
||||||
|
itemID = val(jsonData.ItemID ?: 0);
|
||||||
|
taskType = jsonData.TaskType ?: "employee_photo";
|
||||||
|
instructions = jsonData.Instructions ?: "";
|
||||||
|
pytReward = val(jsonData.PYTReward ?: 0);
|
||||||
|
|
||||||
|
if (businessID == 0) {
|
||||||
|
throw("BusinessID is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get item info if itemID provided
|
||||||
|
itemName = "";
|
||||||
|
if (itemID > 0) {
|
||||||
|
itemQuery = queryExecute("
|
||||||
|
SELECT ItemName FROM Items WHERE ItemID = :itemID
|
||||||
|
", { itemID: itemID });
|
||||||
|
if (itemQuery.recordCount) {
|
||||||
|
itemName = itemQuery.ItemName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create task description
|
||||||
|
taskDescription = "";
|
||||||
|
switch(taskType) {
|
||||||
|
case "employee_photo":
|
||||||
|
taskDescription = "Take a photo of: " & itemName;
|
||||||
|
break;
|
||||||
|
case "user_photo":
|
||||||
|
taskDescription = "Submit a photo of " & itemName & " to earn " & pytReward & " PYT";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
taskDescription = instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert task
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Tasks (
|
||||||
|
TaskBusinessID,
|
||||||
|
TaskItemID,
|
||||||
|
TaskType,
|
||||||
|
TaskDescription,
|
||||||
|
TaskInstructions,
|
||||||
|
TaskPYTReward,
|
||||||
|
TaskStatus,
|
||||||
|
TaskCreatedOn
|
||||||
|
) VALUES (
|
||||||
|
:businessID,
|
||||||
|
:itemID,
|
||||||
|
:taskType,
|
||||||
|
:description,
|
||||||
|
:instructions,
|
||||||
|
:pytReward,
|
||||||
|
'pending',
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
", {
|
||||||
|
businessID: businessID,
|
||||||
|
itemID: itemID,
|
||||||
|
taskType: taskType,
|
||||||
|
description: taskDescription,
|
||||||
|
instructions: instructions,
|
||||||
|
pytReward: pytReward
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the new task ID
|
||||||
|
result = queryExecute("SELECT LAST_INSERT_ID() as newID");
|
||||||
|
taskID = result.newID;
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"OK": true,
|
||||||
|
"TASK_ID": taskID,
|
||||||
|
"MESSAGE": "Task created successfully"
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
|
response = {
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": e.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
cfheader(name="Content-Type", value="application/json");
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
</cfscript>
|
||||||
129
api/tasks/listMine.cfm
Normal file
129
api/tasks/listMine.cfm
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
<cfset UserID = val( structKeyExists(data,"UserID") ? data.UserID : 0 )>
|
||||||
|
<cfset BusinessID = val( structKeyExists(data,"BusinessID") ? data.BusinessID : 0 )>
|
||||||
|
<cfset FilterType = structKeyExists(data,"FilterType") ? lcase(toString(data.FilterType)) : "active">
|
||||||
|
|
||||||
|
<cfif UserID LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "UserID is required." })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<!--- Build WHERE clause based on filter type --->
|
||||||
|
<cfset whereClauses = ["t.TaskClaimedByUserID = ?"]>
|
||||||
|
<cfset params = [ { value = UserID, cfsqltype = "cf_sql_integer" } ]>
|
||||||
|
|
||||||
|
<!--- Filter by business if provided --->
|
||||||
|
<cfif BusinessID GT 0>
|
||||||
|
<cfset arrayAppend(whereClauses, "t.TaskBusinessID = ?")>
|
||||||
|
<cfset arrayAppend(params, { value = BusinessID, cfsqltype = "cf_sql_integer" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Filter by type: active (not completed), completed, today, week --->
|
||||||
|
<cfswitch expression="#FilterType#">
|
||||||
|
<cfcase value="active">
|
||||||
|
<cfset arrayAppend(whereClauses, "t.TaskCompletedOn IS NULL")>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="completed">
|
||||||
|
<cfset arrayAppend(whereClauses, "t.TaskCompletedOn IS NOT NULL")>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="today">
|
||||||
|
<cfset arrayAppend(whereClauses, "DATE(t.TaskClaimedOn) = CURDATE()")>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="week">
|
||||||
|
<cfset arrayAppend(whereClauses, "t.TaskClaimedOn >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)")>
|
||||||
|
</cfcase>
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<cfset whereSQL = arrayToList(whereClauses, " AND ")>
|
||||||
|
|
||||||
|
<cfset qTasks = queryExecute("
|
||||||
|
SELECT
|
||||||
|
t.TaskID,
|
||||||
|
t.TaskBusinessID,
|
||||||
|
t.TaskCategoryID,
|
||||||
|
t.TaskOrderID,
|
||||||
|
t.TaskTypeID,
|
||||||
|
t.TaskAddedOn,
|
||||||
|
t.TaskClaimedByUserID,
|
||||||
|
t.TaskClaimedOn,
|
||||||
|
t.TaskCompletedOn,
|
||||||
|
tc.TaskCategoryName,
|
||||||
|
tc.TaskCategoryColor,
|
||||||
|
b.BusinessName
|
||||||
|
FROM Tasks t
|
||||||
|
LEFT JOIN TaskCategories tc ON tc.TaskCategoryID = t.TaskCategoryID
|
||||||
|
LEFT JOIN Businesses b ON b.BusinessID = t.TaskBusinessID
|
||||||
|
WHERE #whereSQL#
|
||||||
|
ORDER BY t.TaskClaimedOn DESC
|
||||||
|
", params, { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset tasks = []>
|
||||||
|
|
||||||
|
<cfloop query="qTasks">
|
||||||
|
<cfset taskTitle = "Task ##" & qTasks.TaskID>
|
||||||
|
<cfif qTasks.TaskOrderID GT 0>
|
||||||
|
<cfset taskTitle = "Order ##" & qTasks.TaskOrderID>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset arrayAppend(tasks, {
|
||||||
|
"TaskID": qTasks.TaskID,
|
||||||
|
"TaskBusinessID": qTasks.TaskBusinessID,
|
||||||
|
"BusinessName": qTasks.BusinessName,
|
||||||
|
"TaskCategoryID": qTasks.TaskCategoryID,
|
||||||
|
"TaskTitle": taskTitle,
|
||||||
|
"TaskDetails": "",
|
||||||
|
"TaskCreatedOn": dateFormat(qTasks.TaskAddedOn, "yyyy-mm-dd") & "T" & timeFormat(qTasks.TaskAddedOn, "HH:mm:ss"),
|
||||||
|
"TaskClaimedOn": isNull(qTasks.TaskClaimedOn) ? "" : dateFormat(qTasks.TaskClaimedOn, "yyyy-mm-dd") & "T" & timeFormat(qTasks.TaskClaimedOn, "HH:mm:ss"),
|
||||||
|
"TaskCompletedOn": isNull(qTasks.TaskCompletedOn) ? "" : dateFormat(qTasks.TaskCompletedOn, "yyyy-mm-dd") & "T" & timeFormat(qTasks.TaskCompletedOn, "HH:mm:ss"),
|
||||||
|
"TaskStatusID": isNull(qTasks.TaskCompletedOn) ? 1 : 3,
|
||||||
|
"TaskSourceType": "order",
|
||||||
|
"TaskSourceID": qTasks.TaskOrderID,
|
||||||
|
"TaskCategoryName": len(trim(qTasks.TaskCategoryName)) ? qTasks.TaskCategoryName : "General",
|
||||||
|
"TaskCategoryColor": len(trim(qTasks.TaskCategoryColor)) ? qTasks.TaskCategoryColor : "##888888"
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"TASKS": tasks,
|
||||||
|
"COUNT": arrayLen(tasks)
|
||||||
|
})>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "Error loading tasks",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
@ -35,8 +35,8 @@
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cftry>
|
<cftry>
|
||||||
<!--- Build WHERE clause --->
|
<!--- Build WHERE clause - unclaimed tasks only --->
|
||||||
<cfset whereClauses = ["t.TaskBusinessID = ?", "t.TaskStatusID = 0"]>
|
<cfset whereClauses = ["t.TaskBusinessID = ?", "t.TaskClaimedByUserID = 0"]>
|
||||||
<cfset params = [ { value = BusinessID, cfsqltype = "cf_sql_integer" } ]>
|
<cfset params = [ { value = BusinessID, cfsqltype = "cf_sql_integer" } ]>
|
||||||
|
|
||||||
<!--- Filter by category if provided --->
|
<!--- Filter by category if provided --->
|
||||||
|
|
@ -52,35 +52,41 @@
|
||||||
t.TaskID,
|
t.TaskID,
|
||||||
t.TaskBusinessID,
|
t.TaskBusinessID,
|
||||||
t.TaskCategoryID,
|
t.TaskCategoryID,
|
||||||
t.TaskTitle,
|
t.TaskOrderID,
|
||||||
t.TaskDetails,
|
t.TaskTypeID,
|
||||||
t.TaskCreatedOn,
|
t.TaskAddedOn,
|
||||||
t.TaskStatusID,
|
t.TaskClaimedByUserID,
|
||||||
t.TaskSourceType,
|
|
||||||
t.TaskSourceID,
|
|
||||||
tc.TaskCategoryName,
|
tc.TaskCategoryName,
|
||||||
tc.TaskCategoryColor
|
tc.TaskCategoryColor
|
||||||
FROM Tasks t
|
FROM Tasks t
|
||||||
LEFT JOIN TaskCategories tc ON tc.TaskCategoryID = t.TaskCategoryID
|
LEFT JOIN TaskCategories tc ON tc.TaskCategoryID = t.TaskCategoryID
|
||||||
WHERE #whereSQL#
|
WHERE #whereSQL#
|
||||||
ORDER BY t.TaskCreatedOn ASC
|
ORDER BY t.TaskAddedOn ASC
|
||||||
", params, { datasource = "payfrit" })>
|
", params, { datasource = "payfrit" })>
|
||||||
|
|
||||||
<cfset tasks = []>
|
<cfset tasks = []>
|
||||||
|
|
||||||
<cfloop query="qTasks">
|
<cfloop query="qTasks">
|
||||||
|
<!--- Build title based on task type --->
|
||||||
|
<cfset taskTitle = "Task ##" & qTasks.TaskID>
|
||||||
|
<cfset taskDetails = "">
|
||||||
|
|
||||||
|
<cfif qTasks.TaskOrderID GT 0>
|
||||||
|
<cfset taskTitle = "Order ##" & qTasks.TaskOrderID>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
<cfset arrayAppend(tasks, {
|
<cfset arrayAppend(tasks, {
|
||||||
"TaskID": qTasks.TaskID,
|
"TaskID": qTasks.TaskID,
|
||||||
"TaskBusinessID": qTasks.TaskBusinessID,
|
"TaskBusinessID": qTasks.TaskBusinessID,
|
||||||
"TaskCategoryID": qTasks.TaskCategoryID,
|
"TaskCategoryID": qTasks.TaskCategoryID,
|
||||||
"TaskTitle": qTasks.TaskTitle,
|
"TaskTitle": taskTitle,
|
||||||
"TaskDetails": qTasks.TaskDetails,
|
"TaskDetails": taskDetails,
|
||||||
"TaskCreatedOn": dateFormat(qTasks.TaskCreatedOn, "yyyy-mm-dd") & "T" & timeFormat(qTasks.TaskCreatedOn, "HH:mm:ss"),
|
"TaskCreatedOn": dateFormat(qTasks.TaskAddedOn, "yyyy-mm-dd") & "T" & timeFormat(qTasks.TaskAddedOn, "HH:mm:ss"),
|
||||||
"TaskStatusID": qTasks.TaskStatusID,
|
"TaskStatusID": qTasks.TaskClaimedByUserID GT 0 ? 1 : 0,
|
||||||
"TaskSourceType": qTasks.TaskSourceType,
|
"TaskSourceType": "order",
|
||||||
"TaskSourceID": qTasks.TaskSourceID,
|
"TaskSourceID": qTasks.TaskOrderID,
|
||||||
"TaskCategoryName": qTasks.TaskCategoryName,
|
"TaskCategoryName": len(trim(qTasks.TaskCategoryName)) ? qTasks.TaskCategoryName : "General",
|
||||||
"TaskCategoryColor": qTasks.TaskCategoryColor
|
"TaskCategoryColor": len(trim(qTasks.TaskCategoryColor)) ? qTasks.TaskCategoryColor : "##888888"
|
||||||
})>
|
})>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
|
||||||
|
|
|
||||||
46
api/tasks/setup.cfm
Normal file
46
api/tasks/setup.cfm
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset queryExecute("
|
||||||
|
CREATE TABLE IF NOT EXISTS TaskCategories (
|
||||||
|
TaskCategoryID INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
TaskCategoryBusinessID INT NOT NULL,
|
||||||
|
TaskCategoryName VARCHAR(100) NOT NULL,
|
||||||
|
TaskCategoryColor VARCHAR(20) DEFAULT '#888888',
|
||||||
|
TaskCategoryIsActive BIT(1) DEFAULT b'1',
|
||||||
|
TaskCategoryAddedOn DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_business (TaskCategoryBusinessID)
|
||||||
|
)
|
||||||
|
", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<!--- Check if categories exist --->
|
||||||
|
<cfset qCheck = queryExecute("SELECT COUNT(*) AS cnt FROM TaskCategories WHERE TaskCategoryBusinessID = 17", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfif qCheck.cnt EQ 0>
|
||||||
|
<cfset queryExecute("
|
||||||
|
INSERT INTO TaskCategories (TaskCategoryBusinessID, TaskCategoryName, TaskCategoryColor) VALUES
|
||||||
|
(17, 'Delivery', '##FF5722'),
|
||||||
|
(17, 'Pickup', '##4CAF50'),
|
||||||
|
(17, 'Kitchen', '##2196F3')
|
||||||
|
", [], { datasource = "payfrit" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Add test tasks if none exist --->
|
||||||
|
<cfset qTasks = queryExecute("SELECT COUNT(*) AS cnt FROM Tasks WHERE TaskBusinessID = 17 AND TaskStatusID = 0", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfif qTasks.cnt EQ 0>
|
||||||
|
<cfset queryExecute("
|
||||||
|
INSERT INTO Tasks (TaskBusinessID, TaskCategoryID, TaskTitle, TaskDetails, TaskStatusID) VALUES
|
||||||
|
(17, 1, 'Deliver Order ##1042', 'Customer: John Smith, Address: 123 Main St', 0),
|
||||||
|
(17, 2, 'Pickup Ready - Table 5', 'Order ready for customer pickup', 0),
|
||||||
|
(17, 1, 'Deliver Order ##1043', 'Customer: Jane Doe, Address: 456 Oak Ave', 0)
|
||||||
|
", [], { datasource = "payfrit" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfoutput>{"OK": true, "MESSAGE": "Setup complete"}</cfoutput>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfoutput>{"OK": false, "ERROR": "#cfcatch.message#"}</cfoutput>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
81
api/workers/myBusinesses.cfm
Normal file
81
api/workers/myBusinesses.cfm
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
<cfset UserID = val( structKeyExists(data,"UserID") ? data.UserID : 0 )>
|
||||||
|
|
||||||
|
<cfif UserID LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "UserID is required." })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset qBusinesses = queryExecute("
|
||||||
|
SELECT
|
||||||
|
MIN(e.EmployeeID) AS EmployeeID,
|
||||||
|
e.BusinessID,
|
||||||
|
MIN(e.EmployeeStatusID) AS EmployeeStatusID,
|
||||||
|
MAX(e.EmployeeIsActive) AS EmployeeIsActive,
|
||||||
|
b.BusinessName,
|
||||||
|
(SELECT COUNT(*) FROM Tasks t WHERE t.TaskBusinessID = e.BusinessID AND t.TaskClaimedByUserID = 0) AS PendingTaskCount
|
||||||
|
FROM lt_Users_Businesses_Employees e
|
||||||
|
INNER JOIN Businesses b ON b.BusinessID = e.BusinessID
|
||||||
|
WHERE e.UserID = ? AND e.EmployeeIsActive = b'1'
|
||||||
|
GROUP BY e.BusinessID, b.BusinessName
|
||||||
|
ORDER BY b.BusinessName ASC
|
||||||
|
", [ { value = UserID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
||||||
|
<cfset businesses = []>
|
||||||
|
|
||||||
|
<cfloop query="qBusinesses">
|
||||||
|
<cfset arrayAppend(businesses, {
|
||||||
|
"EmployeeID": qBusinesses.EmployeeID,
|
||||||
|
"BusinessID": qBusinesses.BusinessID,
|
||||||
|
"BusinessName": qBusinesses.BusinessName,
|
||||||
|
"BusinessAddress": "",
|
||||||
|
"BusinessCity": "",
|
||||||
|
"EmployeeStatusID": qBusinesses.EmployeeStatusID,
|
||||||
|
"PendingTaskCount": qBusinesses.PendingTaskCount
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"BUSINESSES": businesses,
|
||||||
|
"COUNT": arrayLen(businesses)
|
||||||
|
})>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "Error loading businesses",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
@ -261,6 +261,12 @@ function renderAllModifiers(modifiers, allItems) {
|
||||||
|
|
||||||
function collectLeafModifiers(mods) {
|
function collectLeafModifiers(mods) {
|
||||||
mods.forEach(mod => {
|
mods.forEach(mod => {
|
||||||
|
// Skip default items - they weren't explicitly chosen by the customer
|
||||||
|
if (mod.ItemIsCheckedByDefault === 1 || mod.ItemIsCheckedByDefault === true || mod.ItemIsCheckedByDefault === "1") {
|
||||||
|
console.log(`[renderAllModifiers] Skipping default item: ${mod.ItemName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const children = allItems.filter(item => item.OrderLineItemParentOrderLineItemID === mod.OrderLineItemID);
|
const children = allItems.filter(item => item.OrderLineItemParentOrderLineItemID === mod.OrderLineItemID);
|
||||||
if (children.length === 0) {
|
if (children.length === 0) {
|
||||||
// This is a leaf - no children
|
// This is a leaf - no children
|
||||||
|
|
|
||||||
2252
portal/menu-builder.html
Normal file
2252
portal/menu-builder.html
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -310,13 +310,27 @@ const Portal = {
|
||||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load menu - redirect to full menu editor
|
// Load menu - show menu builder options
|
||||||
async loadMenu() {
|
async loadMenu() {
|
||||||
console.log('[Portal] Loading menu...');
|
console.log('[Portal] Loading menu...');
|
||||||
|
|
||||||
// Show link to full menu editor
|
// Show links to menu tools
|
||||||
const container = document.getElementById('menuGrid');
|
const container = document.getElementById('menuGrid');
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px;">
|
||||||
|
<div class="menu-editor-redirect">
|
||||||
|
<div class="redirect-icon">
|
||||||
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
|
||||||
|
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3>Visual Menu Builder</h3>
|
||||||
|
<p>Drag-and-drop interface to build menus. Clone items, add modifiers, create photo tasks.</p>
|
||||||
|
<a href="/portal/menu-builder.html?bid=${this.config.businessId}" class="btn btn-primary btn-lg">
|
||||||
|
Open Builder
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="menu-editor-redirect">
|
<div class="menu-editor-redirect">
|
||||||
<div class="redirect-icon">
|
<div class="redirect-icon">
|
||||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
|
@ -324,12 +338,13 @@ const Portal = {
|
||||||
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Menu Editor</h3>
|
<h3>Classic Menu Editor</h3>
|
||||||
<p>Use the full-featured menu editor to manage your categories, items, modifiers, and pricing.</p>
|
<p>Traditional form-based editor for detailed menu management.</p>
|
||||||
<a href="/index.cfm?mode=viewmenu" class="btn btn-primary btn-lg">
|
<a href="/index.cfm?mode=viewmenu" class="btn btn-secondary btn-lg">
|
||||||
Open Menu Editor
|
Open Editor
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue