Remove ShopWorker entity and migrate worker identity to ApplicationUser

Removes the ShopWorker and ShopWorkerRoleCost entities, all related DTOs,
mappings, controllers, views, and import/export paths. Worker identity is
now handled entirely through ApplicationUser with per-user LaborCostPerHour.
ShopWorkerRoleCosts table remains in production pending manual data migration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 20:32:32 -04:00
parent 3b5511a703
commit 1a44133a63
43 changed files with 10989 additions and 1055 deletions
@@ -58,7 +58,14 @@ public class ApplicationUser : IdentityUser
public string? SidebarColor { get; set; } = "ocean";
public string? Notes { get; set; }
/// <summary>
/// Per-worker labor cost per hour used for job costing profit/margin calculations.
/// Overrides the company-level LaborCostPerHour when set.
/// Leave null to use the company default.
/// </summary>
public decimal? LaborCostPerHour { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public DateTime? LastLoginDate { get; set; }
+1 -2
View File
@@ -141,8 +141,7 @@ public class Company : BaseEntity
public virtual ICollection<Quote> Quotes { get; set; } = new List<Quote>();
public virtual ICollection<InventoryItem> InventoryItems { get; set; } = new List<InventoryItem>();
public virtual ICollection<Vendor> Vendors { get; set; } = new List<Vendor>();
public virtual ICollection<ShopWorker> ShopWorkers { get; set; } = new List<ShopWorker>();
public virtual ICollection<PricingTier> PricingTiers { get; set; } = new List<PricingTier>();
public virtual ICollection<PricingTier> PricingTiers { get; set; } = new List<PricingTier>();
public virtual CompanyOperatingCosts? OperatingCosts { get; set; }
public virtual CompanyPreferences? Preferences { get; set; }
}
@@ -13,6 +13,14 @@ namespace PowderCoating.Core.Entities
[Range(0, 10000)]
public decimal StandardLaborRate { get; set; }
/// <summary>
/// Actual labor cost per hour (wages + burden) used exclusively for internal job costing and profit/margin display.
/// This is NOT the billing rate — it should reflect what you actually pay workers.
/// When null, the costing engine defaults to 20% of StandardLaborRate.
/// </summary>
[Range(0, 10000)]
public decimal? LaborCostPerHour { get; set; }
// Additional Coat Labor Percentage (percentage of base labor for each additional coat beyond the first)
[Range(0, 100)]
public decimal AdditionalCoatLaborPercent { get; set; } = 30m;
@@ -3,7 +3,6 @@ namespace PowderCoating.Core.Entities;
public class JobTimeEntry : BaseEntity
{
public int JobId { get; set; }
public int? ShopWorkerId { get; set; } // legacy — kept for entries created before user migration
public string? UserId { get; set; } // FK to AspNetUsers
public string? UserDisplayName { get; set; } // snapshot of worker name at entry creation time
public DateTime WorkDate { get; set; }
@@ -13,5 +12,4 @@ public class JobTimeEntry : BaseEntity
// Navigation
public virtual Job Job { get; set; } = null!;
public virtual ShopWorker? Worker { get; set; } // nullable — only populated for legacy entries
}
@@ -1,18 +0,0 @@
using PowderCoating.Core.Enums;
namespace PowderCoating.Core.Entities;
public class ShopWorker : BaseEntity
{
public string Name { get; set; } = string.Empty;
public ShopWorkerRole Role { get; set; } = ShopWorkerRole.GeneralLabor;
public string? Phone { get; set; }
public string? Email { get; set; }
public bool IsActive { get; set; } = true;
public string? Notes { get; set; }
// Relationships
public virtual ICollection<Job> AssignedJobs { get; set; } = new List<Job>();
public virtual ICollection<MaintenanceRecord> AssignedMaintenanceTasks { get; set; } = new List<MaintenanceRecord>();
public virtual ICollection<JobTimeEntry> TimeEntries { get; set; } = new List<JobTimeEntry>();
}
@@ -1,15 +0,0 @@
using PowderCoating.Core.Enums;
namespace PowderCoating.Core.Entities;
/// <summary>
/// Optional per-role labor cost rate for job costing / profitability calculations.
/// If no rate is set for a role, the company's StandardLaborRate is used as fallback.
/// </summary>
public class ShopWorkerRoleCost : BaseEntity
{
public ShopWorkerRole Role { get; set; }
/// <summary>Cost (pay rate) per hour for this role — used in job costing, NOT billing.</summary>
public decimal HourlyRate { get; set; }
}