Add Columbia right-to-delete purge action

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:28:29 -04:00
parent 4506c1f641
commit d2d9f44358
2 changed files with 61 additions and 0 deletions
@@ -452,6 +452,60 @@ public class PowderCatalogController : Controller
return RedirectToAction(nameof(Index));
}
/// <summary>
/// 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.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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)
@@ -144,6 +144,13 @@
{
<span class="text-muted">&mdash; @columbiaLastResult</span>
}
<form asp-action="PurgeColumbiaData" method="post" class="ms-auto"
onsubmit="return confirm('Remove ALL Columbia Coatings API data from the powder catalog? This deletes every record sourced from their feed (including PPG and KP Pigments products served through it) and unlinks inventory items. Inventory stock is preserved. This cannot be undone.');">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-outline-danger btn-sm" title="Right-to-delete: remove all Columbia-sourced catalog data">
<i class="bi bi-trash me-1"></i>Remove Columbia data
</button>
</form>
</div>
<div class="row g-3 mb-4 powder-catalog-summary">