Add drink modifiers, unified schema improvements, and portal fixes
Menu System: - Unified schema with Categories table integration - Virtual category headers with proper parent ID remapping - Filter out legacy category headers when using new schema - Add drink modifier endpoints for Fountain Soda (Size/Flavor) Admin Tools: - addDrinkModifiers.cfm - Add size/flavor modifiers to drinks - copyDrinksToBigDeans.cfm - Copy drink items between businesses - debugDrinkStructure.cfm - Debug drink item hierarchy Portal: - Station assignment improvements with better drag-drop - Enhanced debug task viewer API Fixes: - Application.cfm updated with new admin endpoint allowlist - setLineItem.cfm formatting cleanup - listMine.cfm task query fixes 🤖 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
634148f727
commit
e757a4140b
10 changed files with 620 additions and 38 deletions
|
|
@ -127,6 +127,9 @@ if (len(request._api_path)) {
|
||||||
if (findNoCase("/api/admin/beaconStatus.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/admin/beaconStatus.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
if (findNoCase("/api/admin/updateBeaconMapping.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/admin/updateBeaconMapping.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
if (findNoCase("/api/admin/setupBigDeansStations.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/admin/setupBigDeansStations.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/admin/copyDrinksToBigDeans.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/admin/debugDrinkStructure.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/admin/addDrinkModifiers.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
// Setup/Import endpoints
|
// Setup/Import endpoints
|
||||||
if (findNoCase("/api/setup/importBusiness.cfm", request._api_path)) request._api_isPublic = true;
|
if (findNoCase("/api/setup/importBusiness.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
|
||||||
176
api/admin/addDrinkModifiers.cfm
Normal file
176
api/admin/addDrinkModifiers.cfm
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||||
|
<cfheader name="Cache-Control" value="no-store">
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
/**
|
||||||
|
* Add size and type modifiers to Big Dean's Fountain Soda
|
||||||
|
*/
|
||||||
|
|
||||||
|
response = { "OK": false, "SizesAdded": 0, "TypesAdded": 0 };
|
||||||
|
|
||||||
|
try {
|
||||||
|
bigDeansBusinessId = 27;
|
||||||
|
|
||||||
|
// Find the Fountain Soda item we created
|
||||||
|
qFountain = queryExecute("
|
||||||
|
SELECT ItemID, ItemName FROM Items
|
||||||
|
WHERE ItemBusinessID = :bizId AND ItemName = 'Fountain Soda'
|
||||||
|
", { bizId: bigDeansBusinessId }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qFountain.recordCount == 0) {
|
||||||
|
response["ERROR"] = "Fountain Soda not found in Big Dean's menu";
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
abort;
|
||||||
|
}
|
||||||
|
|
||||||
|
fountainId = qFountain.ItemID;
|
||||||
|
response["FountainSodaID"] = fountainId;
|
||||||
|
|
||||||
|
// Update Fountain Soda to require child selection and be collapsible
|
||||||
|
queryExecute("
|
||||||
|
UPDATE Items
|
||||||
|
SET ItemRequiresChildSelection = 1, ItemIsCollapsible = 1
|
||||||
|
WHERE ItemID = :itemId
|
||||||
|
", { itemId: fountainId }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
// Check if modifiers already exist
|
||||||
|
qExisting = queryExecute("
|
||||||
|
SELECT COUNT(*) as cnt FROM Items WHERE ItemParentItemID = :parentId
|
||||||
|
", { parentId: fountainId }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qExisting.cnt > 0) {
|
||||||
|
response["OK"] = true;
|
||||||
|
response["MESSAGE"] = "Modifiers already exist";
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
abort;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Size group
|
||||||
|
qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
|
||||||
|
sizeGroupId = qMaxItem.nextId;
|
||||||
|
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (
|
||||||
|
ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
|
||||||
|
ItemName, ItemDescription, ItemPrice, ItemIsActive,
|
||||||
|
ItemSortOrder, ItemIsCollapsible, ItemRequiresChildSelection,
|
||||||
|
ItemMaxNumSelectionReq, ItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
:itemId, :bizId, 0, :parentId,
|
||||||
|
'Size', 'Choose your size', 0, 1,
|
||||||
|
0, 1, 1, 1, NOW()
|
||||||
|
)
|
||||||
|
", {
|
||||||
|
itemId: sizeGroupId,
|
||||||
|
bizId: bigDeansBusinessId,
|
||||||
|
parentId: fountainId
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
// Add Size options
|
||||||
|
sizes = [
|
||||||
|
{ name: "Small", price: 0, isDefault: 0 },
|
||||||
|
{ name: "Medium", price: 0.50, isDefault: 1 },
|
||||||
|
{ name: "Large", price: 1.00, isDefault: 0 }
|
||||||
|
];
|
||||||
|
|
||||||
|
sizesAdded = 0;
|
||||||
|
for (size in sizes) {
|
||||||
|
qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (
|
||||||
|
ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
|
||||||
|
ItemName, ItemDescription, ItemPrice, ItemIsActive,
|
||||||
|
ItemSortOrder, ItemIsCollapsible, ItemIsCheckedByDefault,
|
||||||
|
ItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
:itemId, :bizId, 0, :parentId,
|
||||||
|
:name, '', :price, 1,
|
||||||
|
:sortOrder, 0, :isDefault,
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
", {
|
||||||
|
itemId: qMaxItem.nextId,
|
||||||
|
bizId: bigDeansBusinessId,
|
||||||
|
parentId: sizeGroupId,
|
||||||
|
name: size.name,
|
||||||
|
price: size.price,
|
||||||
|
sortOrder: sizesAdded,
|
||||||
|
isDefault: size.isDefault
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
sizesAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Type/Flavor group
|
||||||
|
qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
|
||||||
|
typeGroupId = qMaxItem.nextId;
|
||||||
|
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (
|
||||||
|
ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
|
||||||
|
ItemName, ItemDescription, ItemPrice, ItemIsActive,
|
||||||
|
ItemSortOrder, ItemIsCollapsible, ItemRequiresChildSelection,
|
||||||
|
ItemMaxNumSelectionReq, ItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
:itemId, :bizId, 0, :parentId,
|
||||||
|
'Flavor', 'Choose your drink', 0, 1,
|
||||||
|
1, 1, 1, 1, NOW()
|
||||||
|
)
|
||||||
|
", {
|
||||||
|
itemId: typeGroupId,
|
||||||
|
bizId: bigDeansBusinessId,
|
||||||
|
parentId: fountainId
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
// Add Type options
|
||||||
|
types = [
|
||||||
|
{ name: "Coca-Cola", isDefault: 1 },
|
||||||
|
{ name: "Diet Coke", isDefault: 0 },
|
||||||
|
{ name: "Sprite", isDefault: 0 },
|
||||||
|
{ name: "Fanta Orange", isDefault: 0 },
|
||||||
|
{ name: "Lemonade", isDefault: 0 },
|
||||||
|
{ name: "Root Beer", isDefault: 0 },
|
||||||
|
{ name: "Dr Pepper", isDefault: 0 }
|
||||||
|
];
|
||||||
|
|
||||||
|
typesAdded = 0;
|
||||||
|
for (type in types) {
|
||||||
|
qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (
|
||||||
|
ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
|
||||||
|
ItemName, ItemDescription, ItemPrice, ItemIsActive,
|
||||||
|
ItemSortOrder, ItemIsCollapsible, ItemIsCheckedByDefault,
|
||||||
|
ItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
:itemId, :bizId, 0, :parentId,
|
||||||
|
:name, '', 0, 1,
|
||||||
|
:sortOrder, 0, :isDefault,
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
", {
|
||||||
|
itemId: qMaxItem.nextId,
|
||||||
|
bizId: bigDeansBusinessId,
|
||||||
|
parentId: typeGroupId,
|
||||||
|
name: type.name,
|
||||||
|
sortOrder: typesAdded,
|
||||||
|
isDefault: type.isDefault
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
typesAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
response["OK"] = true;
|
||||||
|
response["SizesAdded"] = sizesAdded;
|
||||||
|
response["TypesAdded"] = typesAdded;
|
||||||
|
response["SizeGroupID"] = sizeGroupId;
|
||||||
|
response["TypeGroupID"] = typeGroupId;
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
|
response["ERROR"] = "server_error";
|
||||||
|
response["MESSAGE"] = e.message;
|
||||||
|
response["DETAIL"] = e.detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
</cfscript>
|
||||||
141
api/admin/copyDrinksToBigDeans.cfm
Normal file
141
api/admin/copyDrinksToBigDeans.cfm
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||||
|
<cfheader name="Cache-Control" value="no-store">
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
/**
|
||||||
|
* Copy drinks from In-N-Out (BusinessID 17) to Big Dean's (BusinessID 27)
|
||||||
|
*/
|
||||||
|
|
||||||
|
response = { "OK": false, "ItemsCreated": 0, "CategoryCreated": false };
|
||||||
|
|
||||||
|
try {
|
||||||
|
bigDeansBusinessId = 27;
|
||||||
|
|
||||||
|
// First, check if Big Dean's has a Beverages/Drinks category
|
||||||
|
qExistingCat = queryExecute("
|
||||||
|
SELECT CategoryID, CategoryName FROM Categories
|
||||||
|
WHERE CategoryBusinessID = :bizId AND (CategoryName LIKE '%Drink%' OR CategoryName LIKE '%Beverage%')
|
||||||
|
", { bizId: bigDeansBusinessId }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qExistingCat.recordCount > 0) {
|
||||||
|
drinksCategoryId = qExistingCat.CategoryID;
|
||||||
|
response["CategoryNote"] = "Using existing category: " & qExistingCat.CategoryName;
|
||||||
|
} else {
|
||||||
|
// Create a new Beverages category for Big Dean's
|
||||||
|
qMaxCat = queryExecute("SELECT COALESCE(MAX(CategoryID), 0) + 1 as nextId FROM Categories", {}, { datasource: "payfrit" });
|
||||||
|
drinksCategoryId = qMaxCat.nextId;
|
||||||
|
|
||||||
|
qMaxSort = queryExecute("
|
||||||
|
SELECT COALESCE(MAX(CategorySortOrder), 0) + 1 as nextSort FROM Categories WHERE CategoryBusinessID = :bizId
|
||||||
|
", { bizId: bigDeansBusinessId }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Categories (CategoryID, CategoryBusinessID, CategoryParentCategoryID, CategoryName, CategorySortOrder, CategoryAddedOn)
|
||||||
|
VALUES (:catId, :bizId, 0, 'Beverages', :sortOrder, NOW())
|
||||||
|
", {
|
||||||
|
catId: drinksCategoryId,
|
||||||
|
bizId: bigDeansBusinessId,
|
||||||
|
sortOrder: qMaxSort.nextSort
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
response["CategoryCreated"] = true;
|
||||||
|
response["CategoryNote"] = "Created new category: Beverages (ID: " & drinksCategoryId & ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drinks to add (from In-N-Out)
|
||||||
|
drinks = [
|
||||||
|
{ name: "Fountain Soda", price: 2.10, desc: "Coca-Cola, Diet Coke, Sprite, Fanta Orange, Lemonade" },
|
||||||
|
{ name: "Bottled Water", price: 1.50, desc: "" },
|
||||||
|
{ name: "Iced Tea", price: 2.25, desc: "Freshly brewed" },
|
||||||
|
{ name: "Coffee", price: 1.95, desc: "Hot brewed coffee" },
|
||||||
|
{ name: "Hot Cocoa", price: 2.25, desc: "" },
|
||||||
|
{ name: "Milk", price: 1.25, desc: "2% milk" },
|
||||||
|
{ name: "Orange Juice", price: 2.50, desc: "Fresh squeezed" },
|
||||||
|
{ name: "Milkshake", price: 4.95, desc: "Chocolate, Vanilla, or Strawberry - made with real ice cream", requiresChild: 1 }
|
||||||
|
];
|
||||||
|
|
||||||
|
itemsCreated = 0;
|
||||||
|
|
||||||
|
for (drink in drinks) {
|
||||||
|
// Check if item already exists
|
||||||
|
qExists = queryExecute("
|
||||||
|
SELECT ItemID FROM Items
|
||||||
|
WHERE ItemBusinessID = :bizId AND ItemName = :name AND ItemCategoryID = :catId
|
||||||
|
", { bizId: bigDeansBusinessId, name: drink.name, catId: drinksCategoryId }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
if (qExists.recordCount == 0) {
|
||||||
|
// Get next ItemID
|
||||||
|
qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
|
||||||
|
newItemId = qMaxItem.nextId;
|
||||||
|
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (
|
||||||
|
ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
|
||||||
|
ItemName, ItemDescription, ItemPrice, ItemIsActive,
|
||||||
|
ItemSortOrder, ItemIsCollapsible, ItemRequiresChildSelection,
|
||||||
|
ItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
:itemId, :bizId, :catId, 0,
|
||||||
|
:name, :desc, :price, 1,
|
||||||
|
:sortOrder, 0, :requiresChild,
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
", {
|
||||||
|
itemId: newItemId,
|
||||||
|
bizId: bigDeansBusinessId,
|
||||||
|
catId: drinksCategoryId,
|
||||||
|
name: drink.name,
|
||||||
|
desc: structKeyExists(drink, "desc") ? drink.desc : "",
|
||||||
|
price: drink.price,
|
||||||
|
sortOrder: itemsCreated,
|
||||||
|
requiresChild: structKeyExists(drink, "requiresChild") ? drink.requiresChild : 0
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
itemsCreated++;
|
||||||
|
|
||||||
|
// If milkshake, add flavor options
|
||||||
|
if (drink.name == "Milkshake") {
|
||||||
|
flavors = ["Chocolate", "Vanilla", "Strawberry"];
|
||||||
|
flavorSort = 0;
|
||||||
|
for (flavor in flavors) {
|
||||||
|
qMaxOpt = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
|
||||||
|
queryExecute("
|
||||||
|
INSERT INTO Items (
|
||||||
|
ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
|
||||||
|
ItemName, ItemDescription, ItemPrice, ItemIsActive,
|
||||||
|
ItemSortOrder, ItemIsCollapsible, ItemIsCheckedByDefault,
|
||||||
|
ItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
:itemId, :bizId, 0, :parentId,
|
||||||
|
:name, '', 0, 1,
|
||||||
|
:sortOrder, 0, :isDefault,
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
", {
|
||||||
|
itemId: qMaxOpt.nextId,
|
||||||
|
bizId: bigDeansBusinessId,
|
||||||
|
parentId: newItemId,
|
||||||
|
name: flavor,
|
||||||
|
sortOrder: flavorSort,
|
||||||
|
isDefault: (flavor == "Chocolate") ? 1 : 0
|
||||||
|
}, { datasource: "payfrit" });
|
||||||
|
flavorSort++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response["OK"] = true;
|
||||||
|
response["ItemsCreated"] = itemsCreated;
|
||||||
|
response["CategoryID"] = drinksCategoryId;
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
|
response["ERROR"] = "server_error";
|
||||||
|
response["MESSAGE"] = e.message;
|
||||||
|
response["DETAIL"] = e.detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
</cfscript>
|
||||||
76
api/admin/debugDrinkStructure.cfm
Normal file
76
api/admin/debugDrinkStructure.cfm
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||||
|
<cfheader name="Cache-Control" value="no-store">
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
// Find Fountain Drinks in In-N-Out (BusinessID 17) and show its hierarchy
|
||||||
|
response = { "OK": true };
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get Fountain Drinks item
|
||||||
|
qFountain = queryExecute("
|
||||||
|
SELECT ItemID, ItemName, ItemParentItemID, ItemPrice, ItemIsCollapsible, ItemRequiresChildSelection
|
||||||
|
FROM Items
|
||||||
|
WHERE ItemBusinessID = 17 AND ItemName LIKE '%Fountain%'
|
||||||
|
", {}, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
response["FountainDrinks"] = [];
|
||||||
|
for (row in qFountain) {
|
||||||
|
fountainItem = {
|
||||||
|
"ItemID": row.ItemID,
|
||||||
|
"ItemName": row.ItemName,
|
||||||
|
"ItemPrice": row.ItemPrice,
|
||||||
|
"ItemIsCollapsible": row.ItemIsCollapsible,
|
||||||
|
"ItemRequiresChildSelection": row.ItemRequiresChildSelection,
|
||||||
|
"Children": []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get children of this item
|
||||||
|
qChildren = queryExecute("
|
||||||
|
SELECT ItemID, ItemName, ItemParentItemID, ItemPrice, ItemIsCollapsible, ItemRequiresChildSelection, ItemIsCheckedByDefault
|
||||||
|
FROM Items
|
||||||
|
WHERE ItemParentItemID = :parentId
|
||||||
|
ORDER BY ItemSortOrder
|
||||||
|
", { parentId: row.ItemID }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
for (child in qChildren) {
|
||||||
|
childItem = {
|
||||||
|
"ItemID": child.ItemID,
|
||||||
|
"ItemName": child.ItemName,
|
||||||
|
"ItemPrice": child.ItemPrice,
|
||||||
|
"ItemIsCollapsible": child.ItemIsCollapsible,
|
||||||
|
"ItemIsCheckedByDefault": child.ItemIsCheckedByDefault,
|
||||||
|
"Grandchildren": []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get grandchildren
|
||||||
|
qGrandchildren = queryExecute("
|
||||||
|
SELECT ItemID, ItemName, ItemPrice, ItemIsCheckedByDefault
|
||||||
|
FROM Items
|
||||||
|
WHERE ItemParentItemID = :parentId
|
||||||
|
ORDER BY ItemSortOrder
|
||||||
|
", { parentId: child.ItemID }, { datasource: "payfrit" });
|
||||||
|
|
||||||
|
for (gc in qGrandchildren) {
|
||||||
|
arrayAppend(childItem.Grandchildren, {
|
||||||
|
"ItemID": gc.ItemID,
|
||||||
|
"ItemName": gc.ItemName,
|
||||||
|
"ItemPrice": gc.ItemPrice,
|
||||||
|
"ItemIsCheckedByDefault": gc.ItemIsCheckedByDefault
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayAppend(fountainItem.Children, childItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayAppend(response.FountainDrinks, fountainItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
|
response["OK"] = false;
|
||||||
|
response["ERROR"] = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
</cfscript>
|
||||||
|
|
@ -3,29 +3,52 @@
|
||||||
<cfcontent type="application/json; charset=utf-8">
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
|
||||||
<cftry>
|
<cftry>
|
||||||
<cfset qDesc = queryExecute("DESCRIBE Tasks", [], { datasource = "payfrit" })>
|
<cfset qTasks = queryExecute("
|
||||||
<cfset cols = []>
|
SELECT
|
||||||
<cfloop query="qDesc">
|
t.TaskID,
|
||||||
<cfset arrayAppend(cols, { "Field": qDesc.Field, "Type": qDesc.Type })>
|
t.TaskBusinessID,
|
||||||
</cfloop>
|
t.TaskOrderID,
|
||||||
|
t.TaskClaimedByUserID,
|
||||||
|
t.TaskClaimedOn,
|
||||||
|
t.TaskCompletedOn,
|
||||||
|
o.OrderStatusID
|
||||||
|
FROM Tasks t
|
||||||
|
LEFT JOIN Orders o ON o.OrderID = t.TaskOrderID
|
||||||
|
ORDER BY t.TaskID DESC
|
||||||
|
LIMIT 20
|
||||||
|
", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
<cfset qCount = queryExecute("SELECT COUNT(*) AS cnt FROM Tasks", [], { datasource = "payfrit" })>
|
|
||||||
|
|
||||||
<cfset qAll = queryExecute("SELECT * FROM Tasks LIMIT 5", [], { datasource = "payfrit" })>
|
|
||||||
<cfset tasks = []>
|
<cfset tasks = []>
|
||||||
<cfloop query="qAll">
|
<cfloop query="qTasks">
|
||||||
<cfset row = {}>
|
<cfset arrayAppend(tasks, {
|
||||||
<cfloop list="#qAll.columnList#" index="col">
|
"TaskID": qTasks.TaskID,
|
||||||
<cfset row[col] = qAll[col]>
|
"TaskBusinessID": qTasks.TaskBusinessID,
|
||||||
</cfloop>
|
"TaskOrderID": qTasks.TaskOrderID,
|
||||||
<cfset arrayAppend(tasks, row)>
|
"TaskClaimedByUserID": qTasks.TaskClaimedByUserID,
|
||||||
|
"TaskClaimedOn": isNull(qTasks.TaskClaimedOn) ? "NULL" : dateTimeFormat(qTasks.TaskClaimedOn, "yyyy-mm-dd HH:nn:ss"),
|
||||||
|
"TaskCompletedOn": isNull(qTasks.TaskCompletedOn) ? "NULL" : dateTimeFormat(qTasks.TaskCompletedOn, "yyyy-mm-dd HH:nn:ss"),
|
||||||
|
"OrderStatusID": isNull(qTasks.OrderStatusID) ? "NULL" : qTasks.OrderStatusID
|
||||||
|
})>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset qStats = queryExecute("
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as Total,
|
||||||
|
SUM(CASE WHEN TaskClaimedByUserID > 0 AND TaskCompletedOn IS NULL THEN 1 ELSE 0 END) as ClaimedNotCompleted,
|
||||||
|
SUM(CASE WHEN TaskClaimedByUserID = 0 OR TaskClaimedByUserID IS NULL THEN 1 ELSE 0 END) as Unclaimed,
|
||||||
|
SUM(CASE WHEN TaskCompletedOn IS NOT NULL THEN 1 ELSE 0 END) as Completed
|
||||||
|
FROM Tasks
|
||||||
|
", [], { datasource = "payfrit" })>
|
||||||
|
|
||||||
<cfoutput>#serializeJSON({
|
<cfoutput>#serializeJSON({
|
||||||
"OK": true,
|
"OK": true,
|
||||||
"COLUMNS": cols,
|
"TASKS": tasks,
|
||||||
"TASK_COUNT": qCount.cnt,
|
"STATS": {
|
||||||
"SAMPLE_TASKS": tasks
|
"Total": qStats.Total,
|
||||||
|
"ClaimedNotCompleted": qStats.ClaimedNotCompleted,
|
||||||
|
"Unclaimed": qStats.Unclaimed,
|
||||||
|
"Completed": qStats.Completed
|
||||||
|
}
|
||||||
})#</cfoutput>
|
})#</cfoutput>
|
||||||
|
|
||||||
<cfcatch>
|
<cfcatch>
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,24 @@
|
||||||
|
|
||||||
<cfif hasCategoriesData>
|
<cfif hasCategoriesData>
|
||||||
<!--- Use Categories table with ItemCategoryID --->
|
<!--- Use Categories table with ItemCategoryID --->
|
||||||
<!--- Only return items that have a valid CategoryID (actual menu items, not category headers) --->
|
<!--- First, get category headers as virtual items --->
|
||||||
|
<cfset qCategories = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
CategoryID,
|
||||||
|
CategoryName,
|
||||||
|
CategorySortOrder
|
||||||
|
FROM Categories
|
||||||
|
WHERE CategoryBusinessID = ?
|
||||||
|
ORDER BY CategorySortOrder
|
||||||
|
",
|
||||||
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<!--- Get menu items --->
|
||||||
|
<!--- Exclude old-style category headers (ParentID=0 AND CategoryID=0 AND no children with CategoryID>0) --->
|
||||||
|
<!--- These are legacy category headers that should be replaced by Categories table entries --->
|
||||||
<cfset q = queryExecute(
|
<cfset q = queryExecute(
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -91,14 +108,14 @@
|
||||||
s.StationName,
|
s.StationName,
|
||||||
s.StationColor
|
s.StationColor
|
||||||
FROM Items i
|
FROM Items i
|
||||||
INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID
|
LEFT JOIN Categories c ON c.CategoryID = i.ItemCategoryID
|
||||||
LEFT JOIN Stations s ON s.StationID = i.ItemStationID
|
LEFT JOIN Stations s ON s.StationID = i.ItemStationID
|
||||||
WHERE i.ItemBusinessID = ?
|
WHERE i.ItemBusinessID = ?
|
||||||
AND i.ItemIsActive = 1
|
AND i.ItemIsActive = 1
|
||||||
AND i.ItemCategoryID > 0
|
|
||||||
AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID)
|
AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID)
|
||||||
AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl2 WHERE tl2.TemplateItemID = i.ItemParentItemID)
|
AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl2 WHERE tl2.TemplateItemID = i.ItemParentItemID)
|
||||||
ORDER BY c.CategorySortOrder, i.ItemSortOrder, i.ItemID
|
AND NOT (i.ItemParentItemID = 0 AND i.ItemCategoryID = 0 AND i.ItemPrice = 0)
|
||||||
|
ORDER BY COALESCE(c.CategorySortOrder, 999), i.ItemSortOrder, i.ItemID
|
||||||
",
|
",
|
||||||
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
||||||
{ datasource = "payfrit" }
|
{ datasource = "payfrit" }
|
||||||
|
|
@ -191,14 +208,66 @@
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cfset rows = []>
|
<cfset rows = []>
|
||||||
|
|
||||||
|
<!--- For unified schema with Categories table, add category headers first --->
|
||||||
|
<cfif newSchemaActive AND isDefined("qCategories")>
|
||||||
|
<cfloop query="qCategories">
|
||||||
|
<!--- Add category as a virtual parent item --->
|
||||||
|
<!--- Use CategoryID as ItemID, and set ItemCategoryID to same value --->
|
||||||
|
<!--- Set ItemParentItemID to 0 to mark as root level --->
|
||||||
|
<cfset arrayAppend(rows, {
|
||||||
|
"ItemID": qCategories.CategoryID,
|
||||||
|
"ItemCategoryID": qCategories.CategoryID,
|
||||||
|
"ItemCategoryName": qCategories.CategoryName,
|
||||||
|
"ItemName": qCategories.CategoryName,
|
||||||
|
"ItemDescription": "",
|
||||||
|
"ItemParentItemID": 0,
|
||||||
|
"ItemPrice": 0,
|
||||||
|
"ItemIsActive": 1,
|
||||||
|
"ItemIsCheckedByDefault": 0,
|
||||||
|
"ItemRequiresChildSelection": 0,
|
||||||
|
"ItemMaxNumSelectionReq": 0,
|
||||||
|
"ItemIsCollapsible": 0,
|
||||||
|
"ItemSortOrder": qCategories.CategorySortOrder,
|
||||||
|
"ItemStationID": "",
|
||||||
|
"ItemStationName": "",
|
||||||
|
"ItemStationColor": ""
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Build a set of category IDs for quick lookup --->
|
||||||
|
<cfset categoryIdSet = {}>
|
||||||
|
<cfif isDefined("qCategories")>
|
||||||
|
<cfloop query="qCategories">
|
||||||
|
<cfset categoryIdSet[qCategories.CategoryID] = true>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
<cfloop query="q">
|
<cfloop query="q">
|
||||||
|
<!--- For unified schema with Categories: set ParentItemID to CategoryID for top-level items --->
|
||||||
|
<!--- Remap old parent IDs to category IDs --->
|
||||||
|
<cfset effectiveParentID = q.ItemParentItemID>
|
||||||
|
<cfif newSchemaActive AND isDefined("qCategories") AND q.ItemCategoryID GT 0>
|
||||||
|
<cfif q.ItemParentItemID EQ 0>
|
||||||
|
<!--- Item has no parent but has a category - link to category --->
|
||||||
|
<cfset effectiveParentID = q.ItemCategoryID>
|
||||||
|
<cfelseif structKeyExists(categoryIdSet, q.ItemParentItemID)>
|
||||||
|
<!--- Item's parent IS a category ID - this is correct, keep it --->
|
||||||
|
<cfset effectiveParentID = q.ItemParentItemID>
|
||||||
|
<cfelseif NOT structKeyExists(categoryIdSet, q.ItemParentItemID)>
|
||||||
|
<!--- Parent ID is an old-style category header - remap to CategoryID --->
|
||||||
|
<cfset effectiveParentID = q.ItemCategoryID>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
<cfset arrayAppend(rows, {
|
<cfset arrayAppend(rows, {
|
||||||
"ItemID": q.ItemID,
|
"ItemID": q.ItemID,
|
||||||
"ItemCategoryID": q.ItemCategoryID,
|
"ItemCategoryID": q.ItemCategoryID,
|
||||||
"ItemCategoryName": q.CategoryName,
|
"ItemCategoryName": len(trim(q.CategoryName)) ? q.CategoryName : "",
|
||||||
"ItemName": q.ItemName,
|
"ItemName": q.ItemName,
|
||||||
"ItemDescription": q.ItemDescription,
|
"ItemDescription": q.ItemDescription,
|
||||||
"ItemParentItemID": q.ItemParentItemID,
|
"ItemParentItemID": effectiveParentID,
|
||||||
"ItemPrice": q.ItemPrice,
|
"ItemPrice": q.ItemPrice,
|
||||||
"ItemIsActive": q.ItemIsActive,
|
"ItemIsActive": q.ItemIsActive,
|
||||||
"ItemIsCheckedByDefault": q.ItemIsCheckedByDefault,
|
"ItemIsCheckedByDefault": q.ItemIsCheckedByDefault,
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@
|
||||||
<cfset updateCount = 0>
|
<cfset updateCount = 0>
|
||||||
|
|
||||||
<!--- First, clear all station assignments for items in this business --->
|
<!--- First, clear all station assignments for items in this business --->
|
||||||
|
<!--- Support both unified schema (ItemBusinessID) and legacy (via Categories) --->
|
||||||
<cfset queryExecute("
|
<cfset queryExecute("
|
||||||
UPDATE Items i
|
UPDATE Items
|
||||||
INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID
|
SET ItemStationID = NULL
|
||||||
SET i.ItemStationID = NULL
|
WHERE ItemBusinessID = ?
|
||||||
WHERE c.CategoryBusinessID = ?
|
|
||||||
", [ { value = BusinessID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
", [ { value = BusinessID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
|
||||||
|
|
||||||
<!--- Then apply the new assignments --->
|
<!--- Then apply the new assignments --->
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
FROM Items
|
FROM Items
|
||||||
WHERE ItemParentItemID = ?
|
WHERE ItemParentItemID = ?
|
||||||
AND ItemIsCheckedByDefault = 1
|
AND ItemIsCheckedByDefault = 1
|
||||||
AND ItemIsActive = b'1'
|
AND ItemIsActive = 1
|
||||||
ORDER BY ItemSortOrder, ItemID
|
ORDER BY ItemSortOrder, ItemID
|
||||||
",
|
",
|
||||||
[ { value = arguments.ParentItemID, cfsqltype = "cf_sql_integer" } ],
|
[ { value = arguments.ParentItemID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
|
@ -254,7 +254,7 @@
|
||||||
{ datasource = "payfrit" }
|
{ datasource = "payfrit" }
|
||||||
)>
|
)>
|
||||||
|
|
||||||
<cfif qItem.recordCount EQ 0 OR qItem.ItemIsActive NEQ true>
|
<cfif qItem.recordCount EQ 0 OR qItem.ItemIsActive NEQ 1>
|
||||||
<cfset apiAbort({ "OK": false, "ERROR": "bad_item", "MESSAGE": "Item not found or inactive.", "DETAIL": "" })>
|
<cfset apiAbort({ "OK": false, "ERROR": "bad_item", "MESSAGE": "Item not found or inactive.", "DETAIL": "" })>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@
|
||||||
"TaskTitle": taskTitle,
|
"TaskTitle": taskTitle,
|
||||||
"TaskDetails": "",
|
"TaskDetails": "",
|
||||||
"TaskCreatedOn": dateFormat(qTasks.TaskAddedOn, "yyyy-mm-dd") & "T" & timeFormat(qTasks.TaskAddedOn, "HH:mm:ss"),
|
"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"),
|
"TaskClaimedOn": (isNull(qTasks.TaskClaimedOn) OR len(trim(qTasks.TaskClaimedOn)) EQ 0) ? "" : 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"),
|
"TaskCompletedOn": (isNull(qTasks.TaskCompletedOn) OR len(trim(qTasks.TaskCompletedOn)) EQ 0) ? "" : dateFormat(qTasks.TaskCompletedOn, "yyyy-mm-dd") & "T" & timeFormat(qTasks.TaskCompletedOn, "HH:mm:ss"),
|
||||||
"TaskStatusID": isNull(qTasks.TaskCompletedOn) ? 1 : 3,
|
"TaskStatusID": (isNull(qTasks.TaskCompletedOn) OR len(trim(qTasks.TaskCompletedOn)) EQ 0) ? 1 : 3,
|
||||||
"TaskSourceType": "order",
|
"TaskSourceType": "order",
|
||||||
"TaskSourceID": qTasks.TaskOrderID,
|
"TaskSourceID": qTasks.TaskOrderID,
|
||||||
"TaskCategoryName": len(trim(qTasks.TaskCategoryName)) ? qTasks.TaskCategoryName : "General",
|
"TaskCategoryName": len(trim(qTasks.TaskCategoryName)) ? qTasks.TaskCategoryName : "General",
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,45 @@
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
padding: 0 4px;
|
padding: 8px 12px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: grab;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-label:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: rgba(0, 255, 136, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-label:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-label.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-label .drag-icon {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-label .cat-name {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-label .cat-count {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: normal;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggable-item {
|
.draggable-item {
|
||||||
|
|
@ -521,8 +559,27 @@
|
||||||
|
|
||||||
if (filteredItems.length === 0) return;
|
if (filteredItems.length === 0) return;
|
||||||
|
|
||||||
|
// Get item IDs for this category (for dragging entire category)
|
||||||
|
const categoryItemIds = filteredItems.map(i => i.ItemID).join(',');
|
||||||
|
|
||||||
html += `<div class="category-group">`;
|
html += `<div class="category-group">`;
|
||||||
html += `<div class="category-label">${this.escapeHtml(catName)}</div>`;
|
html += `
|
||||||
|
<div class="category-label"
|
||||||
|
draggable="true"
|
||||||
|
data-category-items="${categoryItemIds}"
|
||||||
|
ondragstart="StationAssignment.onCategoryDragStart(event, '${categoryItemIds}')"
|
||||||
|
ondragend="StationAssignment.onDragEnd(event)">
|
||||||
|
<span class="drag-icon">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="9" cy="6" r="1.5"/><circle cx="15" cy="6" r="1.5"/>
|
||||||
|
<circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/>
|
||||||
|
<circle cx="9" cy="18" r="1.5"/><circle cx="15" cy="18" r="1.5"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="cat-name">${this.escapeHtml(catName)}</span>
|
||||||
|
<span class="cat-count">${filteredItems.length} items</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
filteredItems.forEach(item => {
|
filteredItems.forEach(item => {
|
||||||
const assigned = this.assignments[item.ItemID];
|
const assigned = this.assignments[item.ItemID];
|
||||||
|
|
@ -643,6 +700,15 @@
|
||||||
onDragStart(event, itemId) {
|
onDragStart(event, itemId) {
|
||||||
console.log('[StationAssignment] Drag start, itemId:', itemId);
|
console.log('[StationAssignment] Drag start, itemId:', itemId);
|
||||||
event.dataTransfer.setData('text/plain', itemId.toString());
|
event.dataTransfer.setData('text/plain', itemId.toString());
|
||||||
|
event.dataTransfer.setData('dragType', 'item');
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
event.target.classList.add('dragging');
|
||||||
|
},
|
||||||
|
|
||||||
|
onCategoryDragStart(event, itemIds) {
|
||||||
|
console.log('[StationAssignment] Category drag start, itemIds:', itemIds);
|
||||||
|
event.dataTransfer.setData('text/plain', itemIds);
|
||||||
|
event.dataTransfer.setData('dragType', 'category');
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
event.target.classList.add('dragging');
|
event.target.classList.add('dragging');
|
||||||
},
|
},
|
||||||
|
|
@ -685,10 +751,38 @@
|
||||||
stationCard.classList.remove('drag-over');
|
stationCard.classList.remove('drag-over');
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemId = parseInt(event.dataTransfer.getData('text/plain'));
|
const droppedData = event.dataTransfer.getData('text/plain');
|
||||||
console.log('[StationAssignment] Dropped itemId:', itemId);
|
console.log('[StationAssignment] Dropped data:', droppedData);
|
||||||
if (itemId) {
|
|
||||||
this.assignToStation(itemId, stationId);
|
// Check if this is a category drop (multiple comma-separated IDs)
|
||||||
|
if (droppedData.includes(',')) {
|
||||||
|
// Category drop - assign all items
|
||||||
|
const itemIds = droppedData.split(',').map(id => parseInt(id.trim())).filter(id => id > 0);
|
||||||
|
console.log('[StationAssignment] Category drop, itemIds:', itemIds);
|
||||||
|
this.assignCategoryToStation(itemIds, stationId);
|
||||||
|
} else {
|
||||||
|
// Single item drop
|
||||||
|
const itemId = parseInt(droppedData);
|
||||||
|
if (itemId) {
|
||||||
|
this.assignToStation(itemId, stationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
assignCategoryToStation(itemIds, stationId) {
|
||||||
|
const station = this.stations.find(s => s.StationID === stationId);
|
||||||
|
let assignedCount = 0;
|
||||||
|
|
||||||
|
itemIds.forEach(itemId => {
|
||||||
|
this.assignments[itemId] = stationId;
|
||||||
|
assignedCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderItems();
|
||||||
|
this.renderStations();
|
||||||
|
|
||||||
|
if (station && assignedCount > 0) {
|
||||||
|
this.toast(`${assignedCount} items assigned to ${station.StationName}`, 'success');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue