- QuotePricingAssemblyService.BuildQuoteItemCoat: map NoExtraLayerCharge from
CreateQuoteItemCoatDto to QuoteItemCoat on every quote save (was always omitted)
- JobsController.EditItems GET: include NoExtraLayerCharge in coat mapping when
reloading existing items for the wizard (was dropped, causing revert on second edit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added comprehensive XML documentation to JobItemAssemblyService and
QuotePricingAssemblyService — the most complex area of the codebase.
Comments explain the three-overload pattern, seed class rationale,
powder-to-order formula and industry default fallbacks, AI prediction
override tracking, and the incoming inventory auto-creation workflow.
PricingCalculationService was already well-documented; no changes needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Store complete PricingBreakdownJson snapshot on Job at every save point so
the Details page reads stored data rather than re-running the pricing engine
- Add 7 missing fields to Quote entity (FacilityOverheadCost, tier/quote discounts,
SubtotalAfterDiscount) and persist them via ApplyPricingSnapshot
- Fix OvenCostId-as-rate bug in JobsController (FK was passed as decimal $/hr)
- Fix hardcoded LaborCost * 0.4 multiplier in two JobItemAssemblyService overloads
- Fix FacilityOverheadCost dropped from invoices in both quote and direct-job paths
- Fix RushFee missing from direct-job invoices (read from PricingBreakdownJson)
- Fix Notes and CatalogItemId not copied to InvoiceItem
- Add 14 unit tests in PricingStageFlowTests covering all three pricing stages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- QuotePricingAssemblyService: catalog no-coat items now respect PowderCostOverride instead of always using DefaultPrice
- PricingCalculationService: removed duplicate catalog fast-path in CalculateQuoteTotalsAsync that bypassed CalculateQuoteItemPriceAsync (and thus ignored PowderCostOverride); all items now go through the single authoritative path
- Jobs/Details.cshtml: timeTracking.del() and timeTracking.save() now call costing.load() after success so the time entry row and costing breakdown stay in sync
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>