Files
PowderCoatingLogix/src/PowderCoating.Core/Entities/Job.cs
T
spouliot 6721de91e4 Fix pricing consistency across Quote → Job → Invoice; add stage-flow tests
- 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>
2026-05-15 15:03:06 -04:00

96 lines
4.2 KiB
C#

using System.ComponentModel.DataAnnotations.Schema;
using PowderCoating.Core.Enums;
namespace PowderCoating.Core.Entities;
public class Job : BaseEntity
{
public string JobNumber { get; set; } = string.Empty;
public int CustomerId { get; set; }
public int? QuoteId { get; set; }
public string? AssignedUserId { get; set; } // Assigned user
public string Description { get; set; } = string.Empty;
// Lookup foreign keys (replacing enums)
public int JobStatusId { get; set; }
public int JobPriorityId { get; set; }
// Dates
public DateTime? ScheduledDate { get; set; }
public DateTime? StartedDate { get; set; }
public DateTime? CompletedDate { get; set; }
public DateTime? DueDate { get; set; }
// Selected oven (carried over from quote; null = company default rate)
public int? OvenCostId { get; set; }
// Oven scheduling (carried over from quote)
public int OvenBatches { get; set; } = 1;
public int? OvenCycleMinutes { get; set; }
// Pricing
public decimal QuotedPrice { get; set; }
public decimal FinalPrice { get; set; }
public decimal OvenBatchCost { get; set; }
public decimal ShopSuppliesAmount { get; set; }
public decimal ShopSuppliesPercent { get; set; }
// Discount & rush (mirrors quote fields; preserved through quote→job conversion and job edits)
public bool IsRushJob { get; set; }
public DiscountType DiscountType { get; set; } = DiscountType.None;
public decimal DiscountValue { get; set; }
public string? DiscountReason { get; set; }
// Job Completion Details
public decimal? ActualTimeSpentHours { get; set; }
// Additional Information
public string? CustomerPO { get; set; }
public string? SpecialInstructions { get; set; }
public string? InternalNotes { get; set; } // Internal notes from quote
public string? Tags { get; set; }
public bool RequiresCustomerApproval { get; set; }
public bool IsCustomerApproved { get; set; }
// Shop floor QR access token (no login required; scoped to this job only)
public Guid ShopAccessCode { get; set; } = Guid.NewGuid();
// Part intake / receiving
public DateTime? IntakeDate { get; set; }
public string? IntakeConditionNotes { get; set; }
public int? IntakePartCount { get; set; }
public string? IntakeCheckedByUserId { get; set; }
// Quote snapshot — UpdatedAt of the source quote at the moment this job was created from it.
// Used to detect when the quote was subsequently edited so the job details page can warn the user.
public DateTime? QuoteSnapshotUpdatedAt { get; set; }
// Pricing snapshot — serialized QuotePricingBreakdownDto stored at save time so Details displays
// the breakdown that was actually calculated, not a re-run against current operating costs.
public string? PricingBreakdownJson { get; set; }
// Rework tracking
public bool IsReworkJob { get; set; }
public int? OriginalJobId { get; set; } // Set when this job was created as a rework
// Relationships
[ForeignKey("IntakeCheckedByUserId")]
public virtual ApplicationUser? IntakeCheckedBy { get; set; }
public virtual OvenCost? OvenCost { get; set; }
public virtual Customer Customer { get; set; } = null!;
public virtual Quote? Quote { get; set; }
public virtual ApplicationUser? AssignedUser { get; set; }
public virtual JobStatusLookup JobStatus { get; set; } = null!;
public virtual JobPriorityLookup JobPriority { get; set; } = null!;
public virtual ICollection<JobItem> JobItems { get; set; } = new List<JobItem>();
public virtual ICollection<JobPhoto> Photos { get; set; } = new List<JobPhoto>();
public virtual ICollection<JobNote> Notes { get; set; } = new List<JobNote>();
public virtual ICollection<JobStatusHistory> StatusHistory { get; set; } = new List<JobStatusHistory>();
public virtual ICollection<JobPrepService> JobPrepServices { get; set; } = new List<JobPrepService>();
public virtual Invoice? Invoice { get; set; }
public virtual ICollection<JobTimeEntry> TimeEntries { get; set; } = new List<JobTimeEntry>();
public virtual ICollection<ReworkRecord> ReworkRecords { get; set; } = new List<ReworkRecord>();
public virtual Job? OriginalJob { get; set; }
}