using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PowderCoating.Core.Entities; using PowderCoating.Core.Interfaces; using PowderCoating.Shared.Constants; namespace PowderCoating.Web.Controllers; /// /// Lightweight API endpoints consumed by the client-side item wizard. /// Intentionally uses CanManageJobs (not CanManageProducts) 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. /// [Authorize(Policy = AppConstants.Policies.CanManageJobs)] public class ItemWizardController : Controller { private readonly IUnitOfWork _unitOfWork; private readonly ITenantContext _tenantContext; private readonly ILogger _logger; public ItemWizardController(IUnitOfWork unitOfWork, ITenantContext tenantContext, ILogger logger) { _unitOfWork = unitOfWork; _tenantContext = tenantContext; _logger = logger; } /// /// 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. /// [HttpGet] public async Task GetCatalogCategories() { var companyId = _tenantContext.GetCurrentCompanyId(); if (companyId == null) return Json(Array.Empty()); 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 { 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)); } /// /// 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. /// [HttpPost] public async Task 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; } }