Files
PowderCoatingLogix/src/PowderCoating.Web/Controllers/ManufacturerLookupPatternsController.cs
T
2026-04-23 21:38:24 -04:00

249 lines
8.8 KiB
C#

using Microsoft.AspNetCore.Authorization;
using PowderCoating.Shared.Constants;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;
namespace PowderCoating.Web.Controllers;
/// <summary>
/// SuperAdmin-only CRUD interface for managing global ManufacturerLookupPattern records.
/// These records have CompanyId = 0 and are shared across all tenants.
/// </summary>
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
public class ManufacturerLookupPatternsController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<ManufacturerLookupPatternsController> _logger;
public ManufacturerLookupPatternsController(
IUnitOfWork unitOfWork,
ILogger<ManufacturerLookupPatternsController> logger)
{
_unitOfWork = unitOfWork;
_logger = logger;
}
/// <summary>
/// Displays all non-deleted manufacturer lookup patterns ordered alphabetically.
/// Uses <c>ignoreQueryFilters: true</c> because these records carry
/// <c>CompanyId = 0</c> and would otherwise be filtered out by the
/// multi-tenancy global query filter.
/// </summary>
public async Task<IActionResult> 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<ManufacturerLookupPattern>());
}
}
/// <summary>
/// Returns the Create form pre-populated with sensible defaults:
/// <c>SlugTransform = "LowerHyphen"</c> (the most common URL slug format for
/// manufacturer product pages) and <c>IsActive = true</c>.
/// </summary>
// GET: ManufacturerLookupPatterns/Create
public IActionResult Create()
{
return View(new ManufacturerLookupPattern
{
SlugTransform = "LowerHyphen",
IsActive = true
});
}
/// <summary>
/// Persists a new manufacturer lookup pattern. Forces <c>CompanyId = 0</c>
/// regardless of the current user's company because these patterns are global
/// — they are shared by <c>InventoryAiLookupService</c> across all tenants.
/// </summary>
// POST: ManufacturerLookupPatterns/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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);
}
}
/// <summary>
/// Returns the Edit form for an existing pattern. Fetches with
/// <c>ignoreQueryFilters: true</c> so the <c>CompanyId = 0</c> records are
/// visible, then checks <c>IsDeleted</c> manually to prevent editing soft-deleted records.
/// </summary>
// GET: ManufacturerLookupPatterns/Edit/5
public async Task<IActionResult> 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));
}
}
/// <summary>
/// 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 <c>CompanyId = 0</c> immutable
/// and prevent an attacker from hijacking the pattern to a specific tenant.
/// </summary>
// POST: ManufacturerLookupPatterns/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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);
}
}
/// <summary>
/// Returns the Delete confirmation view for a pattern, verifying that the record
/// exists and is not already soft-deleted before showing the destructive action.
/// </summary>
// GET: ManufacturerLookupPatterns/Delete/5
public async Task<IActionResult> 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));
}
}
/// <summary>
/// Soft-deletes the pattern (sets <c>IsDeleted = true</c>) 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.
/// </summary>
// POST: ManufacturerLookupPatterns/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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));
}
}
}