From d2d9f44358c5d328441469de93254529aa69cefa Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Wed, 17 Jun 2026 11:28:29 -0400 Subject: [PATCH] Add Columbia right-to-delete purge action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 5 (part): compliance. PurgeColumbiaData (SuperAdmin) deletes every catalog record whose Source is the Columbia Coatings API feed — regardless of derived manufacturer, since PPG and KP Pigments products were served through that feed — and nulls any inventory PowderCatalogItemId links across all tenants. Tenant stock records are preserved (they keep their add-time snapshot, losing only the live catalog link/badge), honoring the boundary that the distributor's right-to-delete covers their catalog, not customers' purchased stock. Adds a confirmed "Remove Columbia data" button to the catalog admin. Co-Authored-By: Claude Opus 4.8 --- .../Controllers/PowderCatalogController.cs | 54 +++++++++++++++++++ .../Views/PowderCatalog/Index.cshtml | 7 +++ 2 files changed, 61 insertions(+) diff --git a/src/PowderCoating.Web/Controllers/PowderCatalogController.cs b/src/PowderCoating.Web/Controllers/PowderCatalogController.cs index 25329c3..32f2148 100644 --- a/src/PowderCoating.Web/Controllers/PowderCatalogController.cs +++ b/src/PowderCoating.Web/Controllers/PowderCatalogController.cs @@ -452,6 +452,60 @@ public class PowderCatalogController : Controller return RedirectToAction(nameof(Index)); } + /// + /// Right-to-delete: removes every catalog record sourced from the Columbia Coatings API + /// (regardless of derived manufacturer, since PPG/KP products were served through that feed) + /// and nulls any inventory links to them across all tenants. The shops' own inventory stock + /// records survive — only the catalog link and discontinued badge are lost. SuperAdmin only. + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task PurgeColumbiaData() + { + var sourced = (await _unitOfWork.PowderCatalog.FindAsync( + p => p.Source == ColumbiaIntegrationConstants.SourceName)).ToList(); + + if (sourced.Count == 0) + { + TempData["Error"] = "There is no Columbia Coatings API data to remove."; + return RedirectToAction(nameof(Index)); + } + + var ids = sourced.Select(p => p.Id).ToList(); + + // Null the inventory links across ALL tenants (platform-level purge). A tenant's stock + // record is their data and must survive — it keeps its add-time snapshot, losing only the + // live catalog link. + var linked = (await _unitOfWork.InventoryItems.FindAsync( + i => i.PowderCatalogItemId.HasValue && ids.Contains(i.PowderCatalogItemId.Value), + ignoreQueryFilters: true)).ToList(); + foreach (var inv in linked) + { + inv.PowderCatalogItemId = null; + await _unitOfWork.InventoryItems.UpdateAsync(inv); + } + + foreach (var p in sourced) + await _unitOfWork.PowderCatalog.DeleteAsync(p); + await _unitOfWork.CompleteAsync(); + + // Reset sync tracking so the admin panel reflects the purge. + await _platformSettings.SetAsync(ColumbiaIntegrationConstants.SettingLastSyncedAt, null, "Columbia Purge"); + await _platformSettings.SetAsync( + ColumbiaIntegrationConstants.SettingLastResult, + $"Purged {sourced.Count:N0} records on {DateTime.UtcNow:yyyy-MM-dd}", + "Columbia Purge"); + + _logger.LogWarning( + "Columbia data purge: deleted {Count} catalog records, unlinked {Linked} inventory items.", + sourced.Count, linked.Count); + + TempData["Success"] = + $"Removed {sourced.Count:N0} Columbia Coatings catalog record(s) and unlinked " + + $"{linked.Count:N0} inventory item(s). Inventory stock was preserved."; + return RedirectToAction(nameof(Index)); + } + // Private helpers private async Task ApplyTdsCureFallbackAsync(InventoryAiLookupResult result, string? colorName) diff --git a/src/PowderCoating.Web/Views/PowderCatalog/Index.cshtml b/src/PowderCoating.Web/Views/PowderCatalog/Index.cshtml index 4ceb16d..7a71a0a 100644 --- a/src/PowderCoating.Web/Views/PowderCatalog/Index.cshtml +++ b/src/PowderCoating.Web/Views/PowderCatalog/Index.cshtml @@ -144,6 +144,13 @@ { — @columbiaLastResult } +
+ @Html.AntiForgeryToken() + +