diff --git a/src/PowderCoating.Core/Entities/Accounting.cs b/src/PowderCoating.Core/Entities/Accounting.cs
index 760494c..f9982da 100644
--- a/src/PowderCoating.Core/Entities/Accounting.cs
+++ b/src/PowderCoating.Core/Entities/Accounting.cs
@@ -395,3 +395,62 @@ public class FixedAssetDepreciationEntry : BaseEntity
public virtual FixedAsset FixedAsset { get; set; } = null!;
public virtual JournalEntry? JournalEntry { get; set; }
}
+
+///
+/// A named annual budget. Contains one BudgetLine per account per month. Supports
+/// multiple budgets per fiscal year (e.g. "Conservative" vs "Optimistic") but only
+/// one is marked IsDefault for the Budget vs. Actual report.
+///
+public class Budget : BaseEntity
+{
+ public string Name { get; set; } = string.Empty;
+ public int FiscalYear { get; set; }
+ public string? Notes { get; set; }
+ public bool IsDefault { get; set; } = false;
+
+ public virtual ICollection Lines { get; set; } = new List();
+}
+
+///
+/// Monthly budget amount for one account within a Budget. Jan–Dec stored as separate
+/// columns so the grid editor can write them in a single POST without a line-item loop.
+/// Annual is a computed property summing all twelve months.
+///
+public class BudgetLine : BaseEntity
+{
+ public int BudgetId { get; set; }
+ public int AccountId { get; set; }
+
+ public decimal Jan { get; set; }
+ public decimal Feb { get; set; }
+ public decimal Mar { get; set; }
+ public decimal Apr { get; set; }
+ public decimal May { get; set; }
+ public decimal Jun { get; set; }
+ public decimal Jul { get; set; }
+ public decimal Aug { get; set; }
+ public decimal Sep { get; set; }
+ public decimal Oct { get; set; }
+ public decimal Nov { get; set; }
+ public decimal Dec { get; set; }
+
+ public decimal Annual => Jan + Feb + Mar + Apr + May + Jun + Jul + Aug + Sep + Oct + Nov + Dec;
+
+ public virtual Budget Budget { get; set; } = null!;
+ public virtual Account Account { get; set; } = null!;
+}
+
+///
+/// Records a completed year-end close. The close posts a JE that zeroes all
+/// Revenue and Expense account balances into Retained Earnings, and marks
+/// the year as closed so it cannot be closed again.
+///
+public class YearEndClose : BaseEntity
+{
+ public int ClosedYear { get; set; }
+ public DateTime ClosedAt { get; set; } = DateTime.UtcNow;
+ public string? ClosedBy { get; set; }
+ public int JournalEntryId { get; set; }
+
+ public virtual JournalEntry JournalEntry { get; set; } = null!;
+}
diff --git a/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs b/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs
index fe97b53..224ff68 100644
--- a/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs
+++ b/src/PowderCoating.Core/Interfaces/IUnitOfWork.cs
@@ -113,6 +113,13 @@ public interface IUnitOfWork : IDisposable
IRepository FixedAssets { get; }
IRepository FixedAssetDepreciationEntries { get; }
+ // Budgeting
+ IRepository Budgets { get; }
+ IRepository BudgetLines { get; }
+
+ // Year-End Close
+ IRepository YearEndCloses { get; }
+
// Notifications — typed repository for IgnoreQueryFilters-based history lookups
INotificationLogRepository NotificationLogs { get; }
IRepository NotificationTemplates { get; }
diff --git a/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs b/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs
index 641319f..65e95d0 100644
--- a/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs
+++ b/src/PowderCoating.Infrastructure/Data/ApplicationDbContext.cs
@@ -343,6 +343,13 @@ public class ApplicationDbContext : IdentityDbContext, IDataPro
/// One record per asset per period for each depreciation posting; soft-delete only.
public DbSet FixedAssetDepreciationEntries { get; set; }
+ /// Named annual budgets with monthly amounts per GL account; tenant-filtered with soft delete.
+ public DbSet Budgets { get; set; }
+ /// One row per account per Budget; contains Jan–Dec decimal columns.
+ public DbSet BudgetLines { get; set; }
+ /// Audit trail of completed year-end closes; tenant-filtered with soft delete.
+ public DbSet YearEndCloses { get; set; }
+
/// Credit notes received from vendors (returned goods, pricing disputes); tenant-filtered with soft delete.
public DbSet VendorCredits { get; set; }
/// Expense-reversal line items on a vendor credit; soft-delete only.
@@ -687,6 +694,27 @@ public class ApplicationDbContext : IdentityDbContext, IDataPro
.HasForeignKey(e => e.JournalEntryId)
.OnDelete(DeleteBehavior.NoAction);
+ // Budgets: tenant-filtered; BudgetLines soft-delete only
+ modelBuilder.Entity().HasQueryFilter(e =>
+ !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
+ modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted);
+
+ // BudgetLine → Account: Restrict delete so removing an account doesn't cascade into budget data
+ modelBuilder.Entity()
+ .HasOne(bl => bl.Account)
+ .WithMany()
+ .HasForeignKey(bl => bl.AccountId)
+ .OnDelete(DeleteBehavior.Restrict);
+
+ // YearEndClose: tenant-filtered; links to a specific JE
+ modelBuilder.Entity().HasQueryFilter(e =>
+ !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
+ modelBuilder.Entity()
+ .HasOne(y => y.JournalEntry)
+ .WithMany()
+ .HasForeignKey(y => y.JournalEntryId)
+ .OnDelete(DeleteBehavior.Restrict);
+
// Vendor Credits: tenant-filtered; child rows soft-delete only
modelBuilder.Entity().HasQueryFilter(e =>
!e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
diff --git a/src/PowderCoating.Infrastructure/Migrations/20260510165511_AddBudgetsAndYearEndClose.Designer.cs b/src/PowderCoating.Infrastructure/Migrations/20260510165511_AddBudgetsAndYearEndClose.Designer.cs
new file mode 100644
index 0000000..1f158e6
--- /dev/null
+++ b/src/PowderCoating.Infrastructure/Migrations/20260510165511_AddBudgetsAndYearEndClose.Designer.cs
@@ -0,0 +1,10585 @@
+//
+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("20260510165511_AddBudgetsAndYearEndClose")]
+ partial class AddBudgetsAndYearEndClose
+ {
+ ///
+ 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("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("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("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