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:
John Mizerek 2026-03-02 19:19:25 -08:00
parent f3a41bf01a
commit d822fcad5a
6 changed files with 385 additions and 162 deletions

View 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>

View file

@ -120,6 +120,7 @@
oli.Quantity, oli.Quantity,
oli.Remark, oli.Remark,
oli.IsDeleted, oli.IsDeleted,
oli.StatusID,
i.Name, i.Name,
i.ParentItemID, i.ParentItemID,
i.IsCheckedByDefault, i.IsCheckedByDefault,
@ -146,7 +147,8 @@
"ParentItemID": qLineItems.ParentItemID, "ParentItemID": qLineItems.ParentItemID,
"ItemParentName": qLineItems.ItemParentName, "ItemParentName": qLineItems.ItemParentName,
"IsCheckedByDefault": qLineItems.IsCheckedByDefault, "IsCheckedByDefault": qLineItems.IsCheckedByDefault,
"StationID": qLineItems.StationID "StationID": qLineItems.StationID,
"StatusID": val(qLineItems.StatusID)
})> })>
</cfloop> </cfloop>

View 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>

View file

@ -86,165 +86,7 @@
---> --->
<!--- Create delivery/pickup task when order moves to status 3 (Final Prep) ---> <!--- Create delivery/pickup task when order moves to status 3 (Final Prep) --->
<cfset taskCreated = false> <cfinclude template="_createOrderTasks.cfm">
<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>
<cfset apiAbort({ <cfset apiAbort({
"OK": true, "OK": true,

View file

@ -86,6 +86,16 @@
/* Other-station items (dimmed) */ /* Other-station items (dimmed) */
.line-item.other-station { opacity: 0.35; } .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> </style>
</head> </head>
<body> <body>
@ -116,6 +126,6 @@
</div> </div>
</div> </div>
<script src="kds.js?v=6"></script> <script src="kds.js?v=7"></script>
</body> </body>
</html> </html>

View file

@ -366,8 +366,9 @@ function renderLineItem(item, allItems, isOtherStation = false) {
const modifiers = allItems.filter(mod => mod.ParentOrderLineItemID === item.OrderLineItemID); 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)); console.log(`Item: ${item.Name} (ID: ${item.OrderLineItemID}) has ${modifiers.length} direct modifiers:`, modifiers.map(m => m.Name));
const otherClass = isOtherStation ? ' other-station' : ''; const otherClass = isOtherStation ? ' other-station' : '';
const doneClass = (item.StatusID === 1) ? ' line-item-done' : '';
return ` return `
<div class="line-item${otherClass}"> <div class="line-item${otherClass}${doneClass}">
<div class="line-item-main"> <div class="line-item-main">
<div class="item-name">${escapeHtml(item.Name)}</div> <div class="item-name">${escapeHtml(item.Name)}</div>
<div class="item-qty">x${item.Quantity}</div> <div class="item-qty">x${item.Quantity}</div>
@ -433,8 +434,36 @@ function renderAllModifiers(modifiers, allItems) {
return html; 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 // Render action buttons based on order status
function renderActionButtons(order) { 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) { switch (order.StatusID) {
case STATUS.NEW: case STATUS.NEW:
return `<button class="btn btn-start" onclick="updateOrderStatus(${order.OrderID}, ${STATUS.PREPARING})">Start Preparing</button>`; 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 // Helper functions
// Get location label based on order type (Table vs Type) // Get location label based on order type (Table vs Type)