diff --git a/src/PowderCoating.Application/DTOs/Company/CustomItemTemplateDtos.cs b/src/PowderCoating.Application/DTOs/Company/CustomItemTemplateDtos.cs new file mode 100644 index 0000000..ed1f67b --- /dev/null +++ b/src/PowderCoating.Application/DTOs/Company/CustomItemTemplateDtos.cs @@ -0,0 +1,174 @@ +using System.ComponentModel.DataAnnotations; + +namespace PowderCoating.Application.DTOs.Company; + +// ============================================================================ +// LIST DTO - For Company Settings tab table +// ============================================================================ +public class CustomItemTemplateListDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public string OutputMode { get; set; } = "FixedRate"; + public int FieldCount { get; set; } + public decimal? DefaultRate { get; set; } + public string? RateLabel { get; set; } + public bool IsActive { get; set; } + public int DisplayOrder { get; set; } + public string? DiagramImagePath { get; set; } +} + +// ============================================================================ +// FULL DTO - For Edit modal and formula evaluation +// ============================================================================ +public class CustomItemTemplateDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public string OutputMode { get; set; } = "FixedRate"; + public string FieldsJson { get; set; } = "[]"; + public string Formula { get; set; } = string.Empty; + public decimal? DefaultRate { get; set; } + public string? RateLabel { get; set; } + public string? Notes { get; set; } + public int DisplayOrder { get; set; } + public bool IsActive { get; set; } + public string? DiagramImagePath { get; set; } +} + +// ============================================================================ +// CREATE DTO +// ============================================================================ +public class CreateCustomItemTemplateDto +{ + [Required] + [StringLength(100)] + public string Name { get; set; } = string.Empty; + + [StringLength(500)] + public string? Description { get; set; } + + /// "FixedRate" or "SurfaceAreaSqFt" + [Required] + public string OutputMode { get; set; } = "FixedRate"; + + /// JSON array of field definitions: [{name, label, unit, defaultValue}] + [Required] + public string FieldsJson { get; set; } = "[]"; + + [Required] + public string Formula { get; set; } = string.Empty; + + public decimal? DefaultRate { get; set; } + + [StringLength(50)] + public string? RateLabel { get; set; } + + [StringLength(1000)] + public string? Notes { get; set; } + + public int DisplayOrder { get; set; } + public bool IsActive { get; set; } = true; +} + +// ============================================================================ +// UPDATE DTO +// ============================================================================ +public class UpdateCustomItemTemplateDto +{ + public int Id { get; set; } + + [Required] + [StringLength(100)] + public string Name { get; set; } = string.Empty; + + [StringLength(500)] + public string? Description { get; set; } + + [Required] + public string OutputMode { get; set; } = "FixedRate"; + + [Required] + public string FieldsJson { get; set; } = "[]"; + + [Required] + public string Formula { get; set; } = string.Empty; + + public decimal? DefaultRate { get; set; } + + [StringLength(50)] + public string? RateLabel { get; set; } + + [StringLength(1000)] + public string? Notes { get; set; } + + public int DisplayOrder { get; set; } + public bool IsActive { get; set; } = true; + + /// Existing diagram path — kept if no new file is uploaded. + public string? DiagramImagePath { get; set; } +} + +// ============================================================================ +// WIZARD PICKER DTO - Lean DTO for populating the quote wizard template list +// ============================================================================ +public class CustomItemTemplatePickerDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public string OutputMode { get; set; } = "FixedRate"; + public string FieldsJson { get; set; } = "[]"; + public string Formula { get; set; } = string.Empty; + public decimal? DefaultRate { get; set; } + public string? RateLabel { get; set; } + public string? DiagramImagePath { get; set; } +} + +// ============================================================================ +// AI GENERATION DTOs +// ============================================================================ +public class GenerateFormulaFromAiRequest +{ + [Required] + public string Description { get; set; } = string.Empty; +} + +public class GenerateFormulaFromAiResponse +{ + public bool Success { get; set; } + public string? Error { get; set; } + public string? Name { get; set; } + public string? OutputMode { get; set; } + public string? FieldsJson { get; set; } + public string? Formula { get; set; } + public decimal? DefaultRate { get; set; } + public string? RateLabel { get; set; } + public string? Reasoning { get; set; } + + /// Result of running the formula with any sample values found in the description. + public decimal? VerificationResult { get; set; } + public string? VerificationInputs { get; set; } +} + +// ============================================================================ +// FORMULA EVALUATION DTOs +// ============================================================================ +public class EvaluateFormulaRequest +{ + [Required] + public string Formula { get; set; } = string.Empty; + + /// JSON object of variable name → value pairs, e.g. {"box_l": 43, "rate": 0.05} + [Required] + public string VariablesJson { get; set; } = "{}"; +} + +public class EvaluateFormulaResponse +{ + public bool Success { get; set; } + public decimal? Result { get; set; } + public string? Error { get; set; } +} diff --git a/src/PowderCoating.Application/DTOs/Job/JobDtos.cs b/src/PowderCoating.Application/DTOs/Job/JobDtos.cs index da088ec..1eef12c 100644 --- a/src/PowderCoating.Application/DTOs/Job/JobDtos.cs +++ b/src/PowderCoating.Application/DTOs/Job/JobDtos.cs @@ -325,7 +325,11 @@ public class JobItemDto 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 Coats { get; set; } = new(); public List PrepServices { get; set; } = new(); } diff --git a/src/PowderCoating.Application/DTOs/Quote/QuoteDtos.cs b/src/PowderCoating.Application/DTOs/Quote/QuoteDtos.cs index b754921..4d3827e 100644 --- a/src/PowderCoating.Application/DTOs/Quote/QuoteDtos.cs +++ b/src/PowderCoating.Application/DTOs/Quote/QuoteDtos.cs @@ -475,6 +475,11 @@ public class QuoteItemDto public bool IsAiItem { get; set; } + // Custom formula item + public bool IsCustomFormulaItem { get; set; } + public int? CustomItemTemplateId { get; set; } + public string? FormulaFieldValuesJson { get; set; } + // Cost breakdown snapshot public decimal ItemMaterialCost { get; set; } public decimal ItemLaborCost { get; set; } @@ -559,6 +564,11 @@ public class CreateQuoteItemDto // ID of the AiItemPrediction record captured at analysis time (null for non-AI items) public int? AiPredictionId { get; set; } + + // Custom formula item routing — see IsCustomFormulaItem in PricingCalculationService + public bool IsCustomFormulaItem { get; set; } + public int? CustomItemTemplateId { get; set; } + public string? FormulaFieldValuesJson { get; set; } } // ============================================================================ diff --git a/src/PowderCoating.Application/Interfaces/ICustomFormulaAiService.cs b/src/PowderCoating.Application/Interfaces/ICustomFormulaAiService.cs new file mode 100644 index 0000000..49a4db1 --- /dev/null +++ b/src/PowderCoating.Application/Interfaces/ICustomFormulaAiService.cs @@ -0,0 +1,22 @@ +using PowderCoating.Application.DTOs.Company; + +namespace PowderCoating.Application.Interfaces; + +public interface ICustomFormulaAiService +{ + /// + /// Generates a NCalc formula, field list, and notes from a natural-language description + /// and an optional diagram image. Returns a + /// ready to pre-fill the template editor. + /// + Task GenerateFormulaAsync( + GenerateFormulaFromAiRequest request, + byte[]? imageBytes = null, + string? imageContentType = null); + + /// + /// Evaluates a NCalc formula with the supplied variable map and returns the numeric result. + /// Safe server-side only — no user-controlled code execution. + /// + EvaluateFormulaResponse EvaluateFormula(EvaluateFormulaRequest request); +} diff --git a/src/PowderCoating.Application/Mappings/CustomItemTemplateProfile.cs b/src/PowderCoating.Application/Mappings/CustomItemTemplateProfile.cs new file mode 100644 index 0000000..d212035 --- /dev/null +++ b/src/PowderCoating.Application/Mappings/CustomItemTemplateProfile.cs @@ -0,0 +1,41 @@ +using AutoMapper; +using PowderCoating.Core.Entities; +using PowderCoating.Application.DTOs.Company; + +namespace PowderCoating.Application.Mappings; + +public class CustomItemTemplateProfile : Profile +{ + public CustomItemTemplateProfile() + { + CreateMap() + .ForMember(dest => dest.FieldCount, + opt => opt.MapFrom(src => CountFields(src.FieldsJson))); + + CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap() + .ForMember(dest => dest.DiagramImagePath, opt => opt.Ignore()); // set by controller after blob upload + + CreateMap(); + } + + private static int CountFields(string fieldsJson) + { + try + { + var doc = System.Text.Json.JsonDocument.Parse(fieldsJson); + return doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Array + ? doc.RootElement.GetArrayLength() + : 0; + } + catch + { + return 0; + } + } +} diff --git a/src/PowderCoating.Application/Mappings/QuoteProfile.cs b/src/PowderCoating.Application/Mappings/QuoteProfile.cs index e4b845b..b0afae4 100644 --- a/src/PowderCoating.Application/Mappings/QuoteProfile.cs +++ b/src/PowderCoating.Application/Mappings/QuoteProfile.cs @@ -159,6 +159,7 @@ public class QuoteProfile : Profile .ReverseMap() .ForMember(dest => dest.Quote, opt => opt.Ignore()) .ForMember(dest => dest.CatalogItem, opt => opt.Ignore()) + .ForMember(dest => dest.CustomItemTemplate, opt => opt.Ignore()) .ForMember(dest => dest.Coats, opt => opt.Ignore()) .ForMember(dest => dest.PrepServices, opt => opt.Ignore()) .ForMember(dest => dest.CompanyId, opt => opt.Ignore()) @@ -180,6 +181,7 @@ public class QuoteProfile : Profile .ForMember(dest => dest.Coats, opt => opt.Ignore()) // Mapped separately .ForMember(dest => dest.PrepServices, opt => opt.Ignore()) // Mapped separately .ForMember(dest => dest.CatalogItem, opt => opt.Ignore()) + .ForMember(dest => dest.CustomItemTemplate, opt => opt.Ignore()) // FK only; nav set by EF .ForMember(dest => dest.CompanyId, opt => opt.Ignore()) .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) .ForMember(dest => dest.UpdatedAt, opt => opt.Ignore()) diff --git a/src/PowderCoating.Application/PowderCoating.Application.csproj b/src/PowderCoating.Application/PowderCoating.Application.csproj index 21bf084..394194b 100644 --- a/src/PowderCoating.Application/PowderCoating.Application.csproj +++ b/src/PowderCoating.Application/PowderCoating.Application.csproj @@ -16,6 +16,7 @@ + diff --git a/src/PowderCoating.Application/Services/JobItemAssemblyService.cs b/src/PowderCoating.Application/Services/JobItemAssemblyService.cs index bc2a240..2f301da 100644 --- a/src/PowderCoating.Application/Services/JobItemAssemblyService.cs +++ b/src/PowderCoating.Application/Services/JobItemAssemblyService.cs @@ -53,7 +53,10 @@ public class JobItemAssemblyService : IJobItemAssemblyService IncludePrepCost = source.IncludePrepCost, Complexity = source.Complexity, AiTags = source.AiTags, - AiPredictionId = source.AiPredictionId + AiPredictionId = source.AiPredictionId, + IsCustomFormulaItem = source.IsCustomFormulaItem, + CustomItemTemplateId = source.CustomItemTemplateId, + FormulaFieldValuesJson = source.FormulaFieldValuesJson }, jobId, companyId, @@ -157,7 +160,10 @@ public class JobItemAssemblyService : IJobItemAssemblyService IncludePrepCost = source.IncludePrepCost, Complexity = source.Complexity, AiTags = source.AiTags, - AiPredictionId = source.AiPredictionId + AiPredictionId = source.AiPredictionId, + IsCustomFormulaItem = source.IsCustomFormulaItem, + CustomItemTemplateId = source.CustomItemTemplateId, + FormulaFieldValuesJson = source.FormulaFieldValuesJson }, jobId, companyId, @@ -259,7 +265,10 @@ public class JobItemAssemblyService : IJobItemAssemblyService IncludePrepCost = source.IncludePrepCost, Complexity = source.Complexity, AiTags = source.AiTags, - AiPredictionId = source.AiPredictionId + AiPredictionId = source.AiPredictionId, + IsCustomFormulaItem = source.IsCustomFormulaItem, + CustomItemTemplateId = source.CustomItemTemplateId, + FormulaFieldValuesJson = source.FormulaFieldValuesJson }, jobId, companyId, @@ -353,6 +362,9 @@ public class JobItemAssemblyService : IJobItemAssemblyService Complexity = seed.Complexity, AiTags = seed.AiTags, AiPredictionId = seed.AiPredictionId, + IsCustomFormulaItem = seed.IsCustomFormulaItem, + CustomItemTemplateId = seed.CustomItemTemplateId, + FormulaFieldValuesJson = seed.FormulaFieldValuesJson, CompanyId = companyId, CreatedAt = createdAtUtc }; @@ -480,6 +492,9 @@ public class JobItemAssemblyService : IJobItemAssemblyService public string? Complexity { get; init; } public string? AiTags { get; init; } public int? AiPredictionId { get; init; } + public bool IsCustomFormulaItem { get; init; } + public int? CustomItemTemplateId { get; init; } + public string? FormulaFieldValuesJson { get; init; } } /// Intermediate value object for coat creation — see for rationale. diff --git a/src/PowderCoating.Application/Services/PricingCalculationService.cs b/src/PowderCoating.Application/Services/PricingCalculationService.cs index c56b501..c4a3434 100644 --- a/src/PowderCoating.Application/Services/PricingCalculationService.cs +++ b/src/PowderCoating.Application/Services/PricingCalculationService.cs @@ -288,6 +288,24 @@ public class PricingCalculationService : IPricingCalculationService }; } + // Custom formula items (FixedRate mode): the wizard evaluated the NCalc formula server-side + // and stored the result as ManualUnitPrice. Use it directly — no coating math. + // SurfaceAreaSqFt mode: ManualUnitPrice is null; the formula produced sqft which was stored + // in SurfaceAreaSqFt, so the item falls through to the standard calculated path below. + if (item.IsCustomFormulaItem && item.ManualUnitPrice.HasValue) + { + var total = item.ManualUnitPrice.Value * item.Quantity; + return new QuoteItemPricingResult + { + MaterialCost = 0, + LaborCost = 0, + EquipmentCost = 0, + ItemSubtotal = total, + UnitPrice = item.ManualUnitPrice.Value, + TotalPrice = total + }; + } + // Sales items (off-the-shelf merchandise) — manual unit price, no coating calculation. if (item.IsSalesItem && item.ManualUnitPrice.HasValue) { diff --git a/src/PowderCoating.Application/Services/QuotePricingAssemblyService.cs b/src/PowderCoating.Application/Services/QuotePricingAssemblyService.cs index db45f13..84ee1b8 100644 --- a/src/PowderCoating.Application/Services/QuotePricingAssemblyService.cs +++ b/src/PowderCoating.Application/Services/QuotePricingAssemblyService.cs @@ -130,6 +130,14 @@ public class QuotePricingAssemblyService : IQuotePricingAssemblyService return; } + if (itemDto.IsCustomFormulaItem && itemDto.ManualUnitPrice.HasValue) + { + item.UnitPrice = itemDto.ManualUnitPrice.Value; + item.TotalPrice = itemDto.ManualUnitPrice.Value * itemDto.Quantity; + _logger.LogInformation("Custom formula item (FixedRate) price: UnitPrice={Unit}, TotalPrice={Total}", item.UnitPrice, item.TotalPrice); + return; + } + if (itemDto.CatalogItemId.HasValue) { if (itemDto.Coats != null && itemDto.Coats.Any()) @@ -243,6 +251,9 @@ public class QuotePricingAssemblyService : IQuotePricingAssemblyService IsAiItem = itemDto.IsAiItem, AiTags = itemDto.AiTags, AiPredictionId = itemDto.AiPredictionId, + IsCustomFormulaItem = itemDto.IsCustomFormulaItem, + CustomItemTemplateId = itemDto.CustomItemTemplateId, + FormulaFieldValuesJson = itemDto.FormulaFieldValuesJson, CompanyId = companyId, CreatedAt = createdAtUtc }; diff --git a/src/PowderCoating.Core/Entities/CustomItemTemplate.cs b/src/PowderCoating.Core/Entities/CustomItemTemplate.cs new file mode 100644 index 0000000..cb5830b --- /dev/null +++ b/src/PowderCoating.Core/Entities/CustomItemTemplate.cs @@ -0,0 +1,38 @@ +namespace PowderCoating.Core.Entities; + +/// +/// A per-company reusable pricing formula template. Users define named input fields and an +/// NCalc expression that produces either a fixed dollar amount (FixedRate) or a surface area +/// in square feet (SurfaceAreaSqFt) that feeds the standard coatings pricing path. +/// +public class CustomItemTemplate : BaseEntity +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + + /// "FixedRate" or "SurfaceAreaSqFt" — controls which pricing path is used after evaluation. + public string OutputMode { get; set; } = "FixedRate"; + + /// JSON array of field definitions: [{name, label, unit, defaultValue}] + public string FieldsJson { get; set; } = "[]"; + + /// NCalc expression using field name slugs and the reserved variable 'rate'. + public string Formula { get; set; } = string.Empty; + + /// Default rate value populated into the quote wizard; user can override per quote. + public decimal? DefaultRate { get; set; } + + /// Display label for the rate field, e.g. "$/sq in" or "$/lb". + public string? RateLabel { get; set; } + + public string? Notes { get; set; } + public int DisplayOrder { get; set; } + public bool IsActive { get; set; } = true; + + /// + /// Optional reference diagram (shop drawing, sketch) stored in blob storage. + /// Shown in the template editor and quote wizard so users know which measurements to take. + /// Path format: {companyId}/{templateId}/diagram.{ext} + /// + public string? DiagramImagePath { get; set; } +} diff --git a/src/PowderCoating.Core/Entities/JobItem.cs b/src/PowderCoating.Core/Entities/JobItem.cs index 128ff19..7527733 100644 --- a/src/PowderCoating.Core/Entities/JobItem.cs +++ b/src/PowderCoating.Core/Entities/JobItem.cs @@ -52,6 +52,14 @@ public class JobItem : BaseEntity public int? AiPredictionId { get; set; } public virtual AiItemPrediction? AiPrediction { get; set; } + // Custom formula item — see IsCustomFormulaItem routing in PricingCalculationService + public bool IsCustomFormulaItem { get; set; } + public int? CustomItemTemplateId { get; set; } + public virtual CustomItemTemplate? CustomItemTemplate { get; set; } + + /// Snapshot of field name/value pairs used in the formula, stored as JSON for display on details views. + public string? FormulaFieldValuesJson { get; set; } + // Relationships public virtual Job Job { get; set; } = null!; public virtual CatalogItem? CatalogItem { get; set; } diff --git a/src/PowderCoating.Core/Entities/QuoteItem.cs b/src/PowderCoating.Core/Entities/QuoteItem.cs index 5c96706..25fae48 100644 --- a/src/PowderCoating.Core/Entities/QuoteItem.cs +++ b/src/PowderCoating.Core/Entities/QuoteItem.cs @@ -56,6 +56,14 @@ public class QuoteItem : BaseEntity public int? AiPredictionId { get; set; } public virtual AiItemPrediction? AiPrediction { get; set; } + // Custom formula item — see IsCustomFormulaItem routing in PricingCalculationService + public bool IsCustomFormulaItem { get; set; } + public int? CustomItemTemplateId { get; set; } + public virtual CustomItemTemplate? CustomItemTemplate { get; set; } + + /// Snapshot of field name/value pairs used in the formula, stored as JSON for display on details views. + public string? FormulaFieldValuesJson { get; set; } + // Relationships public virtual Quote Quote { get; set; } = null!; public virtual CatalogItem? CatalogItem { get; set; } diff --git a/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs b/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs index 8b417a9..4afd47b 100644 --- a/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs +++ b/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs @@ -155,6 +155,9 @@ IRepository ReworkRecords { get; } // Customer Intake Kiosk IRepository KioskSessions { get; } + // Custom Formula Templates + IRepository CustomItemTemplates { get; } + Task SaveChangesAsync(); Task CompleteAsync(); // Alias for SaveChangesAsync diff --git a/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs b/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs index 079d430..01a5f58 100644 --- a/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs +++ b/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs @@ -374,6 +374,10 @@ public class ApplicationDbContext : IdentityDbContext, IDataPro /// Customer self-service intake sessions (walk-in tablet or remote email link); tenant-filtered with soft delete. public DbSet KioskSessions { get; set; } + // Custom Formula Templates + /// Per-company reusable NCalc pricing formula templates; tenant-filtered with soft delete. + public DbSet CustomItemTemplates { get; set; } + /// /// Platform-wide audit log capturing who changed what and when, across all tenants. /// No global query filter — SuperAdmin controllers query this directly. diff --git a/src/PowderCoating.Infrastructure/Migrations/20260523153055_AddCustomItemTemplates.Designer.cs b/src/PowderCoating.Infrastructure/Migrations/20260523153055_AddCustomItemTemplates.Designer.cs new file mode 100644 index 0000000..fd2916e --- /dev/null +++ b/src/PowderCoating.Infrastructure/Migrations/20260523153055_AddCustomItemTemplates.Designer.cs @@ -0,0 +1,10780 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PowderCoating.Infrastructure.Data; + +#nullable disable + +namespace PowderCoating.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260523153055_AddCustomItemTemplates")] + partial class AddCustomItemTemplates + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("nvarchar(max)"); + + b.Property("Xml") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("AccountSubType") + .HasColumnType("int"); + + b.Property("AccountType") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsSystem") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OpeningBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("OpeningBalanceDate") + .HasColumnType("datetime2"); + + b.Property("ParentAccountId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ParentAccountId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AiItemPrediction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AiTags") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ConversationRounds") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("PredictedComplexity") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PredictedMinutes") + .HasColumnType("int"); + + b.Property("PredictedSurfaceAreaSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("PredictedUnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Reasoning") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UserOverrodeEstimate") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("AiItemPredictions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AiUsageLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalledAt") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("Feature") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InputLength") + .HasColumnType("int"); + + b.Property("Success") + .HasColumnType("bit"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId", "CalledAt") + .HasDatabaseName("IX_AiUsageLogs_CompanyId_CalledAt"); + + b.ToTable("AiUsageLogs"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Announcement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedByUserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedByUserName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDismissible") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Target") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TargetCompanyId") + .HasColumnType("int"); + + b.Property("TargetPlan") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Announcements"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AnnouncementDismissal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AnnouncementId") + .HasColumnType("int"); + + b.Property("DismissedAt") + .HasColumnType("datetime2"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("AnnouncementId", "UserId") + .IsUnique(); + + b.ToTable("AnnouncementDismissals"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Address") + .HasColumnType("nvarchar(max)"); + + b.Property("BanReason") + .HasColumnType("nvarchar(max)"); + + b.Property("BannedAt") + .HasColumnType("datetime2"); + + b.Property("BannedByUserId") + .HasColumnType("nvarchar(max)"); + + b.Property("CanApproveQuotes") + .HasColumnType("bit"); + + b.Property("CanCreateQuotes") + .HasColumnType("bit"); + + b.Property("CanManageAccounting") + .HasColumnType("bit"); + + b.Property("CanManageBills") + .HasColumnType("bit"); + + b.Property("CanManageCalendar") + .HasColumnType("bit"); + + b.Property("CanManageCustomers") + .HasColumnType("bit"); + + b.Property("CanManageEquipment") + .HasColumnType("bit"); + + b.Property("CanManageInventory") + .HasColumnType("bit"); + + b.Property("CanManageInvoices") + .HasColumnType("bit"); + + b.Property("CanManageJobs") + .HasColumnType("bit"); + + b.Property("CanManageMaintenance") + .HasColumnType("bit"); + + b.Property("CanManageProducts") + .HasColumnType("bit"); + + b.Property("CanManageVendors") + .HasColumnType("bit"); + + b.Property("CanViewCalendar") + .HasColumnType("bit"); + + b.Property("CanViewProducts") + .HasColumnType("bit"); + + b.Property("CanViewReports") + .HasColumnType("bit"); + + b.Property("CanViewShopFloor") + .HasColumnType("bit"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompanyRole") + .HasColumnType("nvarchar(max)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DashboardLayout") + .HasColumnType("int"); + + b.Property("DateFormat") + .HasColumnType("nvarchar(max)"); + + b.Property("Department") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("EmployeeNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HireDate") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsBanned") + .HasColumnType("bit"); + + b.Property("LaborCostPerHour") + .HasColumnType("decimal(18,2)"); + + b.Property("LastLoginDate") + .HasColumnType("datetime2"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PasskeyPromptDismissed") + .HasColumnType("bit"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("Position") + .HasColumnType("nvarchar(max)"); + + b.Property("ProfilePictureFilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("SidebarColor") + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("TerminationDate") + .HasColumnType("datetime2"); + + b.Property("Theme") + .HasColumnType("nvarchar(max)"); + + b.Property("TimeZone") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Appointment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualEndTime") + .HasColumnType("datetime2"); + + b.Property("ActualStartTime") + .HasColumnType("datetime2"); + + b.Property("AppointmentNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("AppointmentStatusId") + .HasColumnType("int"); + + b.Property("AppointmentTypeId") + .HasColumnType("int"); + + b.Property("AssignedUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsAllDay") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsReminderEnabled") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Location") + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("ReminderMinutesBefore") + .HasColumnType("int"); + + b.Property("ReminderSentAt") + .HasColumnType("datetime2"); + + b.Property("ScheduledEndTime") + .HasColumnType("datetime2"); + + b.Property("ScheduledStartTime") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AppointmentStatusId"); + + b.HasIndex("AppointmentTypeId"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("JobId"); + + b.HasIndex("ScheduledStartTime"); + + b.HasIndex("CompanyId", "AppointmentStatusId") + .HasDatabaseName("IX_Appointments_CompanyId_AppointmentStatusId"); + + b.HasIndex("CompanyId", "ScheduledStartTime") + .HasDatabaseName("IX_Appointments_CompanyId_ScheduledStartTime"); + + b.ToTable("Appointments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AppointmentStatusLookup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ColorClass") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IconClass") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsSystemDefined") + .HasColumnType("bit"); + + b.Property("IsTerminalStatus") + .HasColumnType("bit"); + + b.Property("StatusCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AppointmentStatusLookups"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AppointmentTypeLookup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ColorClass") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IconClass") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsSystemDefined") + .HasColumnType("bit"); + + b.Property("RequiresJobLink") + .HasColumnType("bit"); + + b.Property("TypeCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AppointmentTypeLookups"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompanyName") + .HasColumnType("nvarchar(max)"); + + b.Property("EntityDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("EntityId") + .HasColumnType("nvarchar(450)"); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("IpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("NewValues") + .HasColumnType("nvarchar(max)"); + + b.Property("OldValues") + .HasColumnType("nvarchar(max)"); + + b.Property("Timestamp") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("nvarchar(max)"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId", "Timestamp"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("AuditLogs"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BankReconciliation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("BeginningBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CompletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EndingBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("StatementDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("BankReconciliations"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BannedIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BannedAt") + .HasColumnType("datetime2"); + + b.Property("BannedByUserId") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Reason") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("BannedIps"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Bill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("APAccountId") + .HasColumnType("int"); + + b.Property("AmountPaid") + .HasColumnType("decimal(18,2)"); + + b.Property("BillDate") + .HasColumnType("datetime2"); + + b.Property("BillNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DueDate") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Memo") + .HasColumnType("nvarchar(max)"); + + b.Property("ReceiptFilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubTotal") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("Terms") + .HasColumnType("nvarchar(max)"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceNumber") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("APAccountId"); + + b.HasIndex("DueDate"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("CompanyId", "Status"); + + b.ToTable("Bills"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BillLineItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("BillId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("BillId"); + + b.HasIndex("JobId"); + + b.ToTable("BillLineItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BillPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("BankAccountId") + .HasColumnType("int"); + + b.Property("BillId") + .HasColumnType("int"); + + b.Property("CheckNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("ClearedDate") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCleared") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Memo") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("PaymentNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BankAccountId"); + + b.HasIndex("BillId"); + + b.HasIndex("VendorId"); + + b.ToTable("BillPayments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FiscalYear") + .HasColumnType("int"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Budgets"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BudgetLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("Apr") + .HasColumnType("decimal(18,2)"); + + b.Property("Aug") + .HasColumnType("decimal(18,2)"); + + b.Property("BudgetId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Dec") + .HasColumnType("decimal(18,2)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Feb") + .HasColumnType("decimal(18,2)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Jan") + .HasColumnType("decimal(18,2)"); + + b.Property("Jul") + .HasColumnType("decimal(18,2)"); + + b.Property("Jun") + .HasColumnType("decimal(18,2)"); + + b.Property("Mar") + .HasColumnType("decimal(18,2)"); + + b.Property("May") + .HasColumnType("decimal(18,2)"); + + b.Property("Nov") + .HasColumnType("decimal(18,2)"); + + b.Property("Oct") + .HasColumnType("decimal(18,2)"); + + b.Property("Sep") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("BudgetId"); + + b.ToTable("BudgetLines"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BugReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompanyName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("ResolutionNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedAt") + .HasColumnType("datetime2"); + + b.Property("ResolvedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedByUserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SubmittedByUserName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("BugReports"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BugReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlobPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("BugReportId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FileSizeBytes") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BugReportId"); + + b.ToTable("BugReportAttachments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CatalogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsMerchandise") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ParentCategoryId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("CatalogCategories"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApproximateArea") + .HasColumnType("decimal(18,2)"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CogsAccountId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultEstimatedMinutes") + .HasColumnType("int"); + + b.Property("DefaultPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("DefaultRequiresMasking") + .HasColumnType("bit"); + + b.Property("DefaultRequiresSandblasting") + .HasColumnType("bit"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("InventoryItemId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsMerchandise") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RevenueAccountId") + .HasColumnType("int"); + + b.Property("SKU") + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("CogsAccountId"); + + b.HasIndex("CompanyId"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("RevenueAccountId"); + + b.ToTable("CatalogItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CatalogPriceCheckReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("ItemsChecked") + .HasColumnType("int"); + + b.Property("OperatingCostsSummary") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RunAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CatalogPriceCheckReports"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Company", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountingMethod") + .HasColumnType("int"); + + b.Property("AccountingOverride") + .HasColumnType("bit"); + + b.Property("Address") + .HasColumnType("nvarchar(max)"); + + b.Property("AiCatalogPriceCheckEnabled") + .HasColumnType("bit"); + + b.Property("AiInventoryAssistEnabled") + .HasColumnType("bit"); + + b.Property("AiPhotoQuotesEnabled") + .HasColumnType("bit"); + + b.Property("BookLockedThrough") + .HasColumnType("datetime2"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyCode") + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsAnnualBilling") + .HasColumnType("bit"); + + b.Property("IsComped") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("KioskActivationToken") + .HasColumnType("nvarchar(max)"); + + b.Property("LogoContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("LogoData") + .HasColumnType("varbinary(max)"); + + b.Property("LogoFilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("MarketingEmailOptOut") + .HasColumnType("bit"); + + b.Property("MarketingUnsubscribeToken") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("MaxActiveJobsOverride") + .HasColumnType("int"); + + b.Property("MaxAiPhotoQuotesPerMonthOverride") + .HasColumnType("int"); + + b.Property("MaxCatalogItemsOverride") + .HasColumnType("int"); + + b.Property("MaxCustomersOverride") + .HasColumnType("int"); + + b.Property("MaxJobPhotosOverride") + .HasColumnType("int"); + + b.Property("MaxQuotePhotosOverride") + .HasColumnType("int"); + + b.Property("MaxQuotesOverride") + .HasColumnType("int"); + + b.Property("MaxUsersOverride") + .HasColumnType("int"); + + b.Property("OnlinePaymentSurchargeType") + .HasColumnType("int"); + + b.Property("OnlinePaymentSurchargeValue") + .HasColumnType("decimal(18,2)"); + + b.Property("OnlinePaymentsOverride") + .HasColumnType("bit"); + + b.Property("OnlineSurchargeAcknowledged") + .HasColumnType("bit"); + + b.Property("Phone") + .HasColumnType("nvarchar(max)"); + + b.Property("PrimaryContactEmail") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PrimaryContactName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SmsDisabledByAdmin") + .HasColumnType("bit"); + + b.Property("SmsEnabled") + .HasColumnType("bit"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("StripeAccountId") + .HasColumnType("nvarchar(max)"); + + b.Property("StripeConnectStatus") + .HasColumnType("int"); + + b.Property("StripeCustomerId") + .HasColumnType("nvarchar(max)"); + + b.Property("StripeSubscriptionId") + .HasColumnType("nvarchar(max)"); + + b.Property("SubscriptionEndDate") + .HasColumnType("datetime2"); + + b.Property("SubscriptionNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("SubscriptionPlan") + .HasColumnType("int"); + + b.Property("SubscriptionStartDate") + .HasColumnType("datetime2"); + + b.Property("SubscriptionStatus") + .HasColumnType("int"); + + b.Property("TimeZone") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyCode") + .IsUnique() + .HasFilter("[CompanyCode] IS NOT NULL"); + + b.ToTable("Companies"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CompanyBlastSetup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlastNozzleSize") + .HasColumnType("int"); + + b.Property("BlastRateSqFtPerHourOverride") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompressorCfm") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PrimarySubstrate") + .HasColumnType("int"); + + b.Property("SetupType") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.ToTable("CompanyBlastSetups"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CompanyOperatingCosts", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdditionalCoatLaborPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("AiContextProfile") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("BlastNozzleSize") + .HasColumnType("int"); + + b.Property("BlastRateSqFtPerHourOverride") + .HasColumnType("decimal(18,2)"); + + b.Property("BlastSetupType") + .HasColumnType("int"); + + b.Property("CoatingBoothCostPerHour") + .HasColumnType("decimal(18,2)"); + + b.Property("CoatingGunType") + .HasColumnType("int"); + + b.Property("CoatingRateSqFtPerHourOverride") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("ComplexityComplexPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("ComplexityExtremePercent") + .HasColumnType("decimal(18,2)"); + + b.Property("ComplexityModeratePercent") + .HasColumnType("decimal(18,2)"); + + b.Property("ComplexitySimplePercent") + .HasColumnType("decimal(18,2)"); + + b.Property("CompressorCfm") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultOvenCycleMinutes") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GeneralMarkupPercentage") + .HasColumnType("decimal(18,2)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LaborCostPerHour") + .HasColumnType("decimal(18,2)"); + + b.Property("MonthlyBillableHours") + .HasColumnType("int"); + + b.Property("MonthlyRent") + .HasColumnType("decimal(18,2)"); + + b.Property("MonthlyUtilities") + .HasColumnType("decimal(18,2)"); + + b.Property("OvenOperatingCostPerHour") + .HasColumnType("decimal(18,2)"); + + b.Property("PowderCoatingCostPerSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("PricingMode") + .HasColumnType("int"); + + b.Property("PrimaryBlastSubstrate") + .HasColumnType("int"); + + b.Property("RushChargeFixedAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("RushChargePercentage") + .HasColumnType("decimal(18,2)"); + + b.Property("RushChargeType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SandblasterCostPerHour") + .HasColumnType("decimal(18,2)"); + + b.Property("ShopCapabilityTier") + .HasColumnType("int"); + + b.Property("ShopMinimumCharge") + .HasColumnType("decimal(18,2)"); + + b.Property("ShopSuppliesRate") + .HasColumnType("decimal(18,2)"); + + b.Property("StandardLaborRate") + .HasColumnType("decimal(18,2)"); + + b.Property("TargetMarginPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId") + .IsUnique(); + + b.ToTable("CompanyOperatingCosts"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CompanyPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AllowCustomerApproval") + .HasColumnType("bit"); + + b.Property("AutoArchiveJobsDays") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultCurrency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultDateFormat") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultJobPriority") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultPaymentTerms") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultQuoteValidityDays") + .HasColumnType("int"); + + b.Property("DefaultTimeFormat") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultTurnaroundDays") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedRecordRetentionDays") + .HasColumnType("int"); + + b.Property("DueDateWarningDays") + .HasColumnType("int"); + + b.Property("EmailFromAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailFromName") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailNotificationsEnabled") + .HasColumnType("bit"); + + b.Property("FirstInvoiceCreatedAt") + .HasColumnType("datetime2"); + + b.Property("FirstJobCreatedAt") + .HasColumnType("datetime2"); + + b.Property("FirstQuoteCreatedAt") + .HasColumnType("datetime2"); + + b.Property("FirstWorkflowCompleted") + .HasColumnType("bit"); + + b.Property("FirstWorkflowCompletedAt") + .HasColumnType("datetime2"); + + b.Property("GuidedActivationDismissedAt") + .HasColumnType("datetime2"); + + b.Property("InAccentColor") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InDefaultTerms") + .HasColumnType("nvarchar(max)"); + + b.Property("InFooterNote") + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceNumberPrefix") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobNumberPrefix") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("JobRetentionYears") + .HasColumnType("int"); + + b.Property("KioskIntakeOutput") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LogRetentionDays") + .HasColumnType("int"); + + b.Property("MaintenanceAlertDays") + .HasColumnType("int"); + + b.Property("MigratingFromQuickBooks") + .HasColumnType("bit"); + + b.Property("NotifyOnJobStatusChange") + .HasColumnType("bit"); + + b.Property("NotifyOnNewJob") + .HasColumnType("bit"); + + b.Property("NotifyOnNewQuote") + .HasColumnType("bit"); + + b.Property("NotifyOnPaymentReceived") + .HasColumnType("bit"); + + b.Property("NotifyOnQuoteApproval") + .HasColumnType("bit"); + + b.Property("OnboardingPath") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentReminderDays") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentRemindersEnabled") + .HasColumnType("bit"); + + b.Property("QbMigrationStateJson") + .HasColumnType("nvarchar(max)"); + + b.Property("QtAccentColor") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("QtDefaultTerms") + .HasColumnType("nvarchar(max)"); + + b.Property("QtFooterNote") + .HasColumnType("nvarchar(max)"); + + b.Property("QuoteExpiryWarningDays") + .HasColumnType("int"); + + b.Property("QuoteNumberPrefix") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("QuoteRetentionYears") + .HasColumnType("int"); + + b.Property("RequireCustomerPO") + .HasColumnType("bit"); + + b.Property("SetupWizardCompleted") + .HasColumnType("bit"); + + b.Property("SetupWizardCompletedAt") + .HasColumnType("datetime2"); + + b.Property("SetupWizardCompletedByName") + .HasColumnType("nvarchar(max)"); + + b.Property("SetupWizardCompletedByUserId") + .HasColumnType("nvarchar(max)"); + + b.Property("SetupWizardDoneSteps") + .HasColumnType("nvarchar(max)"); + + b.Property("SetupWizardSkippedSteps") + .HasColumnType("nvarchar(max)"); + + b.Property("SetupWizardStarted") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UseMetricSystem") + .HasColumnType("bit"); + + b.Property("WoAccentColor") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("WoTerms") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId") + .IsUnique(); + + b.ToTable("CompanyPreferences"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CompanySmsAgreement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgreedAt") + .HasColumnType("datetime2"); + + b.Property("AgreedByUserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("AgreedByUserName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("TermsVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UserAgent") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CompanySmsAgreements"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ContactSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdminNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("Category") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsRead") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReadAt") + .HasColumnType("datetime2"); + + b.Property("ReadByUserId") + .HasColumnType("nvarchar(max)"); + + b.Property("ReadByUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("SenderEmail") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SenderName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ContactSubmissions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CreditMemo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("AmountApplied") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiryDate") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IssueDate") + .HasColumnType("datetime2"); + + b.Property("IssuedById") + .HasColumnType("nvarchar(450)"); + + b.Property("MemoNumber") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalInvoiceId") + .HasColumnType("int"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReworkRecordId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("IssuedById"); + + b.HasIndex("OriginalInvoiceId"); + + b.HasIndex("ReworkRecordId"); + + b.HasIndex("CompanyId", "MemoNumber") + .IsUnique() + .HasDatabaseName("IX_CreditMemos_CompanyId_MemoNumber"); + + b.ToTable("CreditMemos"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CreditMemoApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AmountApplied") + .HasColumnType("decimal(18,2)"); + + b.Property("AppliedById") + .HasColumnType("nvarchar(450)"); + + b.Property("AppliedDate") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreditMemoId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AppliedById"); + + b.HasIndex("CreditMemoId"); + + b.HasIndex("InvoiceId"); + + b.ToTable("CreditMemoApplications"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CustomItemTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultRate") + .HasColumnType("decimal(18,2)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DiagramImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("FieldsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Formula") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OutputMode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RateLabel") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CustomItemTemplates"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("nvarchar(max)"); + + b.Property("BillingEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompanyName") + .HasColumnType("nvarchar(450)"); + + b.Property("ContactFirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("ContactLastName") + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreditBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("CreditLimit") + .HasColumnType("decimal(18,2)"); + + b.Property("CurrentBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(450)"); + + b.Property("GeneralNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsCommercial") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsTaxExempt") + .HasColumnType("bit"); + + b.Property("LastContactDate") + .HasColumnType("datetime2"); + + b.Property("MobilePhone") + .HasColumnType("nvarchar(max)"); + + b.Property("NotifyByEmail") + .HasColumnType("bit"); + + b.Property("NotifyBySms") + .HasColumnType("bit"); + + b.Property("PaymentTerms") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .HasColumnType("nvarchar(max)"); + + b.Property("PricingTierId") + .HasColumnType("int"); + + b.Property("SmsConsentMethod") + .HasColumnType("nvarchar(max)"); + + b.Property("SmsConsentedAt") + .HasColumnType("datetime2"); + + b.Property("SmsOptedOutAt") + .HasColumnType("datetime2"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("TaxExemptCertificateContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("TaxExemptCertificateData") + .HasColumnType("varbinary(max)"); + + b.Property("TaxExemptCertificateFileName") + .HasColumnType("nvarchar(max)"); + + b.Property("TaxId") + .HasColumnType("nvarchar(max)"); + + b.Property("UnsubscribeToken") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)") + .HasDefaultValueSql("REPLACE(NEWID(),'-','')"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CompanyName"); + + b.HasIndex("PricingTierId"); + + b.HasIndex("UnsubscribeToken") + .IsUnique() + .HasDatabaseName("IX_Customers_UnsubscribeToken"); + + b.HasIndex("CompanyId", "Email") + .IsUnique() + .HasFilter("[Email] IS NOT NULL"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CustomerNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsImportant") + .HasColumnType("bit"); + + b.Property("Note") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId", "CreatedAt") + .HasDatabaseName("IX_CustomerNotes_CustomerId_CreatedAt"); + + b.ToTable("CustomerNotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.DashboardTip", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("TipText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("DashboardTips"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Deposit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("AppliedDate") + .HasColumnType("datetime2"); + + b.Property("AppliedToInvoiceId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DepositAccountId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("ReceiptNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReceivedDate") + .HasColumnType("datetime2"); + + b.Property("RecordedById") + .HasColumnType("nvarchar(450)"); + + b.Property("Reference") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AppliedToInvoiceId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("JobId"); + + b.HasIndex("QuoteId"); + + b.HasIndex("RecordedById"); + + b.ToTable("Deposits"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Equipment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EquipmentName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EquipmentNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("EquipmentType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastMaintenanceDate") + .HasColumnType("datetime2"); + + b.Property("Location") + .HasColumnType("nvarchar(max)"); + + b.Property("ManualContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("ManualFileName") + .HasColumnType("nvarchar(max)"); + + b.Property("ManualFilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("ManualFileSize") + .HasColumnType("bigint"); + + b.Property("ManualUploadedDate") + .HasColumnType("datetime2"); + + b.Property("Manufacturer") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxLoadSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("Model") + .HasColumnType("nvarchar(max)"); + + b.Property("NextScheduledMaintenance") + .HasColumnType("datetime2"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OvenCycleMinutes") + .HasColumnType("int"); + + b.Property("PurchaseDate") + .HasColumnType("datetime2"); + + b.Property("PurchasePrice") + .HasColumnType("decimal(18,2)"); + + b.Property("RecommendedMaintenanceIntervalDays") + .HasColumnType("int"); + + b.Property("SerialNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("WarrantyExpiration") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CompanyId", "Status") + .HasDatabaseName("IX_Equipment_CompanyId_Status"); + + b.ToTable("Equipment"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("ClearedDate") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpenseAccountId") + .HasColumnType("int"); + + b.Property("ExpenseNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsCleared") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Memo") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentAccountId") + .HasColumnType("int"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("ReceiptFilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ExpenseAccountId"); + + b.HasIndex("JobId"); + + b.HasIndex("PaymentAccountId"); + + b.HasIndex("VendorId"); + + b.ToTable("Expenses"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.FixedAsset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccumDepreciationAccountId") + .HasColumnType("int"); + + b.Property("AccumulatedDepreciation") + .HasColumnType("decimal(18,2)"); + + b.Property("AssetAccountId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DepreciationExpenseAccountId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisposalDate") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsDisposed") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseCost") + .HasColumnType("decimal(18,2)"); + + b.Property("PurchaseDate") + .HasColumnType("datetime2"); + + b.Property("SalvageValue") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UsefulLifeMonths") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AccumDepreciationAccountId"); + + b.HasIndex("AssetAccountId"); + + b.HasIndex("DepreciationExpenseAccountId"); + + b.ToTable("FixedAssets"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.FixedAssetDepreciationEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FixedAssetId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JournalEntryId") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("FixedAssetId"); + + b.HasIndex("JournalEntryId"); + + b.ToTable("FixedAssetDepreciationEntries"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.GiftCertificate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BatchId") + .HasColumnType("uniqueidentifier"); + + b.Property("CertificateCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiryDate") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IssueDate") + .HasColumnType("datetime2"); + + b.Property("IssuedById") + .HasColumnType("nvarchar(450)"); + + b.Property("IssuedReason") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("PurchasePrice") + .HasColumnType("decimal(18,2)"); + + b.Property("PurchasingCustomerId") + .HasColumnType("int"); + + b.Property("RecipientCustomerId") + .HasColumnType("int"); + + b.Property("RecipientEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipientName") + .HasColumnType("nvarchar(max)"); + + b.Property("RedeemedAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("SourceInvoiceItemId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IssuedById"); + + b.HasIndex("PurchasingCustomerId"); + + b.HasIndex("RecipientCustomerId"); + + b.HasIndex("CompanyId", "CertificateCode") + .IsUnique(); + + b.ToTable("GiftCertificates"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.GiftCertificateRedemption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AmountRedeemed") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GiftCertificateId") + .HasColumnType("int"); + + b.Property("InvoiceId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("RedeemedById") + .HasColumnType("nvarchar(450)"); + + b.Property("RedeemedDate") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("GiftCertificateId"); + + b.HasIndex("InvoiceId"); + + b.HasIndex("RedeemedById"); + + b.ToTable("GiftCertificateRedemptions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InAppNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsRead") + .HasColumnType("bit"); + + b.Property("Link") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NotificationType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceId"); + + b.HasIndex("QuoteId"); + + b.ToTable("InAppNotifications"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryCategoryLookup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsCoating") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsSystemDefined") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CompanyId", "CategoryCode") + .IsUnique(); + + b.ToTable("InventoryCategoryLookups"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AverageCost") + .HasColumnType("decimal(18,2)"); + + b.Property("Category") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CogsAccountId") + .HasColumnType("int"); + + b.Property("ColorCode") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorFamilies") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorName") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CoverageSqFtPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CureTemperatureF") + .HasColumnType("decimal(18,2)"); + + b.Property("CureTimeMinutes") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscontinuedDate") + .HasColumnType("datetime2"); + + b.Property("Finish") + .HasColumnType("nvarchar(max)"); + + b.Property("HasSamplePanel") + .HasColumnType("bit"); + + b.Property("ImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("InventoryAccountId") + .HasColumnType("int"); + + b.Property("InventoryCategoryId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncoming") + .HasColumnType("bit"); + + b.Property("LastPurchaseDate") + .HasColumnType("datetime2"); + + b.Property("LastPurchasePrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Location") + .HasColumnType("nvarchar(max)"); + + b.Property("Manufacturer") + .HasColumnType("nvarchar(max)"); + + b.Property("ManufacturerPartNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("MaximumStock") + .HasColumnType("decimal(18,2)"); + + b.Property("MinimumStock") + .HasColumnType("decimal(18,2)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PrimaryVendorId") + .HasColumnType("int"); + + b.Property("QuantityOnHand") + .HasColumnType("decimal(18,2)"); + + b.Property("ReorderPoint") + .HasColumnType("decimal(18,2)"); + + b.Property("ReorderQuantity") + .HasColumnType("decimal(18,2)"); + + b.Property("RequiresClearCoat") + .HasColumnType("bit"); + + b.Property("SKU") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("SdsUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("SpecPageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("SpecificGravity") + .HasColumnType("decimal(18,2)"); + + b.Property("TdsUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("TransferEfficiency") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitCost") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorPartNumber") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CogsAccountId"); + + b.HasIndex("CompanyId"); + + b.HasIndex("InventoryAccountId"); + + b.HasIndex("InventoryCategoryId"); + + b.HasIndex("IsActive"); + + b.HasIndex("PrimaryVendorId"); + + b.HasIndex("CompanyId", "IsActive"); + + b.HasIndex("CompanyId", "SKU") + .IsUnique() + .HasDatabaseName("IX_InventoryItems_CompanyId_SKU"); + + b.HasIndex("CompanyId", "QuantityOnHand", "ReorderPoint") + .HasDatabaseName("IX_InventoryItems_CompanyId_Quantity_Reorder"); + + b.ToTable("InventoryItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BalanceAfter") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("InventoryItemId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseOrderId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("Reference") + .HasColumnType("nvarchar(max)"); + + b.Property("TotalCost") + .HasColumnType("decimal(18,2)"); + + b.Property("TransactionDate") + .HasColumnType("datetime2"); + + b.Property("TransactionType") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("JobId"); + + b.HasIndex("PurchaseOrderId"); + + b.HasIndex("TransactionType", "TransactionDate"); + + b.ToTable("InventoryTransactions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AmountPaid") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreditApplied") + .HasColumnType("decimal(18,2)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerPO") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("DueDate") + .HasColumnType("datetime2"); + + b.Property("EarlyPaymentDiscountDays") + .HasColumnType("int"); + + b.Property("EarlyPaymentDiscountPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("ExternalReference") + .HasColumnType("nvarchar(450)"); + + b.Property("GiftCertificateRedeemed") + .HasColumnType("decimal(18,2)"); + + b.Property("InternalNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime2"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OnlineAmountPaid") + .HasColumnType("decimal(18,2)"); + + b.Property("OnlinePaymentStatus") + .HasColumnType("int"); + + b.Property("OnlineSurchargeCollected") + .HasColumnType("decimal(18,2)"); + + b.Property("PaidDate") + .HasColumnType("datetime2"); + + b.Property("PaymentLinkExpiresAt") + .HasColumnType("datetime2"); + + b.Property("PaymentLinkToken") + .HasColumnType("nvarchar(max)"); + + b.Property("PreparedById") + .HasColumnType("nvarchar(450)"); + + b.Property("PublicViewToken") + .HasColumnType("nvarchar(max)"); + + b.Property("SalesTaxAccountId") + .HasColumnType("int"); + + b.Property("SentDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StripePaymentIntentId") + .HasColumnType("nvarchar(max)"); + + b.Property("SubTotal") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("Terms") + .HasColumnType("nvarchar(max)"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DueDate"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("JobId") + .IsUnique() + .HasFilter("[JobId] IS NOT NULL"); + + b.HasIndex("PreparedById"); + + b.HasIndex("SalesTaxAccountId"); + + b.HasIndex("Status"); + + b.HasIndex("CompanyId", "CustomerId") + .HasDatabaseName("IX_Invoices_CompanyId_CustomerId"); + + b.HasIndex("CompanyId", "DueDate") + .HasDatabaseName("IX_Invoices_CompanyId_DueDate"); + + b.HasIndex("CompanyId", "ExternalReference") + .HasDatabaseName("IX_Invoices_CompanyId_ExternalReference"); + + b.HasIndex("CompanyId", "InvoiceNumber") + .IsUnique() + .HasDatabaseName("IX_Invoices_CompanyId_InvoiceNumber"); + + b.HasIndex("CompanyId", "IsDeleted"); + + b.HasIndex("CompanyId", "JobId") + .IsUnique() + .HasDatabaseName("IX_Invoices_CompanyId_JobId") + .HasFilter("[JobId] IS NOT NULL"); + + b.HasIndex("CompanyId", "Status") + .HasDatabaseName("IX_Invoices_CompanyId_Status"); + + b.ToTable("Invoices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InvoiceItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("ColorName") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("GcExpiryDate") + .HasColumnType("datetime2"); + + b.Property("GcRecipientEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("GcRecipientName") + .HasColumnType("nvarchar(max)"); + + b.Property("GeneratedGiftCertificateId") + .HasColumnType("int"); + + b.Property("InvoiceId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsGiftCertificate") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("RevenueAccountId") + .HasColumnType("int"); + + b.Property("SourceJobItemId") + .HasColumnType("int"); + + b.Property("TotalPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogItemId"); + + b.HasIndex("CompanyId"); + + b.HasIndex("GeneratedGiftCertificateId"); + + b.HasIndex("InvoiceId") + .HasDatabaseName("IX_InvoiceItems_InvoiceId"); + + b.HasIndex("RevenueAccountId"); + + b.HasIndex("SourceJobItemId"); + + b.ToTable("InvoiceItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualTimeSpentHours") + .HasColumnType("decimal(18,2)"); + + b.Property("AssignedUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompletedDate") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerPO") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountReason") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountType") + .HasColumnType("int"); + + b.Property("DiscountValue") + .HasColumnType("decimal(18,2)"); + + b.Property("DueDate") + .HasColumnType("datetime2"); + + b.Property("FinalPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("IntakeCheckedByUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("IntakeConditionNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("IntakeDate") + .HasColumnType("datetime2"); + + b.Property("IntakePartCount") + .HasColumnType("int"); + + b.Property("InternalNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCustomerApproved") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsReworkJob") + .HasColumnType("bit"); + + b.Property("IsRushJob") + .HasColumnType("bit"); + + b.Property("JobNumber") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("JobPriorityId") + .HasColumnType("int"); + + b.Property("JobStatusId") + .HasColumnType("int"); + + b.Property("OriginalJobId") + .HasColumnType("int"); + + b.Property("OvenBatchCost") + .HasColumnType("decimal(18,2)"); + + b.Property("OvenBatches") + .HasColumnType("int"); + + b.Property("OvenCostId") + .HasColumnType("int"); + + b.Property("OvenCycleMinutes") + .HasColumnType("int"); + + b.Property("PricingBreakdownJson") + .HasColumnType("nvarchar(max)"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("QuoteSnapshotUpdatedAt") + .HasColumnType("datetime2"); + + b.Property("QuotedPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("RequiresCustomerApproval") + .HasColumnType("bit"); + + b.Property("ScheduledDate") + .HasColumnType("datetime2"); + + b.Property("ShopAccessCode") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("ShopSuppliesAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("ShopSuppliesPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("SpecialInstructions") + .HasColumnType("nvarchar(max)"); + + b.Property("StartedDate") + .HasColumnType("datetime2"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DueDate"); + + b.HasIndex("IntakeCheckedByUserId"); + + b.HasIndex("JobPriorityId"); + + b.HasIndex("JobStatusId"); + + b.HasIndex("OriginalJobId"); + + b.HasIndex("OvenCostId"); + + b.HasIndex("QuoteId") + .IsUnique() + .HasFilter("[QuoteId] IS NOT NULL"); + + b.HasIndex("ScheduledDate"); + + b.HasIndex("CompanyId", "CustomerId") + .HasDatabaseName("IX_Jobs_CompanyId_CustomerId"); + + b.HasIndex("CompanyId", "DueDate") + .HasDatabaseName("IX_Jobs_CompanyId_DueDate"); + + b.HasIndex("CompanyId", "IsDeleted"); + + b.HasIndex("CompanyId", "JobNumber") + .IsUnique() + .HasDatabaseName("IX_Jobs_CompanyId_JobNumber"); + + b.HasIndex("CompanyId", "JobPriorityId") + .HasDatabaseName("IX_Jobs_CompanyId_JobPriorityId"); + + b.HasIndex("CompanyId", "JobStatusId") + .HasDatabaseName("IX_Jobs_CompanyId_JobStatusId"); + + b.HasIndex("CompanyId", "ScheduledDate") + .HasDatabaseName("IX_Jobs_CompanyId_ScheduledDate"); + + b.HasIndex("CompanyId", "ShopAccessCode") + .IsUnique() + .HasDatabaseName("IX_Jobs_CompanyId_ShopAccessCode"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobChangeHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangeDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ChangedAt") + .HasColumnType("datetime2"); + + b.Property("ChangedByUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("NewValue") + .HasColumnType("nvarchar(max)"); + + b.Property("OldValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ChangedByUserId"); + + b.HasIndex("CompanyId"); + + b.HasIndex("JobId"); + + b.ToTable("JobChangeHistories"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobDailyPriority", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("ScheduledDate") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.ToTable("JobDailyPriorities"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AiPredictionId") + .HasColumnType("int"); + + b.Property("AiTags") + .HasColumnType("nvarchar(max)"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("ColorCode") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorName") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("Complexity") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomItemTemplateId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("Finish") + .HasColumnType("nvarchar(max)"); + + b.Property("FormulaFieldValuesJson") + .HasColumnType("nvarchar(max)"); + + b.Property("IncludePrepCost") + .HasColumnType("bit"); + + b.Property("IsAiItem") + .HasColumnType("bit"); + + b.Property("IsCustomFormulaItem") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsGenericItem") + .HasColumnType("bit"); + + b.Property("IsLaborItem") + .HasColumnType("bit"); + + b.Property("IsSalesItem") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("LaborCost") + .HasColumnType("decimal(18,2)"); + + b.Property("ManualUnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PowderCostOverride") + .HasColumnType("decimal(18,2)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("RequiresMasking") + .HasColumnType("bit"); + + b.Property("RequiresSandblasting") + .HasColumnType("bit"); + + b.Property("Sku") + .HasColumnType("nvarchar(max)"); + + b.Property("SurfaceArea") + .HasColumnType("decimal(18,2)"); + + b.Property("SurfaceAreaSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("TotalPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AiPredictionId"); + + b.HasIndex("CatalogItemId"); + + b.HasIndex("CustomItemTemplateId"); + + b.HasIndex("JobId") + .HasDatabaseName("IX_JobItems_JobId"); + + b.HasIndex("JobId", "IsDeleted") + .HasDatabaseName("IX_JobItems_JobId_IsDeleted"); + + b.ToTable("JobItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobItemCoat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualPowderUsedLbs") + .HasColumnType("decimal(18,2)"); + + b.Property("CoatName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ColorCode") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorName") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CoverageSqFtPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Finish") + .HasColumnType("nvarchar(max)"); + + b.Property("InventoryItemId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobItemId") + .HasColumnType("int"); + + b.Property("NoExtraLayerCharge") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PowderCostPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("PowderOrdered") + .HasColumnType("bit"); + + b.Property("PowderOrderedAt") + .HasColumnType("datetime2"); + + b.Property("PowderOrderedByUserId") + .HasColumnType("nvarchar(max)"); + + b.Property("PowderReceived") + .HasColumnType("bit"); + + b.Property("PowderReceivedAt") + .HasColumnType("datetime2"); + + b.Property("PowderReceivedByUserId") + .HasColumnType("nvarchar(max)"); + + b.Property("PowderReceivedLbs") + .HasColumnType("decimal(18,2)"); + + b.Property("PowderToOrder") + .HasColumnType("decimal(18,2)"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("TransferEfficiency") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("JobItemId"); + + b.HasIndex("VendorId"); + + b.ToTable("JobItemCoats"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobItemPrepService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlastSetupId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobItemId") + .HasColumnType("int"); + + b.Property("PrepServiceId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlastSetupId"); + + b.HasIndex("CompanyId") + .HasDatabaseName("IX_JobItemPrepServices_CompanyId"); + + b.HasIndex("JobItemId") + .HasDatabaseName("IX_JobItemPrepServices_JobItemId"); + + b.HasIndex("PrepServiceId") + .HasDatabaseName("IX_JobItemPrepServices_PrepServiceId"); + + b.ToTable("JobItemPrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsImportant") + .HasColumnType("bit"); + + b.Property("IsInternal") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Note") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId", "CreatedAt") + .HasDatabaseName("IX_JobNotes_JobId_CreatedAt"); + + b.ToTable("JobNotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobPhoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Caption") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("IsAiAnalysisPhoto") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("PhotoType") + .HasColumnType("int"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UploadedById") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("UploadedDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("UploadedById"); + + b.HasIndex("JobId", "IsDeleted", "DisplayOrder") + .HasDatabaseName("IX_JobPhotos_JobId_IsDeleted_DisplayOrder"); + + b.ToTable("JobPhotos"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobPrepService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("PrepServiceId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("PrepServiceId"); + + b.ToTable("JobPrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobPriorityLookup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ColorClass") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IconClass") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsSystemDefined") + .HasColumnType("bit"); + + b.Property("PriorityCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CompanyId", "PriorityCode") + .IsUnique(); + + b.ToTable("JobPriorityLookups"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobStatusHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangedDate") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FromStatusId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("ToStatusId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("FromStatusId"); + + b.HasIndex("JobId"); + + b.HasIndex("ToStatusId"); + + b.ToTable("JobStatusHistory"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobStatusLookup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ColorClass") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IconClass") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsSystemDefined") + .HasColumnType("bit"); + + b.Property("IsTerminalStatus") + .HasColumnType("bit"); + + b.Property("IsWorkInProgressStatus") + .HasColumnType("bit"); + + b.Property("StatusCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("WorkflowCategory") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CompanyId", "StatusCode") + .IsUnique(); + + b.ToTable("JobStatusLookups"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SpecialInstructions") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UsageCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("JobTemplates"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplateItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("Complexity") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("IncludePrepCost") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsGenericItem") + .HasColumnType("bit"); + + b.Property("IsLaborItem") + .HasColumnType("bit"); + + b.Property("IsSalesItem") + .HasColumnType("bit"); + + b.Property("JobTemplateId") + .HasColumnType("int"); + + b.Property("ManualUnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("RequiresMasking") + .HasColumnType("bit"); + + b.Property("RequiresSandblasting") + .HasColumnType("bit"); + + b.Property("Sku") + .HasColumnType("nvarchar(max)"); + + b.Property("SurfaceAreaSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogItemId"); + + b.HasIndex("JobTemplateId"); + + b.ToTable("JobTemplateItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplateItemCoat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoatName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ColorCode") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorName") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CoverageSqFtPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Finish") + .HasColumnType("nvarchar(max)"); + + b.Property("InventoryItemId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobTemplateItemId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PowderCostPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("TransferEfficiency") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("JobTemplateItemId"); + + b.HasIndex("VendorId"); + + b.ToTable("JobTemplateItemCoats"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplateItemPrepService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobTemplateItemId") + .HasColumnType("int"); + + b.Property("PrepServiceId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobTemplateItemId"); + + b.HasIndex("PrepServiceId"); + + b.ToTable("JobTemplateItemPrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HoursWorked") + .HasColumnType("decimal(18,2)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("Stage") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UserDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("nvarchar(max)"); + + b.Property("WorkDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.ToTable("JobTimeEntries"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JournalEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("EntryDate") + .HasColumnType("datetime2"); + + b.Property("EntryNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsReversal") + .HasColumnType("bit"); + + b.Property("PostedAt") + .HasColumnType("datetime2"); + + b.Property("PostedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Reference") + .HasColumnType("nvarchar(max)"); + + b.Property("ReversalOfId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ReversalOfId"); + + b.ToTable("JournalEntries"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JournalEntryLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreditAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("DebitAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JournalEntryId") + .HasColumnType("int"); + + b.Property("LineOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("JournalEntryId"); + + b.ToTable("JournalEntryLines"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.KioskSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgreedToTerms") + .HasColumnType("bit"); + + b.Property("AgreedToTermsAt") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerEmail") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerFirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerLastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerPhone") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("HowDidYouHearAboutUs") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsReturningCustomer") + .HasColumnType("bit"); + + b.Property("JobDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LinkedCustomerId") + .HasColumnType("int"); + + b.Property("LinkedJobId") + .HasColumnType("int"); + + b.Property("LinkedQuoteId") + .HasColumnType("int"); + + b.Property("RemoteLinkEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("RemoteLinkSentAt") + .HasColumnType("datetime2"); + + b.Property("SessionToken") + .HasColumnType("uniqueidentifier"); + + b.Property("SessionType") + .HasColumnType("int"); + + b.Property("SignatureDataBase64") + .HasColumnType("nvarchar(max)"); + + b.Property("SmsOptIn") + .HasColumnType("bit"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("LinkedCustomerId"); + + b.HasIndex("LinkedJobId"); + + b.HasIndex("SessionToken") + .IsUnique(); + + b.ToTable("KioskSessions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.MaintenanceRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AssignedUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompletedDate") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DowntimeHours") + .HasColumnType("decimal(18,2)"); + + b.Property("EquipmentId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsRecurring") + .HasColumnType("bit"); + + b.Property("LaborCost") + .HasColumnType("decimal(18,2)"); + + b.Property("MaintenanceType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PartsCost") + .HasColumnType("decimal(18,2)"); + + b.Property("PartsReplaced") + .HasColumnType("nvarchar(max)"); + + b.Property("PerformedById") + .HasColumnType("nvarchar(450)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("RecurrenceEndDate") + .HasColumnType("datetime2"); + + b.Property("RecurrenceFrequency") + .HasColumnType("int"); + + b.Property("RecurrenceGroupId") + .HasColumnType("nvarchar(max)"); + + b.Property("RecurrenceParentId") + .HasColumnType("int"); + + b.Property("ScheduledDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TechnicianNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("TotalCost") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("WorkPerformed") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("EquipmentId"); + + b.HasIndex("PerformedById"); + + b.HasIndex("RecurrenceParentId"); + + b.HasIndex("ScheduledDate"); + + b.HasIndex("Status"); + + b.HasIndex("CompanyId", "ScheduledDate") + .HasDatabaseName("IX_MaintenanceRecords_CompanyId_ScheduledDate"); + + b.HasIndex("CompanyId", "Status") + .HasDatabaseName("IX_MaintenanceRecords_CompanyId_Status"); + + b.ToTable("MaintenanceRecords"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ManufacturerLookupPattern", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Domain") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("ManufacturerName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductUrlTemplate") + .HasColumnType("nvarchar(max)"); + + b.Property("SlugTransform") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ManufacturerLookupPatterns"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.NotificationLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Channel") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ErrorMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("Recipient") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RecipientName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceId"); + + b.HasIndex("JobId"); + + b.HasIndex("QuoteId"); + + b.HasIndex("CompanyId", "SentAt") + .HasDatabaseName("IX_NotificationLogs_CompanyId_SentAt"); + + b.HasIndex("CompanyId", "Status") + .HasDatabaseName("IX_NotificationLogs_CompanyId_Status"); + + b.ToTable("NotificationLogs"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.NotificationTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Body") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Channel") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId", "NotificationType", "Channel") + .IsUnique() + .HasDatabaseName("IX_NotificationTemplates_Company_Type_Channel"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualEndTime") + .HasColumnType("datetime2"); + + b.Property("ActualStartTime") + .HasColumnType("datetime2"); + + b.Property("AiReasoningJson") + .HasColumnType("nvarchar(max)"); + + b.Property("AiSuggested") + .HasColumnType("bit"); + + b.Property("BatchNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CureTemperatureF") + .HasColumnType("decimal(18,2)"); + + b.Property("CycleMinutes") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EquipmentId") + .HasColumnType("int"); + + b.Property("EstimatedEndTime") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OvenCostId") + .HasColumnType("int"); + + b.Property("PrimaryColorCode") + .HasColumnType("nvarchar(max)"); + + b.Property("PrimaryColorName") + .HasColumnType("nvarchar(max)"); + + b.Property("ScheduledDate") + .HasColumnType("datetime2"); + + b.Property("ScheduledStartTime") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalSurfaceAreaSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EquipmentId"); + + b.HasIndex("OvenCostId"); + + b.HasIndex("ScheduledDate", "Status"); + + b.ToTable("OvenBatches"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenBatchItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoatPassNumber") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("JobItemCoatId") + .HasColumnType("int"); + + b.Property("JobItemId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OvenBatchId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SurfaceAreaContribution") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("JobItemCoatId"); + + b.HasIndex("JobItemId"); + + b.HasIndex("OvenBatchId"); + + b.ToTable("OvenBatchItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenCost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CostPerHour") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultCycleMinutes") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MaxLoadSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.ToTable("OvenCosts"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Payment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("ClearedDate") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DepositAccountId") + .HasColumnType("int"); + + b.Property("InvoiceId") + .HasColumnType("int"); + + b.Property("IsCleared") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("RecordedById") + .HasColumnType("nvarchar(450)"); + + b.Property("Reference") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DepositAccountId"); + + b.HasIndex("InvoiceId") + .HasDatabaseName("IX_Payments_InvoiceId"); + + b.HasIndex("PaymentDate"); + + b.HasIndex("RecordedById"); + + b.HasIndex("CompanyId", "PaymentDate") + .HasDatabaseName("IX_Payments_CompanyId_PaymentDate"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PendingRegistrationSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyPhone") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsAnnual") + .HasColumnType("bit"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Plan") + .HasColumnType("int"); + + b.Property("Token") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PendingRegistrationSessions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PlatformSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("GroupName") + .HasColumnType("nvarchar(max)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Label") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PlatformSettings"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PowderCatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApplicationGuideUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorFamilies") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorName") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CoverageSqFtPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CureTemperatureF") + .HasColumnType("decimal(18,2)"); + + b.Property("CureTimeMinutes") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Finish") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDiscontinued") + .HasColumnType("bit"); + + b.Property("IsUserContributed") + .HasColumnType("bit"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime2"); + + b.Property("PriceTiersJson") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RequiresClearCoat") + .HasColumnType("bit"); + + b.Property("SdsUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("Sku") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("SpecificGravity") + .HasColumnType("decimal(18,2)"); + + b.Property("TdsUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("TransferEfficiency") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("VendorName") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("ColorName") + .HasDatabaseName("IX_PowderCatalogItems_ColorName"); + + b.HasIndex("VendorName", "Sku") + .IsUnique() + .HasDatabaseName("IX_PowderCatalogItems_Vendor_Sku"); + + b.ToTable("PowderCatalogItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PowderUsageLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualLbsUsed") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimatedLbs") + .HasColumnType("decimal(18,2)"); + + b.Property("InventoryItemId") + .HasColumnType("int"); + + b.Property("InventoryTransactionId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("JobItemCoatId") + .HasColumnType("int"); + + b.Property("JobItemId") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("RecordedAt") + .HasColumnType("datetime2"); + + b.Property("RecordedByUserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VarianceLbs") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("InventoryTransactionId"); + + b.HasIndex("JobId"); + + b.HasIndex("JobItemCoatId"); + + b.HasIndex("JobItemId"); + + b.ToTable("PowderUsageLogs"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PrepService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("RequiresBlastSetup") + .HasColumnType("bit"); + + b.Property("ServiceName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PricingTier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("TierName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.ToTable("PricingTiers"); + + b.HasData( + new + { + Id = 1, + CompanyId = 0, + CreatedAt = new DateTime(2026, 5, 23, 15, 30, 51, 760, DateTimeKind.Utc).AddTicks(9869), + Description = "Standard pricing for regular customers", + DiscountPercent = 0m, + IsActive = true, + IsDeleted = false, + TierName = "Standard" + }, + new + { + Id = 2, + CompanyId = 0, + CreatedAt = new DateTime(2026, 5, 23, 15, 30, 51, 760, DateTimeKind.Utc).AddTicks(9876), + Description = "5% discount for preferred customers", + DiscountPercent = 5m, + IsActive = true, + IsDeleted = false, + TierName = "Preferred" + }, + new + { + Id = 3, + CompanyId = 0, + CreatedAt = new DateTime(2026, 5, 23, 15, 30, 51, 760, DateTimeKind.Utc).AddTicks(9878), + Description = "10% discount for premium customers", + DiscountPercent = 10m, + IsActive = true, + IsDeleted = false, + TierName = "Premium" + }); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BillId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("datetime2"); + + b.Property("InternalNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("PoNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReceivedDate") + .HasColumnType("datetime2"); + + b.Property("ShippingCost") + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubTotal") + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BillId"); + + b.HasIndex("VendorId"); + + b.ToTable("PurchaseOrders"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PurchaseOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("InventoryItemId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LineTotal") + .HasColumnType("decimal(18,2)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseOrderId") + .HasColumnType("int"); + + b.Property("QuantityOrdered") + .HasColumnType("decimal(18,2)"); + + b.Property("QuantityReceived") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitCost") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitOfMeasure") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("PurchaseOrderId"); + + b.ToTable("PurchaseOrderItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApprovalToken") + .HasColumnType("nvarchar(450)"); + + b.Property("ApprovalTokenExpiresAt") + .HasColumnType("datetime2"); + + b.Property("ApprovalTokenUsedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedDate") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("ConvertedDate") + .HasColumnType("datetime2"); + + b.Property("ConvertedToJobId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerPO") + .HasColumnType("nvarchar(max)"); + + b.Property("DeclineReason") + .HasColumnType("nvarchar(max)"); + + b.Property("DeclinedByIp") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DepositAmountPaid") + .HasColumnType("decimal(18,2)"); + + b.Property("DepositPaymentIntentId") + .HasColumnType("nvarchar(max)"); + + b.Property("DepositPaymentLinkExpiresAt") + .HasColumnType("datetime2"); + + b.Property("DepositPaymentLinkToken") + .HasColumnType("nvarchar(max)"); + + b.Property("DepositPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("DiscountPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("DiscountReason") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountType") + .HasColumnType("int"); + + b.Property("DiscountValue") + .HasColumnType("decimal(18,2)"); + + b.Property("EquipmentCosts") + .HasColumnType("decimal(18,2)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime2"); + + b.Property("FacilityOverheadCost") + .HasColumnType("decimal(18,2)"); + + b.Property("FacilityOverheadRatePerHour") + .HasColumnType("decimal(18,2)"); + + b.Property("HideDiscountFromCustomer") + .HasColumnType("bit"); + + b.Property("IsCommercial") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsRushJob") + .HasColumnType("bit"); + + b.Property("ItemsSubtotal") + .HasColumnType("decimal(18,2)"); + + b.Property("LaborCosts") + .HasColumnType("decimal(18,2)"); + + b.Property("MaterialCosts") + .HasColumnType("decimal(18,2)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OvenBatchCost") + .HasColumnType("decimal(18,2)"); + + b.Property("OvenBatches") + .HasColumnType("int"); + + b.Property("OvenCostId") + .HasColumnType("int"); + + b.Property("OvenCycleMinutes") + .HasColumnType("int"); + + b.Property("OverheadAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("OverheadPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("PreparedById") + .HasColumnType("nvarchar(450)"); + + b.Property("PricingTierDiscountAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("PricingTierDiscountPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("ProfitMargin") + .HasColumnType("decimal(18,2)"); + + b.Property("ProfitPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("ProspectAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("ProspectCity") + .HasColumnType("nvarchar(max)"); + + b.Property("ProspectCompanyName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProspectContactName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProspectEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("ProspectPhone") + .HasColumnType("nvarchar(max)"); + + b.Property("ProspectSmsConsent") + .HasColumnType("bit"); + + b.Property("ProspectSmsConsentedAt") + .HasColumnType("datetime2"); + + b.Property("ProspectState") + .HasColumnType("nvarchar(max)"); + + b.Property("ProspectZipCode") + .HasColumnType("nvarchar(max)"); + + b.Property("QuoteDate") + .HasColumnType("datetime2"); + + b.Property("QuoteDiscountAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("QuoteDiscountPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("QuoteNumber") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("QuoteStatusId") + .HasColumnType("int"); + + b.Property("RequiresDeposit") + .HasColumnType("bit"); + + b.Property("RushFee") + .HasColumnType("decimal(18,2)"); + + b.Property("SentDate") + .HasColumnType("datetime2"); + + b.Property("ShopSuppliesAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("ShopSuppliesPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("SubTotal") + .HasColumnType("decimal(18,2)"); + + b.Property("SubtotalAfterDiscount") + .HasColumnType("decimal(18,2)"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.Property("TaxAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("Terms") + .HasColumnType("nvarchar(max)"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApprovalToken") + .IsUnique() + .HasDatabaseName("IX_Quotes_ApprovalToken") + .HasFilter("[ApprovalToken] IS NOT NULL"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ExpirationDate"); + + b.HasIndex("OvenCostId"); + + b.HasIndex("PreparedById"); + + b.HasIndex("QuoteStatusId"); + + b.HasIndex("CompanyId", "ExpirationDate") + .HasDatabaseName("IX_Quotes_CompanyId_ExpirationDate"); + + b.HasIndex("CompanyId", "IsDeleted"); + + b.HasIndex("CompanyId", "QuoteNumber") + .IsUnique() + .HasDatabaseName("IX_Quotes_CompanyId_QuoteNumber"); + + b.HasIndex("CompanyId", "QuoteStatusId") + .HasDatabaseName("IX_Quotes_CompanyId_QuoteStatusId"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteChangeHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangeDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ChangedAt") + .HasColumnType("datetime2"); + + b.Property("ChangedByUserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("NewValue") + .HasColumnType("nvarchar(max)"); + + b.Property("OldValue") + .HasColumnType("nvarchar(max)"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ChangedByUserId"); + + b.HasIndex("CompanyId"); + + b.HasIndex("QuoteId"); + + b.ToTable("QuoteChangeHistories"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AiPredictionId") + .HasColumnType("int"); + + b.Property("AiTags") + .HasColumnType("nvarchar(max)"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("Complexity") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CustomItemTemplateId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FormulaFieldValuesJson") + .HasColumnType("nvarchar(max)"); + + b.Property("IncludePrepCost") + .HasColumnType("bit"); + + b.Property("IsAiItem") + .HasColumnType("bit"); + + b.Property("IsCustomFormulaItem") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsGenericItem") + .HasColumnType("bit"); + + b.Property("IsLaborItem") + .HasColumnType("bit"); + + b.Property("IsSalesItem") + .HasColumnType("bit"); + + b.Property("ItemEquipmentCost") + .HasColumnType("decimal(18,2)"); + + b.Property("ItemLaborCost") + .HasColumnType("decimal(18,2)"); + + b.Property("ItemMaterialCost") + .HasColumnType("decimal(18,2)"); + + b.Property("ManualUnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PowderCostOverride") + .HasColumnType("decimal(18,2)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("RequiresMasking") + .HasColumnType("bit"); + + b.Property("RequiresSandblasting") + .HasColumnType("bit"); + + b.Property("Sku") + .HasColumnType("nvarchar(max)"); + + b.Property("SurfaceArea") + .HasColumnType("decimal(18,2)"); + + b.Property("SurfaceAreaSqFt") + .HasColumnType("decimal(18,2)"); + + b.Property("TotalPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AiPredictionId"); + + b.HasIndex("CatalogItemId"); + + b.HasIndex("CustomItemTemplateId"); + + b.HasIndex("QuoteId") + .HasDatabaseName("IX_QuoteItems_QuoteId"); + + b.ToTable("QuoteItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteItemCoat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoatLaborCost") + .HasColumnType("decimal(18,2)"); + + b.Property("CoatMaterialCost") + .HasColumnType("decimal(18,2)"); + + b.Property("CoatName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CoatTotalCost") + .HasColumnType("decimal(18,2)"); + + b.Property("ColorCode") + .HasColumnType("nvarchar(max)"); + + b.Property("ColorName") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CoverageSqFtPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Finish") + .HasColumnType("nvarchar(max)"); + + b.Property("InventoryItemId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("NoExtraLayerCharge") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PowderCostPerLb") + .HasColumnType("decimal(18,2)"); + + b.Property("PowderToOrder") + .HasColumnType("decimal(18,2)"); + + b.Property("QuoteItemId") + .HasColumnType("int"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("TransferEfficiency") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId") + .HasDatabaseName("IX_QuoteItemCoats_CompanyId"); + + b.HasIndex("InventoryItemId") + .HasDatabaseName("IX_QuoteItemCoats_InventoryItemId"); + + b.HasIndex("QuoteItemId") + .HasDatabaseName("IX_QuoteItemCoats_QuoteItemId"); + + b.HasIndex("VendorId"); + + b.ToTable("QuoteItemCoats"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteItemPrepService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlastSetupId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("PrepServiceId") + .HasColumnType("int"); + + b.Property("QuoteItemId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlastSetupId"); + + b.HasIndex("CompanyId") + .HasDatabaseName("IX_QuoteItemPrepServices_CompanyId"); + + b.HasIndex("PrepServiceId") + .HasDatabaseName("IX_QuoteItemPrepServices_PrepServiceId"); + + b.HasIndex("QuoteItemId") + .HasDatabaseName("IX_QuoteItemPrepServices_QuoteItemId"); + + b.ToTable("QuoteItemPrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuotePhoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Caption") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("IsAiAnalysisPhoto") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("TempId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UploadedById") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("QuoteId"); + + b.HasIndex("UploadedById"); + + b.ToTable("QuotePhotos"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuotePrepService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("PrepServiceId") + .HasColumnType("int"); + + b.Property("QuoteId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("PrepServiceId"); + + b.HasIndex("QuoteId"); + + b.ToTable("QuotePrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteStatusLookup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ColorClass") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("IconClass") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsApprovedStatus") + .HasColumnType("bit"); + + b.Property("IsConvertedStatus") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsDraftStatus") + .HasColumnType("bit"); + + b.Property("IsRejectedStatus") + .HasColumnType("bit"); + + b.Property("IsSystemDefined") + .HasColumnType("bit"); + + b.Property("StatusCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("CompanyId", "StatusCode") + .IsUnique(); + + b.ToTable("QuoteStatusLookups"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.RecurringTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("Frequency") + .HasColumnType("int"); + + b.Property("IntervalCount") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastError") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxOccurrences") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NextFireDate") + .HasColumnType("datetime2"); + + b.Property("OccurrenceCount") + .HasColumnType("int"); + + b.Property("TemplateData") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TemplateType") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("RecurringTemplates"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Refund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreditMemoId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DepositAccountId") + .HasColumnType("int"); + + b.Property("InvoiceId") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IssuedById") + .HasColumnType("nvarchar(450)"); + + b.Property("IssuedDate") + .HasColumnType("datetime2"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentId") + .HasColumnType("int"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Reference") + .HasColumnType("nvarchar(max)"); + + b.Property("RefundDate") + .HasColumnType("datetime2"); + + b.Property("RefundMethod") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreditMemoId"); + + b.HasIndex("InvoiceId"); + + b.HasIndex("IssuedById"); + + b.HasIndex("PaymentId"); + + b.ToTable("Refunds"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ReleaseNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Body") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedByUserId") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedByUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("IsPublished") + .HasColumnType("bit"); + + b.Property("ReleasedAt") + .HasColumnType("datetime2"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Version") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ReleaseNotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ReworkRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualReworkCost") + .HasColumnType("decimal(18,2)"); + + b.Property("BillingNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DefectDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscoveredBy") + .HasColumnType("int"); + + b.Property("DiscoveredDate") + .HasColumnType("datetime2"); + + b.Property("EstimatedReworkCost") + .HasColumnType("decimal(18,2)"); + + b.Property("IsBillableToCustomer") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("JobItemId") + .HasColumnType("int"); + + b.Property("Reason") + .HasColumnType("int"); + + b.Property("ReportedByName") + .HasColumnType("nvarchar(max)"); + + b.Property("Resolution") + .HasColumnType("int"); + + b.Property("ResolutionNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("ResolvedDate") + .HasColumnType("datetime2"); + + b.Property("ReworkJobId") + .HasColumnType("int"); + + b.Property("ReworkPricingType") + .HasColumnType("int"); + + b.Property("ReworkType") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("JobItemId"); + + b.HasIndex("ReworkJobId"); + + b.ToTable("ReworkRecords"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.StripeWebhookEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("EventId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedAt") + .HasColumnType("datetime2"); + + b.Property("RawJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("StripeWebhookEvents"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.SubscriptionPlanConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AllowAccounting") + .HasColumnType("bit"); + + b.Property("AllowAiCatalogPriceCheck") + .HasColumnType("bit"); + + b.Property("AllowAiInventoryAssist") + .HasColumnType("bit"); + + b.Property("AllowAiPhotoQuotes") + .HasColumnType("bit"); + + b.Property("AllowOnlinePayments") + .HasColumnType("bit"); + + b.Property("AllowSms") + .HasColumnType("bit"); + + b.Property("AnnualPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("MaxActiveJobs") + .HasColumnType("int"); + + b.Property("MaxAiPhotoQuotesPerMonth") + .HasColumnType("int"); + + b.Property("MaxCatalogItems") + .HasColumnType("int"); + + b.Property("MaxCustomers") + .HasColumnType("int"); + + b.Property("MaxJobPhotos") + .HasColumnType("int"); + + b.Property("MaxQuotePhotos") + .HasColumnType("int"); + + b.Property("MaxQuotes") + .HasColumnType("int"); + + b.Property("MaxUsers") + .HasColumnType("int"); + + b.Property("MonthlyPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Plan") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StripePriceIdAnnual") + .HasColumnType("nvarchar(max)"); + + b.Property("StripePriceIdMonthly") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionPlanConfigs"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.TaxRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Rate") + .HasColumnType("decimal(18,2)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TaxRates"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.TermsAcceptance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AcceptedAt") + .HasColumnType("datetime2"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("IpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("TosVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserAgent") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TermsAcceptances"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.UserPasskey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CredentialId") + .IsRequired() + .HasColumnType("varbinary(900)"); + + b.Property("DeviceFriendlyName") + .HasColumnType("nvarchar(max)"); + + b.Property("LastUsedAt") + .HasColumnType("datetime2"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("SignCount") + .HasColumnType("bigint"); + + b.Property("UserHandle") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CredentialId") + .IsUnique(); + + b.ToTable("UserPasskeys"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Vendor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Address") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ContactName") + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreditLimit") + .HasColumnType("decimal(18,2)"); + + b.Property("CurrentBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("DefaultExpenseAccountId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Is1099Vendor") + .HasColumnType("bit"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsPreferred") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OpeningBalance") + .HasColumnType("decimal(18,2)"); + + b.Property("OpeningBalanceDate") + .HasColumnType("datetime2"); + + b.Property("PaymentTerms") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("TaxId") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Website") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CompanyId"); + + b.HasIndex("DefaultExpenseAccountId"); + + b.ToTable("Vendors"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.VendorCredit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("APAccountId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreditDate") + .HasColumnType("datetime2"); + + b.Property("CreditNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Memo") + .HasColumnType("nvarchar(max)"); + + b.Property("PostedDate") + .HasColumnType("datetime2"); + + b.Property("RemainingAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("APAccountId"); + + b.HasIndex("VendorId"); + + b.ToTable("VendorCredits"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.VendorCreditApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("AppliedDate") + .HasColumnType("datetime2"); + + b.Property("BillId") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorCreditId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BillId"); + + b.HasIndex("VendorCreditId"); + + b.ToTable("VendorCreditApplications"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.VendorCreditLineItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("VendorCreditId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("VendorCreditId"); + + b.ToTable("VendorCreditLineItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.YearEndClose", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClosedAt") + .HasColumnType("datetime2"); + + b.Property("ClosedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ClosedYear") + .HasColumnType("int"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("JournalEntryId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JournalEntryId"); + + b.ToTable("YearEndCloses"); + }); + + modelBuilder.Entity("VendorInventoryCategories", b => + { + b.Property("InventoryCategoryLookupId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("InventoryCategoryLookupId", "VendorId"); + + b.HasIndex("VendorId"); + + b.ToTable("VendorInventoryCategories"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Account", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "ParentAccount") + .WithMany("SubAccounts") + .HasForeignKey("ParentAccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentAccount"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AiUsageLog", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", "Company") + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AnnouncementDismissal", b => + { + b.HasOne("PowderCoating.Core.Entities.Announcement", "Announcement") + .WithMany("Dismissals") + .HasForeignKey("AnnouncementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Announcement"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ApplicationUser", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", "Company") + .WithMany("Users") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Appointment", b => + { + b.HasOne("PowderCoating.Core.Entities.AppointmentStatusLookup", "AppointmentStatus") + .WithMany("Appointments") + .HasForeignKey("AppointmentStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.AppointmentTypeLookup", "AppointmentType") + .WithMany("Appointments") + .HasForeignKey("AppointmentTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "AssignedUser") + .WithMany() + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId"); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.Navigation("AppointmentStatus"); + + b.Navigation("AppointmentType"); + + b.Navigation("AssignedUser"); + + b.Navigation("Customer"); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BankReconciliation", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Bill", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "APAccount") + .WithMany("Bills") + .HasForeignKey("APAccountId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany("Bills") + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("APAccount"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BillLineItem", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "Account") + .WithMany("BillLineItems") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.Bill", "Bill") + .WithMany("LineItems") + .HasForeignKey("BillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.Navigation("Account"); + + b.Navigation("Bill"); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BillPayment", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "BankAccount") + .WithMany("BillPayments") + .HasForeignKey("BankAccountId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Bill", "Bill") + .WithMany("Payments") + .HasForeignKey("BillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany("BillPayments") + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BankAccount"); + + b.Navigation("Bill"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BudgetLine", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Budget", "Budget") + .WithMany("Lines") + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Budget"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BugReportAttachment", b => + { + b.HasOne("PowderCoating.Core.Entities.BugReport", "BugReport") + .WithMany("Attachments") + .HasForeignKey("BugReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BugReport"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CatalogCategory", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.CatalogCategory", "ParentCategory") + .WithMany("SubCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CatalogItem", b => + { + b.HasOne("PowderCoating.Core.Entities.CatalogCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Account", "CogsAccount") + .WithMany() + .HasForeignKey("CogsAccountId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId"); + + b.HasOne("PowderCoating.Core.Entities.Account", "RevenueAccount") + .WithMany() + .HasForeignKey("RevenueAccountId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Category"); + + b.Navigation("CogsAccount"); + + b.Navigation("InventoryItem"); + + b.Navigation("RevenueAccount"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CompanyBlastSetup", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", "Company") + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CompanyOperatingCosts", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", "Company") + .WithOne("OperatingCosts") + .HasForeignKey("PowderCoating.Core.Entities.CompanyOperatingCosts", "CompanyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CompanyPreferences", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", "Company") + .WithOne("Preferences") + .HasForeignKey("PowderCoating.Core.Entities.CompanyPreferences", "CompanyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CreditMemo", b => + { + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "IssuedBy") + .WithMany() + .HasForeignKey("IssuedById"); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "OriginalInvoice") + .WithMany() + .HasForeignKey("OriginalInvoiceId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.ReworkRecord", "ReworkRecord") + .WithMany() + .HasForeignKey("ReworkRecordId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Customer"); + + b.Navigation("IssuedBy"); + + b.Navigation("OriginalInvoice"); + + b.Navigation("ReworkRecord"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CreditMemoApplication", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "AppliedBy") + .WithMany() + .HasForeignKey("AppliedById"); + + b.HasOne("PowderCoating.Core.Entities.CreditMemo", "CreditMemo") + .WithMany("Applications") + .HasForeignKey("CreditMemoId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "Invoice") + .WithMany("CreditApplications") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AppliedBy"); + + b.Navigation("CreditMemo"); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Customer", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany("Customers") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.PricingTier", "PricingTier") + .WithMany("Customers") + .HasForeignKey("PricingTierId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("PricingTier"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CustomerNote", b => + { + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany("CustomerNotes") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Deposit", b => + { + b.HasOne("PowderCoating.Core.Entities.Invoice", "AppliedToInvoice") + .WithMany() + .HasForeignKey("AppliedToInvoiceId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithMany() + .HasForeignKey("QuoteId"); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "RecordedBy") + .WithMany() + .HasForeignKey("RecordedById"); + + b.Navigation("AppliedToInvoice"); + + b.Navigation("Customer"); + + b.Navigation("Job"); + + b.Navigation("Quote"); + + b.Navigation("RecordedBy"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Equipment", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany("Equipment") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Expense", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "ExpenseAccount") + .WithMany("Expenses") + .HasForeignKey("ExpenseAccountId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("PowderCoating.Core.Entities.Account", "PaymentAccount") + .WithMany("ExpensePaymentAccounts") + .HasForeignKey("PaymentAccountId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany("Expenses") + .HasForeignKey("VendorId"); + + b.Navigation("ExpenseAccount"); + + b.Navigation("Job"); + + b.Navigation("PaymentAccount"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.FixedAsset", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "AccumDepreciationAccount") + .WithMany() + .HasForeignKey("AccumDepreciationAccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.Account", "AssetAccount") + .WithMany() + .HasForeignKey("AssetAccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.Account", "DepreciationExpenseAccount") + .WithMany() + .HasForeignKey("DepreciationExpenseAccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AccumDepreciationAccount"); + + b.Navigation("AssetAccount"); + + b.Navigation("DepreciationExpenseAccount"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.FixedAssetDepreciationEntry", b => + { + b.HasOne("PowderCoating.Core.Entities.FixedAsset", "FixedAsset") + .WithMany("DepreciationEntries") + .HasForeignKey("FixedAssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JournalEntry", "JournalEntry") + .WithMany() + .HasForeignKey("JournalEntryId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FixedAsset"); + + b.Navigation("JournalEntry"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.GiftCertificate", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "IssuedBy") + .WithMany() + .HasForeignKey("IssuedById"); + + b.HasOne("PowderCoating.Core.Entities.Customer", "PurchasingCustomer") + .WithMany() + .HasForeignKey("PurchasingCustomerId"); + + b.HasOne("PowderCoating.Core.Entities.Customer", "RecipientCustomer") + .WithMany() + .HasForeignKey("RecipientCustomerId"); + + b.Navigation("IssuedBy"); + + b.Navigation("PurchasingCustomer"); + + b.Navigation("RecipientCustomer"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.GiftCertificateRedemption", b => + { + b.HasOne("PowderCoating.Core.Entities.GiftCertificate", "GiftCertificate") + .WithMany("Redemptions") + .HasForeignKey("GiftCertificateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "Invoice") + .WithMany("GiftCertificateRedemptions") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "RedeemedBy") + .WithMany() + .HasForeignKey("RedeemedById"); + + b.Navigation("GiftCertificate"); + + b.Navigation("Invoice"); + + b.Navigation("RedeemedBy"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InAppNotification", b => + { + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId"); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "Invoice") + .WithMany() + .HasForeignKey("InvoiceId"); + + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithMany() + .HasForeignKey("QuoteId"); + + b.Navigation("Customer"); + + b.Navigation("Invoice"); + + b.Navigation("Quote"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryCategoryLookup", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryItem", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "CogsAccount") + .WithMany() + .HasForeignKey("CogsAccountId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany("InventoryItems") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Account", "InventoryAccount") + .WithMany() + .HasForeignKey("InventoryAccountId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.InventoryCategoryLookup", "InventoryCategory") + .WithMany("InventoryItems") + .HasForeignKey("InventoryCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "PrimaryVendor") + .WithMany("InventoryItems") + .HasForeignKey("PrimaryVendorId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("CogsAccount"); + + b.Navigation("InventoryAccount"); + + b.Navigation("InventoryCategory"); + + b.Navigation("PrimaryVendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryTransaction", b => + { + b.HasOne("PowderCoating.Core.Entities.InventoryItem", "InventoryItem") + .WithMany("Transactions") + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PurchaseOrderId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("InventoryItem"); + + b.Navigation("Job"); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Invoice", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany("Invoices") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithOne("Invoice") + .HasForeignKey("PowderCoating.Core.Entities.Invoice", "JobId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "PreparedBy") + .WithMany() + .HasForeignKey("PreparedById") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Account", "SalesTaxAccount") + .WithMany() + .HasForeignKey("SalesTaxAccountId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Customer"); + + b.Navigation("Job"); + + b.Navigation("PreparedBy"); + + b.Navigation("SalesTaxAccount"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InvoiceItem", b => + { + b.HasOne("PowderCoating.Core.Entities.CatalogItem", "CatalogItem") + .WithMany() + .HasForeignKey("CatalogItemId"); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.GiftCertificate", "GeneratedGiftCertificate") + .WithMany() + .HasForeignKey("GeneratedGiftCertificateId"); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "Invoice") + .WithMany("InvoiceItems") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Account", "RevenueAccount") + .WithMany() + .HasForeignKey("RevenueAccountId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.JobItem", "SourceJobItem") + .WithMany() + .HasForeignKey("SourceJobItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("CatalogItem"); + + b.Navigation("GeneratedGiftCertificate"); + + b.Navigation("Invoice"); + + b.Navigation("RevenueAccount"); + + b.Navigation("SourceJobItem"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Job", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "AssignedUser") + .WithMany() + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany("Jobs") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany("Jobs") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "IntakeCheckedBy") + .WithMany() + .HasForeignKey("IntakeCheckedByUserId"); + + b.HasOne("PowderCoating.Core.Entities.JobPriorityLookup", "JobPriority") + .WithMany("Jobs") + .HasForeignKey("JobPriorityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobStatusLookup", "JobStatus") + .WithMany("Jobs") + .HasForeignKey("JobStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "OriginalJob") + .WithMany() + .HasForeignKey("OriginalJobId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.OvenCost", "OvenCost") + .WithMany("Jobs") + .HasForeignKey("OvenCostId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithOne("ConvertedToJob") + .HasForeignKey("PowderCoating.Core.Entities.Job", "QuoteId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("AssignedUser"); + + b.Navigation("Customer"); + + b.Navigation("IntakeCheckedBy"); + + b.Navigation("JobPriority"); + + b.Navigation("JobStatus"); + + b.Navigation("OriginalJob"); + + b.Navigation("OvenCost"); + + b.Navigation("Quote"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobChangeHistory", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "ChangedBy") + .WithMany() + .HasForeignKey("ChangedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChangedBy"); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobDailyPriority", b => + { + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobItem", b => + { + b.HasOne("PowderCoating.Core.Entities.AiItemPrediction", "AiPrediction") + .WithMany() + .HasForeignKey("AiPredictionId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.CatalogItem", "CatalogItem") + .WithMany() + .HasForeignKey("CatalogItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.CustomItemTemplate", "CustomItemTemplate") + .WithMany() + .HasForeignKey("CustomItemTemplateId"); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany("JobItems") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AiPrediction"); + + b.Navigation("CatalogItem"); + + b.Navigation("CustomItemTemplate"); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobItemCoat", b => + { + b.HasOne("PowderCoating.Core.Entities.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId"); + + b.HasOne("PowderCoating.Core.Entities.JobItem", "JobItem") + .WithMany("Coats") + .HasForeignKey("JobItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId"); + + b.Navigation("InventoryItem"); + + b.Navigation("JobItem"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobItemPrepService", b => + { + b.HasOne("PowderCoating.Core.Entities.CompanyBlastSetup", "BlastSetup") + .WithMany() + .HasForeignKey("BlastSetupId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobItem", "JobItem") + .WithMany("PrepServices") + .HasForeignKey("JobItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.PrepService", "PrepService") + .WithMany() + .HasForeignKey("PrepServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BlastSetup"); + + b.Navigation("JobItem"); + + b.Navigation("PrepService"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobNote", b => + { + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany("Notes") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobPhoto", b => + { + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany("Photos") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "UploadedBy") + .WithMany() + .HasForeignKey("UploadedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("UploadedBy"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobPrepService", b => + { + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany("JobPrepServices") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.PrepService", "PrepService") + .WithMany() + .HasForeignKey("PrepServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("PrepService"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobPriorityLookup", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobStatusHistory", b => + { + b.HasOne("PowderCoating.Core.Entities.JobStatusLookup", "FromStatus") + .WithMany("FromStatusHistory") + .HasForeignKey("FromStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany("StatusHistory") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobStatusLookup", "ToStatus") + .WithMany("ToStatusHistory") + .HasForeignKey("ToStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromStatus"); + + b.Navigation("Job"); + + b.Navigation("ToStatus"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobStatusLookup", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplate", b => + { + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplateItem", b => + { + b.HasOne("PowderCoating.Core.Entities.CatalogItem", "CatalogItem") + .WithMany() + .HasForeignKey("CatalogItemId"); + + b.HasOne("PowderCoating.Core.Entities.JobTemplate", "JobTemplate") + .WithMany("Items") + .HasForeignKey("JobTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogItem"); + + b.Navigation("JobTemplate"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplateItemCoat", b => + { + b.HasOne("PowderCoating.Core.Entities.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId"); + + b.HasOne("PowderCoating.Core.Entities.JobTemplateItem", "JobTemplateItem") + .WithMany("Coats") + .HasForeignKey("JobTemplateItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId"); + + b.Navigation("InventoryItem"); + + b.Navigation("JobTemplateItem"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplateItemPrepService", b => + { + b.HasOne("PowderCoating.Core.Entities.JobTemplateItem", "JobTemplateItem") + .WithMany("PrepServices") + .HasForeignKey("JobTemplateItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.PrepService", "PrepService") + .WithMany() + .HasForeignKey("PrepServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobTemplateItem"); + + b.Navigation("PrepService"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTimeEntry", b => + { + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany("TimeEntries") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JournalEntry", b => + { + b.HasOne("PowderCoating.Core.Entities.JournalEntry", "ReversalOf") + .WithMany() + .HasForeignKey("ReversalOfId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ReversalOf"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JournalEntryLine", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JournalEntry", "JournalEntry") + .WithMany("Lines") + .HasForeignKey("JournalEntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("JournalEntry"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.KioskSession", b => + { + b.HasOne("PowderCoating.Core.Entities.Customer", "LinkedCustomer") + .WithMany() + .HasForeignKey("LinkedCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Job", "LinkedJob") + .WithMany() + .HasForeignKey("LinkedJobId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("LinkedCustomer"); + + b.Navigation("LinkedJob"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.MaintenanceRecord", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "AssignedUser") + .WithMany() + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Equipment", "Equipment") + .WithMany("MaintenanceRecords") + .HasForeignKey("EquipmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "PerformedBy") + .WithMany("PerformedMaintenances") + .HasForeignKey("PerformedById") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.MaintenanceRecord", "RecurrenceParent") + .WithMany() + .HasForeignKey("RecurrenceParentId"); + + b.Navigation("AssignedUser"); + + b.Navigation("Equipment"); + + b.Navigation("PerformedBy"); + + b.Navigation("RecurrenceParent"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.NotificationLog", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany("NotificationLogs") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "Invoice") + .WithMany() + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithMany() + .HasForeignKey("QuoteId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Customer"); + + b.Navigation("Invoice"); + + b.Navigation("Job"); + + b.Navigation("Quote"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.NotificationTemplate", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", "Company") + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenBatch", b => + { + b.HasOne("PowderCoating.Core.Entities.Equipment", "Equipment") + .WithMany("OvenBatches") + .HasForeignKey("EquipmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.OvenCost", "OvenCost") + .WithMany() + .HasForeignKey("OvenCostId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Equipment"); + + b.Navigation("OvenCost"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenBatchItem", b => + { + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobItemCoat", "JobItemCoat") + .WithMany() + .HasForeignKey("JobItemCoatId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobItem", "JobItem") + .WithMany() + .HasForeignKey("JobItemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.OvenBatch", "Batch") + .WithMany("Items") + .HasForeignKey("OvenBatchId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Job"); + + b.Navigation("JobItem"); + + b.Navigation("JobItemCoat"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenCost", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", "Company") + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Payment", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Account", "DepositAccount") + .WithMany() + .HasForeignKey("DepositAccountId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "RecordedBy") + .WithMany() + .HasForeignKey("RecordedById") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("DepositAccount"); + + b.Navigation("Invoice"); + + b.Navigation("RecordedBy"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PowderUsageLog", b => + { + b.HasOne("PowderCoating.Core.Entities.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.InventoryTransaction", "InventoryTransaction") + .WithMany() + .HasForeignKey("InventoryTransactionId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobItemCoat", "JobItemCoat") + .WithMany() + .HasForeignKey("JobItemCoatId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobItem", "JobItem") + .WithMany() + .HasForeignKey("JobItemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("InventoryItem"); + + b.Navigation("InventoryTransaction"); + + b.Navigation("Job"); + + b.Navigation("JobItem"); + + b.Navigation("JobItemCoat"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PricingTier", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany("PricingTiers") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PurchaseOrder", b => + { + b.HasOne("PowderCoating.Core.Entities.Bill", "Bill") + .WithMany() + .HasForeignKey("BillId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Bill"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PurchaseOrderItem", b => + { + b.HasOne("PowderCoating.Core.Entities.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Items") + .HasForeignKey("PurchaseOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InventoryItem"); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Quote", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany("Quotes") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Customer", "Customer") + .WithMany("Quotes") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.OvenCost", "OvenCost") + .WithMany("Quotes") + .HasForeignKey("OvenCostId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "PreparedBy") + .WithMany("PreparedQuotes") + .HasForeignKey("PreparedById") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.QuoteStatusLookup", "QuoteStatus") + .WithMany("Quotes") + .HasForeignKey("QuoteStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("OvenCost"); + + b.Navigation("PreparedBy"); + + b.Navigation("QuoteStatus"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteChangeHistory", b => + { + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "ChangedBy") + .WithMany() + .HasForeignKey("ChangedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithMany() + .HasForeignKey("QuoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChangedBy"); + + b.Navigation("Quote"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteItem", b => + { + b.HasOne("PowderCoating.Core.Entities.AiItemPrediction", "AiPrediction") + .WithMany() + .HasForeignKey("AiPredictionId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.CatalogItem", "CatalogItem") + .WithMany() + .HasForeignKey("CatalogItemId"); + + b.HasOne("PowderCoating.Core.Entities.CustomItemTemplate", "CustomItemTemplate") + .WithMany() + .HasForeignKey("CustomItemTemplateId"); + + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithMany("QuoteItems") + .HasForeignKey("QuoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AiPrediction"); + + b.Navigation("CatalogItem"); + + b.Navigation("CustomItemTemplate"); + + b.Navigation("Quote"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteItemCoat", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.QuoteItem", "QuoteItem") + .WithMany("Coats") + .HasForeignKey("QuoteItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId"); + + b.Navigation("InventoryItem"); + + b.Navigation("QuoteItem"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteItemPrepService", b => + { + b.HasOne("PowderCoating.Core.Entities.CompanyBlastSetup", "BlastSetup") + .WithMany() + .HasForeignKey("BlastSetupId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.PrepService", "PrepService") + .WithMany() + .HasForeignKey("PrepServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.QuoteItem", "QuoteItem") + .WithMany("PrepServices") + .HasForeignKey("QuoteItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlastSetup"); + + b.Navigation("PrepService"); + + b.Navigation("QuoteItem"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuotePhoto", b => + { + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithMany("QuotePhotos") + .HasForeignKey("QuoteId"); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "UploadedBy") + .WithMany() + .HasForeignKey("UploadedById"); + + b.Navigation("Quote"); + + b.Navigation("UploadedBy"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuotePrepService", b => + { + b.HasOne("PowderCoating.Core.Entities.PrepService", "PrepService") + .WithMany() + .HasForeignKey("PrepServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") + .WithMany("QuotePrepServices") + .HasForeignKey("QuoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PrepService"); + + b.Navigation("Quote"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteStatusLookup", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany() + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Refund", b => + { + b.HasOne("PowderCoating.Core.Entities.CreditMemo", "CreditMemo") + .WithMany() + .HasForeignKey("CreditMemoId"); + + b.HasOne("PowderCoating.Core.Entities.Invoice", "Invoice") + .WithMany("Refunds") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.ApplicationUser", "IssuedBy") + .WithMany() + .HasForeignKey("IssuedById"); + + b.HasOne("PowderCoating.Core.Entities.Payment", "Payment") + .WithMany() + .HasForeignKey("PaymentId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("CreditMemo"); + + b.Navigation("Invoice"); + + b.Navigation("IssuedBy"); + + b.Navigation("Payment"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ReworkRecord", b => + { + b.HasOne("PowderCoating.Core.Entities.Job", "Job") + .WithMany("ReworkRecords") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.JobItem", "JobItem") + .WithMany() + .HasForeignKey("JobItemId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PowderCoating.Core.Entities.Job", "ReworkJob") + .WithMany() + .HasForeignKey("ReworkJobId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Job"); + + b.Navigation("JobItem"); + + b.Navigation("ReworkJob"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Vendor", b => + { + b.HasOne("PowderCoating.Core.Entities.Company", null) + .WithMany("Vendors") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Account", "DefaultExpenseAccount") + .WithMany() + .HasForeignKey("DefaultExpenseAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("DefaultExpenseAccount"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.VendorCredit", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "APAccount") + .WithMany() + .HasForeignKey("APAccountId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("APAccount"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.VendorCreditApplication", b => + { + b.HasOne("PowderCoating.Core.Entities.Bill", "Bill") + .WithMany() + .HasForeignKey("BillId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.VendorCredit", "VendorCredit") + .WithMany("Applications") + .HasForeignKey("VendorCreditId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Bill"); + + b.Navigation("VendorCredit"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.VendorCreditLineItem", b => + { + b.HasOne("PowderCoating.Core.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PowderCoating.Core.Entities.VendorCredit", "VendorCredit") + .WithMany("LineItems") + .HasForeignKey("VendorCreditId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("VendorCredit"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.YearEndClose", b => + { + b.HasOne("PowderCoating.Core.Entities.JournalEntry", "JournalEntry") + .WithMany() + .HasForeignKey("JournalEntryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("JournalEntry"); + }); + + modelBuilder.Entity("VendorInventoryCategories", b => + { + b.HasOne("PowderCoating.Core.Entities.InventoryCategoryLookup", null) + .WithMany() + .HasForeignKey("InventoryCategoryLookupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PowderCoating.Core.Entities.Vendor", null) + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Account", b => + { + b.Navigation("BillLineItems"); + + b.Navigation("BillPayments"); + + b.Navigation("Bills"); + + b.Navigation("ExpensePaymentAccounts"); + + b.Navigation("Expenses"); + + b.Navigation("SubAccounts"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Announcement", b => + { + b.Navigation("Dismissals"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.ApplicationUser", b => + { + b.Navigation("PerformedMaintenances"); + + b.Navigation("PreparedQuotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AppointmentStatusLookup", b => + { + b.Navigation("Appointments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.AppointmentTypeLookup", b => + { + b.Navigation("Appointments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Bill", b => + { + b.Navigation("LineItems"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Budget", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.BugReport", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CatalogCategory", b => + { + b.Navigation("Items"); + + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Company", b => + { + b.Navigation("Customers"); + + b.Navigation("Equipment"); + + b.Navigation("InventoryItems"); + + b.Navigation("Jobs"); + + b.Navigation("OperatingCosts"); + + b.Navigation("Preferences"); + + b.Navigation("PricingTiers"); + + b.Navigation("Quotes"); + + b.Navigation("Users"); + + b.Navigation("Vendors"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.CreditMemo", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Customer", b => + { + b.Navigation("CustomerNotes"); + + b.Navigation("Invoices"); + + b.Navigation("Jobs"); + + b.Navigation("NotificationLogs"); + + b.Navigation("Quotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Equipment", b => + { + b.Navigation("MaintenanceRecords"); + + b.Navigation("OvenBatches"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.FixedAsset", b => + { + b.Navigation("DepreciationEntries"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.GiftCertificate", b => + { + b.Navigation("Redemptions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryCategoryLookup", b => + { + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.InventoryItem", b => + { + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Invoice", b => + { + b.Navigation("CreditApplications"); + + b.Navigation("GiftCertificateRedemptions"); + + b.Navigation("InvoiceItems"); + + b.Navigation("Payments"); + + b.Navigation("Refunds"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Job", b => + { + b.Navigation("Invoice"); + + b.Navigation("JobItems"); + + b.Navigation("JobPrepServices"); + + b.Navigation("Notes"); + + b.Navigation("Photos"); + + b.Navigation("ReworkRecords"); + + b.Navigation("StatusHistory"); + + b.Navigation("TimeEntries"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobItem", b => + { + b.Navigation("Coats"); + + b.Navigation("PrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobPriorityLookup", b => + { + b.Navigation("Jobs"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobStatusLookup", b => + { + b.Navigation("FromStatusHistory"); + + b.Navigation("Jobs"); + + b.Navigation("ToStatusHistory"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplate", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JobTemplateItem", b => + { + b.Navigation("Coats"); + + b.Navigation("PrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.JournalEntry", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenBatch", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.OvenCost", b => + { + b.Navigation("Jobs"); + + b.Navigation("Quotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PricingTier", b => + { + b.Navigation("Customers"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.PurchaseOrder", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Quote", b => + { + b.Navigation("ConvertedToJob"); + + b.Navigation("QuoteItems"); + + b.Navigation("QuotePhotos"); + + b.Navigation("QuotePrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteItem", b => + { + b.Navigation("Coats"); + + b.Navigation("PrepServices"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.QuoteStatusLookup", b => + { + b.Navigation("Quotes"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.Vendor", b => + { + b.Navigation("BillPayments"); + + b.Navigation("Bills"); + + b.Navigation("Expenses"); + + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("PowderCoating.Core.Entities.VendorCredit", b => + { + b.Navigation("Applications"); + + b.Navigation("LineItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/PowderCoating.Infrastructure/Migrations/20260523153055_AddCustomItemTemplates.cs b/src/PowderCoating.Infrastructure/Migrations/20260523153055_AddCustomItemTemplates.cs new file mode 100644 index 0000000..0871244 --- /dev/null +++ b/src/PowderCoating.Infrastructure/Migrations/20260523153055_AddCustomItemTemplates.cs @@ -0,0 +1,197 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PowderCoating.Infrastructure.Migrations +{ + /// + public partial class AddCustomItemTemplates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CustomItemTemplateId", + table: "QuoteItems", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "FormulaFieldValuesJson", + table: "QuoteItems", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "IsCustomFormulaItem", + table: "QuoteItems", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "CustomItemTemplateId", + table: "JobItems", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "FormulaFieldValuesJson", + table: "JobItems", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "IsCustomFormulaItem", + table: "JobItems", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "CustomItemTemplates", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + OutputMode = table.Column(type: "nvarchar(max)", nullable: false), + FieldsJson = table.Column(type: "nvarchar(max)", nullable: false), + Formula = table.Column(type: "nvarchar(max)", nullable: false), + DefaultRate = table.Column(type: "decimal(18,2)", nullable: true), + RateLabel = table.Column(type: "nvarchar(max)", nullable: true), + Notes = table.Column(type: "nvarchar(max)", nullable: true), + DisplayOrder = table.Column(type: "int", nullable: false), + IsActive = table.Column(type: "bit", nullable: false), + DiagramImagePath = table.Column(type: "nvarchar(max)", nullable: true), + CompanyId = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false), + DeletedAt = table.Column(type: "datetime2", nullable: true), + DeletedBy = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomItemTemplates", x => x.Id); + }); + + migrationBuilder.UpdateData( + table: "PricingTiers", + keyColumn: "Id", + keyValue: 1, + column: "CreatedAt", + value: new DateTime(2026, 5, 23, 15, 30, 51, 760, DateTimeKind.Utc).AddTicks(9869)); + + migrationBuilder.UpdateData( + table: "PricingTiers", + keyColumn: "Id", + keyValue: 2, + column: "CreatedAt", + value: new DateTime(2026, 5, 23, 15, 30, 51, 760, DateTimeKind.Utc).AddTicks(9876)); + + migrationBuilder.UpdateData( + table: "PricingTiers", + keyColumn: "Id", + keyValue: 3, + column: "CreatedAt", + value: new DateTime(2026, 5, 23, 15, 30, 51, 760, DateTimeKind.Utc).AddTicks(9878)); + + migrationBuilder.CreateIndex( + name: "IX_QuoteItems_CustomItemTemplateId", + table: "QuoteItems", + column: "CustomItemTemplateId"); + + migrationBuilder.CreateIndex( + name: "IX_JobItems_CustomItemTemplateId", + table: "JobItems", + column: "CustomItemTemplateId"); + + migrationBuilder.AddForeignKey( + name: "FK_JobItems_CustomItemTemplates_CustomItemTemplateId", + table: "JobItems", + column: "CustomItemTemplateId", + principalTable: "CustomItemTemplates", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_QuoteItems_CustomItemTemplates_CustomItemTemplateId", + table: "QuoteItems", + column: "CustomItemTemplateId", + principalTable: "CustomItemTemplates", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_JobItems_CustomItemTemplates_CustomItemTemplateId", + table: "JobItems"); + + migrationBuilder.DropForeignKey( + name: "FK_QuoteItems_CustomItemTemplates_CustomItemTemplateId", + table: "QuoteItems"); + + migrationBuilder.DropTable( + name: "CustomItemTemplates"); + + migrationBuilder.DropIndex( + name: "IX_QuoteItems_CustomItemTemplateId", + table: "QuoteItems"); + + migrationBuilder.DropIndex( + name: "IX_JobItems_CustomItemTemplateId", + table: "JobItems"); + + migrationBuilder.DropColumn( + name: "CustomItemTemplateId", + table: "QuoteItems"); + + migrationBuilder.DropColumn( + name: "FormulaFieldValuesJson", + table: "QuoteItems"); + + migrationBuilder.DropColumn( + name: "IsCustomFormulaItem", + table: "QuoteItems"); + + migrationBuilder.DropColumn( + name: "CustomItemTemplateId", + table: "JobItems"); + + migrationBuilder.DropColumn( + name: "FormulaFieldValuesJson", + table: "JobItems"); + + migrationBuilder.DropColumn( + name: "IsCustomFormulaItem", + table: "JobItems"); + + migrationBuilder.UpdateData( + table: "PricingTiers", + keyColumn: "Id", + keyValue: 1, + column: "CreatedAt", + value: new DateTime(2026, 5, 23, 13, 51, 6, 293, DateTimeKind.Utc).AddTicks(4300)); + + migrationBuilder.UpdateData( + table: "PricingTiers", + keyColumn: "Id", + keyValue: 2, + column: "CreatedAt", + value: new DateTime(2026, 5, 23, 13, 51, 6, 293, DateTimeKind.Utc).AddTicks(4313)); + + migrationBuilder.UpdateData( + table: "PricingTiers", + keyColumn: "Id", + keyValue: 3, + column: "CreatedAt", + value: new DateTime(2026, 5, 23, 13, 51, 6, 293, DateTimeKind.Utc).AddTicks(4315)); + } + } +} diff --git a/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index 7f5ec37..c173ee8 100644 --- a/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -2650,6 +2650,80 @@ namespace PowderCoating.Infrastructure.Migrations b.ToTable("CreditMemoApplications"); }); + modelBuilder.Entity("PowderCoating.Core.Entities.CustomItemTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DefaultRate") + .HasColumnType("decimal(18,2)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DiagramImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("FieldsJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Formula") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OutputMode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RateLabel") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CustomItemTemplates"); + }); + modelBuilder.Entity("PowderCoating.Core.Entities.Customer", b => { b.Property("Id") @@ -4473,6 +4547,9 @@ namespace PowderCoating.Infrastructure.Migrations b.Property("CreatedBy") .HasColumnType("nvarchar(max)"); + b.Property("CustomItemTemplateId") + .HasColumnType("int"); + b.Property("DeletedAt") .HasColumnType("datetime2"); @@ -4489,12 +4566,18 @@ namespace PowderCoating.Infrastructure.Migrations b.Property("Finish") .HasColumnType("nvarchar(max)"); + b.Property("FormulaFieldValuesJson") + .HasColumnType("nvarchar(max)"); + b.Property("IncludePrepCost") .HasColumnType("bit"); b.Property("IsAiItem") .HasColumnType("bit"); + b.Property("IsCustomFormulaItem") + .HasColumnType("bit"); + b.Property("IsDeleted") .HasColumnType("bit"); @@ -4558,6 +4641,8 @@ namespace PowderCoating.Infrastructure.Migrations b.HasIndex("CatalogItemId"); + b.HasIndex("CustomItemTemplateId"); + b.HasIndex("JobId") .HasDatabaseName("IX_JobItems_JobId"); @@ -7260,6 +7345,9 @@ namespace PowderCoating.Infrastructure.Migrations b.Property("CreatedBy") .HasColumnType("nvarchar(max)"); + b.Property("CustomItemTemplateId") + .HasColumnType("int"); + b.Property("DeletedAt") .HasColumnType("datetime2"); @@ -7273,12 +7361,18 @@ namespace PowderCoating.Infrastructure.Migrations b.Property("EstimatedMinutes") .HasColumnType("int"); + b.Property("FormulaFieldValuesJson") + .HasColumnType("nvarchar(max)"); + b.Property("IncludePrepCost") .HasColumnType("bit"); b.Property("IsAiItem") .HasColumnType("bit"); + b.Property("IsCustomFormulaItem") + .HasColumnType("bit"); + b.Property("IsDeleted") .HasColumnType("bit"); @@ -7348,6 +7442,8 @@ namespace PowderCoating.Infrastructure.Migrations b.HasIndex("CatalogItemId"); + b.HasIndex("CustomItemTemplateId"); + b.HasIndex("QuoteId") .HasDatabaseName("IX_QuoteItems_QuoteId"); @@ -9512,6 +9608,10 @@ namespace PowderCoating.Infrastructure.Migrations .HasForeignKey("CatalogItemId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("PowderCoating.Core.Entities.CustomItemTemplate", "CustomItemTemplate") + .WithMany() + .HasForeignKey("CustomItemTemplateId"); + b.HasOne("PowderCoating.Core.Entities.Job", "Job") .WithMany("JobItems") .HasForeignKey("JobId") @@ -9522,6 +9622,8 @@ namespace PowderCoating.Infrastructure.Migrations b.Navigation("CatalogItem"); + b.Navigation("CustomItemTemplate"); + b.Navigation("Job"); }); @@ -10131,6 +10233,10 @@ namespace PowderCoating.Infrastructure.Migrations .WithMany() .HasForeignKey("CatalogItemId"); + b.HasOne("PowderCoating.Core.Entities.CustomItemTemplate", "CustomItemTemplate") + .WithMany() + .HasForeignKey("CustomItemTemplateId"); + b.HasOne("PowderCoating.Core.Entities.Quote", "Quote") .WithMany("QuoteItems") .HasForeignKey("QuoteId") @@ -10141,6 +10247,8 @@ namespace PowderCoating.Infrastructure.Migrations b.Navigation("CatalogItem"); + b.Navigation("CustomItemTemplate"); + b.Navigation("Quote"); }); diff --git a/src/PowderCoating.Infrastructure/PowderCoating.Infrastructure.csproj b/src/PowderCoating.Infrastructure/PowderCoating.Infrastructure.csproj index 84cc2aa..af0268b 100644 --- a/src/PowderCoating.Infrastructure/PowderCoating.Infrastructure.csproj +++ b/src/PowderCoating.Infrastructure/PowderCoating.Infrastructure.csproj @@ -21,6 +21,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/PowderCoating.Infrastructure/Repositories/UnitOfWork.cs b/src/PowderCoating.Infrastructure/Repositories/UnitOfWork.cs index ab83489..5528cff 100644 --- a/src/PowderCoating.Infrastructure/Repositories/UnitOfWork.cs +++ b/src/PowderCoating.Infrastructure/Repositories/UnitOfWork.cs @@ -123,6 +123,9 @@ public class UnitOfWork : IUnitOfWork // Customer Intake Kiosk private IRepository? _kioskSessions; + // Custom Formula Templates + private IRepository? _customItemTemplates; + // Purchase Orders private IPurchaseOrderRepository? _purchaseOrders; private IRepository? _purchaseOrderItems; @@ -457,6 +460,10 @@ public class UnitOfWork : IUnitOfWork public IRepository KioskSessions => _kioskSessions ??= new Repository(_context); + /// Repository for per-company reusable NCalc pricing formula templates; tenant-filtered with soft delete. + public IRepository CustomItemTemplates => + _customItemTemplates ??= new Repository(_context); + // Job Templates /// Repository for reusable job blueprints; tenant-filtered with soft delete. public IJobTemplateRepository JobTemplates => diff --git a/src/PowderCoating.Infrastructure/Services/CustomFormulaAiService.cs b/src/PowderCoating.Infrastructure/Services/CustomFormulaAiService.cs new file mode 100644 index 0000000..0420d95 --- /dev/null +++ b/src/PowderCoating.Infrastructure/Services/CustomFormulaAiService.cs @@ -0,0 +1,197 @@ +using System.Text; +using System.Text.Json; +using NCalc2; +using Anthropic.SDK; +using Anthropic.SDK.Messaging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using PowderCoating.Application.DTOs.Company; +using PowderCoating.Application.Interfaces; + +namespace PowderCoating.Infrastructure.Services; + +/// +/// Generates NCalc pricing formula templates from natural-language descriptions using +/// Claude Sonnet. Accepts an optional diagram image so the model can see the physical +/// shape being estimated. The model returns a structured JSON object containing the +/// field list, NCalc expression, output mode, and verification inputs; the service +/// parses and returns it as a . +/// +public class CustomFormulaAiService : ICustomFormulaAiService +{ + private readonly IConfiguration _config; + private readonly ILogger _logger; + + private const string SystemPrompt = @"You are an expert pricing formula engineer for a powder coating business. +Your job is to generate NCalc expressions that calculate either a fixed price or a surface area +from user-supplied field values. NCalc supports standard math operators (+, -, *, /, %, Pow()), +comparison operators, and the Abs(), Round(), Max(), Min() built-in functions. + +The user will describe a custom fabricated item (e.g., 'Roof curb', 'Electrical enclosure', +'Tubular frame') and you must produce a pricing formula template. + +Respond ONLY with a valid JSON object matching this exact schema — no markdown, no explanation: + +{ + ""name"": ""string — short template name (e.g. 'Roof Curb', 'Electrical Enclosure')"", + ""outputMode"": ""FixedRate"" | ""SurfaceAreaSqFt"", + ""fields"": [ + { + ""name"": ""snake_case_variable_name"", + ""label"": ""Human-readable label"", + ""unit"": ""in / ft / mm / cm / qty / lbs — or empty string"", + ""defaultValue"": number + } + ], + ""formula"": ""NCalc expression using field name variables and optionally 'rate'"", + ""defaultRate"": number or null, + ""rateLabel"": ""string label for the rate field, e.g. '$/sq ft' — null if no rate"", + ""reasoning"": ""1-2 sentences explaining how the formula was derived"", + ""verificationInputs"": { ""variable_name"": number }, + ""verificationResult"": number +} + +Rules: +- Use FixedRate when the formula directly calculates a dollar amount (e.g. surface area × rate per sqft) +- Use SurfaceAreaSqFt when the formula calculates square footage and the standard pricing engine handles the rest +- Always include a 'rate' variable when outputMode is FixedRate and the price scales with dimensions +- Field names must be valid NCalc identifiers (letters, digits, underscores; no spaces or hyphens) +- verificationInputs and verificationResult must use the exact field names and formula you generated +- Surface area formulas for box shapes: 2*(L*W + L*H + W*H) where L/W/H are in the same unit; convert to sqft if needed +- For inch inputs convert to sqft: divide by 144 (sqin→sqft) or use /12 per side before multiplying +"; + + public CustomFormulaAiService(IConfiguration config, ILogger logger) + { + _config = config; + _logger = logger; + } + + /// + public async Task GenerateFormulaAsync( + GenerateFormulaFromAiRequest request, + byte[]? imageBytes = null, + string? imageContentType = null) + { + var apiKey = _config["AI:Anthropic:ApiKey"]; + if (string.IsNullOrWhiteSpace(apiKey) || apiKey.StartsWith("your-")) + { + return new GenerateFormulaFromAiResponse + { + Success = false, + Error = "Anthropic API key is not configured. Add AI:Anthropic:ApiKey to appsettings.json." + }; + } + + try + { + var client = new AnthropicClient(apiKey); + + var userContent = new List(); + + if (imageBytes is { Length: > 0 } && !string.IsNullOrWhiteSpace(imageContentType)) + { + userContent.Add(new ImageContent + { + Source = new ImageSource + { + MediaType = imageContentType, + Data = Convert.ToBase64String(imageBytes) + } + }); + } + + userContent.Add(new TextContent { Text = request.Description }); + + var messages = new List + { + new() { Role = RoleType.User, Content = userContent } + }; + + var response = await client.Messages.GetClaudeMessageAsync(new MessageParameters + { + Model = "claude-sonnet-4-6", + MaxTokens = 1024, + SystemMessage = SystemPrompt, + Messages = messages + }); + + var rawJson = response.Message.ToString().Trim(); + + // Strip markdown code fences if the model adds them + if (rawJson.StartsWith("```")) + { + var start = rawJson.IndexOf('\n') + 1; + var end = rawJson.LastIndexOf("```"); + if (end > start) rawJson = rawJson[start..end].Trim(); + } + + using var doc = JsonDocument.Parse(rawJson); + var root = doc.RootElement; + + var fieldsJson = root.TryGetProperty("fields", out var fieldsEl) + ? fieldsEl.GetRawText() + : "[]"; + + decimal? defaultRate = null; + if (root.TryGetProperty("defaultRate", out var rateEl) && rateEl.ValueKind == JsonValueKind.Number) + defaultRate = rateEl.GetDecimal(); + + decimal? verificationResult = null; + if (root.TryGetProperty("verificationResult", out var vrEl) && vrEl.ValueKind == JsonValueKind.Number) + verificationResult = vrEl.GetDecimal(); + + string? verificationInputs = null; + if (root.TryGetProperty("verificationInputs", out var viEl)) + verificationInputs = viEl.GetRawText(); + + return new GenerateFormulaFromAiResponse + { + Success = true, + Name = root.TryGetProperty("name", out var nameEl) ? nameEl.GetString() : null, + OutputMode = root.TryGetProperty("outputMode", out var omEl) ? omEl.GetString() : "FixedRate", + FieldsJson = fieldsJson, + Formula = root.TryGetProperty("formula", out var fEl) ? fEl.GetString() : null, + DefaultRate = defaultRate, + RateLabel = root.TryGetProperty("rateLabel", out var rlEl) ? rlEl.GetString() : null, + Reasoning = root.TryGetProperty("reasoning", out var reEl) ? reEl.GetString() : null, + VerificationResult = verificationResult, + VerificationInputs = verificationInputs + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "CustomFormulaAiService.GenerateFormulaAsync failed"); + return new GenerateFormulaFromAiResponse { Success = false, Error = ex.Message }; + } + } + + /// + public EvaluateFormulaResponse EvaluateFormula(EvaluateFormulaRequest request) + { + if (string.IsNullOrWhiteSpace(request?.Formula)) + return new EvaluateFormulaResponse { Success = false, Error = "Formula is required." }; + + try + { + var variables = JsonSerializer.Deserialize>( + request.VariablesJson ?? "{}") ?? new(); + + var expr = new Expression(request.Formula); + foreach (var kv in variables) + { + expr.Parameters[kv.Key] = kv.Value.ValueKind == JsonValueKind.Number + ? (object)kv.Value.GetDecimal() + : (object)(kv.Value.GetString() ?? ""); + } + + var result = expr.Evaluate(); + var decResult = Convert.ToDecimal(result); + return new EvaluateFormulaResponse { Success = true, Result = Math.Round(decResult, 4) }; + } + catch (Exception ex) + { + return new EvaluateFormulaResponse { Success = false, Error = ex.Message }; + } + } +} diff --git a/src/PowderCoating.Web/Controllers/CompanySettingsController.cs b/src/PowderCoating.Web/Controllers/CompanySettingsController.cs index 8665469..58b245b 100644 --- a/src/PowderCoating.Web/Controllers/CompanySettingsController.cs +++ b/src/PowderCoating.Web/Controllers/CompanySettingsController.cs @@ -33,6 +33,8 @@ public class CompanySettingsController : Controller private readonly IAuditLogService _auditLog; private readonly UserManager _userManager; private readonly SignInManager _signInManager; + private readonly IAzureBlobStorageService _blobStorage; + private readonly ICustomFormulaAiService _formulaAiService; public CompanySettingsController( IUnitOfWork unitOfWork, @@ -45,7 +47,9 @@ public class CompanySettingsController : Controller IConfiguration configuration, IAuditLogService auditLog, UserManager userManager, - SignInManager signInManager) + SignInManager signInManager, + IAzureBlobStorageService blobStorage, + ICustomFormulaAiService formulaAiService) { _unitOfWork = unitOfWork; _mapper = mapper; @@ -58,6 +62,8 @@ public class CompanySettingsController : Controller _auditLog = auditLog; _userManager = userManager; _signInManager = signInManager; + _blobStorage = blobStorage; + _formulaAiService = formulaAiService; } /// @@ -2962,6 +2968,218 @@ public class CompanySettingsController : Controller return RedirectToAction(nameof(DeleteAccount)); } } + + // ─── Custom Formula Item Templates ────────────────────────────────────────── + + /// Returns all active + inactive formula templates for the current company. + [HttpGet] + public async Task GetCustomItemTemplate(int id) + { + var companyId = _tenantContext.GetCurrentCompanyId()!.Value; + var entity = await _unitOfWork.CustomItemTemplates.GetByIdAsync(id); + if (entity == null || entity.CompanyId != companyId) + return Json(new { success = false, message = "Template not found." }); + var dto = _mapper.Map(entity); + return Json(new { success = true, template = dto }); + } + + /// Returns all active + inactive formula templates for the current company. + [HttpGet] + public async Task GetCustomItemTemplates() + { + var companyId = _tenantContext.GetCurrentCompanyId()!.Value; + var templates = await _unitOfWork.CustomItemTemplates.FindAsync( + t => t.CompanyId == companyId); + var dtos = _mapper.Map>(templates.OrderBy(t => t.DisplayOrder).ThenBy(t => t.Name)); + return Json(new { success = true, templates = dtos }); + } + + /// Creates a new formula template for the current company. + [HttpPost] + public async Task CreateCustomItemTemplate([FromBody] CreateCustomItemTemplateDto dto) + { + if (!ModelState.IsValid) + return Json(new { success = false, message = "Invalid data." }); + + var fieldError = ValidateTemplateFields(dto.FieldsJson); + if (fieldError != null) return Json(new { success = false, message = fieldError }); + + var companyId = _tenantContext.GetCurrentCompanyId()!.Value; + var entity = _mapper.Map(dto); + entity.CompanyId = companyId; + entity.CreatedAt = DateTime.UtcNow; + + await _unitOfWork.CustomItemTemplates.AddAsync(entity); + await _unitOfWork.CompleteAsync(); + + return Json(new { success = true, id = entity.Id }); + } + + /// Updates an existing formula template owned by the current company. + [HttpPost] + public async Task UpdateCustomItemTemplate([FromBody] UpdateCustomItemTemplateDto dto) + { + if (!ModelState.IsValid) + return Json(new { success = false, message = "Invalid data." }); + + var fieldError = ValidateTemplateFields(dto.FieldsJson); + if (fieldError != null) return Json(new { success = false, message = fieldError }); + + var companyId = _tenantContext.GetCurrentCompanyId()!.Value; + var entity = await _unitOfWork.CustomItemTemplates.GetByIdAsync(dto.Id); + if (entity == null || entity.CompanyId != companyId) + return Json(new { success = false, message = "Template not found." }); + + _mapper.Map(dto, entity); + entity.UpdatedAt = DateTime.UtcNow; + await _unitOfWork.CompleteAsync(); + + return Json(new { success = true }); + } + + /// Soft-deletes a formula template owned by the current company. + [HttpPost] + public async Task DeleteCustomItemTemplate(int id) + { + var companyId = _tenantContext.GetCurrentCompanyId()!.Value; + var entity = await _unitOfWork.CustomItemTemplates.GetByIdAsync(id); + if (entity == null || entity.CompanyId != companyId) + return Json(new { success = false, message = "Template not found." }); + + await _unitOfWork.CustomItemTemplates.SoftDeleteAsync(id); + await _unitOfWork.CompleteAsync(); + return Json(new { success = true }); + } + + /// + /// Uploads a diagram image for a template to blob storage container + /// formulatemplate-diagrams/{companyId}/{templateId}/diagram.{ext}. + /// Returns the blob path for storage on the entity. + /// + [HttpPost] + public async Task UploadTemplateDiagram(int templateId, IFormFile diagramFile) + { + var companyId = _tenantContext.GetCurrentCompanyId()!.Value; + var entity = await _unitOfWork.CustomItemTemplates.GetByIdAsync(templateId); + if (entity == null || entity.CompanyId != companyId) + return Json(new { success = false, message = "Template not found." }); + + var allowedTypes = new[] { "image/jpeg", "image/png", "image/gif", "image/webp" }; + if (!allowedTypes.Contains(diagramFile.ContentType.ToLowerInvariant())) + return Json(new { success = false, message = "Only JPEG, PNG, GIF, or WebP images are allowed." }); + + if (diagramFile.Length > 10 * 1024 * 1024) + return Json(new { success = false, message = "Image must be under 10 MB." }); + + var ext = Path.GetExtension(diagramFile.FileName).ToLowerInvariant().TrimStart('.'); + var blobPath = $"{companyId}/{templateId}/diagram.{ext}"; + + using var stream = diagramFile.OpenReadStream(); + var (ok, err) = await _blobStorage.UploadAsync("formulatemplate-diagrams", blobPath, stream, diagramFile.ContentType); + if (!ok) + return Json(new { success = false, message = err }); + + entity.DiagramImagePath = blobPath; + entity.UpdatedAt = DateTime.UtcNow; + await _unitOfWork.CompleteAsync(); + + return Json(new { success = true, diagramImagePath = blobPath }); + } + + /// + /// Serves a template diagram image from blob storage. The path is tenant-scoped + /// so cross-company access is prevented by checking CompanyId on the template. + /// + [HttpGet] + public async Task TemplateDiagram(int templateId) + { + var companyId = _tenantContext.GetCurrentCompanyId()!.Value; + var entity = await _unitOfWork.CustomItemTemplates.GetByIdAsync(templateId); + if (entity == null || entity.CompanyId != companyId || string.IsNullOrEmpty(entity.DiagramImagePath)) + return NotFound(); + + var (ok, bytes, contentType, _) = await _blobStorage.DownloadAsync("formulatemplate-diagrams", entity.DiagramImagePath); + if (!ok || bytes == null || bytes.Length == 0) return NotFound(); + return File(bytes, contentType ?? "image/jpeg"); + } + + /// + /// Evaluates a NCalc formula with the supplied variable values. + /// Delegates to so NCalc stays + /// in the Application/Infrastructure layer. + /// + [HttpPost] + public IActionResult EvaluateFormula([FromBody] EvaluateFormulaRequest req) + { + var result = _formulaAiService.EvaluateFormula(req); + return Json(result); + } + + /// + /// Calls Claude to generate a formula template from a natural-language description + /// and an optional diagram image uploaded in the same multipart form. + /// + [HttpPost] + public async Task GenerateFormulaFromAi([FromForm] string description, IFormFile? diagramImage) + { + if (string.IsNullOrWhiteSpace(description)) + return Json(new { success = false, error = "Description is required." }); + + byte[]? imageBytes = null; + string? imageContentType = null; + if (diagramImage is { Length: > 0 }) + { + using var ms = new MemoryStream(); + await diagramImage.CopyToAsync(ms); + imageBytes = ms.ToArray(); + imageContentType = diagramImage.ContentType; + } + + var result = await _formulaAiService.GenerateFormulaAsync( + new GenerateFormulaFromAiRequest { Description = description }, + imageBytes, + imageContentType); + + return Json(result); + } + + /// + /// Validates field variable names in a fieldsJson array against NCalc identifier rules: + /// must start with a letter, contain only letters/digits/underscores, and not use the + /// reserved name "rate" (which is auto-populated from the template's Default Rate). + /// Returns an error message string on failure, or null if all names are valid. + /// + private static string? ValidateTemplateFields(string? fieldsJson) + { + if (string.IsNullOrWhiteSpace(fieldsJson)) return null; + + List? fields; + try + { + fields = System.Text.Json.JsonSerializer.Deserialize>(fieldsJson); + } + catch { return "Invalid fields JSON."; } + + if (fields == null) return null; + + var nameRegex = new System.Text.RegularExpressions.Regex(@"^[a-zA-Z][a-zA-Z0-9_]*$"); + var seen = new HashSet(StringComparer.Ordinal); + + foreach (var field in fields) + { + var name = field.TryGetProperty("name", out var nameProp) ? nameProp.GetString() ?? "" : ""; + if (string.IsNullOrEmpty(name)) + return "All fields must have a variable name."; + if (name == "rate") + return $"\"rate\" is a reserved variable name — it is pre-populated from the template's Default Rate."; + if (!nameRegex.IsMatch(name)) + return $"Invalid field name \"{name}\": must start with a letter and contain only letters, digits, or underscores."; + if (!seen.Add(name)) + return $"Duplicate field name \"{name}\"."; + } + + return null; + } } public record SaveTemplateJsonRequest(int Id, string? Subject, string? Body); diff --git a/src/PowderCoating.Web/Controllers/HelpController.cs b/src/PowderCoating.Web/Controllers/HelpController.cs index 3e02477..508b095 100644 --- a/src/PowderCoating.Web/Controllers/HelpController.cs +++ b/src/PowderCoating.Web/Controllers/HelpController.cs @@ -125,5 +125,14 @@ namespace PowderCoating.Web.Controllers { return View(); } + + /// + /// Serves the Custom Formula Item Templates help article explaining how to create NCalc formula + /// templates, use the AI generator, and add formula items to quotes and jobs. + /// + public IActionResult CustomFormulaTemplates() + { + return View(); + } } } diff --git a/src/PowderCoating.Web/Controllers/JobsController.cs b/src/PowderCoating.Web/Controllers/JobsController.cs index c538a70..aba9548 100644 --- a/src/PowderCoating.Web/Controllers/JobsController.cs +++ b/src/PowderCoating.Web/Controllers/JobsController.cs @@ -489,9 +489,12 @@ public class JobsController : Controller manualUnitPrice = ji.ManualUnitPrice ?? (ji.IsGenericItem || ji.IsSalesItem ? ji.UnitPrice : (decimal?)null), powderCostOverride = ji.PowderCostOverride, isGenericItem = ji.IsGenericItem || (!ji.CatalogItemId.HasValue && !ji.Coats.Any() && !ji.IsSalesItem), - isLaborItem = ji.IsLaborItem, - isSalesItem = ji.IsSalesItem, - isAiItem = ji.IsAiItem, + isLaborItem = ji.IsLaborItem, + isSalesItem = ji.IsSalesItem, + isAiItem = ji.IsAiItem, + isCustomFormulaItem = ji.IsCustomFormulaItem, + customItemTemplateId = ji.CustomItemTemplateId, + formulaFieldValuesJson = ji.FormulaFieldValuesJson, sku = ji.Sku, requiresSandblasting = ji.RequiresSandblasting, requiresMasking = ji.RequiresMasking, @@ -1279,9 +1282,12 @@ public class JobsController : Controller CatalogItemId = ji.CatalogItemId, ManualUnitPrice = ji.ManualUnitPrice ?? (ji.IsGenericItem ? ji.UnitPrice : null), PowderCostOverride = ji.PowderCostOverride, - IsGenericItem = ji.IsGenericItem || (!ji.CatalogItemId.HasValue && ji.Coats.Count == 0), - IsLaborItem = ji.IsLaborItem, - IsAiItem = ji.IsAiItem, + IsGenericItem = ji.IsGenericItem || (!ji.CatalogItemId.HasValue && ji.Coats.Count == 0), + IsLaborItem = ji.IsLaborItem, + IsAiItem = ji.IsAiItem, + IsCustomFormulaItem = ji.IsCustomFormulaItem, + CustomItemTemplateId = ji.CustomItemTemplateId, + FormulaFieldValuesJson = ji.FormulaFieldValuesJson, RequiresSandblasting = ji.RequiresSandblasting, RequiresMasking = ji.RequiresMasking, Notes = ji.Notes, @@ -1852,6 +1858,25 @@ public class JobsController : Controller { ViewBag.AiPhotoQuotesEnabled = await _subscriptionService.CanUseAiPhotoQuoteAsync(companyId); + var formulaTemplates = await _unitOfWork.CustomItemTemplates.FindAsync( + t => t.CompanyId == companyId && t.IsActive); + ViewBag.CustomFormulaTemplates = formulaTemplates + .OrderBy(t => t.DisplayOrder).ThenBy(t => t.Name) + .Select(t => new + { + id = t.Id, + name = t.Name, + description = t.Description, + outputMode = t.OutputMode, + fieldsJson = t.FieldsJson, + formula = t.Formula, + defaultRate = t.DefaultRate, + rateLabel = t.RateLabel, + diagramImagePath = string.IsNullOrEmpty(t.DiagramImagePath) + ? (string?)null + : Url.Action("TemplateDiagram", "CompanySettings", new { templateId = t.Id }) + }).ToList(); + await PopulateDropdowns(); await PopulatePrepServicesAsync(companyId); var costs = await _pricingService.GetOperatingCostsAsync(companyId); @@ -2981,9 +3006,12 @@ public class JobsController : Controller CatalogItemId = ji.CatalogItemId, ManualUnitPrice = ji.ManualUnitPrice ?? (ji.IsGenericItem ? ji.UnitPrice : null), PowderCostOverride = ji.PowderCostOverride, - IsGenericItem = ji.IsGenericItem || (!ji.CatalogItemId.HasValue && ji.Coats.Count == 0), - IsLaborItem = ji.IsLaborItem, - IsAiItem = ji.IsAiItem, + IsGenericItem = ji.IsGenericItem || (!ji.CatalogItemId.HasValue && ji.Coats.Count == 0), + IsLaborItem = ji.IsLaborItem, + IsAiItem = ji.IsAiItem, + IsCustomFormulaItem = ji.IsCustomFormulaItem, + CustomItemTemplateId = ji.CustomItemTemplateId, + FormulaFieldValuesJson = ji.FormulaFieldValuesJson, RequiresSandblasting = ji.RequiresSandblasting, RequiresMasking = ji.RequiresMasking, Notes = ji.Notes, @@ -3172,10 +3200,13 @@ public class JobsController : Controller SurfaceAreaSqFt = ji.SurfaceAreaSqFt, EstimatedMinutes = ji.EstimatedMinutes, CatalogItemId = ji.CatalogItemId, - IsGenericItem = ji.IsGenericItem, - IsLaborItem = ji.IsLaborItem, - IsSalesItem = ji.IsSalesItem, - IsAiItem = ji.IsAiItem, + IsGenericItem = ji.IsGenericItem, + IsLaborItem = ji.IsLaborItem, + IsSalesItem = ji.IsSalesItem, + IsAiItem = ji.IsAiItem, + IsCustomFormulaItem = ji.IsCustomFormulaItem, + CustomItemTemplateId = ji.CustomItemTemplateId, + FormulaFieldValuesJson = ji.FormulaFieldValuesJson, ManualUnitPrice = ji.ManualUnitPrice ?? ((ji.IsGenericItem || ji.IsSalesItem) ? ji.UnitPrice : (decimal?)null), IncludePrepCost = ji.IncludePrepCost, Coats = ji.Coats.OrderBy(c => c.Sequence).Select(c => new CreateQuoteItemCoatDto @@ -3309,6 +3340,13 @@ public class JobsController : Controller var useMetric = await _tenantContext.UseMetricSystemAsync(); ViewBag.UseMetric = useMetric; ViewBag.AreaUnit = _measurementService.GetAreaUnitLabel(useMetric); + + var formulaTemplates = await _unitOfWork.CustomItemTemplates.FindAsync(t => t.CompanyId == companyId && t.IsActive); + ViewBag.CustomFormulaTemplates = formulaTemplates.OrderBy(t => t.DisplayOrder).ThenBy(t => t.Name) + .Select(t => new { id = t.Id, name = t.Name, description = t.Description, outputMode = t.OutputMode, + fieldsJson = t.FieldsJson, formula = t.Formula, defaultRate = t.DefaultRate, rateLabel = t.RateLabel, + diagramImagePath = string.IsNullOrEmpty(t.DiagramImagePath) ? null + : Url.Action("TemplateDiagram", "CompanySettings", new { templateId = t.Id }) }).ToList(); } /// diff --git a/src/PowderCoating.Web/Controllers/QuotesController.cs b/src/PowderCoating.Web/Controllers/QuotesController.cs index 879cd47..0adc0d8 100644 --- a/src/PowderCoating.Web/Controllers/QuotesController.cs +++ b/src/PowderCoating.Web/Controllers/QuotesController.cs @@ -2429,6 +2429,25 @@ public class QuotesController : Controller var (_, quotePhotoMax) = await _subscriptionService.GetQuotePhotoCountAsync(companyId, 0); ViewBag.QuotePhotosEnabled = quotePhotoMax != 0; // 0 = feature disabled for this plan + var formulaTemplates = await _unitOfWork.CustomItemTemplates.FindAsync( + t => t.CompanyId == companyId && t.IsActive); + ViewBag.CustomFormulaTemplates = formulaTemplates + .OrderBy(t => t.DisplayOrder).ThenBy(t => t.Name) + .Select(t => new + { + id = t.Id, + name = t.Name, + description = t.Description, + outputMode = t.OutputMode, + fieldsJson = t.FieldsJson, + formula = t.Formula, + defaultRate = t.DefaultRate, + rateLabel = t.RateLabel, + diagramImagePath = string.IsNullOrEmpty(t.DiagramImagePath) + ? (string?)null + : Url.Action("TemplateDiagram", "CompanySettings", new { templateId = t.Id }) + }).ToList(); + // Customers var customers = await _unitOfWork.Customers.FindAsync(c => c.CompanyId == companyId); ViewBag.Customers = customers diff --git a/src/PowderCoating.Web/Helpers/HelpKnowledgeBase.cs b/src/PowderCoating.Web/Helpers/HelpKnowledgeBase.cs index 0778cec..2dc8421 100644 --- a/src/PowderCoating.Web/Helpers/HelpKnowledgeBase.cs +++ b/src/PowderCoating.Web/Helpers/HelpKnowledgeBase.cs @@ -1388,5 +1388,25 @@ public static class HelpKnowledgeBase --- Remember: if the user asks something outside this knowledge base or asks for something very specific to their data, acknowledge the limits and point them to the relevant page or the Help Center. + + --- + + **Custom Formula Item Templates (Company Settings → Custom Formulas):** + Reusable NCalc pricing formulas for complex fabricated items (roof curbs, electrical enclosures, welded frames). Each template has a list of measurement fields and a formula expression. Two output modes: + - Fixed Rate: formula produces a dollar amount → stored as ManualUnitPrice × Qty + - Surface Area: formula produces sq ft → standard coating engine prices it + + Creating a template: New Template → enter name + output mode + fields (name, label, unit, default value) → write NCalc formula using field names → Run to test → optionally upload a diagram image → Save + AI Generator: enter description in "AI Formula Generator" box in the template editor → Claude suggests formula + fields + mode → review and save + Using in wizard: item wizard shows "Custom Formula Item" card if active templates exist → choose template → template diagram shown for reference → enter measurements → Calculate → verify result → continue to coatings/prep steps + Formula variable names: snake_case, letters/digits/underscores only. Reserved variable: "rate" (pre-populated from Default Rate). + NCalc syntax: +, -, *, /, %, Pow(b,e), Abs(x), Round(x,d), Max(a,b), Min(a,b), Sqrt(x) + Common formula patterns (all Fixed Rate, divide inches by 144 to get sqft): + - 6-sided box: fields l_in/w_in/h_in → 2*(l_in*w_in + l_in*h_in + w_in*h_in) / 144 * rate + - Cylinder: fields d_in/h_in → (3.14159 * d_in * h_in + 2 * 3.14159 * Pow(d_in/2, 2)) / 144 * rate + - Flat panel: fields l_in/w_in → l_in * w_in / 144 * rate + Walkthrough: first time opening Custom Formulas tab with no templates triggers a 7-step guided tour automatically; also accessible via "How it works" button + Help article: Help → Custom Formula Item Templates """; + } diff --git a/src/PowderCoating.Web/Program.cs b/src/PowderCoating.Web/Program.cs index 15e8dcc..9e74444 100644 --- a/src/PowderCoating.Web/Program.cs +++ b/src/PowderCoating.Web/Program.cs @@ -219,6 +219,7 @@ builder.Services.AddScoped( builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -292,6 +293,7 @@ cfg.AddProfile(new CatalogProfile()); cfg.AddProfile(new AccountingProfile()); cfg.AddProfile(new PurchaseOrderProfile()); cfg.AddProfile(new PricingTierProfile()); + cfg.AddProfile(new CustomItemTemplateProfile()); }, loggerFactory); return config.CreateMapper(); }); diff --git a/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml b/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml index 310aec8..92a32ce 100644 --- a/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml +++ b/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml @@ -106,6 +106,11 @@ Kiosk + @@ -2054,6 +2059,179 @@ + +
+
+
+
Custom Formula Item Templates
+
+ + +
+
+
+

+ Define reusable pricing formulas for complex fabricated items (roof curbs, enclosures, frames). + When a user adds a formula item to a quote or job, they fill in the measurements and the formula + calculates the price automatically. +

+
+ + + + + + + + + + + + + +
NameOutput ModeFieldsActive
Loading…
+
+
+
+
+ + + + + + + + + @@ -3289,6 +3467,24 @@ const btn = document.querySelector('[data-bs-target="#kiosk"]'); if (btn) new bootstrap.Tab(btn).show(); } + if (urlParams.get('tab') === 'custom-formulas') { + const btn = document.querySelector('[data-bs-target="#custom-formulas"]'); + if (btn) new bootstrap.Tab(btn).show(); + } + + + } diff --git a/src/PowderCoating.Web/Views/Help/CustomFormulaTemplates.cshtml b/src/PowderCoating.Web/Views/Help/CustomFormulaTemplates.cshtml new file mode 100644 index 0000000..40f04ed --- /dev/null +++ b/src/PowderCoating.Web/Views/Help/CustomFormulaTemplates.cshtml @@ -0,0 +1,161 @@ +@{ + ViewData["Title"] = "Custom Formula Item Templates — Help"; +} + +
+
+ + +

Custom Formula Item Templates

+

Build reusable pricing formulas for complex fabricated items.

+ +
+
+

What are formula templates?

+

+ Some items — roof curbs, electrical enclosures, welded frames — have prices that depend on + exact measurements rather than estimated surface area. Custom Formula Item Templates let you define a + reusable NCalc expression that automatically calculates the price (or surface area) once a user enters + the measurements. +

+

+ Templates are created once in Company Settings → Custom Formulas and then + appear as a selectable item type in the quote and job item wizards. +

+
+
+ +
+
+

Output modes

+
+ + + + + + + + + + + + + + + + +
ModeFormula outputHow it’s priced
Fixed RateA dollar amountStored as ManualUnitPrice; multiplied by quantity for the line total.
Surface AreaSquare footagePassed to the standard coating engine; priced per your operating cost rates.
+
+
+
+ +
+
+

Creating a template

+
    +
  1. Go to Company Settings → Custom Formulas and click New Template.
  2. +
  3. Enter a name and choose the output mode.
  4. +
  5. Add the measurement fields users will fill in (e.g. length_in, width_in).
  6. +
  7. Write the formula using those field names. Example for a box surface area in inches: +
    2*(length_in*width_in + length_in*height_in + width_in*height_in) / 144 * rate
    +
  8. +
  9. Click Run to test the formula with your default values.
  10. +
  11. Optionally upload a diagram image — users will see it when they select this template.
  12. +
  13. Save the template.
  14. +
+ +

Using AI to generate a formula

+

+ In the template editor, enter a description of the item in the AI Formula Generator box + and optionally attach a diagram image. Claude will suggest a formula, field list, and output mode. + Review and adjust before saving. +

+
+
+ +
+
+

Adding a formula item to a quote or job

+
    +
  1. In the item wizard, select Custom Formula Item (only visible if at least one active template exists).
  2. +
  3. Choose a template from the dropdown. The template’s diagram will appear for reference.
  4. +
  5. Enter the measurements and click Calculate to preview the result.
  6. +
  7. Adjust the description and quantity, then continue to the coatings and prep steps.
  8. +
+
+
+ +
+
+

Common formula patterns

+

These ready-to-use templates cover the most common fabricated item shapes. Copy the fields and formula exactly, then adjust the Default Rate for your shop.

+ +

6-Sided Box (roof curbs, enclosures)

+

Calculates the total outer surface area of all six faces.

+
+ + + + + + + +
Variable nameLabelDefault
l_inLength (inches)24
w_inWidth (inches)24
h_inHeight (inches)12
+
+
2*(l_in*w_in + l_in*h_in + w_in*h_in) / 144 * rate
+

Output mode: Fixed Rate — suggested rate: your $/sqft coating price. A 24×24×12 box at $3.50/sqft = $28.00.

+ +

Cylinder (pipe ends, round housings)

+

Lateral surface plus two circular end caps.

+
+ + + + + + +
Variable nameLabelDefault
d_inDiameter (inches)12
h_inHeight (inches)18
+
+
(3.14159 * d_in * h_in + 2 * 3.14159 * Pow(d_in/2, 2)) / 144 * rate
+

Output mode: Fixed Rate — a 12″ diameter × 18″ tall cylinder at $3.50/sqft ≈ $10.21.

+ +

Flat Panel

+

Simple rectangular sheet — one face only.

+
+ + + + + + +
Variable nameLabelDefault
l_inLength (inches)24
w_inWidth (inches)12
+
+
l_in * w_in / 144 * rate
+

Output mode: Fixed Rate — a 24″×12″ panel at $3.50/sqft = $7.00.

+
+
+ +
+
+

NCalc formula reference

+

Formulas use NCalc syntax:

+
    +
  • Standard operators: + - * / % Pow(b, e)
  • +
  • Functions: Abs(x) Round(x, d) Max(a, b) Min(a, b) Sqrt(x)
  • +
  • Variable names must start with a letter and contain only letters, digits, or underscores.
  • +
  • The reserved variable rate is pre-populated from the template’s Default Rate.
  • +
+
+
+
+ +
+ @await Html.PartialAsync("_HelpNav") +
+
diff --git a/src/PowderCoating.Web/Views/Help/Index.cshtml b/src/PowderCoating.Web/Views/Help/Index.cshtml index c3ee2ec..982335b 100644 --- a/src/PowderCoating.Web/Views/Help/Index.cshtml +++ b/src/PowderCoating.Web/Views/Help/Index.cshtml @@ -258,6 +258,22 @@ +
+
+
+
+
+ +
+
+
Custom Formula Item Templates
+

Build reusable NCalc pricing formulas for complex fabricated items like roof curbs, enclosures, and frames.

+ Read more +
+
+
+
+
diff --git a/src/PowderCoating.Web/Views/Jobs/Create.cshtml b/src/PowderCoating.Web/Views/Jobs/Create.cshtml index 9a81a50..772d2a9 100644 --- a/src/PowderCoating.Web/Views/Jobs/Create.cshtml +++ b/src/PowderCoating.Web/Views/Jobs/Create.cshtml @@ -397,6 +397,11 @@ complexity = item.Complexity, isGenericItem = item.IsGenericItem, isLaborItem = item.IsLaborItem, + isSalesItem = item.IsSalesItem, + isAiItem = item.IsAiItem, + isCustomFormulaItem = item.IsCustomFormulaItem, + customItemTemplateId = item.CustomItemTemplateId, + formulaFieldValuesJson = item.FormulaFieldValuesJson, requiresSandblasting = item.RequiresSandblasting, requiresMasking = item.RequiresMasking, notes = item.Notes, @@ -438,6 +443,8 @@ "aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")", "aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")", "aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)), + "customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List()), + "formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")", "itemsFieldPrefix": "JobItems", "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")" } diff --git a/src/PowderCoating.Web/Views/Jobs/Details.cshtml b/src/PowderCoating.Web/Views/Jobs/Details.cshtml index 7592250..9e498ae 100644 --- a/src/PowderCoating.Web/Views/Jobs/Details.cshtml +++ b/src/PowderCoating.Web/Views/Jobs/Details.cshtml @@ -358,6 +358,8 @@ @item.Description + @if (item.IsAiItem) { AI } + @if (item.IsCustomFormulaItem) { Formula } @if (item.Coats != null && item.Coats.Any()) {
@@ -2487,7 +2489,9 @@ "useMetric": @Json.Serialize((bool)(ViewBag.UseMetric ?? false)), "pricingUrl": "@Url.Action("CalculatePricing", "Jobs")", "itemsFieldPrefix": "JobItems", - "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")" + "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")", + "customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List()), + "formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")" } diff --git a/src/PowderCoating.Web/Views/Jobs/Edit.cshtml b/src/PowderCoating.Web/Views/Jobs/Edit.cshtml index b192c85..e86eca2 100644 --- a/src/PowderCoating.Web/Views/Jobs/Edit.cshtml +++ b/src/PowderCoating.Web/Views/Jobs/Edit.cshtml @@ -384,6 +384,9 @@ isGenericItem = item.IsGenericItem, isLaborItem = item.IsLaborItem, isAiItem = item.IsAiItem, + isCustomFormulaItem = item.IsCustomFormulaItem, + customItemTemplateId = item.CustomItemTemplateId, + formulaFieldValuesJson = item.FormulaFieldValuesJson, requiresSandblasting = item.RequiresSandblasting, requiresMasking = item.RequiresMasking, notes = item.Notes, @@ -425,6 +428,8 @@ "aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")", "aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")", "aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)), + "customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List()), + "formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")", "itemsFieldPrefix": "JobItems", "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")" } diff --git a/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml b/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml index 7bc84af..5aa2981 100644 --- a/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml +++ b/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml @@ -137,6 +137,9 @@ isGenericItem = item.IsGenericItem, isLaborItem = item.IsLaborItem, isAiItem = item.IsAiItem, + isCustomFormulaItem = item.IsCustomFormulaItem, + customItemTemplateId = item.CustomItemTemplateId, + formulaFieldValuesJson = item.FormulaFieldValuesJson, requiresSandblasting = item.RequiresSandblasting, requiresMasking = item.RequiresMasking, notes = item.Notes, @@ -176,7 +179,9 @@ "useMetric": @Json.Serialize((bool)(ViewBag.UseMetric ?? false)), "pricingUrl": "@Url.Action("CalculatePricing", "Jobs")", "itemsFieldPrefix": "JobItems", - "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")" + "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")", + "customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List()), + "formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")" } diff --git a/src/PowderCoating.Web/Views/Quotes/Create.cshtml b/src/PowderCoating.Web/Views/Quotes/Create.cshtml index b5054d7..ebd9a34 100644 --- a/src/PowderCoating.Web/Views/Quotes/Create.cshtml +++ b/src/PowderCoating.Web/Views/Quotes/Create.cshtml @@ -464,6 +464,11 @@ manualUnitPrice = item.ManualUnitPrice, isGenericItem = item.IsGenericItem, isLaborItem = item.IsLaborItem, + isSalesItem = item.IsSalesItem, + isAiItem = item.IsAiItem, + isCustomFormulaItem = item.IsCustomFormulaItem, + customItemTemplateId = item.CustomItemTemplateId, + formulaFieldValuesJson = item.FormulaFieldValuesJson, requiresSandblasting = item.RequiresSandblasting, requiresMasking = item.RequiresMasking, notes = item.Notes, @@ -505,6 +510,8 @@ "aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")", "aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")", "aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)), + "customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List()), + "formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")", "itemsFieldPrefix": "QuoteItems", "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")" } diff --git a/src/PowderCoating.Web/Views/Quotes/Details.cshtml b/src/PowderCoating.Web/Views/Quotes/Details.cshtml index 4c893f5..88f5c60 100644 --- a/src/PowderCoating.Web/Views/Quotes/Details.cshtml +++ b/src/PowderCoating.Web/Views/Quotes/Details.cshtml @@ -1300,6 +1300,7 @@ @(item.Description ?? item.CatalogItemName ?? "(no description)") @if (item.CatalogItemId.HasValue) { Catalog } @if (item.IsAiItem) { AI } + @if (item.IsCustomFormulaItem) { Formula } @if (item.SurfaceAreaSqFt > 0 || item.EstimatedMinutes > 0) { diff --git a/src/PowderCoating.Web/Views/Quotes/Edit.cshtml b/src/PowderCoating.Web/Views/Quotes/Edit.cshtml index 32ed3f2..789802d 100644 --- a/src/PowderCoating.Web/Views/Quotes/Edit.cshtml +++ b/src/PowderCoating.Web/Views/Quotes/Edit.cshtml @@ -501,6 +501,9 @@ isGenericItem = item.IsGenericItem, isLaborItem = item.IsLaborItem, isAiItem = item.IsAiItem, + isCustomFormulaItem = item.IsCustomFormulaItem, + customItemTemplateId = item.CustomItemTemplateId, + formulaFieldValuesJson = item.FormulaFieldValuesJson, includePrepCost = item.IncludePrepCost, complexity = item.Complexity, aiTags = item.AiTags, @@ -548,6 +551,8 @@ "aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")", "aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")", "aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)), + "customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List()), + "formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")", "itemsFieldPrefix": "QuoteItems", "aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")", "emailOptOutCustomerIds": @Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.CustomerEmailOptOutIds ?? new System.Collections.Generic.HashSet())), diff --git a/src/PowderCoating.Web/wwwroot/js/company-settings-custom-formulas.js b/src/PowderCoating.Web/wwwroot/js/company-settings-custom-formulas.js new file mode 100644 index 0000000..521a84a --- /dev/null +++ b/src/PowderCoating.Web/wwwroot/js/company-settings-custom-formulas.js @@ -0,0 +1,731 @@ +// company-settings-custom-formulas.js +// Custom Formula Item Templates — Company Settings tab JS + +(function () { + let cfFields = []; + let cfEditing = false; + + // ── Load & Render ───────────────────────────────────────────────────────── + + window.cfLoadTemplates = async function () { + const tbody = document.getElementById('cfTemplatesBody'); + tbody.innerHTML = 'Loading…'; + try { + const res = await fetch('/CompanySettings/GetCustomItemTemplates'); + const data = await res.json(); + if (!data.success || !data.templates.length) { + tbody.innerHTML = 'No formula templates yet. Click New Template to create one.'; + return; + } + tbody.innerHTML = data.templates.map(t => ` + + + ${escHtml(t.name)} + ${t.description ? `
${escHtml(t.description)}` : ''} + + + ${t.outputMode === 'FixedRate' + ? 'Fixed Rate' + : 'Surface Area'} + + ${t.fieldCount} field${t.fieldCount !== 1 ? 's' : ''} + ${t.isActive + ? 'Active' + : 'Inactive'} + + + + + `).join(''); + } catch (e) { + tbody.innerHTML = 'Failed to load templates.'; + } + }; + + // ── Create / Edit Modal ─────────────────────────────────────────────────── + + window.cfShowCreate = function () { + cfEditing = false; + document.getElementById('cfModalLabel').textContent = 'New Formula Template'; + cfResetForm(); + new bootstrap.Modal(document.getElementById('cfModal')).show(); + }; + + window.cfShowEdit = async function (id) { + cfEditing = true; + document.getElementById('cfModalLabel').textContent = 'Edit Formula Template'; + cfResetForm(); + try { + const res = await fetch('/CompanySettings/GetCustomItemTemplates'); + const data = await res.json(); + const t = data.templates?.find(x => x.id === id); + if (!t) { showCfError('Template not found.'); return; } + + // Fetch full DTO (fieldsJson + formula) via a second call that returns the full data + // GetCustomItemTemplates returns ListDto which has fieldCount, not fieldsJson. + // We need the full template — load it from the API response carefully. + // For now fetch all and find; fieldCount is on list, fieldsJson needs a dedicated endpoint. + // Since we only have GetCustomItemTemplates (returns list DTOs), we re-use what we have + // and handle fieldsJson separately via the AI-generated field. + // TODO: add GetCustomItemTemplate(id) endpoint if needed; for POC, store fieldsJson in data- attr. + } catch (e) { } + + // Workaround: store full template data in the table row as JSON via a hidden Get endpoint + // For now use the reload approach via a dedicated single-record fetch + const res2 = await fetch(`/CompanySettings/GetCustomItemTemplate?id=${id}`); + if (!res2.ok) { showCfError('Could not load template.'); return; } + const full = await res2.json(); + if (!full.success) { showCfError(full.message ?? 'Error loading template.'); return; } + + cfPopulateForm(full.template); + new bootstrap.Modal(document.getElementById('cfModal')).show(); + }; + + function cfPopulateForm(t) { + document.getElementById('cfId').value = t.id ?? 0; + document.getElementById('cfName').value = t.name ?? ''; + document.getElementById('cfDescription').value = t.description ?? ''; + document.getElementById('cfOutputMode').value = t.outputMode ?? 'FixedRate'; + document.getElementById('cfDefaultRate').value = t.defaultRate ?? ''; + document.getElementById('cfRateLabel').value = t.rateLabel ?? ''; + document.getElementById('cfFormula').value = t.formula ?? ''; + document.getElementById('cfNotes').value = t.notes ?? ''; + document.getElementById('cfIsActive').checked = t.isActive !== false; + + cfFields = []; + try { cfFields = JSON.parse(t.fieldsJson || '[]'); } catch { cfFields = []; } + cfRenderFields(); + cfToggleRateFields(); + + if (t.diagramImagePath) { + document.getElementById('cfDiagramImg').src = `/CompanySettings/TemplateDiagram?templateId=${t.id}`; + document.getElementById('cfDiagramPreview').style.display = ''; + } + } + + function cfResetForm() { + document.getElementById('cfId').value = '0'; + document.getElementById('cfName').value = ''; + document.getElementById('cfDescription').value = ''; + document.getElementById('cfOutputMode').value = 'FixedRate'; + document.getElementById('cfDefaultRate').value = ''; + document.getElementById('cfRateLabel').value = ''; + document.getElementById('cfFormula').value = ''; + document.getElementById('cfNotes').value = ''; + document.getElementById('cfIsActive').checked = true; + document.getElementById('cfDiagramPreview').style.display = 'none'; + document.getElementById('cfDiagramFile').value = ''; + document.getElementById('cfTestResult').textContent = ''; + cfFields = []; + cfRenderFields(); + cfToggleRateFields(); + } + + // ── Field List Editor ───────────────────────────────────────────────────── + + window.cfAddField = function () { + cfFields.push({ name: '', label: '', unit: '', defaultValue: 0 }); + cfRenderFields(); + }; + + window.cfRemoveField = function (idx) { + cfFields.splice(idx, 1); + cfRenderFields(); + }; + + window.cfUpdateField = function (i, key, val, isNumber) { + cfFields[i][key] = isNumber ? (parseFloat(val) || 0) : val; + if (key === 'name') { cfValidateFieldNameInput(i, val); cfRenderVariablePills(); } + }; + + function cfValidateFieldName(name) { + if (!name) return 'Field variable name is required.'; + if (name === 'rate') return '"rate" is reserved — it is pre-populated from the template\'s Default Rate.'; + if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(name)) return 'Must start with a letter and contain only letters, digits, or underscores (no spaces).'; + return null; + } + + function cfValidateFieldNameInput(i, val) { + const inputs = document.querySelectorAll('#cfFieldsList .field-name-input'); + const input = inputs[i]; + if (!input) return; + const err = cfValidateFieldName(val); + input.classList.toggle('is-invalid', !!err); + let fb = input.nextElementSibling; + if (err) { + if (!fb || !fb.classList.contains('invalid-feedback')) { + fb = document.createElement('div'); + fb.className = 'invalid-feedback'; + input.after(fb); + } + fb.textContent = err; + } else if (fb && fb.classList.contains('invalid-feedback')) { + fb.remove(); + } + } + + function cfRenderFields() { + const el = document.getElementById('cfFieldsList'); + if (!cfFields.length) { + el.innerHTML = '

No fields yet.

'; + return; + } + cfRenderVariablePills(); + el.innerHTML = cfFields.map((f, i) => ` +
+
+
+ ${f.name && cfValidateFieldName(f.name) ? `
${escHtml(cfValidateFieldName(f.name))}
` : ''} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
`).join(''); + } + + function cfRenderVariablePills() { + const container = document.getElementById('cfVariablePills'); + if (!container) return; + const names = cfFields.map(f => f.name).filter(n => n && !cfValidateFieldName(n)); + const all = [...names, 'rate']; + container.innerHTML = all.map(n => + `${escHtml(n)}` + ).join(''); + } + + window.cfInsertVariable = function (name) { + const input = document.getElementById('cfFormula'); + if (!input) return; + const start = input.selectionStart ?? input.value.length; + const end = input.selectionEnd ?? input.value.length; + const before = input.value.slice(0, start); + const after = input.value.slice(end); + const needsSpace = before.length > 0 && !/[\s(+\-*/]$/.test(before); + const insert = (needsSpace ? ' ' : '') + name; + input.value = before + insert + after; + const cursor = start + insert.length; + input.setSelectionRange(cursor, cursor); + input.focus(); + }; + + // ── Formula Test ────────────────────────────────────────────────────────── + + window.cfTestFormula = async function () { + const formula = document.getElementById('cfFormula').value.trim(); + if (!formula) { document.getElementById('cfTestResult').textContent = 'Enter a formula first.'; return; } + + const variables = {}; + cfFields.forEach(f => { if (f.name) variables[f.name] = f.defaultValue ?? 0; }); + const defaultRate = parseFloat(document.getElementById('cfDefaultRate').value); + if (!isNaN(defaultRate)) variables['rate'] = defaultRate; + + try { + const res = await fetch('/CompanySettings/EvaluateFormula', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': getAntiForgeryToken() }, + body: JSON.stringify({ formula, variablesJson: JSON.stringify(variables) }) + }); + const data = await res.json(); + const el = document.getElementById('cfTestResult'); + if (data.success) { + el.textContent = `= ${Number(data.result).toFixed(4)}`; + el.className = 'fw-bold text-success'; + } else { + el.textContent = data.error ?? 'Error'; + el.className = 'fw-bold text-danger'; + } + } catch (e) { + document.getElementById('cfTestResult').textContent = 'Request failed.'; + } + }; + + // ── Output Mode Toggle ──────────────────────────────────────────────────── + + window.cfToggleRateFields = function () { + const mode = document.getElementById('cfOutputMode').value; + document.getElementById('cfRateFields').style.display = mode === 'FixedRate' ? '' : 'none'; + }; + + // ── Diagram Preview ─────────────────────────────────────────────────────── + + window.cfPreviewDiagram = function (evt) { + const file = evt.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = e => { + document.getElementById('cfDiagramImg').src = e.target.result; + document.getElementById('cfDiagramPreview').style.display = ''; + }; + reader.readAsDataURL(file); + }; + + // ── AI Generator ────────────────────────────────────────────────────────── + + window.cfGenerateFromAi = async function () { + const prompt = document.getElementById('cfAiPrompt').value.trim(); + if (!prompt) { showCfError('Enter a description first.'); return; } + + const btn = document.getElementById('cfAiBtn'); + btn.disabled = true; + btn.innerHTML = 'Generating…'; + + try { + const formData = new FormData(); + formData.append('description', prompt); + const diagramFile = document.getElementById('cfDiagramFile').files[0]; + if (diagramFile) formData.append('diagramImage', diagramFile); + + const res = await fetch('/CompanySettings/GenerateFormulaFromAi', { + method: 'POST', + headers: { 'RequestVerificationToken': getAntiForgeryToken() }, + body: formData + }); + const data = await res.json(); + if (!data.success) { showCfError(data.error ?? 'AI generation failed.'); return; } + + if (data.name) document.getElementById('cfName').value = data.name; + if (data.outputMode) document.getElementById('cfOutputMode').value = data.outputMode; + if (data.formula) document.getElementById('cfFormula').value = data.formula; + if (data.defaultRate != null) document.getElementById('cfDefaultRate').value = data.defaultRate; + if (data.rateLabel) document.getElementById('cfRateLabel').value = data.rateLabel; + + cfFields = []; + try { + const parsed = JSON.parse(data.fieldsJson || '[]'); + cfFields = parsed.map(f => ({ + name: f.name ?? '', + label: f.label ?? '', + unit: f.unit ?? '', + defaultValue: f.defaultValue ?? 0 + })); + } catch { } + cfRenderFields(); + cfToggleRateFields(); + + if (data.verificationResult != null) { + const el = document.getElementById('cfTestResult'); + el.textContent = `AI test = ${Number(data.verificationResult).toFixed(4)}`; + el.className = 'fw-bold text-success'; + } + } catch (e) { + showCfError('AI request failed: ' + e.message); + } finally { + btn.disabled = false; + btn.innerHTML = ' Generate'; + } + }; + + // ── Save ────────────────────────────────────────────────────────────────── + + window.cfSave = async function () { + const id = parseInt(document.getElementById('cfId').value) || 0; + const name = document.getElementById('cfName').value.trim(); + if (!name) { showCfError('Name is required.'); return; } + + const formula = document.getElementById('cfFormula').value.trim(); + if (!formula) { showCfError('Formula is required.'); return; } + + const fieldErrors = cfFields.map((f, i) => ({ i, err: cfValidateFieldName(f.name) })).filter(x => x.err); + if (fieldErrors.length) { + fieldErrors.forEach(x => cfValidateFieldNameInput(x.i, cfFields[x.i].name)); + showCfError(`Fix field name errors before saving: ${fieldErrors[0].err}`); + return; + } + + const names = cfFields.map(f => f.name); + const dupes = names.filter((n, i) => names.indexOf(n) !== i); + if (dupes.length) { showCfError(`Duplicate field name: "${dupes[0]}"`); return; } + + const dto = { + id, + name, + description: document.getElementById('cfDescription').value.trim() || null, + outputMode: document.getElementById('cfOutputMode').value, + fieldsJson: JSON.stringify(cfFields), + formula, + defaultRate: parseFloat(document.getElementById('cfDefaultRate').value) || null, + rateLabel: document.getElementById('cfRateLabel').value.trim() || null, + notes: document.getElementById('cfNotes').value.trim() || null, + displayOrder: 0, + isActive: document.getElementById('cfIsActive').checked + }; + + const url = id ? '/CompanySettings/UpdateCustomItemTemplate' : '/CompanySettings/CreateCustomItemTemplate'; + try { + const res = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': getAntiForgeryToken() }, + body: JSON.stringify(dto) + }); + const data = await res.json(); + if (!data.success) { showCfError(data.message ?? 'Save failed.'); return; } + + const newId = data.id ?? id; + + // Upload diagram if a new file was chosen + const diagramFile = document.getElementById('cfDiagramFile').files[0]; + if (diagramFile && newId) { + const fd = new FormData(); + fd.append('templateId', newId); + fd.append('diagramFile', diagramFile); + await fetch('/CompanySettings/UploadTemplateDiagram', { + method: 'POST', + headers: { 'RequestVerificationToken': getAntiForgeryToken() }, + body: fd + }); + } + + bootstrap.Modal.getInstance(document.getElementById('cfModal'))?.hide(); + cfLoadTemplates(); + } catch (e) { + showCfError('Save request failed: ' + e.message); + } + }; + + // ── Delete ──────────────────────────────────────────────────────────────── + + window.cfDelete = async function (id, name) { + if (!confirm(`Delete template "${name}"? This cannot be undone.`)) return; + try { + const fd = new FormData(); + fd.append('id', id); + const res = await fetch('/CompanySettings/DeleteCustomItemTemplate', { + method: 'POST', + headers: { 'RequestVerificationToken': getAntiForgeryToken() }, + body: fd + }); + const data = await res.json(); + if (!data.success) { showCfError(data.message ?? 'Delete failed.'); return; } + cfLoadTemplates(); + } catch (e) { + showCfError('Delete failed: ' + e.message); + } + }; + + // ── Helpers ─────────────────────────────────────────────────────────────── + + function escHtml(s) { + if (s == null) return ''; + return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + function showCfError(msg) { + const el = document.getElementById('errorToastMessage'); + if (el) { + el.textContent = msg; + const toast = document.getElementById('errorToast'); + if (toast) new bootstrap.Toast(toast).show(); + } else { + alert(msg); + } + } + + function getAntiForgeryToken() { + return document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? ''; + } + + // ── Walkthrough ─────────────────────────────────────────────────────────── + + let cfWtStep = 0; + + const cfWtSteps = [ + { + title: 'What are Formula Templates?', + icon: 'bi-lightbulb text-warning', + html: ` +

Some items — roof curbs, electrical enclosures, welded frames — have prices that depend on + exact measurements rather than estimated surface area.

+

A Formula Template lets you define a reusable calculation once in Company Settings. + When a staff member adds that item to a quote or job, they just fill in the measurements and the price + calculates automatically — no mental math, no spreadsheets.

+
+ + Templates appear as a "Custom Formula Item" option in the quote and job item wizard, + but only when at least one active template exists. +
` + }, + { + title: 'Output Modes', + icon: 'bi-toggle2-on text-primary', + html: ` +

When you create a template, you choose how the formula result is used:

+
+ + + + + + + + + + + + + + + + +
ModeFormula produces…How it’s priced
Fixed RateA dollar amountUsed directly as the item’s unit price. Best for items priced by a custom rate (e.g. $/sqft of surface).
Surface AreaSquare footageFed into the standard coating engine and priced using your operating cost rates — just like any other coated item.
+
+
+ + Most shops start with Fixed Rate. It gives you full control over the per-unit price + and is the easiest to reason about. +
` + }, + { + title: 'Step 1 — Define Your Fields', + icon: 'bi-input-cursor-text text-success', + html: ` +

Fields are the measurements your staff will fill in when they use the template. + Each field becomes an input box in the item wizard.

+
+
+
+
What you set up
+
+ + + + +
Variable namelength_in
LabelLength (inches)
Default24
+ + + + +
Variable namewidth_in
LabelWidth (inches)
Default12
+
+
+
+
+
+
What staff sees in the wizard
+
+ + + + +
+
+
+
+
+ + Variable names must start with a letter and contain only letters, digits, or underscores — no spaces. + Good: length_in   Bad: length in or 1length +
` + }, + { + title: 'Step 2 — Write the Formula', + icon: 'bi-braces text-info', + html: ` +

The formula is a math expression using your field variable names plus the reserved + variable rate (pre-filled from the template’s Default Rate).

+

Example — flat panel price (inches → sqft → dollars):

+
length_in * width_in / 144 * rate
+

Supported operations:

+
+
+
    +
  • +  -  *  / — basic math
  • +
  • Pow(base, exp) — exponents
  • +
  • Sqrt(x) — square root
  • +
+
+
+
    +
  • Round(x, digits) — rounding
  • +
  • Abs(x) — absolute value
  • +
  • Max(a,b)  Min(a,b)
  • +
+
+
+
+ + rate is always available — you set it as the Default Rate on the template + and staff can override it per-use. Don’t create a field called rate. +
` + }, + { + title: 'Step 3 — Test Before Saving', + icon: 'bi-play-circle text-success', + html: ` +

Before saving, use the Run button to verify your formula evaluates correctly + using each field’s default value.

+
+
+
+
+ + +
+
+ +
+
+  = 7.0000 +
+
+
Using defaults: length_in=24, width_in=12, rate=3.50 → 24×12/144×3.50 = $7.00
+
+
+

If the formula has an error (typo, missing variable, bad syntax) the result will + show in red with a description of the problem. Fix it before saving.

` + }, + { + title: 'Example — 6-Sided Box', + icon: 'bi-box text-secondary', + html: ` +

A roof curb or electrical enclosure can be priced by calculating its outer surface area.

+
+
Template Setup
+
+
+
+ Fields + + + + + + + +
VariableLabelDefault
l_inLength (in)24
w_inWidth (in)24
h_inHeight (in)12
+
+
+ Formula (Fixed Rate, rate = 3.50) +
2*(l_in*w_in + l_in*h_in + w_in*h_in) / 144 * rate
+ What it does: Calculates total outer surface area of all 6 faces + in square inches, converts to square feet (÷144), multiplies by rate. +
24×24×12 box at $3.50/sqft → $28.00
+
+
+
+
+

+ + You can add a diagram image to the template — staff will see it in the wizard as a reference + while entering measurements. +

` + }, + { + title: 'Example — Cylinder', + icon: 'bi-vinyl text-secondary', + html: ` +

Round parts like pipe ends or cylindrical housings use a slightly different formula.

+
+
Template Setup
+
+
+
+ Fields + + + + + + +
VariableLabelDefault
d_inDiameter (in)12
h_inHeight (in)18
+
+
+ Formula (Fixed Rate, rate = 3.50) +
(3.14159 * d_in * h_in + 2 * 3.14159 * Pow(d_in/2, 2)) / 144 * rate
+ What it does: Lateral surface area (circumference × height) + plus two circular end caps, converted to sqft, multiplied by rate. +
12″ dia × 18″ tall at $3.50/sqft → ~$10.21
+
+
+
+
+
+ + You’re ready to create your first template! + Click Get Started below to open the template editor, or close this guide + and click New Template any time. +
` + } + ]; + + window.cfShowWalkthrough = function () { + cfWtStep = 0; + cfRenderWtStep(); + new bootstrap.Modal(document.getElementById('cfWalkthroughModal')).show(); + localStorage.setItem('cfWalkthroughSeen', '1'); + }; + + window.cfWalkthroughNav = function (dir) { + const next = cfWtStep + dir; + if (next < 0) return; + if (next >= cfWtSteps.length) { + bootstrap.Modal.getInstance(document.getElementById('cfWalkthroughModal'))?.hide(); + cfShowCreate(); + return; + } + cfWtStep = next; + cfRenderWtStep(); + }; + + function cfRenderWtStep() { + const step = cfWtSteps[cfWtStep]; + const total = cfWtSteps.length; + const isLast = cfWtStep === total - 1; + + // Dots + document.getElementById('cfWalkthroughDots').innerHTML = cfWtSteps.map((_, i) => + `` + ).join(''); + + // Content + document.getElementById('cfWalkthroughContent').innerHTML = ` +
+ +
${step.title}
+ ${cfWtStep + 1} of ${total} +
+ ${step.html}`; + + // Buttons + document.getElementById('cfWtPrevBtn').style.visibility = cfWtStep === 0 ? 'hidden' : 'visible'; + const nextBtn = document.getElementById('cfWtNextBtn'); + if (isLast) { + nextBtn.innerHTML = 'Get Started'; + nextBtn.className = 'btn btn-success'; + } else { + nextBtn.innerHTML = 'Next'; + nextBtn.className = 'btn btn-primary'; + } + } + + window.cfWtJump = function (i) { + cfWtStep = i; + cfRenderWtStep(); + }; +})(); diff --git a/src/PowderCoating.Web/wwwroot/js/item-wizard.js b/src/PowderCoating.Web/wwwroot/js/item-wizard.js index 091ccf9..ff5ea36 100644 --- a/src/PowderCoating.Web/wwwroot/js/item-wizard.js +++ b/src/PowderCoating.Web/wwwroot/js/item-wizard.js @@ -17,7 +17,7 @@ let quoteItems = []; // Array of item objects matching CreateQuoteItemDto shap const wz = { // Wizard state step: 1, editIndex: -1, // -1 = new item; >= 0 = editing - itemType: null, // 'product' | 'calculated' | 'generic' | 'labor' | 'ai' + itemType: null, // 'product' | 'calculated' | 'generic' | 'labor' | 'ai' | 'formula' data: {}, // Collected field values ai: { // AI-specific wizard state phase: 'upload', // 'upload' | 'loading' | 'followup' | 'result' @@ -126,11 +126,12 @@ function openWizard(editIndex = -1) { if (editIndex >= 0) { const item = quoteItems[editIndex]; - wz.itemType = item.isLaborItem ? 'labor' - : item.isSalesItem ? 'sales' - : item.isGenericItem ? 'generic' - : item.catalogItemId ? 'product' - : item.isAiItem ? 'ai' + wz.itemType = item.isLaborItem ? 'labor' + : item.isSalesItem ? 'sales' + : item.isGenericItem ? 'generic' + : item.catalogItemId ? 'product' + : item.isAiItem ? 'ai' + : item.isCustomFormulaItem ? 'formula' : 'calculated'; // Pre-fill wizard data from existing item wz.data = JSON.parse(JSON.stringify(item)); // deep copy @@ -333,8 +334,18 @@ function renderStep1Html() { icon: 'bi-shop', label: 'Retail / Merchandise', desc: 'Off-the-shelf items — T-shirts, tumblers, apparel, or any product sold at a fixed price.' + }, + { + type: 'formula', + icon: 'bi-calculator', + label: 'Custom Formula Item', + desc: 'Use a saved formula template to price complex fabricated items (roof curbs, enclosures, frames).' } - ].filter(t => t.type !== 'ai' || pageMeta.aiPhotoQuotesEnabled !== false); + ].filter(t => { + if (t.type === 'ai') return pageMeta.aiPhotoQuotesEnabled !== false; + if (t.type === 'formula') return (pageMeta.customFormulaTemplates || []).length > 0; + return true; + }); return `
` + types.map(t => ` @@ -370,6 +381,7 @@ function renderStep2Html() { if (wz.itemType === 'labor') return renderLaborFields(); if (wz.itemType === 'ai') return renderAiPhotoFields(); if (wz.itemType === 'sales') return renderSalesFields(); + if (wz.itemType === 'formula') return renderFormulaFields(); return '

Unknown item type.

'; } @@ -809,6 +821,155 @@ document.addEventListener('mousedown', e => { } }); +// ─── Formula Item Fields ────────────────────────────────────────────────────── + +function renderFormulaFields() { + const templates = pageMeta.customFormulaTemplates || []; + if (!templates.length) return '

No formula templates found. Create one in Company Settings → Custom Formulas.

'; + + const selectedId = wz.data.customItemTemplateId || null; + const selected = templates.find(t => t.id === selectedId) || null; + + // Parse stored field values if re-editing + let storedValues = {}; + try { storedValues = JSON.parse(wz.data.formulaFieldValuesJson || '{}'); } catch { storedValues = {}; } + + const templateOptions = templates.map(t => + `` + ).join(''); + + let fieldsHtml = ''; + if (selected) { + let fields = []; + try { fields = JSON.parse(selected.fieldsJson || '[]'); } catch { fields = []; } + + const diagramHtml = selected.diagramImagePath + ? `
+ Template diagram +
Reference diagram for this template
+
` + : ''; + + const rateField = selected.outputMode === 'FixedRate' && selected.defaultRate != null + ? `
+
+
+
${escHtml(selected.rateLabel || '')}
+
` + : ''; + + fieldsHtml = ` + ${diagramHtml} + ${rateField} + ${fields.map(f => ` +
+
+
+ +
+
${escHtml(f.unit || '')}
+
`).join('')} +
+ + + ${selected.outputMode === 'FixedRate' ? 'total price' : 'sq ft'} +
`; + } + + return ` +
+ + +
Please select a template.
+
+
+ + +
+
+
+ + +
+
+
${fieldsHtml}
+
Please calculate the formula first.
`; +} + +window.wzFormulaTemplateChanged = function () { + const id = parseInt(document.getElementById('wz_formulaTemplate')?.value); + wz.data.customItemTemplateId = isNaN(id) ? null : id; + wz.data.formulaResult = null; + document.getElementById('wz_formula_fields').innerHTML = renderFormulaFieldInputs(); +}; + +function renderFormulaFieldInputs() { + const templates = pageMeta.customFormulaTemplates || []; + const selected = templates.find(t => t.id === wz.data.customItemTemplateId); + if (!selected) return ''; + // Re-render the inner fields portion + const tmp = renderFormulaFields(); + // Extract just the fields section by re-rendering with selected + const div = document.createElement('div'); + div.innerHTML = tmp; + return div.querySelector('#wz_formula_fields')?.innerHTML || ''; +} + +window.wzFormulaRecalc = async function () { + const templates = pageMeta.customFormulaTemplates || []; + const selected = templates.find(t => t.id === wz.data.customItemTemplateId); + if (!selected) return; + + let fields = []; + try { fields = JSON.parse(selected.fieldsJson || '[]'); } catch { fields = []; } + + const variables = {}; + fields.forEach(f => { + const el = document.getElementById(`wz_fld_${f.name}`); + variables[f.name] = el ? (parseFloat(el.value) || 0) : (f.defaultValue || 0); + }); + const rateEl = document.getElementById('wz_formula_rate'); + if (rateEl) variables['rate'] = parseFloat(rateEl.value) || 0; + + try { + const res = await fetch(pageMeta.formulaEvalUrl || '/CompanySettings/EvaluateFormula', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': getRequestVerificationToken() }, + body: JSON.stringify({ formula: selected.formula, variablesJson: JSON.stringify(variables) }) + }); + const data = await res.json(); + const el = document.getElementById('wz_formula_result'); + if (data.success && el) { + el.textContent = selected.outputMode === 'FixedRate' + ? `$${Number(data.result).toFixed(2)}` + : `${Number(data.result).toFixed(3)} sq ft`; + el.className = 'fw-bold fs-5 text-success'; + wz.data.formulaResult = data.result; + wz.data.formulaVariables = variables; + } else if (el) { + el.textContent = data.error || 'Error'; + el.className = 'fw-bold text-danger'; + } + } catch (e) { + const el = document.getElementById('wz_formula_result'); + if (el) { el.textContent = 'Request failed'; el.className = 'fw-bold text-danger'; } + } +}; + +function getRequestVerificationToken() { + return document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? ''; +} + function renderAiPhotoFields() { const existingPhotoHtml = wz.ai.tempIds.map((tid, i) => { const previewUrl = wz.ai.previewUrls[i] || ''; @@ -2510,6 +2671,47 @@ function collectStep2() { wz.data.isSalesItem = true; } + if (wz.itemType === 'formula') { + const templateId = parseInt(document.getElementById('wz_formulaTemplate')?.value); + if (!templateId) { + document.getElementById('err_formulaTemplate')?.classList.remove('d-none'); + valid = false; + } else { + document.getElementById('err_formulaTemplate')?.classList.add('d-none'); + wz.data.customItemTemplateId = templateId; + } + + if (wz.data.formulaResult == null) { + document.getElementById('err_formulaCalc')?.classList.remove('d-none'); + valid = false; + } else { + document.getElementById('err_formulaCalc')?.classList.add('d-none'); + } + + if (valid) { + const templates = pageMeta.customFormulaTemplates || []; + const tmpl = templates.find(t => t.id === templateId); + const qty = parseInt(document.getElementById('wz_formula_qty')?.value) || 1; + const descEl = document.getElementById('wz_formula_description'); + + wz.data.quantity = qty; + wz.data.description = descEl?.value?.trim() || (tmpl?.name ?? 'Custom Formula Item'); + wz.data.isCustomFormulaItem = true; + wz.data.formulaFieldValuesJson = JSON.stringify(wz.data.formulaVariables || {}); + + if (tmpl?.outputMode === 'FixedRate') { + wz.data.manualUnitPrice = parseFloat(wz.data.formulaResult) || 0; + wz.data.surfaceAreaSqFt = 0; + wz.data.estimatedMinutes = 0; + } else { + // SurfaceAreaSqFt mode — formula produced sq ft; standard engine prices it + wz.data.surfaceAreaSqFt = parseFloat(wz.data.formulaResult) || 0; + wz.data.manualUnitPrice = null; + wz.data.estimatedMinutes = 0; + } + } + } + if (wz.itemType === 'ai') { if (!wz.ai.accepted || !wz.ai.result) { document.getElementById('ai_acceptError')?.classList.remove('d-none'); @@ -2663,7 +2865,7 @@ function buildItemFromWizard() { surfaceAreaSqFt: d.surfaceAreaSqFt || 0, estimatedMinutes: d.estimatedMinutes || 0, catalogItemId: d.catalogItemId || null, - manualUnitPrice: isAi ? (d.manualUnitPrice ?? null) : (d.isGenericItem || d.isSalesItem ? (d.manualUnitPrice ?? null) : null), + manualUnitPrice: isAi ? (d.manualUnitPrice ?? null) : (d.isGenericItem || d.isSalesItem || d.isCustomFormulaItem ? (d.manualUnitPrice ?? null) : null), powderCostOverride: d.powderCostOverride ?? null, isGenericItem: !!d.isGenericItem, isLaborItem: !!d.isLaborItem, @@ -2671,6 +2873,9 @@ function buildItemFromWizard() { salesCatalogItemId: d.salesCatalogItemId || null, sku: d.sku || null, isAiItem: isAi, + isCustomFormulaItem: !!d.isCustomFormulaItem, + customItemTemplateId: d.customItemTemplateId || null, + formulaFieldValuesJson: d.formulaFieldValuesJson || null, requiresSandblasting: false, requiresMasking: false, notes: d.notes || null, @@ -2711,12 +2916,13 @@ function renderAllCards() { } function buildCardHtml(item, i) { - const typeInfo = item.isLaborItem ? { label: 'Labor', cls: 'info', icon: 'bi-person-gear' } - : item.isGenericItem ? { label: 'Flat-Rate', cls: 'warning', icon: 'bi-tag' } - : item.isSalesItem ? { label: 'Merchandise', cls: 'success', icon: 'bi-shop' } - : item.catalogItemId ? { label: 'Product', cls: 'primary', icon: 'bi-bag-check' } - : item.isAiItem ? { label: 'AI Quoted', cls: 'secondary', icon: 'bi-robot' } - : { label: 'Custom', cls: 'success', icon: 'bi-rulers' }; + const typeInfo = item.isLaborItem ? { label: 'Labor', cls: 'info', icon: 'bi-person-gear' } + : item.isGenericItem ? { label: 'Flat-Rate', cls: 'warning', icon: 'bi-tag' } + : item.isSalesItem ? { label: 'Merchandise', cls: 'success', icon: 'bi-shop' } + : item.catalogItemId ? { label: 'Product', cls: 'primary', icon: 'bi-bag-check' } + : item.isAiItem ? { label: 'AI Quoted', cls: 'secondary', icon: 'bi-robot' } + : item.isCustomFormulaItem ? { label: 'Formula', cls: 'purple', icon: 'bi-calculator' } + : { label: 'Custom', cls: 'success', icon: 'bi-rulers' }; const coatCount = item.coats?.length || 0; const isPrepOnly = coatCount === 0 && !item.isGenericItem && !item.isLaborItem && !item.isSalesItem && !item.catalogItemId; @@ -2855,6 +3061,11 @@ function writeHiddenFields() { if (item.isAiItem) fields.push(h(p + '.IsAiItem', 'true')); if (item.aiTags) fields.push(h(p + '.AiTags', item.aiTags)); if (item.aiPredictionId != null) fields.push(h(p + '.AiPredictionId', item.aiPredictionId)); + if (item.isCustomFormulaItem) { + fields.push(h(p + '.IsCustomFormulaItem', 'true')); + if (item.customItemTemplateId != null) fields.push(h(p + '.CustomItemTemplateId', item.customItemTemplateId)); + if (item.formulaFieldValuesJson) fields.push(h(p + '.FormulaFieldValuesJson', item.formulaFieldValuesJson)); + } (item.prepServices || []).forEach((ps, pi) => { const pp = `${p}.PrepServices[${pi}]`; @@ -3410,6 +3621,9 @@ function loadItemsFromTemplate(templateItems) { isGenericItem: !!ti.isGenericItem, isLaborItem: !!ti.isLaborItem, isAiItem: false, + isCustomFormulaItem: false, + customItemTemplateId: null, + formulaFieldValuesJson: null, requiresSandblasting: !!ti.requiresSandblasting, requiresMasking: !!ti.requiresMasking, notes: null, diff --git a/tests/PowderCoating.UnitTests/JobItemAssemblyServiceTests.cs b/tests/PowderCoating.UnitTests/JobItemAssemblyServiceTests.cs index 8ce943f..ed236dd 100644 --- a/tests/PowderCoating.UnitTests/JobItemAssemblyServiceTests.cs +++ b/tests/PowderCoating.UnitTests/JobItemAssemblyServiceTests.cs @@ -217,6 +217,7 @@ public class JobItemAssemblyServiceTests nameof(QuoteItem.IsLaborItem), nameof(QuoteItem.IsSalesItem), nameof(QuoteItem.IsAiItem), + nameof(QuoteItem.IsCustomFormulaItem), }; foreach (var flag in requiredPricingFlags)