249 lines
8.8 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|