Update all SQL queries, query result references, and ColdFusion code to match
the renamed database schema. Tables use plural CamelCase, PKs are all `ID`,
column prefixes stripped (e.g. BusinessName→Name, UserFirstName→FirstName).
Key changes:
- Strip table-name prefixes from all column references (Businesses, Users,
Addresses, Hours, Menus, Categories, Items, Stations, Orders,
OrderLineItems, Tasks, TaskCategories, TaskRatings, QuickTaskTemplates,
ScheduledTaskDefinitions, ChatMessages, Beacons, ServicePoints, Employees,
VisitorTrackings, ApiPerfLogs, tt_States, tt_Days, tt_AddressTypes,
tt_OrderTypes, tt_TaskTypes)
- Rename PK references from {TableName}ID to ID in all queries
- Rewrite 7 admin beacon files to use ServicePoints.BeaconID instead of
dropped lt_Beacon_Businesses_ServicePoints link table
- Rewrite beacon assignment files (list, save, delete) for new schema
- Fix FK references incorrectly changed to ID (OrderLineItems.OrderID,
Categories.MenuID, Tasks.CategoryID, ServicePoints.BeaconID)
- Update Addresses: AddressLat→Latitude, AddressLng→Longitude
- Update Users: UserPassword→Password, UserIsEmailVerified→IsEmailVerified,
UserIsActive→IsActive, UserBalance→Balance, etc.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
252 lines
8.8 KiB
Text
252 lines
8.8 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
<cfsetting requesttimeout="300">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
|
|
<!--- Only allow from localhost --->
|
|
<cfif NOT (cgi.remote_addr EQ "127.0.0.1" OR cgi.remote_addr EQ "::1" OR findNoCase("localhost", cgi.server_name))>
|
|
<cfoutput>#serializeJSON({"OK": false, "ERROR": "admin_only"})#</cfoutput>
|
|
<cfabort>
|
|
</cfif>
|
|
|
|
<cfscript>
|
|
/**
|
|
* Eliminate Categories Table - Schema Migration
|
|
*
|
|
* Final unified schema:
|
|
* - Everything is an Item with BusinessID
|
|
* - ParentID=0 items are either categories or templates (derived from usage)
|
|
* - Categories = items at ParentID=0 that have menu items as children
|
|
* - Templates = items at ParentID=0 that appear in lt_ItemID_TemplateItemID
|
|
* - Orphans = ParentID=0 items that are neither (cleanup candidates)
|
|
*
|
|
* Steps:
|
|
* 1. Add BusinessID column to Items
|
|
* 2. For each Category: create Item, re-parent menu items, set BusinessID
|
|
* 3. Set BusinessID on templates based on linked items
|
|
*
|
|
* Query param: ?dryRun=1 to preview without making changes
|
|
*/
|
|
|
|
response = { "OK": false, "steps": [], "migrations": [], "dryRun": false };
|
|
|
|
try {
|
|
dryRun = structKeyExists(url, "dryRun") && url.dryRun == 1;
|
|
response["dryRun"] = dryRun;
|
|
|
|
// Step 1: Add BusinessID column if it doesn't exist
|
|
try {
|
|
if (!dryRun) {
|
|
queryExecute("
|
|
ALTER TABLE Items ADD COLUMN BusinessID INT DEFAULT 0 AFTER ItemID
|
|
", {}, { datasource: "payfrit" });
|
|
}
|
|
arrayAppend(response.steps, "Added BusinessID column to Items table");
|
|
} catch (any e) {
|
|
if (findNoCase("Duplicate column", e.message)) {
|
|
arrayAppend(response.steps, "BusinessID column already exists");
|
|
} else {
|
|
throw(e);
|
|
}
|
|
}
|
|
|
|
// Step 2: Add index on BusinessID
|
|
try {
|
|
if (!dryRun) {
|
|
queryExecute("
|
|
CREATE INDEX idx_item_business ON Items (BusinessID)
|
|
", {}, { datasource: "payfrit" });
|
|
}
|
|
arrayAppend(response.steps, "Added index on BusinessID");
|
|
} catch (any e) {
|
|
if (findNoCase("Duplicate key name", e.message)) {
|
|
arrayAppend(response.steps, "Index idx_item_business already exists");
|
|
} else {
|
|
arrayAppend(response.steps, "Index warning: " & e.message);
|
|
}
|
|
}
|
|
|
|
// Step 3: Drop foreign key constraint on CategoryID if it exists
|
|
try {
|
|
if (!dryRun) {
|
|
queryExecute("
|
|
ALTER TABLE Items DROP FOREIGN KEY Items_ibfk_1
|
|
", {}, { datasource: "payfrit" });
|
|
}
|
|
arrayAppend(response.steps, "Dropped foreign key constraint Items_ibfk_1");
|
|
} catch (any e) {
|
|
if (findNoCase("check that column", e.message) || findNoCase("Can't DROP", e.message)) {
|
|
arrayAppend(response.steps, "Foreign key Items_ibfk_1 already dropped or doesn't exist");
|
|
} else {
|
|
arrayAppend(response.steps, "FK warning: " & e.message);
|
|
}
|
|
}
|
|
|
|
// Step 4: Get all Categories
|
|
qCategories = queryExecute("
|
|
SELECT ID, BusinessID, Name
|
|
FROM Categories
|
|
ORDER BY BusinessID, Name
|
|
", {}, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.steps, "Found " & qCategories.recordCount & " categories to migrate");
|
|
|
|
// Step 4: Migrate each category
|
|
for (cat in qCategories) {
|
|
migration = {
|
|
"oldCategoryID": cat.ID,
|
|
"categoryName": cat.Name,
|
|
"businessID": cat.BusinessID,
|
|
"newItemID": 0,
|
|
"itemsUpdated": 0
|
|
};
|
|
|
|
if (!dryRun) {
|
|
// Create new Item for this category (ParentID=0, no template flag needed)
|
|
// Note: CategoryID set to 0 temporarily until we drop that column
|
|
queryExecute("
|
|
INSERT INTO Items (
|
|
BusinessID,
|
|
CategoryID,
|
|
Name,
|
|
Description,
|
|
ParentItemID,
|
|
Price,
|
|
IsActive,
|
|
IsCheckedByDefault,
|
|
RequiresChildSelection,
|
|
SortOrder,
|
|
AddedOn
|
|
) VALUES (
|
|
:businessID,
|
|
0,
|
|
:categoryName,
|
|
'',
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
NOW()
|
|
)
|
|
", {
|
|
businessID: cat.BusinessID,
|
|
categoryName: cat.Name
|
|
}, { datasource: "payfrit" });
|
|
|
|
// Get the new Item ID
|
|
qNewItem = queryExecute("
|
|
SELECT ID FROM Items
|
|
WHERE BusinessID = :businessID
|
|
AND Name = :categoryName
|
|
AND ParentItemID = 0
|
|
ORDER BY ID DESC
|
|
LIMIT 1
|
|
", {
|
|
businessID: cat.BusinessID,
|
|
categoryName: cat.Name
|
|
}, { datasource: "payfrit" });
|
|
|
|
newItemID = qNewItem.ID;
|
|
migration["newItemID"] = newItemID;
|
|
|
|
// Update menu items in this category:
|
|
// - Set ParentItemID = newItemID (for top-level items only)
|
|
// - Set BusinessID = businessID (for all items)
|
|
queryExecute("
|
|
UPDATE Items
|
|
SET BusinessID = :businessID,
|
|
ParentItemID = :newItemID
|
|
WHERE CategoryID = :categoryID
|
|
AND ParentItemID = 0
|
|
", {
|
|
businessID: cat.BusinessID,
|
|
newItemID: newItemID,
|
|
categoryID: cat.ID
|
|
}, { datasource: "payfrit" });
|
|
|
|
// Set BusinessID on ALL items in this category (including nested)
|
|
queryExecute("
|
|
UPDATE Items
|
|
SET BusinessID = :businessID
|
|
WHERE CategoryID = :categoryID
|
|
AND (BusinessID IS NULL OR BusinessID = 0)
|
|
", {
|
|
businessID: cat.BusinessID,
|
|
categoryID: cat.ID
|
|
}, { datasource: "payfrit" });
|
|
|
|
// Count how many were updated
|
|
qCount = queryExecute("
|
|
SELECT COUNT(*) as cnt FROM Items
|
|
WHERE ParentItemID = :newItemID
|
|
", { newItemID: newItemID }, { datasource: "payfrit" });
|
|
|
|
migration["itemsUpdated"] = qCount.cnt;
|
|
} else {
|
|
// Dry run - count what would be updated
|
|
qCount = queryExecute("
|
|
SELECT COUNT(*) as cnt FROM Items
|
|
WHERE CategoryID = :categoryID
|
|
AND ParentItemID = 0
|
|
", { categoryID: cat.ID }, { datasource: "payfrit" });
|
|
|
|
migration["itemsToUpdate"] = qCount.cnt;
|
|
}
|
|
|
|
arrayAppend(response.migrations, migration);
|
|
}
|
|
|
|
// Step 5: Set BusinessID for templates (items in lt_ItemID_TemplateItemID)
|
|
// Templates get their BusinessID from the items they're linked to
|
|
if (!dryRun) {
|
|
queryExecute("
|
|
UPDATE Items t
|
|
INNER JOIN lt_ItemID_TemplateItemID tl ON tl.TemplateItemID = t.ItemID
|
|
INNER JOIN Items i ON i.ID = tl.ItemID
|
|
SET t.BusinessID = i.BusinessID
|
|
WHERE (t.BusinessID IS NULL OR t.BusinessID = 0)
|
|
AND i.BusinessID > 0
|
|
", {}, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.steps, "Set BusinessID on templates from linked items");
|
|
|
|
// Set BusinessID on template children (options)
|
|
queryExecute("
|
|
UPDATE Items c
|
|
INNER JOIN Items t ON t.ItemID = c.ParentItemID
|
|
SET c.BusinessID = t.BusinessID
|
|
WHERE t.BusinessID > 0
|
|
AND (c.BusinessID IS NULL OR c.BusinessID = 0)
|
|
", {}, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.steps, "Set BusinessID on template children");
|
|
|
|
// Make sure templates have ParentID=0 (they live at top level)
|
|
queryExecute("
|
|
UPDATE Items t
|
|
INNER JOIN lt_ItemID_TemplateItemID tl ON tl.TemplateItemID = t.ItemID
|
|
SET t.ParentItemID = 0
|
|
WHERE t.ParentItemID != 0
|
|
", {}, { datasource: "payfrit" });
|
|
|
|
arrayAppend(response.steps, "Ensured templates have ParentItemID=0");
|
|
}
|
|
|
|
response["OK"] = true;
|
|
|
|
if (dryRun) {
|
|
arrayAppend(response.steps, "DRY RUN COMPLETE - No changes made. Run without ?dryRun=1 to execute.");
|
|
} else {
|
|
arrayAppend(response.steps, "MIGRATION COMPLETE - Categories converted to Items");
|
|
arrayAppend(response.steps, "Run cleanupCategories.cfm to drop old columns/tables after verification");
|
|
}
|
|
|
|
} catch (any e) {
|
|
response["ERROR"] = e.message;
|
|
response["DETAIL"] = e.detail;
|
|
}
|
|
|
|
writeOutput(serializeJSON(response));
|
|
</cfscript>
|