Add facility overhead (rent + utilities) to operating costs and pricing engine

Adds MonthlyRent, MonthlyUtilities, and MonthlyBillableHours to CompanyOperatingCosts so fixed shop occupancy costs are recovered on every quote. The pricing engine converts these into a per-hour rate and applies it as a transparent "Facility Overhead" line between oven batch cost and shop supplies. UI added in Company Settings Operating Costs tab and Setup Wizard Step 3; migration AddFacilityOverheadFields applied. Help docs and AI knowledge base updated to cover the new fields and the revised quote pricing calculation order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 19:35:00 -04:00
parent 813f76138c
commit 4153acf3aa
14 changed files with 9575 additions and 21 deletions
@@ -542,6 +542,8 @@ public class PricingCalculationService : IPricingCalculationService
ItemsSubtotal = 0,
ShopSuppliesAmount = 0,
ShopSuppliesPercent = 0,
FacilityOverheadCost = 0,
FacilityOverheadRatePerHour = 0,
OverheadCosts = 0,
OverheadPercent = 0,
ProfitMargin = 0,
@@ -683,14 +685,27 @@ public class PricingCalculationService : IPricingCalculationService
var itemsAndOvenSubtotal = itemsSubtotal + ovenBatchCost;
// 5. SHOP SUPPLIES (percentage of items + oven subtotal)
// 5. FACILITY OVERHEAD (rent + utilities apportioned by estimated job hours)
var facilityOverheadRatePerHour = 0m;
var facilityOverheadCost = 0m;
if (costs.MonthlyBillableHours > 0 && (costs.MonthlyRent + costs.MonthlyUtilities) > 0)
{
facilityOverheadRatePerHour = (costs.MonthlyRent + costs.MonthlyUtilities) / costs.MonthlyBillableHours;
var totalEstimatedMinutes = items.Sum(i => (decimal)i.EstimatedMinutes * i.Quantity);
facilityOverheadCost = facilityOverheadRatePerHour * (totalEstimatedMinutes / 60m);
_logger.LogInformation(
"Facility overhead: ${Rate:F2}/hr × {Min:F0} min = ${Cost:F2}",
facilityOverheadRatePerHour, totalEstimatedMinutes, facilityOverheadCost);
}
// 6. SHOP SUPPLIES (percentage of items + oven subtotal — does not include facility overhead)
var shopSuppliesPercent = costs.ShopSuppliesRate;
var shopSuppliesAmount = itemsAndOvenSubtotal * (shopSuppliesPercent / 100m);
// 6. SUBTOTAL BEFORE DISCOUNT (items + oven + shop supplies)
var subtotalBeforeDiscount = itemsAndOvenSubtotal + shopSuppliesAmount;
// 7. SUBTOTAL BEFORE DISCOUNT (items + oven + facility overhead + shop supplies)
var subtotalBeforeDiscount = itemsAndOvenSubtotal + facilityOverheadCost + shopSuppliesAmount;
// 7. CUSTOMER PRICING TIER DISCOUNT (if applicable)
// 8. CUSTOMER PRICING TIER DISCOUNT (if applicable)
var pricingTierDiscountPercent = 0m;
var pricingTierDiscountAmount = 0m;
@@ -782,6 +797,8 @@ public class PricingCalculationService : IPricingCalculationService
OvenBatchCost = Math.Round(ovenBatchCost, 2),
OvenBatches = effectiveBatches,
OvenCycleMinutes = effectiveCycleMinutes,
FacilityOverheadCost = Math.Round(facilityOverheadCost, 2),
FacilityOverheadRatePerHour = Math.Round(facilityOverheadRatePerHour, 4),
ShopSuppliesAmount = Math.Round(shopSuppliesAmount, 2),
ShopSuppliesPercent = Math.Round(shopSuppliesPercent, 2),
OverheadCosts = Math.Round(overheadCosts, 2),