Initial commit
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user