Add oven/batch settings to job create and edit forms
CreateJobDto and UpdateJobDto now carry OvenCostId, OvenBatches, and OvenCycleMinutes. The Create POST sets these on the new Job entity and passes them to the pricing engine; the Edit GET populates them from the existing job so the form reflects saved values, and the Edit POST writes them back before repricing. Both Jobs/Create.cshtml and Jobs/Edit.cshtml now include an Oven & Batch Settings card (matching the quote form) with oven selector, batch count, and cycle time inputs. The wizard init block now passes the selected OvenCostId instead of null so live auto-pricing reflects the oven cost. ViewBag.DefaultOvenCycleMinutes added to PopulateCreateEditWizardViewBagsAsync so the placeholder in both views shows the company default. Also fixed: NoExtraLayerCharge was missing from the Edit GET coat DTO mapping (would have caused the flag to reset to false on next edit). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1078,6 +1078,8 @@ public class JobsController : Controller
|
||||
QuoteId = dto.QuoteId,
|
||||
AssignedUserId = dto.AssignedUserId,
|
||||
OvenCostId = dto.OvenCostId,
|
||||
OvenBatches = dto.OvenBatches > 0 ? dto.OvenBatches : 1,
|
||||
OvenCycleMinutes = dto.OvenCycleMinutes,
|
||||
Description = dto.Description,
|
||||
JobPriorityId = dto.JobPriorityId,
|
||||
JobStatusId = pendingStatus?.Id ?? 1,
|
||||
@@ -1149,7 +1151,7 @@ public class JobsController : Controller
|
||||
var totals = await _pricingService.CalculateQuoteTotalsAsync(
|
||||
dto.JobItems, companyId, dto.CustomerId,
|
||||
await GetEffectiveTaxPercentAsync(dto.CustomerId, createCosts?.TaxPercent ?? 0m),
|
||||
dto.DiscountType, dto.DiscountValue, dto.IsRushJob, createOvenRate, job.OvenBatches, job.OvenCycleMinutes);
|
||||
dto.DiscountType, dto.DiscountValue, dto.IsRushJob, createOvenRate, dto.OvenBatches > 0 ? dto.OvenBatches : 1, dto.OvenCycleMinutes);
|
||||
|
||||
job.FinalPrice = totals.Total;
|
||||
job.OvenBatchCost = totals.OvenBatchCost;
|
||||
@@ -1217,6 +1219,9 @@ public class JobsController : Controller
|
||||
CustomerId = job.CustomerId,
|
||||
QuoteId = job.QuoteId,
|
||||
AssignedUserId = job.AssignedUserId,
|
||||
OvenCostId = job.OvenCostId,
|
||||
OvenBatches = job.OvenBatches > 0 ? job.OvenBatches : 1,
|
||||
OvenCycleMinutes = job.OvenCycleMinutes,
|
||||
Description = job.Description,
|
||||
JobStatusId = job.JobStatusId,
|
||||
JobPriorityId = job.JobPriorityId,
|
||||
@@ -1261,6 +1266,7 @@ public class JobsController : Controller
|
||||
TransferEfficiency = c.TransferEfficiency,
|
||||
PowderCostPerLb = c.PowderCostPerLb,
|
||||
PowderToOrder = c.PowderToOrder,
|
||||
NoExtraLayerCharge = c.NoExtraLayerCharge,
|
||||
Notes = c.Notes
|
||||
}).ToList(),
|
||||
PrepServices = ji.PrepServices.Select(ps => new CreateQuoteItemPrepServiceDto
|
||||
@@ -1391,6 +1397,9 @@ public class JobsController : Controller
|
||||
job.CustomerId = dto.CustomerId;
|
||||
job.QuoteId = dto.QuoteId;
|
||||
job.Description = dto.Description;
|
||||
job.OvenCostId = dto.OvenCostId;
|
||||
job.OvenBatches = dto.OvenBatches > 0 ? dto.OvenBatches : 1;
|
||||
job.OvenCycleMinutes = dto.OvenCycleMinutes;
|
||||
await RecordStatusChangeAsync(job, dto.JobStatusId);
|
||||
job.JobStatusId = dto.JobStatusId;
|
||||
job.JobPriorityId = dto.JobPriorityId;
|
||||
@@ -1617,7 +1626,7 @@ public class JobsController : Controller
|
||||
var totals = await _pricingService.CalculateQuoteTotalsAsync(
|
||||
dto.JobItems, companyId, dto.CustomerId,
|
||||
await GetEffectiveTaxPercentAsync(dto.CustomerId, editCosts?.TaxPercent ?? 0m),
|
||||
dto.DiscountType, dto.DiscountValue, dto.IsRushJob, editOvenRate, job.OvenBatches, job.OvenCycleMinutes);
|
||||
dto.DiscountType, dto.DiscountValue, dto.IsRushJob, editOvenRate, dto.OvenBatches > 0 ? dto.OvenBatches : 1, dto.OvenCycleMinutes);
|
||||
job.FinalPrice = totals.Total;
|
||||
job.OvenBatchCost = totals.OvenBatchCost;
|
||||
job.ShopSuppliesAmount = totals.ShopSuppliesAmount;
|
||||
@@ -1818,6 +1827,7 @@ public class JobsController : Controller
|
||||
ViewBag.ComplexityModeratePercent = costs?.ComplexityModeratePercent ?? 5m;
|
||||
ViewBag.ComplexityComplexPercent = costs?.ComplexityComplexPercent ?? 15m;
|
||||
ViewBag.ComplexityExtremePercent = costs?.ComplexityExtremePercent ?? 25m;
|
||||
ViewBag.DefaultOvenCycleMinutes = costs?.DefaultOvenCycleMinutes ?? 45;
|
||||
var useMetric = await _tenantContext.UseMetricSystemAsync();
|
||||
ViewBag.UseMetric = useMetric;
|
||||
ViewBag.AreaUnit = _measurementService.GetAreaUnitLabel(useMetric);
|
||||
|
||||
@@ -221,6 +221,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Oven & Batch Settings -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<h5 class="mb-0"><i class="bi bi-thermometer-half me-2"></i>Oven & Batch Settings</h5>
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Oven & Batch Pricing"
|
||||
data-bs-content="The oven cost is charged once per batch at the job level, not per item. Estimate how many oven loads the full job will fill &mdash; for example, if you have 20 small parts and your oven fits 10, that&#39;s 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven&#39;s hourly rate in Settings.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-3">Estimate how many oven loads the complete job will require. The oven cycle cost is added once at the job level, not per item.</p>
|
||||
<div class="row g-3 align-items-end">
|
||||
@if (ViewBag.OvenCosts != null && ((List<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.OvenCosts).Count > 1)
|
||||
{
|
||||
<div class="col-md-4">
|
||||
<label asp-for="OvenCostId" class="form-label fw-semibold"><i class="bi bi-thermometer-half me-1"></i>Oven</label>
|
||||
<select asp-for="OvenCostId" asp-items="@((List<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.OvenCosts)"
|
||||
class="form-select" onchange="scheduleAutoPricing()"></select>
|
||||
</div>
|
||||
}
|
||||
<div class="col-sm-3 col-md-2">
|
||||
<label asp-for="OvenBatches" class="form-label fw-semibold">Batches</label>
|
||||
<input asp-for="OvenBatches" class="form-control" type="number" min="1"
|
||||
value="@(Model.OvenBatches > 0 ? Model.OvenBatches : 1)"
|
||||
id="OvenBatches" onchange="scheduleAutoPricing()" />
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-3">
|
||||
<label asp-for="OvenCycleMinutes" class="form-label fw-semibold">
|
||||
Cycle Time per Batch (min)
|
||||
<small class="text-muted fw-normal">default: @(ViewBag.DefaultOvenCycleMinutes ?? 45)</small>
|
||||
</label>
|
||||
<input asp-for="OvenCycleMinutes" class="form-control" type="number" min="1"
|
||||
id="OvenCycleMinutes" placeholder="@(ViewBag.DefaultOvenCycleMinutes ?? 45)"
|
||||
onchange="scheduleAutoPricing()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pricing Options (Rush / Discount) -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
@@ -388,7 +431,7 @@
|
||||
"discountType": @Json.Serialize(Model.DiscountType),
|
||||
"discountValue": @Model.DiscountValue,
|
||||
"isRushJob": @Json.Serialize(Model.IsRushJob),
|
||||
"ovenCostId": null,
|
||||
"ovenCostId": @Json.Serialize(Model.OvenCostId),
|
||||
"areaUnit": @Json.Serialize((string?)ViewBag.AreaUnit),
|
||||
"useMetric": @Json.Serialize((bool)(ViewBag.UseMetric ?? false)),
|
||||
"pricingUrl": "@Url.Action("CalculatePricing", "Jobs")",
|
||||
|
||||
@@ -190,6 +190,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Oven & Batch Settings -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<h5 class="mb-0"><i class="bi bi-thermometer-half me-2"></i>Oven & Batch Settings</h5>
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Oven & Batch Pricing"
|
||||
data-bs-content="The oven cost is charged once per batch at the job level, not per item. Estimate how many oven loads the full job will fill &mdash; for example, if you have 20 small parts and your oven fits 10, that&#39;s 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven&#39;s hourly rate in Settings.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-3">Estimate how many oven loads the complete job will require. The oven cycle cost is added once at the job level, not per item.</p>
|
||||
<div class="row g-3 align-items-end">
|
||||
@if (ViewBag.OvenCosts != null && ((List<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.OvenCosts).Count > 1)
|
||||
{
|
||||
<div class="col-md-4">
|
||||
<label asp-for="OvenCostId" class="form-label fw-semibold"><i class="bi bi-thermometer-half me-1"></i>Oven</label>
|
||||
<select asp-for="OvenCostId" asp-items="@((List<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.OvenCosts)"
|
||||
class="form-select" onchange="scheduleAutoPricing()"></select>
|
||||
</div>
|
||||
}
|
||||
<div class="col-sm-3 col-md-2">
|
||||
<label asp-for="OvenBatches" class="form-label fw-semibold">Batches</label>
|
||||
<input asp-for="OvenBatches" class="form-control" type="number" min="1"
|
||||
value="@(Model.OvenBatches > 0 ? Model.OvenBatches : 1)"
|
||||
id="OvenBatches" onchange="scheduleAutoPricing()" />
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-3">
|
||||
<label asp-for="OvenCycleMinutes" class="form-label fw-semibold">
|
||||
Cycle Time per Batch (min)
|
||||
<small class="text-muted fw-normal">default: @(ViewBag.DefaultOvenCycleMinutes ?? 45)</small>
|
||||
</label>
|
||||
<input asp-for="OvenCycleMinutes" class="form-control" type="number" min="1"
|
||||
id="OvenCycleMinutes" placeholder="@(ViewBag.DefaultOvenCycleMinutes ?? 45)"
|
||||
onchange="scheduleAutoPricing()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pricing Options (Rush / Discount) -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
@@ -375,7 +418,7 @@
|
||||
"discountType": @Json.Serialize(Model.DiscountType),
|
||||
"discountValue": @Model.DiscountValue,
|
||||
"isRushJob": @Json.Serialize(Model.IsRushJob),
|
||||
"ovenCostId": null,
|
||||
"ovenCostId": @Json.Serialize(Model.OvenCostId),
|
||||
"areaUnit": @Json.Serialize((string?)ViewBag.AreaUnit),
|
||||
"useMetric": @Json.Serialize((bool)(ViewBag.UseMetric ?? false)),
|
||||
"pricingUrl": "@Url.Action("CalculatePricing", "Jobs")",
|
||||
|
||||
Reference in New Issue
Block a user