using Microsoft.AspNetCore.Authorization; using PowderCoating.Shared.Constants; using Microsoft.AspNetCore.Mvc; using PowderCoating.Core.Entities; using PowderCoating.Core.Interfaces; namespace PowderCoating.Web.Controllers; /// /// SuperAdmin-only CRUD interface for managing global ManufacturerLookupPattern records. /// These records have CompanyId = 0 and are shared across all tenants. /// [Authorize(Policy = AppConstants.Policies.SuperAdminOnly)] public class ManufacturerLookupPatternsController : Controller { private readonly IUnitOfWork _unitOfWork; private readonly ILogger _logger; public ManufacturerLookupPatternsController( IUnitOfWork unitOfWork, ILogger logger) { _unitOfWork = unitOfWork; _logger = logger; } /// /// Displays all non-deleted manufacturer lookup patterns ordered alphabetically. /// Uses ignoreQueryFilters: true because these records carry /// CompanyId = 0 and would otherwise be filtered out by the /// multi-tenancy global query filter. /// public async Task Index() { try { var patterns = await _unitOfWork.ManufacturerLookupPatterns.GetAllAsync(ignoreQueryFilters: true); var ordered = patterns .Where(p => !p.IsDeleted) .OrderBy(p => p.ManufacturerName) .ToList(); return View(ordered); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving manufacturer lookup patterns"); TempData["Error"] = "An error occurred while loading manufacturer lookup patterns."; return View(new List()); } } /// /// Returns the Create form pre-populated with sensible defaults: /// SlugTransform = "LowerHyphen" (the most common URL slug format for /// manufacturer product pages) and IsActive = true. /// // GET: ManufacturerLookupPatterns/Create public IActionResult Create() { return View(new ManufacturerLookupPattern { SlugTransform = "LowerHyphen", IsActive = true }); } /// /// Persists a new manufacturer lookup pattern. Forces CompanyId = 0 /// regardless of the current user's company because these patterns are global /// — they are shared by InventoryAiLookupService across all tenants. /// // POST: ManufacturerLookupPatterns/Create [HttpPost] [ValidateAntiForgeryToken] public async Task Create(ManufacturerLookupPattern item) { if (!ModelState.IsValid) { return View(item); } try { item.CompanyId = 0; item.CreatedAt = DateTime.UtcNow; await _unitOfWork.ManufacturerLookupPatterns.AddAsync(item); await _unitOfWork.SaveChangesAsync(); TempData["Success"] = $"Manufacturer pattern for \"{item.ManufacturerName}\" created successfully."; return RedirectToAction(nameof(Index)); } catch (Exception ex) { _logger.LogError(ex, "Error creating manufacturer lookup pattern"); TempData["Error"] = "An error occurred while creating the pattern."; return View(item); } } /// /// Returns the Edit form for an existing pattern. Fetches with /// ignoreQueryFilters: true so the CompanyId = 0 records are /// visible, then checks IsDeleted manually to prevent editing soft-deleted records. /// // GET: ManufacturerLookupPatterns/Edit/5 public async Task Edit(int? id) { if (id == null) { return NotFound(); } try { var patterns = await _unitOfWork.ManufacturerLookupPatterns.GetAllAsync(ignoreQueryFilters: true); var item = patterns.FirstOrDefault(p => p.Id == id.Value && !p.IsDeleted); if (item == null) { return NotFound(); } return View(item); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving manufacturer lookup pattern {Id} for edit", id); TempData["Error"] = "An error occurred while loading the pattern."; return RedirectToAction(nameof(Index)); } } /// /// Applies the edited field values to the tracked entity and saves. /// Only whitelisted fields are copied from the posted model (manual mapping) /// to prevent over-posting — particularly to keep CompanyId = 0 immutable /// and prevent an attacker from hijacking the pattern to a specific tenant. /// // POST: ManufacturerLookupPatterns/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public async Task Edit(int id, ManufacturerLookupPattern item) { if (id != item.Id) { return NotFound(); } if (!ModelState.IsValid) { return View(item); } try { var patterns = await _unitOfWork.ManufacturerLookupPatterns.GetAllAsync(ignoreQueryFilters: true); var existing = patterns.FirstOrDefault(p => p.Id == id && !p.IsDeleted); if (existing == null) { return NotFound(); } existing.ManufacturerName = item.ManufacturerName; existing.Domain = item.Domain; existing.ProductUrlTemplate = item.ProductUrlTemplate; existing.SlugTransform = item.SlugTransform; existing.IsActive = item.IsActive; existing.Notes = item.Notes; existing.UpdatedAt = DateTime.UtcNow; await _unitOfWork.ManufacturerLookupPatterns.UpdateAsync(existing); await _unitOfWork.SaveChangesAsync(); TempData["Success"] = $"Pattern for \"{existing.ManufacturerName}\" updated successfully."; return RedirectToAction(nameof(Index)); } catch (Exception ex) { _logger.LogError(ex, "Error updating manufacturer lookup pattern {Id}", id); TempData["Error"] = "An error occurred while updating the pattern."; return View(item); } } /// /// Returns the Delete confirmation view for a pattern, verifying that the record /// exists and is not already soft-deleted before showing the destructive action. /// // GET: ManufacturerLookupPatterns/Delete/5 public async Task Delete(int? id) { if (id == null) { return NotFound(); } try { var patterns = await _unitOfWork.ManufacturerLookupPatterns.GetAllAsync(ignoreQueryFilters: true); var item = patterns.FirstOrDefault(p => p.Id == id.Value && !p.IsDeleted); if (item == null) { return NotFound(); } return View(item); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving manufacturer lookup pattern {Id} for delete", id); TempData["Error"] = "An error occurred while loading the pattern."; return RedirectToAction(nameof(Index)); } } /// /// Soft-deletes the pattern (sets IsDeleted = true) rather than physically /// removing it. Soft delete is preferred so that historical AI lookup results that /// referenced this pattern remain traceable even after the record is retired. /// // POST: ManufacturerLookupPatterns/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task DeleteConfirmed(int id) { try { var patterns = await _unitOfWork.ManufacturerLookupPatterns.GetAllAsync(ignoreQueryFilters: true); var item = patterns.FirstOrDefault(p => p.Id == id && !p.IsDeleted); if (item == null) { return NotFound(); } await _unitOfWork.ManufacturerLookupPatterns.SoftDeleteAsync(item); await _unitOfWork.SaveChangesAsync(); TempData["Success"] = $"Pattern for \"{item.ManufacturerName}\" deleted successfully."; return RedirectToAction(nameof(Index)); } catch (Exception ex) { _logger.LogError(ex, "Error deleting manufacturer lookup pattern {Id}", id); TempData["Error"] = "An error occurred while deleting the pattern."; return RedirectToAction(nameof(Index)); } } }