Add manual "Sync Columbia" action and status to powder catalog admin

Phase 3: SuperAdmin-triggered sync. Adds a SyncColumbia POST action that runs
a full catalog sync on demand (bypassing the schedule) and reports the result
via TempData. The catalog index header gains a "Sync Columbia" button (with a
syncing spinner) and a status line showing the scheduled-sync on/off state,
last-synced time, and last-run summary, read from the platform settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:05:21 -04:00
parent 2b420d4623
commit 9aa3a99488
2 changed files with 60 additions and 0 deletions
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Application.Constants;
using PowderCoating.Application.DTOs.Common;
using PowderCoating.Application.DTOs.Inventory;
using PowderCoating.Application.Interfaces;
@@ -19,15 +20,21 @@ public class PowderCatalogController : Controller
private readonly IUnitOfWork _unitOfWork;
private readonly IInventoryAiLookupService _aiLookupService;
private readonly IColumbiaCatalogSyncService _columbiaSyncService;
private readonly IPlatformSettingsService _platformSettings;
private readonly ILogger<PowderCatalogController> _logger;
public PowderCatalogController(
IUnitOfWork unitOfWork,
IInventoryAiLookupService aiLookupService,
IColumbiaCatalogSyncService columbiaSyncService,
IPlatformSettingsService platformSettings,
ILogger<PowderCatalogController> logger)
{
_unitOfWork = unitOfWork;
_aiLookupService = aiLookupService;
_columbiaSyncService = columbiaSyncService;
_platformSettings = platformSettings;
_logger = logger;
}
@@ -135,6 +142,11 @@ public class PowderCatalogController : Controller
}
};
// Columbia sync status for the admin panel (last run + master switch).
ViewBag.ColumbiaSyncEnabled = await _platformSettings.GetBoolAsync(ColumbiaIntegrationConstants.SettingEnabled);
ViewBag.ColumbiaLastSyncedAt = await _platformSettings.GetAsync(ColumbiaIntegrationConstants.SettingLastSyncedAt);
ViewBag.ColumbiaLastResult = await _platformSettings.GetAsync(ColumbiaIntegrationConstants.SettingLastResult);
return View(vm);
}
@@ -422,6 +434,24 @@ public class PowderCatalogController : Controller
return Json(results);
}
/// <summary>
/// Manually triggers a full Columbia Coatings catalog sync (SuperAdmin only). Bypasses the
/// scheduled interval. Reports the run outcome via TempData on the catalog index.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SyncColumbia(CancellationToken cancellationToken)
{
var result = await _columbiaSyncService.RunSyncAsync(cancellationToken);
if (result.Success)
TempData["Success"] = $"Columbia sync complete &mdash; {result.Summary}";
else
TempData["Error"] = $"Columbia sync failed: {result.ErrorMessage}";
return RedirectToAction(nameof(Index));
}
// Private helpers
private async Task ApplyTdsCureFallbackAsync(InventoryAiLookupResult result, string? colorName)
@@ -110,12 +110,42 @@
<div class="text-muted small">Platform-level lookup library for inventory autofill, SDS/TDS links, and curing specs.</div>
</div>
<div class="d-flex gap-2">
<form asp-action="SyncColumbia" method="post" class="d-inline"
onsubmit="this.querySelector('button').disabled=true;this.querySelector('button').innerHTML='<span class=\'spinner-border spinner-border-sm me-1\'></span>Syncing&hellip;';">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-outline-primary" title="Pull the latest Columbia Coatings catalog now">
<i class="bi bi-cloud-download me-1"></i>Sync Columbia
</button>
</form>
<a asp-action="Create" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i>Add Powder
</a>
</div>
</div>
@{
var columbiaLastSynced = ViewBag.ColumbiaLastSyncedAt as string;
var columbiaLastResult = ViewBag.ColumbiaLastResult as string;
var columbiaEnabled = ViewBag.ColumbiaSyncEnabled is true;
}
<div class="alert alert-light border d-flex flex-wrap align-items-center gap-2 small mb-4 alert-permanent">
<span class="badge @(columbiaEnabled ? "bg-success" : "bg-secondary")">
Scheduled sync @(columbiaEnabled ? "on" : "off")
</span>
@if (!string.IsNullOrWhiteSpace(columbiaLastSynced) && DateTime.TryParse(columbiaLastSynced, out var lastSyncedAt))
{
<span class="text-muted">Last synced @lastSyncedAt.ToLocalTime().ToString("MMM d, yyyy h:mm tt")</span>
}
else
{
<span class="text-muted">Never synced</span>
}
@if (!string.IsNullOrWhiteSpace(columbiaLastResult))
{
<span class="text-muted">&mdash; @columbiaLastResult</span>
}
</div>
<div class="row g-3 mb-4 powder-catalog-summary">
<div class="col-sm-6 col-xl-2">
<div class="card h-100">