1eba50cf0f
Introduces per-company reusable NCalc2 pricing formula templates for complex fabricated items (roof curbs, enclosures, welded frames). Templates support two output modes — FixedRate (formula yields a dollar amount) and SurfaceAreaSqFt (formula yields sq ft fed into the standard coating engine). Includes: - CustomItemTemplate entity, migration (AddCustomItemTemplates), IUnitOfWork repo - IsCustomFormulaItem / CustomItemTemplateId / FormulaFieldValuesJson flags on QuoteItem, JobItem, CreateQuoteItemDto; mapped in all 3 JobItemAssemblyService overloads and all existingItemsData JSON projections + pageMeta blocks - ICustomFormulaAiService / CustomFormulaAiService: Claude-powered formula generator (natural language + optional diagram image) and NCalc2 evaluator - CompanySettings CRUD endpoints: GetCustomItemTemplates, Create/Update/Delete, UploadTemplateDiagram, TemplateDiagram (blob serve), EvaluateFormula, GenerateFormulaFromAi - Company Settings "Custom Formulas" tab + cfModal + company-settings-custom-formulas.js - item-wizard.js: formula item type card, renderFormulaFields, wzFormulaRecalc (live evaluate via POST), collectStep2 formula branch, buildCardHtml / emitHiddenFields - Formula badge in Quotes/Details and Jobs/Details; AI badge gap fixed in Jobs/Details - Help article (CustomFormulaTemplates.cshtml), Help Index card, HelpController action, HelpKnowledgeBase entry; 225/225 unit tests passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
569 lines
21 KiB
C#
569 lines
21 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using PowderCoating.Application.DTOs.PrepService;
|
|
using PowderCoating.Application.DTOs.Quote;
|
|
|
|
namespace PowderCoating.Application.DTOs.Job;
|
|
|
|
public class JobDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string JobNumber { get; set; } = string.Empty;
|
|
public int CustomerId { get; set; }
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public string? CustomerCompanyName { get; set; }
|
|
public string? CustomerContactName { get; set; }
|
|
public int? QuoteId { get; set; }
|
|
public string? QuoteNumber { get; set; }
|
|
public string? AssignedUserId { get; set; }
|
|
public string? AssignedWorkerName { get; set; }
|
|
public string Description { get; set; } = string.Empty;
|
|
|
|
// Job Status (from lookup table)
|
|
public int JobStatusId { get; set; }
|
|
public string StatusCode { get; set; } = string.Empty; // For code logic
|
|
public string StatusDisplayName { get; set; } = string.Empty; // For UI
|
|
public string StatusColorClass { get; set; } = "secondary"; // For badges
|
|
public string? StatusIconClass { get; set; } // For icons
|
|
public int StatusDisplayOrder { get; set; } // For comparisons
|
|
public bool StatusIsTerminal { get; set; } // For filtering
|
|
public bool StatusIsWIP { get; set; } // For stats
|
|
|
|
// Job Priority (from lookup table)
|
|
public int JobPriorityId { get; set; }
|
|
public string PriorityCode { get; set; } = string.Empty;
|
|
public string PriorityDisplayName { get; set; } = string.Empty;
|
|
public string PriorityColorClass { get; set; } = "secondary";
|
|
public string? PriorityIconClass { get; set; }
|
|
public int PriorityDisplayOrder { get; set; }
|
|
|
|
public DateTime? ScheduledDate { get; set; }
|
|
public DateTime? DueDate { get; set; }
|
|
public DateTime? CompletedDate { get; set; }
|
|
// Oven selection (carried over from quote)
|
|
public int? OvenCostId { get; set; }
|
|
public string? OvenLabel { get; set; }
|
|
|
|
public decimal QuotedPrice { get; set; }
|
|
public decimal FinalPrice { get; set; }
|
|
public decimal ShopSuppliesAmount { get; set; }
|
|
public decimal ShopSuppliesPercent { get; set; }
|
|
public bool IsRushJob { get; set; }
|
|
public string DiscountType { get; set; } = "None";
|
|
public decimal DiscountValue { get; set; }
|
|
public string? DiscountReason { get; set; }
|
|
public string? CustomerPO { get; set; }
|
|
public string? SpecialInstructions { get; set; }
|
|
public string? InternalNotes { get; set; }
|
|
public string? Tags { get; set; }
|
|
public bool RequiresCustomerApproval { get; set; }
|
|
public bool IsCustomerApproved { get; set; }
|
|
|
|
public string? CustomerEmail { get; set; }
|
|
public bool CustomerNotifyByEmail { get; set; } = true;
|
|
// Customer SMS opt-in — used for SMS compose modal on job details
|
|
public bool CustomerNotifyBySms { get; set; }
|
|
public string? CustomerMobilePhone { get; set; }
|
|
|
|
// Job Completion Details
|
|
public decimal? ActualTimeSpentHours { get; set; }
|
|
|
|
// Part intake / receiving
|
|
public DateTime? IntakeDate { get; set; }
|
|
public string? IntakeConditionNotes { get; set; }
|
|
public int? IntakePartCount { get; set; }
|
|
public string? IntakeCheckedByUserId { get; set; }
|
|
public string? IntakeCheckedByName { get; set; }
|
|
|
|
// Time tracking
|
|
public List<JobTimeEntryDto> TimeEntries { get; set; } = new();
|
|
public decimal TotalLoggedHours => TimeEntries.Sum(t => t.HoursWorked);
|
|
|
|
// Rework
|
|
public bool IsReworkJob { get; set; }
|
|
public int? OriginalJobId { get; set; }
|
|
public string? OriginalJobNumber { get; set; }
|
|
|
|
public List<JobItemDto> Items { get; set; } = new();
|
|
public List<PrepServiceDto> PrepServices { get; set; } = new();
|
|
public List<int> PrepServiceIds { get; set; } = new();
|
|
public DateTime CreatedAt { get; set; }
|
|
}
|
|
|
|
public class JobListDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string JobNumber { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public string? AssignedUserId { get; set; }
|
|
public string? AssignedWorkerName { get; set; }
|
|
|
|
// Job Status (from lookup table)
|
|
public int JobStatusId { get; set; }
|
|
public string StatusCode { get; set; } = string.Empty;
|
|
public string StatusDisplayName { get; set; } = string.Empty;
|
|
public string StatusColorClass { get; set; } = "secondary";
|
|
public bool StatusIsWIP { get; set; }
|
|
|
|
// Job Priority (from lookup table)
|
|
public int JobPriorityId { get; set; }
|
|
public string PriorityCode { get; set; } = string.Empty;
|
|
public string PriorityDisplayName { get; set; } = string.Empty;
|
|
public string PriorityColorClass { get; set; } = "secondary";
|
|
|
|
public string? CustomerEmail { get; set; }
|
|
public bool CustomerNotifyByEmail { get; set; } = true;
|
|
public DateTime? ScheduledDate { get; set; }
|
|
public DateTime? DueDate { get; set; }
|
|
public decimal FinalPrice { get; set; }
|
|
public DateTime CreatedAt { get; set; }
|
|
public string? Tags { get; set; }
|
|
public bool IsReworkJob { get; set; }
|
|
public int? OriginalJobId { get; set; }
|
|
}
|
|
|
|
public class CreateJobDto
|
|
{
|
|
[Required(ErrorMessage = "Customer is required")]
|
|
[Display(Name = "Customer")]
|
|
public int CustomerId { get; set; }
|
|
|
|
[Display(Name = "Quote")]
|
|
public int? QuoteId { get; set; }
|
|
|
|
[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")]
|
|
public string Description { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Priority is required")]
|
|
[Display(Name = "Priority")]
|
|
public int JobPriorityId { get; set; } // FK to lookup table
|
|
|
|
[Display(Name = "Scheduled Date")]
|
|
public DateTime? ScheduledDate { get; set; }
|
|
|
|
[Display(Name = "Due Date")]
|
|
public DateTime? DueDate { get; set; }
|
|
|
|
[Range(0, 9999999.99, ErrorMessage = "Quoted price must be between 0 and 9,999,999.99")]
|
|
[Display(Name = "Quoted Price")]
|
|
public decimal QuotedPrice { get; set; }
|
|
|
|
[StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")]
|
|
[Display(Name = "Customer PO")]
|
|
public string? CustomerPO { get; set; }
|
|
|
|
[StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")]
|
|
[Display(Name = "Special Instructions")]
|
|
public string? SpecialInstructions { get; set; }
|
|
|
|
[StringLength(2000, ErrorMessage = "Internal notes cannot exceed 2000 characters")]
|
|
[Display(Name = "Internal Notes")]
|
|
public string? InternalNotes { get; set; }
|
|
|
|
[Display(Name = "Tags")]
|
|
[StringLength(500)]
|
|
public string? Tags { get; set; }
|
|
|
|
[Display(Name = "Requires Customer Approval")]
|
|
public bool RequiresCustomerApproval { get; set; }
|
|
|
|
[Display(Name = "Rush Job")]
|
|
public bool IsRushJob { get; set; }
|
|
|
|
[Display(Name = "Discount Type")]
|
|
public string DiscountType { get; set; } = "None";
|
|
|
|
[Range(0, double.MaxValue, ErrorMessage = "Discount value must be 0 or greater")]
|
|
[Display(Name = "Discount Value")]
|
|
public decimal DiscountValue { get; set; }
|
|
|
|
[StringLength(500)]
|
|
[Display(Name = "Discount Reason")]
|
|
public string? DiscountReason { get; set; }
|
|
|
|
public List<CreateQuoteItemDto> JobItems { get; set; } = new();
|
|
public List<int> PrepServiceIds { get; set; } = new();
|
|
}
|
|
|
|
public class UpdateJobDto
|
|
{
|
|
[Required]
|
|
public int Id { get; set; }
|
|
|
|
[Required(ErrorMessage = "Customer is required")]
|
|
[Display(Name = "Customer")]
|
|
public int CustomerId { get; set; }
|
|
|
|
[Display(Name = "Quote")]
|
|
public int? QuoteId { get; set; }
|
|
|
|
[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")]
|
|
public string Description { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Status is required")]
|
|
[Display(Name = "Status")]
|
|
public int JobStatusId { get; set; } // FK to lookup table
|
|
|
|
[Required(ErrorMessage = "Priority is required")]
|
|
[Display(Name = "Priority")]
|
|
public int JobPriorityId { get; set; } // FK to lookup table
|
|
|
|
[Display(Name = "Scheduled Date")]
|
|
public DateTime? ScheduledDate { get; set; }
|
|
|
|
[Display(Name = "Due Date")]
|
|
public DateTime? DueDate { get; set; }
|
|
|
|
[Range(0, 9999999.99, ErrorMessage = "Quoted price must be between 0 and 9,999,999.99")]
|
|
[Display(Name = "Quoted Price")]
|
|
public decimal QuotedPrice { get; set; }
|
|
|
|
[StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")]
|
|
[Display(Name = "Customer PO")]
|
|
public string? CustomerPO { get; set; }
|
|
|
|
[StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")]
|
|
[Display(Name = "Special Instructions")]
|
|
public string? SpecialInstructions { get; set; }
|
|
|
|
[StringLength(2000, ErrorMessage = "Internal notes cannot exceed 2000 characters")]
|
|
[Display(Name = "Internal Notes")]
|
|
public string? InternalNotes { get; set; }
|
|
|
|
[Display(Name = "Tags")]
|
|
[StringLength(500)]
|
|
public string? Tags { get; set; }
|
|
|
|
[Display(Name = "Requires Customer Approval")]
|
|
public bool RequiresCustomerApproval { get; set; }
|
|
|
|
[Display(Name = "Rush Job")]
|
|
public bool IsRushJob { get; set; }
|
|
|
|
[Display(Name = "Discount Type")]
|
|
public string DiscountType { get; set; } = "None";
|
|
|
|
[Range(0, double.MaxValue, ErrorMessage = "Discount value must be 0 or greater")]
|
|
[Display(Name = "Discount Value")]
|
|
public decimal DiscountValue { get; set; }
|
|
|
|
[StringLength(500)]
|
|
[Display(Name = "Discount Reason")]
|
|
public string? DiscountReason { get; set; }
|
|
|
|
public List<CreateQuoteItemDto> JobItems { get; set; } = new();
|
|
public List<int> PrepServiceIds { get; set; } = new();
|
|
|
|
[Display(Name = "Notify customer of status change via email")]
|
|
public bool SendEmailOnStatusChange { get; set; } = false;
|
|
}
|
|
|
|
public class UpdateJobItemDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string Description { get; set; } = string.Empty;
|
|
public decimal Quantity { get; set; }
|
|
public string? ColorName { get; set; }
|
|
public string? ColorCode { get; set; }
|
|
public decimal? SurfaceArea { get; set; }
|
|
public decimal UnitPrice { get; set; }
|
|
public decimal TotalPrice { get; set; }
|
|
public bool RequiresSandblasting { get; set; }
|
|
public bool RequiresMasking { get; set; }
|
|
public int EstimatedMinutes { get; set; }
|
|
public string? Notes { get; set; }
|
|
}
|
|
|
|
public class JobItemDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string Description { get; set; } = string.Empty;
|
|
public decimal Quantity { get; set; }
|
|
public string? ColorName { get; set; }
|
|
public string? ColorCode { get; set; }
|
|
public string? Finish { get; set; }
|
|
public decimal? SurfaceArea { get; set; }
|
|
public decimal SurfaceAreaSqFt { get; set; }
|
|
public int EstimatedMinutes { get; set; }
|
|
public decimal UnitPrice { get; set; }
|
|
public decimal TotalPrice { get; set; }
|
|
public decimal LaborCost { get; set; }
|
|
public bool RequiresSandblasting { get; set; }
|
|
public bool RequiresMasking { get; set; }
|
|
public string? Notes { get; set; }
|
|
public int? CatalogItemId { get; set; }
|
|
public bool IsGenericItem { get; set; }
|
|
public bool IsLaborItem { get; set; }
|
|
public bool IsSalesItem { get; set; }
|
|
public bool IsAiItem { get; set; }
|
|
public string? Sku { get; set; }
|
|
public bool IsCustomFormulaItem { get; set; }
|
|
public int? CustomItemTemplateId { get; set; }
|
|
public string? FormulaFieldValuesJson { get; set; }
|
|
public List<JobItemCoatDto> Coats { get; set; } = new();
|
|
public List<JobItemPrepServiceDto> PrepServices { get; set; } = new();
|
|
}
|
|
|
|
public class JobItemPrepServiceDto
|
|
{
|
|
public int PrepServiceId { get; set; }
|
|
public string? PrepServiceName { get; set; }
|
|
public int EstimatedMinutes { get; set; }
|
|
/// <summary>Blast setup selected in wizard for this sandblasting prep service.</summary>
|
|
public int? BlastSetupId { get; set; }
|
|
}
|
|
|
|
public class CreateJobItemDto
|
|
{
|
|
public string Description { get; set; } = string.Empty;
|
|
public decimal Quantity { get; set; }
|
|
public string? ColorName { get; set; }
|
|
public string? ColorCode { get; set; }
|
|
public string? Finish { get; set; }
|
|
public decimal UnitPrice { get; set; }
|
|
public bool RequiresSandblasting { get; set; }
|
|
public bool RequiresMasking { get; set; }
|
|
public int EstimatedMinutes { get; set; }
|
|
public string? Notes { get; set; }
|
|
}
|
|
|
|
// DTO for Shop Floor Display
|
|
public class ShopFloorJobDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string JobNumber { get; set; } = string.Empty;
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
|
|
// Job Status (from lookup table)
|
|
public int JobStatusId { get; set; }
|
|
public string StatusCode { get; set; } = string.Empty;
|
|
public string StatusDisplayName { get; set; } = string.Empty;
|
|
public string StatusColorClass { get; set; } = "secondary";
|
|
|
|
// Job Priority (from lookup table)
|
|
public int JobPriorityId { get; set; }
|
|
public string PriorityCode { get; set; } = string.Empty;
|
|
public string PriorityDisplayName { get; set; } = string.Empty;
|
|
public string PriorityColorClass { get; set; } = "secondary";
|
|
|
|
public string? AssignedWorkerName { get; set; }
|
|
public DateTime? ScheduledDate { get; set; }
|
|
public DateTime? DueDate { get; set; }
|
|
public int ItemCount { get; set; }
|
|
public List<string> NextSteps { get; set; } = new();
|
|
}
|
|
|
|
// DTO for Job Item Coat (multi-coat support)
|
|
public class JobItemCoatDto
|
|
{
|
|
public int Id { get; set; }
|
|
public int JobItemId { get; set; }
|
|
public string CoatName { get; set; } = string.Empty;
|
|
public int Sequence { get; set; }
|
|
public int? InventoryItemId { get; set; }
|
|
public string? ColorName { get; set; }
|
|
public int? VendorId { get; set; }
|
|
public string? VendorName { get; set; }
|
|
public string? ColorCode { get; set; }
|
|
public string? Finish { get; set; }
|
|
public decimal CoverageSqFtPerLb { get; set; }
|
|
public decimal TransferEfficiency { get; set; }
|
|
public decimal? PowderCostPerLb { get; set; }
|
|
public decimal? PowderToOrder { get; set; }
|
|
public decimal? ActualPowderUsedLbs { get; set; } // Filled during job completion
|
|
public bool NoExtraLayerCharge { get; set; }
|
|
public string? Notes { get; set; }
|
|
}
|
|
|
|
// DTO for completing a job
|
|
public class CompleteJobDto
|
|
{
|
|
public int JobId { get; set; }
|
|
public decimal? ActualTimeSpentHours { get; set; }
|
|
public List<JobPowderUsageDto> PowderUsages { get; set; } = new();
|
|
public bool SendEmailToCustomer { get; set; } = false;
|
|
}
|
|
|
|
// DTO for the Admin/Manager compose-before-send SMS endpoint
|
|
public class SendJobSmsRequest
|
|
{
|
|
public int JobId { get; set; }
|
|
public string Message { get; set; } = string.Empty;
|
|
}
|
|
|
|
// DTO for tracking actual powder usage per inventory item (color) for the whole job
|
|
public class JobPowderUsageDto
|
|
{
|
|
public int InventoryItemId { get; set; }
|
|
public decimal? ActualPowderUsedLbs { get; set; }
|
|
}
|
|
|
|
// ── Time Tracking DTOs ────────────────────────────────────────────────────────
|
|
|
|
public class JobTimeEntryDto
|
|
{
|
|
public int Id { get; set; }
|
|
public int JobId { get; set; }
|
|
public string? UserId { get; set; }
|
|
public string WorkerName { get; set; } = string.Empty;
|
|
public DateTime WorkDate { get; set; }
|
|
public decimal HoursWorked { get; set; }
|
|
public string? Stage { get; set; }
|
|
public string? Notes { get; set; }
|
|
public DateTime CreatedAt { get; set; }
|
|
}
|
|
|
|
public class CreateJobTimeEntryDto
|
|
{
|
|
public int JobId { get; set; }
|
|
public string UserId { get; set; } = string.Empty;
|
|
public DateTime WorkDate { get; set; }
|
|
public decimal HoursWorked { get; set; }
|
|
public string? Stage { get; set; }
|
|
public string? Notes { get; set; }
|
|
}
|
|
|
|
public class UpdateJobTimeEntryDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string UserId { get; set; } = string.Empty;
|
|
public DateTime WorkDate { get; set; }
|
|
public decimal HoursWorked { get; set; }
|
|
public string? Stage { get; set; }
|
|
public string? Notes { get; set; }
|
|
}
|
|
|
|
// ── Rework / Warranty DTOs ────────────────────────────────────────────────────
|
|
|
|
public class ReworkRecordDto
|
|
{
|
|
public int Id { get; set; }
|
|
public int JobId { get; set; }
|
|
public int? JobItemId { get; set; }
|
|
public string? JobItemDescription { get; set; }
|
|
public int? ReworkJobId { get; set; }
|
|
public string? ReworkJobNumber { get; set; }
|
|
|
|
public PowderCoating.Core.Enums.ReworkType ReworkType { get; set; }
|
|
public string ReworkTypeDisplay { get; set; } = string.Empty;
|
|
public PowderCoating.Core.Enums.ReworkReason Reason { get; set; }
|
|
public string ReasonDisplay { get; set; } = string.Empty;
|
|
public string DefectDescription { get; set; } = string.Empty;
|
|
|
|
public PowderCoating.Core.Enums.ReworkDiscoveredBy DiscoveredBy { get; set; }
|
|
public string DiscoveredByDisplay { get; set; } = string.Empty;
|
|
public DateTime DiscoveredDate { get; set; }
|
|
public string? ReportedByName { get; set; }
|
|
|
|
public decimal EstimatedReworkCost { get; set; }
|
|
public decimal ActualReworkCost { get; set; }
|
|
public bool IsBillableToCustomer { get; set; }
|
|
public string? BillingNotes { get; set; }
|
|
public PowderCoating.Core.Enums.ReworkPricingType? ReworkPricingType { get; set; }
|
|
|
|
public PowderCoating.Core.Enums.ReworkStatus Status { get; set; }
|
|
public string StatusDisplay { get; set; } = string.Empty;
|
|
public string StatusColorClass { get; set; } = "secondary";
|
|
public PowderCoating.Core.Enums.ReworkResolution? Resolution { get; set; }
|
|
public string? ResolutionDisplay { get; set; }
|
|
public DateTime? ResolvedDate { get; set; }
|
|
public string? ResolutionNotes { get; set; }
|
|
|
|
public DateTime CreatedAt { get; set; }
|
|
}
|
|
|
|
public class CreateReworkRecordDto
|
|
{
|
|
public int JobId { get; set; }
|
|
public int? JobItemId { get; set; }
|
|
public PowderCoating.Core.Enums.ReworkType ReworkType { get; set; }
|
|
public PowderCoating.Core.Enums.ReworkReason Reason { get; set; }
|
|
public string DefectDescription { get; set; } = string.Empty;
|
|
public PowderCoating.Core.Enums.ReworkDiscoveredBy DiscoveredBy { get; set; }
|
|
public DateTime DiscoveredDate { get; set; } = DateTime.Today;
|
|
public string? ReportedByName { get; set; }
|
|
public decimal EstimatedReworkCost { get; set; }
|
|
public bool IsBillableToCustomer { get; set; }
|
|
public string? BillingNotes { get; set; }
|
|
|
|
// Rework job creation (opt-in)
|
|
public bool CreateReworkJob { get; set; }
|
|
public List<int>? ReworkJobItemIds { get; set; } // null = not creating a job
|
|
public PowderCoating.Core.Enums.ReworkPricingType? ReworkPricingType { get; set; }
|
|
}
|
|
|
|
public class UpdateReworkRecordDto
|
|
{
|
|
public int Id { get; set; }
|
|
public PowderCoating.Core.Enums.ReworkStatus Status { get; set; }
|
|
public PowderCoating.Core.Enums.ReworkResolution? Resolution { get; set; }
|
|
public decimal ActualReworkCost { get; set; }
|
|
public bool IsBillableToCustomer { get; set; }
|
|
public string? BillingNotes { get; set; }
|
|
public DateTime? ResolvedDate { get; set; }
|
|
public string? ResolutionNotes { get; set; }
|
|
public int? ReworkJobId { get; set; }
|
|
}
|
|
|
|
// ViewModel for the Edit Items wizard page
|
|
public class JobEditItemsViewModel
|
|
{
|
|
public int JobId { get; set; }
|
|
public string JobNumber { get; set; } = string.Empty;
|
|
public int? CustomerId { get; set; }
|
|
public decimal TaxPercent { get; set; }
|
|
public int? OvenCostId { get; set; }
|
|
public int OvenBatches { get; set; } = 1;
|
|
public int? OvenCycleMinutes { get; set; }
|
|
public List<CreateQuoteItemDto> JobItems { get; set; } = new();
|
|
}
|
|
|
|
// DTO for the part intake / receiving form
|
|
public class IntakeJobDto
|
|
{
|
|
[Required]
|
|
public int JobId { get; set; }
|
|
|
|
[Display(Name = "Actual Part Count")]
|
|
[Range(0, 10000, ErrorMessage = "Part count must be between 0 and 10,000")]
|
|
public int? ActualPartCount { get; set; }
|
|
|
|
[StringLength(2000, ErrorMessage = "Condition notes cannot exceed 2000 characters")]
|
|
[Display(Name = "Condition Notes")]
|
|
public string? ConditionNotes { get; set; }
|
|
|
|
[Display(Name = "Advance status to In Preparation")]
|
|
public bool AdvanceToInPreparation { get; set; } = true;
|
|
}
|