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
@@ -112,6 +112,7 @@ namespace PowderCoating.Application.DTOs.Company
// Labor Rates
public decimal StandardLaborRate { get; set; }
public decimal? LaborCostPerHour { get; set; }
public decimal AdditionalCoatLaborPercent { get; set; }
// Equipment Operating Costs
@@ -185,6 +186,10 @@ namespace PowderCoating.Application.DTOs.Company
[Display(Name = "Standard Labor Rate ($/hr)")]
public decimal StandardLaborRate { get; set; }
[Range(0, 10000, ErrorMessage = "Labor cost rate must be between 0 and 10,000")]
[Display(Name = "Shop Labor Cost Rate ($/hr)")]
public decimal? LaborCostPerHour { get; set; }
[Range(0, 100, ErrorMessage = "Additional coat labor percent must be between 0 and 100")]
[Display(Name = "Additional Coat Labor (%)")]
public decimal AdditionalCoatLaborPercent { get; set; } = 30m;
@@ -1,28 +0,0 @@
using CsvHelper.Configuration.Attributes;
namespace PowderCoating.Application.DTOs.Import;
/// <summary>
/// DTO for importing shop workers from CSV files.
/// Valid Role values: GeneralLabor, Sandblaster, Coater, Masker, QualityControl, OvenOperator, Supervisor, Maintenance
/// </summary>
public class ShopWorkerImportDto
{
[Name("Name")]
public string Name { get; set; } = string.Empty;
[Name("Role")]
public string Role { get; set; } = "GeneralLabor";
[Name("Phone")]
public string? Phone { get; set; }
[Name("Email")]
public string? Email { get; set; }
[Name("IsActive")]
public bool? IsActive { get; set; }
[Name("Notes")]
public string? Notes { get; set; }
}
@@ -1,27 +0,0 @@
using System.ComponentModel.DataAnnotations;
using PowderCoating.Core.Enums;
namespace PowderCoating.Application.DTOs.ShopWorker;
public class CreateShopWorkerDto
{
[Required(ErrorMessage = "Worker name is required")]
[StringLength(100, ErrorMessage = "Name cannot exceed 100 characters")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "Role is required")]
public ShopWorkerRole Role { get; set; } = ShopWorkerRole.GeneralLabor;
[Phone(ErrorMessage = "Invalid phone number format")]
[StringLength(20, ErrorMessage = "Phone cannot exceed 20 characters")]
public string? Phone { get; set; }
[EmailAddress(ErrorMessage = "Invalid email address format")]
[StringLength(100, ErrorMessage = "Email cannot exceed 100 characters")]
public string? Email { get; set; }
public bool IsActive { get; set; } = true;
[StringLength(500, ErrorMessage = "Notes cannot exceed 500 characters")]
public string? Notes { get; set; }
}
@@ -1,16 +0,0 @@
using PowderCoating.Core.Enums;
namespace PowderCoating.Application.DTOs.ShopWorker;
public class ShopWorkerDto
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public ShopWorkerRole Role { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public bool IsActive { get; set; }
public string? Notes { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
@@ -1,29 +0,0 @@
using System.ComponentModel.DataAnnotations;
using PowderCoating.Core.Enums;
namespace PowderCoating.Application.DTOs.ShopWorker;
public class UpdateShopWorkerDto
{
public int Id { get; set; }
[Required(ErrorMessage = "Worker name is required")]
[StringLength(100, ErrorMessage = "Name cannot exceed 100 characters")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "Role is required")]
public ShopWorkerRole Role { get; set; }
[Phone(ErrorMessage = "Invalid phone number format")]
[StringLength(20, ErrorMessage = "Phone cannot exceed 20 characters")]
public string? Phone { get; set; }
[EmailAddress(ErrorMessage = "Invalid email address format")]
[StringLength(100, ErrorMessage = "Email cannot exceed 100 characters")]
public string? Email { get; set; }
public bool IsActive { get; set; }
[StringLength(500, ErrorMessage = "Notes cannot exceed 500 characters")]
public string? Notes { get; set; }
}
@@ -217,6 +217,10 @@ public class UpdateCompanyUserDto
[Display(Name = "Active")]
public bool IsActive { get; set; }
[Range(0, 10000, ErrorMessage = "Labor cost rate must be between 0 and 10,000")]
[Display(Name = "Labor Cost Rate ($/hr)")]
public decimal? LaborCostPerHour { get; set; }
[Required(ErrorMessage = "Hire date is required")]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
@@ -136,18 +136,7 @@ public interface ICsvImportService
/// </summary>
Task<CsvImportResultDto> ImportVendorsAsync(Stream csvStream, int companyId);
/// <summary>
/// Generate a CSV template file for shop worker imports.
/// </summary>
byte[] GenerateShopWorkerTemplate();
/// <summary>
/// Import shop workers from a CSV stream.
/// Updates existing workers matched by Name; creates new ones otherwise.
/// </summary>
Task<CsvImportResultDto> ImportShopWorkersAsync(Stream csvStream, int companyId);
/// <summary>
/// <summary>
/// Generate a CSV template file for prep service imports.
/// </summary>
byte[] GeneratePrepServiceTemplate();
@@ -73,7 +73,7 @@ public class JobProfile : Profile
// JobTimeEntry → JobTimeEntryDto
CreateMap<JobTimeEntry, JobTimeEntryDto>()
.ForMember(dest => dest.WorkerName, opt => opt.MapFrom(src =>
src.UserDisplayName ?? (src.Worker != null ? src.Worker.Name : string.Empty)));
src.UserDisplayName ?? string.Empty));
// CreateJobDto to Job
CreateMap<CreateJobDto, Job>()
@@ -1,23 +0,0 @@
using AutoMapper;
using PowderCoating.Application.DTOs.ShopWorker;
using PowderCoating.Core.Entities;
namespace PowderCoating.Application.Mappings;
public class ShopWorkerProfile : Profile
{
public ShopWorkerProfile()
{
// Entity to DTO
CreateMap<ShopWorker, ShopWorkerDto>();
// DTO to Entity
CreateMap<CreateShopWorkerDto, ShopWorker>();
CreateMap<UpdateShopWorkerDto, ShopWorker>();
// Reverse mappings
CreateMap<ShopWorkerDto, ShopWorker>();
CreateMap<ShopWorker, CreateShopWorkerDto>();
CreateMap<ShopWorker, UpdateShopWorkerDto>();
}
}