#serializeJSON({"OK": false, "ERROR": "admin_only"})# /** * Cleanup Categories - Final step after migration verification * * This script: * 1. Verifies all Items have ItemBusinessID set * 2. Finds orphan items (ParentID=0, no children, not in links) * 3. Drops ItemCategoryID column * 4. Drops ItemIsModifierTemplate column (derived from ItemTemplateLinks now) * 5. Drops Categories table * * Query param: ?confirm=YES to actually execute (otherwise shows verification only) */ response = { "OK": false, "verification": {}, "orphans": [], "steps": [] }; try { confirm = structKeyExists(url, "confirm") && url.confirm == "YES"; // Verification Step 1: Check for items without BusinessID qNoBusinessID = queryExecute(" SELECT COUNT(*) as cnt FROM Items WHERE ItemBusinessID IS NULL OR ItemBusinessID = 0 ", {}, { datasource: "payfrit" }); response.verification["itemsWithoutBusinessID"] = qNoBusinessID.cnt; // Verification Step 2: Check that all categories were converted qCategories = queryExecute(" SELECT COUNT(*) as cnt FROM Categories ", {}, { datasource: "payfrit" }); response.verification["categoriesRemaining"] = qCategories.cnt; // Verification Step 3: Check category Items exist (ParentID=0 with children) qCategoryItems = queryExecute(" SELECT COUNT(DISTINCT p.ItemID) as cnt FROM Items p INNER JOIN Items c ON c.ItemParentItemID = p.ItemID WHERE p.ItemParentItemID = 0 AND p.ItemBusinessID > 0 AND NOT EXISTS ( SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = p.ItemID ) ", {}, { datasource: "payfrit" }); response.verification["categoryItemsCreated"] = qCategoryItems.cnt; // Verification Step 4: Check templates exist (in ItemTemplateLinks) qTemplates = queryExecute(" SELECT COUNT(DISTINCT tl.TemplateItemID) as cnt FROM ItemTemplateLinks tl INNER JOIN Items t ON t.ItemID = tl.TemplateItemID ", {}, { datasource: "payfrit" }); response.verification["templatesInLinks"] = qTemplates.cnt; // Verification Step 5: Find orphans at ParentID=0 // Orphan = ParentID=0, no children pointing to it, not in ItemTemplateLinks qOrphans = queryExecute(" SELECT i.ItemID, i.ItemName, i.ItemBusinessID FROM Items i WHERE i.ItemParentItemID = 0 AND NOT EXISTS ( SELECT 1 FROM Items child WHERE child.ItemParentItemID = i.ItemID ) AND NOT EXISTS ( SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID ) ORDER BY i.ItemBusinessID, i.ItemName ", {}, { datasource: "payfrit" }); response.verification["orphanCount"] = qOrphans.recordCount; for (orphan in qOrphans) { arrayAppend(response.orphans, { "ItemID": orphan.ItemID, "ItemName": orphan.ItemName, "BusinessID": orphan.ItemBusinessID }); } // Summary safeToCleanup = (qNoBusinessID.cnt == 0); response.verification["safeToCleanup"] = safeToCleanup; if (!safeToCleanup) { arrayAppend(response.steps, "VERIFICATION FAILED - Cannot cleanup yet"); arrayAppend(response.steps, "- " & qNoBusinessID.cnt & " items still missing ItemBusinessID"); response["OK"] = false; writeOutput(serializeJSON(response)); abort; } if (qOrphans.recordCount > 0) { arrayAppend(response.steps, "WARNING: " & qOrphans.recordCount & " orphan items found (see orphans array)"); arrayAppend(response.steps, "These will NOT be deleted - review and handle manually if needed"); } arrayAppend(response.steps, "Verification passed - safe to cleanup"); if (!confirm) { arrayAppend(response.steps, "Add ?confirm=YES to execute cleanup"); response["OK"] = true; writeOutput(serializeJSON(response)); abort; } // Execute cleanup arrayAppend(response.steps, "Executing cleanup..."); // Step 1: Drop ItemCategoryID column try { queryExecute(" ALTER TABLE Items DROP COLUMN ItemCategoryID ", {}, { datasource: "payfrit" }); arrayAppend(response.steps, "Dropped ItemCategoryID column from Items"); } catch (any e) { if (findNoCase("check that column", e.message) || findNoCase("Unknown column", e.message)) { arrayAppend(response.steps, "ItemCategoryID column already dropped"); } else { arrayAppend(response.steps, "Warning dropping ItemCategoryID: " & e.message); } } // Step 2: Drop ItemIsModifierTemplate column (now derived from ItemTemplateLinks) try { queryExecute(" ALTER TABLE Items DROP COLUMN ItemIsModifierTemplate ", {}, { datasource: "payfrit" }); arrayAppend(response.steps, "Dropped ItemIsModifierTemplate column from Items"); } catch (any e) { if (findNoCase("check that column", e.message) || findNoCase("Unknown column", e.message)) { arrayAppend(response.steps, "ItemIsModifierTemplate column already dropped"); } else { arrayAppend(response.steps, "Warning dropping ItemIsModifierTemplate: " & e.message); } } // Step 3: Drop Categories table try { queryExecute(" DROP TABLE Categories ", {}, { datasource: "payfrit" }); arrayAppend(response.steps, "Dropped Categories table"); } catch (any e) { if (findNoCase("Unknown table", e.message)) { arrayAppend(response.steps, "Categories table already dropped"); } else { arrayAppend(response.steps, "Warning dropping Categories: " & e.message); } } response["OK"] = true; arrayAppend(response.steps, "CLEANUP COMPLETE - Schema simplified"); } catch (any e) { response["ERROR"] = e.message; response["DETAIL"] = e.detail; } writeOutput(serializeJSON(response));