diff --git a/src/PowderCoating.Application/Services/PricingCalculationService.cs b/src/PowderCoating.Application/Services/PricingCalculationService.cs
index 8c8e2a1..ff16d20 100644
--- a/src/PowderCoating.Application/Services/PricingCalculationService.cs
+++ b/src/PowderCoating.Application/Services/PricingCalculationService.cs
@@ -422,12 +422,14 @@ public class PricingCalculationService : IPricingCalculationService
else
{
// Non-catalog: derive base from first coat's material + labor + equipment + markup
+ decimal coatLaborCost = 0m; // coat-only labor, used for coating booth (not prep/sandblast)
if (item.Coats != null && item.Coats.Count > 0)
{
var firstCoatResult = await CalculateCoatPriceAsync(
item.Coats[0], item.SurfaceAreaSqFt, item.Quantity, 0, item.EstimatedMinutes, companyId);
totalMaterialCost = firstCoatResult.CoatMaterialCost;
- totalLaborCost = firstCoatResult.CoatLaborCost;
+ coatLaborCost = firstCoatResult.CoatLaborCost;
+ totalLaborCost = coatLaborCost;
}
// Prep service labor (done once per item batch)
@@ -443,9 +445,10 @@ public class PricingCalculationService : IPricingCalculationService
// Consumables surcharge (5% of material)
totalMaterialCost += totalMaterialCost * ConsumablesSurchargePercent;
- // Equipment cost: coating booth only (oven cost moved to quote-level batch calculation)
- var totalLaborHours = totalLaborCost / costs.StandardLaborRate;
- totalEquipmentCost = totalLaborHours * costs.CoatingBoothCostPerHour;
+ // Equipment cost: coating booth only — use coat labor hours, not prep/sandblast hours
+ // (sandblasting happens in a blast cabinet, not the powder coating booth)
+ var coatLaborHours = costs.StandardLaborRate > 0 ? coatLaborCost / costs.StandardLaborRate : 0m;
+ totalEquipmentCost = coatLaborHours * costs.CoatingBoothCostPerHour;
// Apply pricing mode: markup on material only, or target margin on total cost
if (costs.PricingMode == PowderCoating.Core.Enums.PricingMode.MarginOnTotalCost)
@@ -675,22 +678,24 @@ public class PricingCalculationService : IPricingCalculationService
var effectiveBatches = Math.Max(1, ovenBatches);
var fullOvenBatchCost = effectiveBatches * (effectiveCycleMinutes / 60m) * effectiveOvenRate;
- // Scale oven cost by the fraction of total surface area coming from non-AI items.
- // Use item count as a fallback when surface areas are all zero.
- var totalSqFt = items.Sum(i => i.SurfaceAreaSqFt * i.Quantity);
- var aiSqFt = items.Where(i => i.IsAiItem).Sum(i => i.SurfaceAreaSqFt * i.Quantity);
- var nonAiSqFt = totalSqFt - aiSqFt;
+ // Only items with coating layers go in the oven — sandblast/prep-only items (zero coats) don't.
+ // Of those coating items, AI items already have oven cost baked into their AI price.
+ var coatingItems = items.Where(i => i.Coats != null && i.Coats.Any()).ToList();
+ var nonAiCoatItems = coatingItems.Where(i => !i.IsAiItem).ToList();
decimal nonAiFraction;
- if (totalSqFt > 0)
+ if (!coatingItems.Any())
{
- nonAiFraction = nonAiSqFt / totalSqFt;
+ nonAiFraction = 0m; // No coated items — no oven charge
}
else
{
- var totalCount = items.Count;
- var aiCount = items.Count(i => i.IsAiItem);
- nonAiFraction = totalCount > 0 ? (decimal)(totalCount - aiCount) / totalCount : 1m;
+ var totalCoatSqFt = coatingItems.Sum(i => i.SurfaceAreaSqFt * i.Quantity);
+ var nonAiCoatSqFt = nonAiCoatItems.Sum(i => i.SurfaceAreaSqFt * i.Quantity);
+ if (totalCoatSqFt > 0)
+ nonAiFraction = nonAiCoatSqFt / totalCoatSqFt;
+ else
+ nonAiFraction = coatingItems.Count > 0 ? (decimal)nonAiCoatItems.Count / coatingItems.Count : 1m;
}
var ovenBatchCost = fullOvenBatchCost * nonAiFraction;
diff --git a/src/PowderCoating.Web/Views/Dashboard/Index.cshtml b/src/PowderCoating.Web/Views/Dashboard/Index.cshtml
index 46cdb0e..69dc8bd 100644
--- a/src/PowderCoating.Web/Views/Dashboard/Index.cshtml
+++ b/src/PowderCoating.Web/Views/Dashboard/Index.cshtml
@@ -479,7 +479,7 @@
Powder in Queue to be Ordered
@Model.PowderOrdersNeededCount item@(Model.PowderOrdersNeededCount == 1 ? "" : "s")
- Grouped by vendor · Mark lines as ordered to remove them
+ Grouped by vendor · Mark lines as ordered to remove them
@foreach (var vendorGroup in Model.PowderOrdersNeeded)
@@ -574,7 +574,7 @@
Powder Ordered — Awaiting Receipt
@Model.PowderOrdersPlacedCount item@(Model.PowderOrdersPlacedCount == 1 ? "" : "s")
- Grouped by vendor · Enter lbs received to update inventory
+ Grouped by vendor · Enter lbs received to update inventory