using PowderCoating.Application.DTOs.Quote; using PowderCoating.Application.Interfaces; using PowderCoating.Application.Services; using PowderCoating.Core.Entities; namespace PowderCoating.UnitTests; public class JobItemAssemblyServiceTests { private static readonly DateTime CreatedAtUtc = new(2026, 5, 9, 14, 30, 0, DateTimeKind.Utc); private readonly IJobItemAssemblyService _service = new JobItemAssemblyService(); [Fact] public void CreateJobItem_FromWizardDto_PreservesSalesFieldsAndCalculatedChildren() { var source = new CreateQuoteItemDto { Description = "Powder coated tumbler", Quantity = 2m, SurfaceAreaSqFt = 12m, EstimatedMinutes = 18, CatalogItemId = 44, IsSalesItem = true, Sku = "TMB-RED-20", ManualUnitPrice = 29.99m, PowderCostOverride = 7.25m, RequiresSandblasting = true, RequiresMasking = true, Notes = "Merch item", IncludePrepCost = false, Complexity = "Moderate", AiTags = "merch,tumbler", AiPredictionId = 91, Coats = [ new CreateQuoteItemCoatDto { CoatName = "Base", Sequence = 1, ColorName = "Signal Red", ColorCode = "RAL3001", Finish = "Gloss", CoverageSqFtPerLb = 30m, TransferEfficiency = 50m } ], PrepServices = [ new CreateQuoteItemPrepServiceDto { PrepServiceId = 7, EstimatedMinutes = 12, BlastSetupId = 88 } ] }; var pricing = new QuoteItemPricingResult { UnitPrice = 29.99m, TotalPrice = 59.98m }; var jobItem = _service.CreateJobItem(source, jobId: 10, companyId: 3, pricing: pricing, createdAtUtc: CreatedAtUtc); var coats = _service.CreateJobItemCoats(source, jobItemId: 25, companyId: 3, CreatedAtUtc); var prepServices = _service.CreateJobItemPrepServices(source, jobItemId: 25, companyId: 3, CreatedAtUtc); Assert.Equal(10, jobItem.JobId); Assert.Equal("Powder coated tumbler", jobItem.Description); Assert.True(jobItem.IsSalesItem); Assert.Equal("TMB-RED-20", jobItem.Sku); Assert.False(jobItem.IncludePrepCost); Assert.Equal(91, jobItem.AiPredictionId); Assert.Equal("merch,tumbler", jobItem.AiTags); Assert.Equal(59.98m, jobItem.TotalPrice); Assert.Equal(23.992m, jobItem.LaborCost); Assert.Equal(CreatedAtUtc, jobItem.CreatedAt); var coat = Assert.Single(coats); Assert.Equal(25, coat.JobItemId); Assert.Equal(1.6m, coat.PowderToOrder); Assert.Equal("Signal Red", coat.ColorName); Assert.Equal(CreatedAtUtc, coat.CreatedAt); var prepService = Assert.Single(prepServices); Assert.Equal(88, prepService.BlastSetupId); Assert.Equal(12, prepService.EstimatedMinutes); Assert.Equal(CreatedAtUtc, prepService.CreatedAt); } [Fact] public void CreateJobItem_FromQuoteItem_PreservesQuoteShapeAndPrepCostFlag() { var quoteItem = new QuoteItem { Description = "Bracket set", Quantity = 3m, SurfaceAreaSqFt = 10m, CatalogItemId = 14, IsGenericItem = false, IsLaborItem = false, IsSalesItem = true, Sku = "BRK-SET", ManualUnitPrice = 18m, PowderCostOverride = 6m, UnitPrice = 42m, TotalPrice = 126m, RequiresSandblasting = true, RequiresMasking = false, EstimatedMinutes = 25, Notes = "Use existing hang points", Complexity = "Complex", IncludePrepCost = true, AiTags = "bracket,steel", AiPredictionId = 55, Coats = [ new QuoteItemCoat { CoatName = "Top Coat", Sequence = 1, InventoryItemId = 12, ColorName = "Stale Name", ColorCode = "STALE", Finish = "Stale", CoverageSqFtPerLb = 20m, TransferEfficiency = 80m, PowderCostPerLb = 5m, Notes = "Resolved from inventory", InventoryItem = new InventoryItem { Id = 12, Name = "Gloss Black", ColorCode = "RAL9005", Finish = "Gloss" } } ], PrepServices = [ new QuoteItemPrepService { PrepServiceId = 4, EstimatedMinutes = 9, BlastSetupId = 41 } ] }; var jobItem = _service.CreateJobItem(quoteItem, jobId: 99, companyId: 6, createdAtUtc: CreatedAtUtc); var coats = _service.CreateJobItemCoats(quoteItem, jobItemId: 70, companyId: 6, CreatedAtUtc); var prepServices = _service.CreateJobItemPrepServices(quoteItem, jobItemId: 70, companyId: 6, CreatedAtUtc); Assert.Equal(99, jobItem.JobId); Assert.True(jobItem.IsSalesItem); Assert.Equal("BRK-SET", jobItem.Sku); Assert.True(jobItem.IncludePrepCost); Assert.Equal(55, jobItem.AiPredictionId); Assert.Equal("bracket,steel", jobItem.AiTags); var coat = Assert.Single(coats); Assert.Equal("Gloss Black", coat.ColorName); Assert.Equal("RAL9005", coat.ColorCode); Assert.Equal("Gloss", coat.Finish); Assert.Equal(1.88m, coat.PowderToOrder); var prepService = Assert.Single(prepServices); Assert.Equal(41, prepService.BlastSetupId); } [Fact] public void CreateJobItem_FromQuoteItem_UsesStoredPowderToOrderWhenPresent() { var quoteItem = new QuoteItem { Description = "Wheel", Quantity = 4m, SurfaceAreaSqFt = 15m, Coats = [ new QuoteItemCoat { CoatName = "Primer", Sequence = 1, CoverageSqFtPerLb = 30m, TransferEfficiency = 65m, PowderToOrder = 9.5m } ] }; var coat = Assert.Single(_service.CreateJobItemCoats(quoteItem, jobItemId: 5, companyId: 1, CreatedAtUtc)); Assert.Equal(9.5m, coat.PowderToOrder); } [Fact] public void CreateJobItem_FromExistingJobItem_PreservesTransferableShapeForRework() { var source = new JobItem { Description = "Gate panel", Quantity = 1m, ColorName = "Bronze", ColorCode = "BZ-22", Finish = "Textured", SurfaceArea = 22m, SurfaceAreaSqFt = 22m, CatalogItemId = 8, IsGenericItem = false, IsLaborItem = false, IsSalesItem = true, Sku = "GATE-BRZ", ManualUnitPrice = 140m, PowderCostOverride = 9m, UnitPrice = 140m, TotalPrice = 140m, LaborCost = 56m, RequiresSandblasting = true, RequiresMasking = true, EstimatedMinutes = 90, Notes = "Rework copy", IncludePrepCost = false, Complexity = "Extreme", AiTags = "gate,outdoor", AiPredictionId = 12, Coats = [ new JobItemCoat { CoatName = "Top Coat", Sequence = 1, InventoryItemId = 21, ColorName = "Bronze", VendorId = 13, ColorCode = "BZ-22", Finish = "Textured", CoverageSqFtPerLb = 26m, TransferEfficiency = 70m, PowderCostPerLb = 8m, PowderToOrder = 2.75m, Notes = "Keep order qty" } ], PrepServices = [ new JobItemPrepService { PrepServiceId = 3, EstimatedMinutes = 45, BlastSetupId = 77 } ] }; var jobItem = _service.CreateJobItem(source, jobId: 222, companyId: 9, createdAtUtc: CreatedAtUtc); var coats = _service.CreateJobItemCoats(source, jobItemId: 333, companyId: 9, CreatedAtUtc); var prepServices = _service.CreateJobItemPrepServices(source, jobItemId: 333, companyId: 9, CreatedAtUtc); Assert.Equal(222, jobItem.JobId); Assert.Equal("Bronze", jobItem.ColorName); Assert.True(jobItem.IsSalesItem); Assert.Equal("GATE-BRZ", jobItem.Sku); Assert.False(jobItem.IncludePrepCost); Assert.Equal(56m, jobItem.LaborCost); Assert.Equal(12, jobItem.AiPredictionId); var coat = Assert.Single(coats); Assert.Equal(2.75m, coat.PowderToOrder); Assert.Equal("Bronze", coat.ColorName); var prepService = Assert.Single(prepServices); Assert.Equal(77, prepService.BlastSetupId); Assert.Equal(45, prepService.EstimatedMinutes); } }