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:
@@ -361,6 +361,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Facility Overhead -->
|
||||
<h6 class="border-bottom pb-2 mb-3 mt-3">Facility Overhead
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-title="Facility Overhead"
|
||||
data-bs-content="Enter your monthly shop rent and combined utility costs. The system divides these by your estimated billable hours to derive a per-hour overhead rate, which is then added to every quote proportionally to the estimated job time. This ensures fixed facility costs are recovered across all jobs rather than absorbed into your markup.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</h6>
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label for="monthlyRent" class="form-label">Monthly Rent</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" step="0.01" class="form-control facility-overhead-input" id="monthlyRent" name="MonthlyRent" value="@(Model.OperatingCosts?.MonthlyRent ?? 0)" min="0" max="1000000">
|
||||
<span class="input-group-text">/mo</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label for="monthlyUtilities" class="form-label">Monthly Utilities</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" step="0.01" class="form-control facility-overhead-input" id="monthlyUtilities" name="MonthlyUtilities" value="@(Model.OperatingCosts?.MonthlyUtilities ?? 0)" min="0" max="1000000">
|
||||
<span class="input-group-text">/mo</span>
|
||||
</div>
|
||||
<small class="text-muted">Electricity, gas, water, internet</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label for="monthlyBillableHours" class="form-label">Billable Hours/Month</label>
|
||||
<input type="number" step="1" class="form-control facility-overhead-input" id="monthlyBillableHours" name="MonthlyBillableHours" value="@(Model.OperatingCosts?.MonthlyBillableHours ?? 160)" min="1" max="10000">
|
||||
<small class="text-muted">Typical: 160 hrs (4 wks × 40 hrs)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted">Calculated Rate</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="bi bi-calculator"></i></span>
|
||||
<input type="text" class="form-control bg-light" id="facilityOverheadRateDisplay" readonly
|
||||
value="@((Model.OperatingCosts?.FacilityOverheadRatePerHour ?? 0).ToString("C2")) / hr">
|
||||
</div>
|
||||
<small class="text-muted">Added to quotes per estimated labor hour</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment Operating Costs -->
|
||||
<h6 class="border-bottom pb-2 mb-3 mt-3">Equipment Operating Costs
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
@@ -2131,6 +2182,16 @@
|
||||
});
|
||||
|
||||
// Operating Costs Form Submit
|
||||
// Live facility overhead rate preview
|
||||
function updateFacilityOverheadRate() {
|
||||
var rent = parseFloat($('#monthlyRent').val()) || 0;
|
||||
var utilities = parseFloat($('#monthlyUtilities').val()) || 0;
|
||||
var hours = parseInt($('#monthlyBillableHours').val()) || 1;
|
||||
var rate = hours > 0 ? (rent + utilities) / hours : 0;
|
||||
$('#facilityOverheadRateDisplay').val('$' + rate.toFixed(2) + ' / hr');
|
||||
}
|
||||
$('.facility-overhead-input').on('input', updateFacilityOverheadRate);
|
||||
|
||||
$('#operatingCostsForm').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -2153,7 +2214,10 @@
|
||||
ComplexitySimplePercent: parseFloat($('#complexitySimplePercent').val()) || 0,
|
||||
ComplexityModeratePercent: parseFloat($('#complexityModeratePercent').val()) || 5,
|
||||
ComplexityComplexPercent: parseFloat($('#complexityComplexPercent').val()) || 15,
|
||||
ComplexityExtremePercent: parseFloat($('#complexityExtremePercent').val()) || 25
|
||||
ComplexityExtremePercent: parseFloat($('#complexityExtremePercent').val()) || 25,
|
||||
MonthlyRent: parseFloat($('#monthlyRent').val()) || 0,
|
||||
MonthlyUtilities: parseFloat($('#monthlyUtilities').val()) || 0,
|
||||
MonthlyBillableHours: parseInt($('#monthlyBillableHours').val()) || 160
|
||||
};
|
||||
|
||||
const btn = $('#btnSaveOperatingCosts');
|
||||
|
||||
@@ -101,6 +101,33 @@
|
||||
<li class="mb-1"><strong>Shop Supplies Rate (%)</strong> — a percentage applied to material and labor costs to cover miscellaneous shop supplies (abrasives, tape, fasteners, etc.) that are not tracked per-job.</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-3 mb-2" id="facility-overhead">Facility Overhead (Rent & Utilities)</h3>
|
||||
<p>
|
||||
Facility overhead lets you recover your shop's fixed occupancy costs on every quote automatically.
|
||||
Rather than burying rent and utilities in your General Markup, entering them here makes the cost
|
||||
transparent in the pricing breakdown and ensures accurate job costing.
|
||||
</p>
|
||||
<ul class="mb-3">
|
||||
<li class="mb-1"><strong>Monthly Rent ($)</strong> — your monthly shop lease or mortgage payment for the production facility.</li>
|
||||
<li class="mb-1"><strong>Monthly Utilities ($)</strong> — monthly gas, electricity, and water costs for the shop. Do not include costs already captured in your oven/equipment hourly rates.</li>
|
||||
<li class="mb-1"><strong>Monthly Billable Hours</strong> — the number of productive labor hours your shop operates per month (default: 160 — roughly one full-time worker for a month). This is used to convert the combined rent + utilities into a per-hour overhead rate.</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>How it is applied:</strong> The system calculates a per-hour overhead rate as
|
||||
<code>(Monthly Rent + Monthly Utilities) ÷ Monthly Billable Hours</code>. For each quote, it
|
||||
multiplies that rate by the total estimated labor hours across all line items and adds the result
|
||||
as a separate line in the pricing breakdown. If rent and utilities are both $0, no overhead charge
|
||||
is added.
|
||||
</p>
|
||||
<div class="alert alert-permanent alert-info d-flex gap-2 mb-3" role="alert">
|
||||
<i class="bi bi-lightbulb-fill flex-shrink-0 mt-1"></i>
|
||||
<div>
|
||||
<strong>Example:</strong> $2,000/month rent + $800/month utilities ÷ 160 billable hours =
|
||||
<strong>$17.50/hr overhead rate</strong>. A quote with 4 total estimated hours would add
|
||||
$70 to the price as "Facility Overhead."
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-3 mb-2">Pricing Mode, Markup, Minimums & Rush Charges</h3>
|
||||
<ul class="mb-3">
|
||||
<li class="mb-1">
|
||||
|
||||
@@ -903,6 +903,14 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.PricingBreakdown.FacilityOverheadCost > 0)
|
||||
{
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span><i class="bi bi-building me-1"></i>Facility Overhead (@Model.PricingBreakdown.FacilityOverheadRatePerHour.ToString("C2")/hr):</span>
|
||||
<strong>@Model.PricingBreakdown.FacilityOverheadCost.ToString("C")</strong>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.PricingBreakdown.ShopSuppliesAmount > 0)
|
||||
{
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
@@ -1043,7 +1051,7 @@
|
||||
</div>
|
||||
|
||||
@* ── SECTION 2: Quote-Level Additions ───────────────────── *@
|
||||
@if (pb.OvenBatchCost > 0 || pb.ShopSuppliesAmount > 0 || pb.OverheadCosts > 0)
|
||||
@if (pb.OvenBatchCost > 0 || pb.FacilityOverheadCost > 0 || pb.ShopSuppliesAmount > 0 || pb.OverheadCosts > 0)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<div class="text-uppercase text-muted fw-semibold small mb-2" style="letter-spacing:.05em;">
|
||||
@@ -1056,6 +1064,13 @@
|
||||
<span>@pb.OvenBatchCost.ToString("C")</span>
|
||||
</div>
|
||||
}
|
||||
@if (pb.FacilityOverheadCost > 0)
|
||||
{
|
||||
<div class="d-flex justify-content-between small mb-1">
|
||||
<span class="text-muted">Facility overhead (@pb.FacilityOverheadRatePerHour.ToString("C2")/hr × estimated hours)</span>
|
||||
<span>@pb.FacilityOverheadCost.ToString("C")</span>
|
||||
</div>
|
||||
}
|
||||
@if (pb.ShopSuppliesAmount > 0)
|
||||
{
|
||||
<div class="d-flex justify-content-between small mb-1">
|
||||
@@ -1112,7 +1127,7 @@
|
||||
<span>@pb.Total.ToString("C")</span>
|
||||
</div>
|
||||
@{
|
||||
var totalDirectCost = pb.MaterialCosts + pb.LaborCosts + pb.EquipmentCosts + pb.OvenBatchCost + pb.ShopSuppliesAmount;
|
||||
var totalDirectCost = pb.MaterialCosts + pb.LaborCosts + pb.EquipmentCosts + pb.OvenBatchCost + pb.FacilityOverheadCost + pb.ShopSuppliesAmount;
|
||||
var grossProfit = pb.Total - totalDirectCost;
|
||||
var effectiveMargin = pb.Total > 0 ? (grossProfit / pb.Total * 100m) : 0m;
|
||||
var pricingModeLabel = dbgCosts?.PricingMode == PowderCoating.Core.Enums.PricingMode.MarginOnTotalCost ? "margin" : "markup";
|
||||
|
||||
@@ -59,6 +59,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Facility Overhead -->
|
||||
<div class="wizard-card">
|
||||
<h5 class="wizard-card-title mb-3">Facility Overhead</h5>
|
||||
<p class="text-secondary small mb-3">
|
||||
Enter your monthly shop rent and utilities so the system can recover those costs proportionally
|
||||
across every job. Leave at zero if you work from home or your facility costs are already factored
|
||||
into your markup.
|
||||
</p>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label asp-for="MonthlyRent" class="form-label fw-semibold"></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="MonthlyRent" class="form-control wz-overhead" step="0.01" type="number" min="0" />
|
||||
<span class="input-group-text">/mo</span>
|
||||
</div>
|
||||
<div class="form-text">Your monthly lease or rent payment for the shop space.</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="MonthlyUtilities" class="form-label fw-semibold"></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="MonthlyUtilities" class="form-control wz-overhead" step="0.01" type="number" min="0" />
|
||||
<span class="input-group-text">/mo</span>
|
||||
</div>
|
||||
<div class="form-text">Combined electricity, gas, water, and internet.</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label asp-for="MonthlyBillableHours" class="form-label fw-semibold"></label>
|
||||
<input asp-for="MonthlyBillableHours" class="form-control wz-overhead" step="1" type="number" min="1" />
|
||||
<div class="form-text">Hours per month the shop is actively producing work. Default: 160 (4 wks × 40 hrs).</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted">Calculated overhead rate</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="bi bi-calculator"></i></span>
|
||||
<input type="text" id="wzOverheadRate" class="form-control bg-light" readonly value="$0.00 / hr">
|
||||
</div>
|
||||
<div class="form-text">This amount is added to quotes per estimated labor hour.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment Costs -->
|
||||
<div class="wizard-card">
|
||||
<h5 class="wizard-card-title mb-3">Equipment Costs (per hour) <button type="button" class="btn btn-link btn-sm p-0 text-primary fw-normal ms-1" data-bs-toggle="modal" data-bs-target="#equipCalcModal"><i class="bi bi-calculator me-1"></i>Help me calculate</button></h5>
|
||||
@@ -336,6 +379,19 @@
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
// ── Facility Overhead Rate Preview ────────────────────────
|
||||
function updateWzOverheadRate() {
|
||||
var rent = parseFloat(document.querySelector('[name="MonthlyRent"]').value) || 0;
|
||||
var utils = parseFloat(document.querySelector('[name="MonthlyUtilities"]').value) || 0;
|
||||
var hours = parseInt(document.querySelector('[name="MonthlyBillableHours"]').value) || 1;
|
||||
var rate = hours > 0 ? (rent + utils) / hours : 0;
|
||||
document.getElementById('wzOverheadRate').value = '$' + rate.toFixed(2) + ' / hr';
|
||||
}
|
||||
document.querySelectorAll('.wz-overhead').forEach(function(el) {
|
||||
el.addEventListener('input', updateWzOverheadRate);
|
||||
});
|
||||
updateWzOverheadRate();
|
||||
|
||||
// ── Labor Calculator ──────────────────────────────────────
|
||||
function laborCalc() {
|
||||
var employees = parseFloat(document.getElementById('lc_employees').value) || 0;
|
||||
|
||||
Reference in New Issue
Block a user