feat: per-station item completion on KDS
Each station can mark their items done independently. When all stations are done, the order auto-promotes to Ready (status 3) and delivery/pickup tasks are created automatically. - New markStationDone.cfm endpoint for per-station completion - Extract task creation into shared _createOrderTasks.cfm include - Add line item StatusID to listForKDS.cfm response - KDS shows per-station "Mark Station Done" button when filtered - Done items display with strikethrough and checkmark - Manager view retains full manual control (no station selected) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f3a41bf01a
commit
d822fcad5a
6 changed files with 385 additions and 162 deletions
171
api/orders/_createOrderTasks.cfm
Normal file
171
api/orders/_createOrderTasks.cfm
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
<!---
|
||||
Shared task creation logic for orders moving to status 3 (Ready).
|
||||
Expects these variables in calling scope:
|
||||
- OrderID (integer)
|
||||
- qOrder (query with BusinessID, ServicePointID, Name)
|
||||
- NewStatusID (integer)
|
||||
- oldStatusID (integer)
|
||||
Sets:
|
||||
- taskCreated (boolean)
|
||||
- cashTaskCreated (boolean)
|
||||
--->
|
||||
|
||||
<cfset taskCreated = false>
|
||||
<cfset cashTaskCreated = false>
|
||||
<cfif NewStatusID EQ 3 AND oldStatusID NEQ 3>
|
||||
<cftry>
|
||||
<!--- Get order type --->
|
||||
<cfset qOrderDetails = queryExecute("
|
||||
SELECT o.OrderTypeID
|
||||
FROM Orders o
|
||||
WHERE o.ID = ?
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfset orderTypeID = qOrderDetails.recordCount GT 0 ? val(qOrderDetails.OrderTypeID) : 1>
|
||||
|
||||
<!--- Look up task type for this business based on order type --->
|
||||
<!--- OrderTypeID: 1=dine-in, 2=takeaway, 3=delivery --->
|
||||
<cfif orderTypeID EQ 1>
|
||||
<cfset taskTypeName = "Deliver to Table">
|
||||
<cfelseif orderTypeID EQ 2>
|
||||
<cfset taskTypeName = "Order Ready for Pickup">
|
||||
<cfelseif orderTypeID EQ 3>
|
||||
<cfset taskTypeName = "Deliver to Address">
|
||||
<cfelse>
|
||||
<cfset taskTypeName = "">
|
||||
</cfif>
|
||||
|
||||
<cfset taskTypeID = 0>
|
||||
<cfif len(taskTypeName)>
|
||||
<cfset qTaskType = queryExecute("
|
||||
SELECT ID FROM tt_TaskTypes WHERE BusinessID = ? AND Name = ? LIMIT 1
|
||||
", [
|
||||
{ value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskTypeName, cfsqltype = "cf_sql_varchar" }
|
||||
], { datasource = "payfrit" })>
|
||||
<cfset taskTypeID = qTaskType.recordCount GT 0 ? qTaskType.ID : 0>
|
||||
</cfif>
|
||||
|
||||
<!--- Check if this type of task already exists for this order to prevent duplicates --->
|
||||
<cfset qExisting = queryExecute("
|
||||
SELECT ID FROM Tasks WHERE OrderID = ? AND TaskTypeID = ? LIMIT 1
|
||||
", [
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskTypeID, cfsqltype = "cf_sql_integer" }
|
||||
], { datasource = "payfrit" })>
|
||||
|
||||
<cfif qExisting.recordCount EQ 0 AND taskTypeID GT 0>
|
||||
<!--- Build task title based on order type --->
|
||||
<cfif orderTypeID EQ 1>
|
||||
<!--- Dine-in: Server delivers to service point --->
|
||||
<cfset tableName = len(qOrder.Name) ? qOrder.Name : "Table">
|
||||
<cfset taskTitle = "Deliver Order ###OrderID# to " & tableName>
|
||||
<cfset taskCategoryID = 3>
|
||||
<cfelseif orderTypeID EQ 2>
|
||||
<!--- Takeaway: Expo task to notify customer order is ready --->
|
||||
<cfset taskTitle = "Order ###OrderID# Ready for Pickup">
|
||||
<cfset taskCategoryID = 4>
|
||||
<cfelseif orderTypeID EQ 3>
|
||||
<!--- Delivery: Hand off to delivery driver --->
|
||||
<cfset taskTitle = "Deliver Order ###OrderID# to Address">
|
||||
<cfset taskCategoryID = 5>
|
||||
<cfelse>
|
||||
<cfset taskTitle = "">
|
||||
</cfif>
|
||||
|
||||
<cfif len(taskTitle)>
|
||||
<cfset queryExecute("
|
||||
INSERT INTO Tasks (
|
||||
BusinessID,
|
||||
OrderID,
|
||||
ServicePointID,
|
||||
TaskTypeID,
|
||||
CategoryID,
|
||||
Title,
|
||||
ClaimedByUserID,
|
||||
CreatedOn
|
||||
) VALUES (
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
0,
|
||||
NOW()
|
||||
)
|
||||
", [
|
||||
{ value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = qOrder.ServicePointID, cfsqltype = "cf_sql_integer", null = isNull(qOrder.ServicePointID) OR val(qOrder.ServicePointID) EQ 0 },
|
||||
{ value = taskTypeID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskCategoryID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskTitle, cfsqltype = "cf_sql_varchar" }
|
||||
], { datasource = "payfrit" })>
|
||||
<cfset taskCreated = true>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- Check for pending cash payment and create "Pay With Cash" task --->
|
||||
<cfset qCashPayment = queryExecute("
|
||||
SELECT p.PaymentPaidInCash, o.PaymentStatus, o.ServicePointID, sp.Name AS ServicePointName
|
||||
FROM Orders o
|
||||
LEFT JOIN Payments p ON p.PaymentID = o.PaymentID
|
||||
LEFT JOIN ServicePoints sp ON sp.ID = o.ServicePointID
|
||||
WHERE o.ID = ?
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfif qCashPayment.recordCount GT 0 AND val(qCashPayment.PaymentPaidInCash) GT 0 AND qCashPayment.PaymentStatus EQ "pending">
|
||||
<!--- Check if there's already an active cash task for this order --->
|
||||
<cfset qExistingCashTask = queryExecute("
|
||||
SELECT t.ID FROM Tasks t
|
||||
INNER JOIN tt_TaskTypes tt ON tt.ID = t.TaskTypeID
|
||||
WHERE t.OrderID = ? AND tt.Name LIKE '%Cash%' AND t.CompletedOn IS NULL
|
||||
LIMIT 1
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfif qExistingCashTask.recordCount EQ 0>
|
||||
<!--- Get "Pay With Cash" task type ID for this business --->
|
||||
<cfset qCashTaskType = queryExecute("
|
||||
SELECT ID FROM tt_TaskTypes WHERE BusinessID = ? AND Name LIKE '%Cash%' LIMIT 1
|
||||
", [ { value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfset cashTaskTypeID = qCashTaskType.recordCount GT 0 ? qCashTaskType.ID : 0>
|
||||
<cfset cashTaskTitle = "Pay With Cash - Order ###OrderID#">
|
||||
<cfif len(qCashPayment.ServicePointName)>
|
||||
<cfset cashTaskTitle = cashTaskTitle & " (" & qCashPayment.ServicePointName & ")">
|
||||
</cfif>
|
||||
|
||||
<cfset queryExecute("
|
||||
INSERT INTO Tasks (
|
||||
BusinessID,
|
||||
OrderID,
|
||||
TaskTypeID,
|
||||
Title,
|
||||
ClaimedByUserID,
|
||||
CreatedOn,
|
||||
ServicePointID
|
||||
) VALUES (
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
0,
|
||||
NOW(),
|
||||
?
|
||||
)
|
||||
", [
|
||||
{ value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = cashTaskTypeID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = cashTaskTitle, cfsqltype = "cf_sql_varchar" },
|
||||
{ value = qCashPayment.ServicePointID, cfsqltype = "cf_sql_integer", null = isNull(qCashPayment.ServicePointID) OR val(qCashPayment.ServicePointID) EQ 0 }
|
||||
], { datasource = "payfrit" })>
|
||||
<cfset cashTaskCreated = true>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfcatch>
|
||||
<!--- Task creation failed, but don't fail the status update --->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfif>
|
||||
|
|
@ -120,6 +120,7 @@
|
|||
oli.Quantity,
|
||||
oli.Remark,
|
||||
oli.IsDeleted,
|
||||
oli.StatusID,
|
||||
i.Name,
|
||||
i.ParentItemID,
|
||||
i.IsCheckedByDefault,
|
||||
|
|
@ -146,7 +147,8 @@
|
|||
"ParentItemID": qLineItems.ParentItemID,
|
||||
"ItemParentName": qLineItems.ItemParentName,
|
||||
"IsCheckedByDefault": qLineItems.IsCheckedByDefault,
|
||||
"StationID": qLineItems.StationID
|
||||
"StationID": qLineItems.StationID,
|
||||
"StatusID": val(qLineItems.StatusID)
|
||||
})>
|
||||
</cfloop>
|
||||
|
||||
|
|
|
|||
149
api/orders/markStationDone.cfm
Normal file
149
api/orders/markStationDone.cfm
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
<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 OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>
|
||||
<cfset StationID = val( structKeyExists(data,"StationID") ? data.StationID : 0 )>
|
||||
|
||||
<cfif OrderID LTE 0 OR StationID LTE 0>
|
||||
<cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "OrderID and StationID are required.", "DETAIL": "" })>
|
||||
</cfif>
|
||||
|
||||
<cftry>
|
||||
<!--- Verify order exists and get details --->
|
||||
<cfset qOrder = queryExecute("
|
||||
SELECT o.ID, o.StatusID, o.BusinessID, o.ServicePointID,
|
||||
sp.Name
|
||||
FROM Orders o
|
||||
LEFT JOIN ServicePoints sp ON sp.ID = o.ServicePointID
|
||||
WHERE o.ID = ?
|
||||
LIMIT 1
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfif qOrder.recordCount EQ 0>
|
||||
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found.", "DETAIL": "" })>
|
||||
</cfif>
|
||||
|
||||
<cfset oldStatusID = qOrder.StatusID>
|
||||
|
||||
<!--- Mark root line items for this station as done (StatusID = 1) --->
|
||||
<cfset queryExecute("
|
||||
UPDATE OrderLineItems oli
|
||||
INNER JOIN Items i ON i.ID = oli.ItemID
|
||||
SET oli.StatusID = 1
|
||||
WHERE oli.OrderID = ?
|
||||
AND i.StationID = ?
|
||||
AND oli.ParentOrderLineItemID = 0
|
||||
AND oli.IsDeleted = b'0'
|
||||
", [
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = StationID, cfsqltype = "cf_sql_integer" }
|
||||
], { datasource = "payfrit" })>
|
||||
|
||||
<!--- Also mark children (modifiers) of those root items as done --->
|
||||
<cfset queryExecute("
|
||||
UPDATE OrderLineItems oli
|
||||
SET oli.StatusID = 1
|
||||
WHERE oli.OrderID = ?
|
||||
AND oli.IsDeleted = b'0'
|
||||
AND oli.ParentOrderLineItemID IN (
|
||||
SELECT sub.ID FROM (
|
||||
SELECT oli2.ID
|
||||
FROM OrderLineItems oli2
|
||||
INNER JOIN Items i2 ON i2.ID = oli2.ItemID
|
||||
WHERE oli2.OrderID = ?
|
||||
AND i2.StationID = ?
|
||||
AND oli2.ParentOrderLineItemID = 0
|
||||
AND oli2.IsDeleted = b'0'
|
||||
) sub
|
||||
)
|
||||
", [
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = StationID, cfsqltype = "cf_sql_integer" }
|
||||
], { datasource = "payfrit" })>
|
||||
|
||||
<!--- If order was at status 1 (Submitted), auto-promote to status 2 (Preparing) --->
|
||||
<cfif oldStatusID EQ 1>
|
||||
<cfset queryExecute("
|
||||
UPDATE Orders
|
||||
SET StatusID = 2, LastEditedOn = NOW()
|
||||
WHERE ID = ?
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
<cfset oldStatusID = 2>
|
||||
</cfif>
|
||||
|
||||
<!--- Check if ALL station-assigned root items are now done --->
|
||||
<cfset qRemaining = queryExecute("
|
||||
SELECT COUNT(*) AS RemainingCount
|
||||
FROM OrderLineItems oli
|
||||
INNER JOIN Items i ON i.ID = oli.ItemID
|
||||
WHERE oli.OrderID = ?
|
||||
AND oli.ParentOrderLineItemID = 0
|
||||
AND oli.IsDeleted = b'0'
|
||||
AND i.StationID > 0
|
||||
AND oli.StatusID = 0
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfset allStationsDone = val(qRemaining.RemainingCount) EQ 0>
|
||||
<cfset NewStatusID = oldStatusID>
|
||||
|
||||
<!--- If all stations done, auto-promote to status 3 (Ready) and create tasks --->
|
||||
<cfif allStationsDone AND oldStatusID LT 3>
|
||||
<cfset NewStatusID = 3>
|
||||
<cfset queryExecute("
|
||||
UPDATE Orders
|
||||
SET StatusID = 3, LastEditedOn = NOW()
|
||||
WHERE ID = ?
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<!--- Create delivery/pickup tasks via shared include --->
|
||||
<cfinclude template="_createOrderTasks.cfm">
|
||||
</cfif>
|
||||
|
||||
<cfset apiAbort({
|
||||
"OK": true,
|
||||
"ERROR": "",
|
||||
"MESSAGE": "Station items marked as done.",
|
||||
"OrderID": OrderID,
|
||||
"StationID": StationID,
|
||||
"StationDone": true,
|
||||
"AllStationsDone": allStationsDone,
|
||||
"NewOrderStatusID": NewStatusID,
|
||||
"TaskCreated": allStationsDone AND isDefined("taskCreated") AND taskCreated
|
||||
})>
|
||||
|
||||
<cfcatch>
|
||||
<cfset apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "server_error",
|
||||
"MESSAGE": "Error marking station done",
|
||||
"DETAIL": cfcatch.message
|
||||
})>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
|
@ -86,165 +86,7 @@
|
|||
--->
|
||||
|
||||
<!--- Create delivery/pickup task when order moves to status 3 (Final Prep) --->
|
||||
<cfset taskCreated = false>
|
||||
<cfset cashTaskCreated = false>
|
||||
<cfif NewStatusID EQ 3 AND oldStatusID NEQ 3>
|
||||
<cftry>
|
||||
<!--- Get order type --->
|
||||
<cfset qOrderDetails = queryExecute("
|
||||
SELECT o.OrderTypeID
|
||||
FROM Orders o
|
||||
WHERE o.ID = ?
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfset orderTypeID = qOrderDetails.recordCount GT 0 ? val(qOrderDetails.OrderTypeID) : 1>
|
||||
|
||||
<!--- Look up task type for this business based on order type --->
|
||||
<!--- OrderTypeID: 1=dine-in, 2=takeaway, 3=delivery --->
|
||||
<cfif orderTypeID EQ 1>
|
||||
<cfset taskTypeName = "Deliver to Table">
|
||||
<cfelseif orderTypeID EQ 2>
|
||||
<cfset taskTypeName = "Order Ready for Pickup">
|
||||
<cfelseif orderTypeID EQ 3>
|
||||
<cfset taskTypeName = "Deliver to Address">
|
||||
<cfelse>
|
||||
<cfset taskTypeName = "">
|
||||
</cfif>
|
||||
|
||||
<cfset taskTypeID = 0>
|
||||
<cfif len(taskTypeName)>
|
||||
<cfset qTaskType = queryExecute("
|
||||
SELECT ID FROM tt_TaskTypes WHERE BusinessID = ? AND Name = ? LIMIT 1
|
||||
", [
|
||||
{ value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskTypeName, cfsqltype = "cf_sql_varchar" }
|
||||
], { datasource = "payfrit" })>
|
||||
<cfset taskTypeID = qTaskType.recordCount GT 0 ? qTaskType.ID : 0>
|
||||
</cfif>
|
||||
|
||||
<!--- Check if this type of task already exists for this order to prevent duplicates --->
|
||||
<cfset qExisting = queryExecute("
|
||||
SELECT ID FROM Tasks WHERE OrderID = ? AND TaskTypeID = ? LIMIT 1
|
||||
", [
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskTypeID, cfsqltype = "cf_sql_integer" }
|
||||
], { datasource = "payfrit" })>
|
||||
|
||||
<cfif qExisting.recordCount EQ 0 AND taskTypeID GT 0>
|
||||
<!--- Build task title based on order type --->
|
||||
<cfif orderTypeID EQ 1>
|
||||
<!--- Dine-in: Server delivers to service point --->
|
||||
<cfset tableName = len(qOrder.Name) ? qOrder.Name : "Table">
|
||||
<cfset taskTitle = "Deliver Order ###OrderID# to " & tableName>
|
||||
<cfset taskCategoryID = 3>
|
||||
<cfelseif orderTypeID EQ 2>
|
||||
<!--- Takeaway: Expo task to notify customer order is ready --->
|
||||
<cfset taskTitle = "Order ###OrderID# Ready for Pickup">
|
||||
<cfset taskCategoryID = 4> <!--- Expo/Pickup category --->
|
||||
<cfelseif orderTypeID EQ 3>
|
||||
<!--- Delivery: Hand off to delivery driver --->
|
||||
<cfset taskTitle = "Deliver Order ###OrderID# to Address">
|
||||
<cfset taskCategoryID = 5> <!--- Delivery category --->
|
||||
<cfelse>
|
||||
<cfset taskTitle = "">
|
||||
</cfif>
|
||||
|
||||
<cfif len(taskTitle)>
|
||||
<cfset queryExecute("
|
||||
INSERT INTO Tasks (
|
||||
BusinessID,
|
||||
OrderID,
|
||||
ServicePointID,
|
||||
TaskTypeID,
|
||||
CategoryID,
|
||||
Title,
|
||||
ClaimedByUserID,
|
||||
CreatedOn
|
||||
) VALUES (
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
0,
|
||||
NOW()
|
||||
)
|
||||
", [
|
||||
{ value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = qOrder.ServicePointID, cfsqltype = "cf_sql_integer", null = isNull(qOrder.ServicePointID) OR val(qOrder.ServicePointID) EQ 0 },
|
||||
{ value = taskTypeID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskCategoryID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = taskTitle, cfsqltype = "cf_sql_varchar" }
|
||||
], { datasource = "payfrit" })>
|
||||
<cfset taskCreated = true>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- Check for pending cash payment and create "Pay With Cash" task --->
|
||||
<cfset qCashPayment = queryExecute("
|
||||
SELECT p.PaymentPaidInCash, o.PaymentStatus, o.ServicePointID, sp.Name AS ServicePointName
|
||||
FROM Orders o
|
||||
LEFT JOIN Payments p ON p.PaymentID = o.PaymentID
|
||||
LEFT JOIN ServicePoints sp ON sp.ID = o.ServicePointID
|
||||
WHERE o.ID = ?
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfif qCashPayment.recordCount GT 0 AND val(qCashPayment.PaymentPaidInCash) GT 0 AND qCashPayment.PaymentStatus EQ "pending">
|
||||
<!--- Check if there's already an active cash task for this order --->
|
||||
<cfset qExistingCashTask = queryExecute("
|
||||
SELECT t.ID FROM Tasks t
|
||||
INNER JOIN tt_TaskTypes tt ON tt.ID = t.TaskTypeID
|
||||
WHERE t.OrderID = ? AND tt.Name LIKE '%Cash%' AND t.CompletedOn IS NULL
|
||||
LIMIT 1
|
||||
", [ { value = OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfif qExistingCashTask.recordCount EQ 0>
|
||||
<!--- Get "Pay With Cash" task type ID for this business --->
|
||||
<cfset qCashTaskType = queryExecute("
|
||||
SELECT ID FROM tt_TaskTypes WHERE BusinessID = ? AND Name LIKE '%Cash%' LIMIT 1
|
||||
", [ { value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||
|
||||
<cfset cashTaskTypeID = qCashTaskType.recordCount GT 0 ? qCashTaskType.ID : 0>
|
||||
<cfset cashTaskTitle = "Pay With Cash - Order ###OrderID#">
|
||||
<cfif len(qCashPayment.ServicePointName)>
|
||||
<cfset cashTaskTitle = cashTaskTitle & " (" & qCashPayment.ServicePointName & ")">
|
||||
</cfif>
|
||||
|
||||
<cfset queryExecute("
|
||||
INSERT INTO Tasks (
|
||||
BusinessID,
|
||||
OrderID,
|
||||
TaskTypeID,
|
||||
Title,
|
||||
ClaimedByUserID,
|
||||
CreatedOn,
|
||||
ServicePointID
|
||||
) VALUES (
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
0,
|
||||
NOW(),
|
||||
?
|
||||
)
|
||||
", [
|
||||
{ value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = cashTaskTypeID, cfsqltype = "cf_sql_integer" },
|
||||
{ value = cashTaskTitle, cfsqltype = "cf_sql_varchar" },
|
||||
{ value = qCashPayment.ServicePointID, cfsqltype = "cf_sql_integer", null = isNull(qCashPayment.ServicePointID) OR val(qCashPayment.ServicePointID) EQ 0 }
|
||||
], { datasource = "payfrit" })>
|
||||
<cfset cashTaskCreated = true>
|
||||
</cfif><!--- qExistingCashTask.recordCount EQ 0 --->
|
||||
</cfif>
|
||||
<cfcatch>
|
||||
<!--- Task creation failed, but don't fail the status update --->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfif>
|
||||
<cfinclude template="_createOrderTasks.cfm">
|
||||
|
||||
<cfset apiAbort({
|
||||
"OK": true,
|
||||
|
|
|
|||
|
|
@ -86,6 +86,16 @@
|
|||
|
||||
/* Other-station items (dimmed) */
|
||||
.line-item.other-station { opacity: 0.35; }
|
||||
|
||||
/* Station-done items */
|
||||
.line-item.line-item-done { position: relative; }
|
||||
.line-item.line-item-done .item-name { text-decoration: line-through; opacity: 0.5; }
|
||||
.line-item.line-item-done .item-qty { opacity: 0.5; }
|
||||
.line-item.line-item-done .modifiers { opacity: 0.4; }
|
||||
.line-item.line-item-done::after { content: '\2713'; position: absolute; top: 10px; right: 10px; color: #4caf50; font-weight: bold; font-size: 16px; }
|
||||
.btn-station-done { background: #002a10; color: #22c55e; border: 1px solid #22c55e33; }
|
||||
.btn-station-done:hover { background: #003a15; }
|
||||
.btn-station-done:disabled { background: #0a1a0a; color: #2a6a2a; border-color: #1a3a1a; opacity: 0.7; cursor: default; transform: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -116,6 +126,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script src="kds.js?v=6"></script>
|
||||
<script src="kds.js?v=7"></script>
|
||||
</body>
|
||||
</html>
|
||||
51
kds/kds.js
51
kds/kds.js
|
|
@ -366,8 +366,9 @@ function renderLineItem(item, allItems, isOtherStation = false) {
|
|||
const modifiers = allItems.filter(mod => mod.ParentOrderLineItemID === item.OrderLineItemID);
|
||||
console.log(`Item: ${item.Name} (ID: ${item.OrderLineItemID}) has ${modifiers.length} direct modifiers:`, modifiers.map(m => m.Name));
|
||||
const otherClass = isOtherStation ? ' other-station' : '';
|
||||
const doneClass = (item.StatusID === 1) ? ' line-item-done' : '';
|
||||
return `
|
||||
<div class="line-item${otherClass}">
|
||||
<div class="line-item${otherClass}${doneClass}">
|
||||
<div class="line-item-main">
|
||||
<div class="item-name">${escapeHtml(item.Name)}</div>
|
||||
<div class="item-qty">x${item.Quantity}</div>
|
||||
|
|
@ -433,8 +434,36 @@ function renderAllModifiers(modifiers, allItems) {
|
|||
return html;
|
||||
}
|
||||
|
||||
// Check if this station's items are all done for an order
|
||||
function isStationDoneForOrder(order) {
|
||||
if (!config.stationId || config.stationId === 0) return false;
|
||||
const stationRootItems = order.LineItems.filter(li =>
|
||||
li.ParentOrderLineItemID === 0 && (parseInt(li.StationID) || 0) === config.stationId
|
||||
);
|
||||
if (stationRootItems.length === 0) return true; // no items for this station
|
||||
return stationRootItems.every(li => li.StatusID === 1);
|
||||
}
|
||||
|
||||
// Render action buttons based on order status
|
||||
function renderActionButtons(order) {
|
||||
const isFiltering = config.stationId && config.stationId > 0;
|
||||
|
||||
if (isFiltering) {
|
||||
// Station worker view: per-station "Done" button
|
||||
if (order.StatusID === STATUS.NEW || order.StatusID === STATUS.PREPARING) {
|
||||
if (isStationDoneForOrder(order)) {
|
||||
return `<button class="btn btn-station-done" disabled>Station Done</button>`;
|
||||
} else {
|
||||
return `<button class="btn btn-station-done" onclick="markStationDone(${order.OrderID})">Mark Station Done</button>`;
|
||||
}
|
||||
}
|
||||
if (order.StatusID === STATUS.READY) {
|
||||
return `<button class="btn btn-complete" onclick="updateOrderStatus(${order.OrderID}, ${STATUS.COMPLETED})">Complete</button>`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Manager view (no station selected): whole-order buttons
|
||||
switch (order.StatusID) {
|
||||
case STATUS.NEW:
|
||||
return `<button class="btn btn-start" onclick="updateOrderStatus(${order.OrderID}, ${STATUS.PREPARING})">Start Preparing</button>`;
|
||||
|
|
@ -467,6 +496,26 @@ async function updateOrderStatus(orderId, newStatusId) {
|
|||
}
|
||||
}
|
||||
|
||||
// Mark station's items as done for an order
|
||||
async function markStationDone(orderId) {
|
||||
try {
|
||||
const response = await fetch(`${config.apiBaseUrl}/orders/markStationDone.cfm`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ OrderID: orderId, StationID: config.stationId })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.OK) {
|
||||
await loadOrders();
|
||||
} else {
|
||||
alert(`Failed to mark station done: ${data.MESSAGE || data.ERROR}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to mark station done:', error);
|
||||
alert('Failed to mark station done. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// Get location label based on order type (Table vs Type)
|
||||
|
|
|
|||
Reference in a new issue