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

132 lines
5.5 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;
using PowderCoating.Shared.Constants;
namespace PowderCoating.Web.Controllers;
/// <summary>
/// Lightweight API endpoints consumed by the client-side item wizard.
/// Intentionally uses <c>CanManageJobs</c> (not <c>CanManageProducts</c>) so that
/// any user who can create quotes or jobs can also save items to the catalog directly
/// from the wizard without needing a separate catalog-management permission.
/// </summary>
[Authorize(Policy = AppConstants.Policies.CanManageJobs)]
public class ItemWizardController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly ITenantContext _tenantContext;
private readonly ILogger<ItemWizardController> _logger;
public ItemWizardController(IUnitOfWork unitOfWork, ITenantContext tenantContext,
ILogger<ItemWizardController> logger)
{
_unitOfWork = unitOfWork;
_tenantContext = tenantContext;
_logger = logger;
}
/// <summary>
/// Returns active catalog categories for the current company with their full hierarchy path
/// (e.g. "Automotive > Wheels") so the wizard dropdown can distinguish categories that share
/// the same name but sit under different parents. Paths are built in-memory after a single
/// query — no extra round-trips needed since ParentCategoryId is a scalar field.
/// </summary>
[HttpGet]
public async Task<IActionResult> GetCatalogCategories()
{
var companyId = _tenantContext.GetCurrentCompanyId();
if (companyId == null) return Json(Array.Empty<object>());
var cats = await _unitOfWork.CatalogCategories.FindAsync(
c => c.CompanyId == companyId.Value && !c.IsDeleted);
var lookup = cats.ToDictionary(c => c.Id);
string BuildPath(PowderCoating.Core.Entities.CatalogCategory cat)
{
var parts = new System.Collections.Generic.List<string> { cat.Name };
var current = cat;
while (current.ParentCategoryId.HasValue &&
lookup.TryGetValue(current.ParentCategoryId.Value, out var parent))
{
parts.Insert(0, parent.Name);
current = parent;
}
return string.Join(" > ", parts);
}
return Json(cats
.Select(c => new { c.Id, Name = BuildPath(c) })
.OrderBy(c => c.Name));
}
/// <summary>
/// Creates a new catalog item from the item wizard's Save-to-Catalog step.
/// Called via AJAX immediately when the user confirms the save — before the quote
/// or job form is submitted — so the catalog item is saved even if the user later
/// abandons the quote.
/// </summary>
[HttpPost]
public async Task<IActionResult> SaveToCatalog([FromBody] SaveToCatalogRequest request)
{
try
{
var companyId = _tenantContext.GetCurrentCompanyId();
if (companyId == null)
return Json(new { ok = false, error = "No company context." });
if (string.IsNullOrWhiteSpace(request.Name))
return Json(new { ok = false, error = "Item name is required." });
if (request.CategoryId <= 0)
return Json(new { ok = false, error = "Please select a category." });
var category = await _unitOfWork.CatalogCategories.GetByIdAsync(request.CategoryId);
if (category == null || category.CompanyId != companyId.Value)
return Json(new { ok = false, error = "Invalid category." });
var item = new CatalogItem
{
CompanyId = companyId.Value,
Name = request.Name.Trim(),
Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(),
CategoryId = request.CategoryId,
DefaultPrice = request.DefaultPrice,
ApproximateArea = request.ApproximateArea > 0 ? request.ApproximateArea : null,
DefaultEstimatedMinutes = request.DefaultEstimatedMinutes > 0 ? request.DefaultEstimatedMinutes : null,
DefaultRequiresSandblasting = request.DefaultRequiresSandblasting,
DefaultRequiresMasking = request.DefaultRequiresMasking,
IsActive = true,
DisplayOrder = 0
};
await _unitOfWork.CatalogItems.AddAsync(item);
await _unitOfWork.CompleteAsync();
_logger.LogInformation("Catalog item '{Name}' (id {Id}) created from item wizard by company {CompanyId}",
item.Name, item.Id, companyId.Value);
return Json(new { ok = true, id = item.Id, name = item.Name });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving catalog item from wizard");
return Json(new { ok = false, error = "An error occurred saving the item. Please try again." });
}
}
}
public class SaveToCatalogRequest
{
public string Name { get; set; } = string.Empty;
public int CategoryId { get; set; }
public decimal DefaultPrice { get; set; }
public string? Description { get; set; }
public decimal ApproximateArea { get; set; }
public int DefaultEstimatedMinutes { get; set; }
public bool DefaultRequiresSandblasting { get; set; }
public bool DefaultRequiresMasking { get; set; }
}