From df504674e93cdc07435f77a8be79b5986008a77b Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Tue, 19 May 2026 16:27:54 -0400 Subject: [PATCH] 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 --- .../DTOs/Job/JobDtos.cs | 17 +++++++ .../Controllers/JobsController.cs | 14 +++++- .../Views/Jobs/Create.cshtml | 45 ++++++++++++++++++- src/PowderCoating.Web/Views/Jobs/Edit.cshtml | 45 ++++++++++++++++++- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/src/PowderCoating.Application/DTOs/Job/JobDtos.cs b/src/PowderCoating.Application/DTOs/Job/JobDtos.cs index 6291f24..dc46d4c 100644 --- a/src/PowderCoating.Application/DTOs/Job/JobDtos.cs +++ b/src/PowderCoating.Application/DTOs/Job/JobDtos.cs @@ -137,6 +137,13 @@ public class CreateJobDto [Display(Name = "Oven")] public int? OvenCostId { get; set; } + [Display(Name = "Batches")] + [Range(1, 999)] + public int OvenBatches { get; set; } = 1; + + [Display(Name = "Cycle Time (min)")] + public int? OvenCycleMinutes { get; set; } + [Required(ErrorMessage = "Description is required")] [StringLength(2000, ErrorMessage = "Description cannot exceed 2000 characters")] [Display(Name = "Description")] @@ -208,6 +215,16 @@ public class UpdateJobDto [Display(Name = "Assigned Worker")] public string? AssignedUserId { get; set; } + [Display(Name = "Oven")] + public int? OvenCostId { get; set; } + + [Display(Name = "Batches")] + [Range(1, 999)] + public int OvenBatches { get; set; } = 1; + + [Display(Name = "Cycle Time (min)")] + public int? OvenCycleMinutes { get; set; } + [Required(ErrorMessage = "Description is required")] [StringLength(2000, ErrorMessage = "Description cannot exceed 2000 characters")] [Display(Name = "Description")] diff --git a/src/PowderCoating.Web/Controllers/JobsController.cs b/src/PowderCoating.Web/Controllers/JobsController.cs index c160ee5..c2fe1ea 100644 --- a/src/PowderCoating.Web/Controllers/JobsController.cs +++ b/src/PowderCoating.Web/Controllers/JobsController.cs @@ -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); diff --git a/src/PowderCoating.Web/Views/Jobs/Create.cshtml b/src/PowderCoating.Web/Views/Jobs/Create.cshtml index 1b93496..ff2c988 100644 --- a/src/PowderCoating.Web/Views/Jobs/Create.cshtml +++ b/src/PowderCoating.Web/Views/Jobs/Create.cshtml @@ -221,6 +221,49 @@ + +
+
+
+
Oven & Batch Settings
+ + + +
+
+
+

Estimate how many oven loads the complete job will require. The oven cycle cost is added once at the job level, not per item.

+
+ @if (ViewBag.OvenCosts != null && ((List)ViewBag.OvenCosts).Count > 1) + { +
+ + +
+ } +
+ + +
+
+ + +
+
+
+
+
@@ -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")", diff --git a/src/PowderCoating.Web/Views/Jobs/Edit.cshtml b/src/PowderCoating.Web/Views/Jobs/Edit.cshtml index 0a975bc..bb81cb2 100644 --- a/src/PowderCoating.Web/Views/Jobs/Edit.cshtml +++ b/src/PowderCoating.Web/Views/Jobs/Edit.cshtml @@ -190,6 +190,49 @@
+ +
+
+
+
Oven & Batch Settings
+ + + +
+
+
+

Estimate how many oven loads the complete job will require. The oven cycle cost is added once at the job level, not per item.

+
+ @if (ViewBag.OvenCosts != null && ((List)ViewBag.OvenCosts).Count > 1) + { +
+ + +
+ } +
+ + +
+
+ + +
+
+
+
+
@@ -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")",