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
@@ -140,6 +140,14 @@ namespace PowderCoating.Application.DTOs.Company
public decimal DerivedBlastRateSqFtPerHour { get; set; }
/// <summary>Derived coating rate — shown to the user as a sanity-check value.</summary>
public decimal DerivedCoatingRateSqFtPerHour { get; set; }
// Facility Overhead
public decimal MonthlyRent { get; set; }
public decimal MonthlyUtilities { get; set; }
public int MonthlyBillableHours { get; set; } = 160;
/// <summary>Derived facility overhead rate = (MonthlyRent + MonthlyUtilities) / MonthlyBillableHours.</summary>
public decimal FacilityOverheadRatePerHour { get; set; }
}
/// <summary>
@@ -228,6 +236,19 @@ namespace PowderCoating.Application.DTOs.Company
[Range(0, 500)]
public decimal ComplexityExtremePercent { get; set; } = 25m;
// Facility Overhead
[Range(0, 1000000, ErrorMessage = "Monthly rent must be between 0 and 1,000,000")]
[Display(Name = "Monthly Rent ($)")]
public decimal MonthlyRent { get; set; } = 0m;
[Range(0, 1000000, ErrorMessage = "Monthly utilities must be between 0 and 1,000,000")]
[Display(Name = "Monthly Utilities ($)")]
public decimal MonthlyUtilities { get; set; } = 0m;
[Range(1, 10000, ErrorMessage = "Billable hours must be between 1 and 10,000")]
[Display(Name = "Billable Hours/Month")]
public int MonthlyBillableHours { get; set; } = 160;
}
/// <summary>DTO for updating the company AI profile text used for AI Photo Quote calibration.</summary>
@@ -606,6 +606,9 @@ public class QuotePricingBreakdownDto
public int OvenBatches { get; set; }
public int OvenCycleMinutes { get; set; }
public decimal FacilityOverheadCost { get; set; }
public decimal FacilityOverheadRatePerHour { get; set; }
public decimal Total { get; set; }
// Cost Breakdown Details
@@ -822,6 +825,10 @@ public class QuotePricingResult
public int OvenBatches { get; set; }
public int OvenCycleMinutes { get; set; }
// Facility overhead (rent + utilities apportioned by estimated job hours)
public decimal FacilityOverheadCost { get; set; }
public decimal FacilityOverheadRatePerHour { get; set; }
// Detailed breakdown for transparency
public decimal MaterialCosts { get; set; }
public decimal LaborCosts { get; set; }
@@ -112,6 +112,19 @@ public class WizardStep2Dto
[Display(Name = "Shop Capability Tier")]
public ShopCapabilityTier ShopCapabilityTier { get; set; } = ShopCapabilityTier.Small;
// Facility Overhead
[Range(0, 1000000)]
[Display(Name = "Monthly Rent ($)")]
public decimal MonthlyRent { get; set; } = 0m;
[Range(0, 1000000)]
[Display(Name = "Monthly Utilities ($)")]
public decimal MonthlyUtilities { get; set; } = 0m;
[Range(1, 10000)]
[Display(Name = "Billable Hours/Month")]
public int MonthlyBillableHours { get; set; } = 160;
}
// ─── Step 3: Branding & Numbering ───────────────────────────────────────────