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:
@@ -3368,8 +3368,7 @@ public class JobsController : Controller
|
||||
public async Task<IActionResult> GetTimeEntries(int jobId)
|
||||
{
|
||||
var entries = await _unitOfWork.JobTimeEntries.FindAsync(
|
||||
e => e.JobId == jobId, false,
|
||||
e => e.Worker); // Worker nav loaded for display of legacy entries that pre-date user migration
|
||||
e => e.JobId == jobId, false);
|
||||
var dtos = _mapper.Map<List<JobTimeEntryDto>>(entries.OrderByDescending(e => e.WorkDate).ToList());
|
||||
return Json(dtos);
|
||||
}
|
||||
@@ -3823,15 +3822,24 @@ public class JobsController : Controller
|
||||
|
||||
// Operating costs for fallback labor rate and oven rate
|
||||
var opCosts = (await _unitOfWork.CompanyOperatingCosts.FindAsync(c => c.CompanyId == companyId)).FirstOrDefault();
|
||||
var fallbackLaborRate = opCosts?.StandardLaborRate ?? 0m;
|
||||
var effectiveOvenMinutes = (opCosts?.DefaultOvenCycleMinutes > 0 ? (int?)opCosts!.DefaultOvenCycleMinutes : null) ?? 45;
|
||||
var defaultOvenCycleHours = effectiveOvenMinutes / 60.0m;
|
||||
|
||||
// Role cost rates map: role → hourly rate
|
||||
var roleCosts = await _unitOfWork.ShopWorkerRoleCosts.FindAsync(r => r.CompanyId == companyId);
|
||||
var roleCostMap = roleCosts.ToDictionary(r => r.Role, r => r.HourlyRate);
|
||||
// Labor cost rate priority: per-user LaborCostPerHour → company LaborCostPerHour → 20% of StandardLaborRate
|
||||
var companyLaborCostRate = opCosts?.LaborCostPerHour ?? ((opCosts?.StandardLaborRate ?? 0m) * 0.20m);
|
||||
var companyUsers = await _userManager.Users
|
||||
.Where(u => u.CompanyId == companyId && u.LaborCostPerHour != null)
|
||||
.Select(u => new { u.Id, u.LaborCostPerHour })
|
||||
.ToListAsync();
|
||||
var userLaborCostMap = companyUsers.ToDictionary(u => u.Id, u => u.LaborCostPerHour!.Value);
|
||||
|
||||
// 1. Powder / Material cost
|
||||
// Priority: PowderUsageLog actuals (sum per coat) > coat.ActualPowderUsedLbs > coat.PowderToOrder (estimated)
|
||||
var usageLogs = await _unitOfWork.PowderUsageLogs.FindAsync(u => u.JobId == jobId);
|
||||
var actualByCoat = usageLogs
|
||||
.GroupBy(u => u.JobItemCoatId)
|
||||
.ToDictionary(g => g.Key, g => g.Sum(u => u.ActualLbsUsed));
|
||||
|
||||
decimal powderCost = 0m;
|
||||
var powderLines = new List<object>();
|
||||
bool hasCoatsWithRateButNoQty = false;
|
||||
@@ -3839,7 +3847,19 @@ public class JobsController : Controller
|
||||
{
|
||||
foreach (var coat in item.Coats)
|
||||
{
|
||||
var lbs = coat.ActualPowderUsedLbs ?? coat.PowderToOrder ?? 0m;
|
||||
bool isActual;
|
||||
decimal lbs;
|
||||
if (actualByCoat.TryGetValue(coat.Id, out var loggedLbs) && loggedLbs > 0)
|
||||
{
|
||||
lbs = loggedLbs;
|
||||
isActual = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
lbs = coat.ActualPowderUsedLbs ?? coat.PowderToOrder ?? 0m;
|
||||
isActual = coat.ActualPowderUsedLbs.HasValue;
|
||||
}
|
||||
|
||||
var costPerLb = coat.PowderCostPerLb ?? 0m;
|
||||
var lineCost = lbs * costPerLb;
|
||||
powderCost += lineCost;
|
||||
@@ -3850,7 +3870,7 @@ public class JobsController : Controller
|
||||
lbs = Math.Round(lbs, 3),
|
||||
costPerLb = Math.Round(costPerLb, 4),
|
||||
total = Math.Round(lineCost, 2),
|
||||
isActual = coat.ActualPowderUsedLbs.HasValue
|
||||
isActual
|
||||
});
|
||||
}
|
||||
else if (costPerLb > 0 && lbs == 0)
|
||||
@@ -3862,20 +3882,23 @@ public class JobsController : Controller
|
||||
}
|
||||
|
||||
// 2. Labor cost
|
||||
// Priority: per-user LaborCostPerHour → company LaborCostPerHour → 20% of StandardLaborRate
|
||||
decimal laborCost = 0m;
|
||||
var laborLines = new List<object>();
|
||||
foreach (var entry in job.TimeEntries)
|
||||
{
|
||||
var rate = entry.Worker != null && roleCostMap.TryGetValue(entry.Worker.Role, out var r) ? r : fallbackLaborRate;
|
||||
bool usingPerUser = entry.UserId != null && userLaborCostMap.TryGetValue(entry.UserId, out _);
|
||||
var rate = usingPerUser
|
||||
? userLaborCostMap[entry.UserId!]
|
||||
: companyLaborCostRate;
|
||||
var lineCost = entry.HoursWorked * rate;
|
||||
laborCost += lineCost;
|
||||
laborLines.Add(new {
|
||||
worker = entry.Worker?.Name ?? "Unknown",
|
||||
role = entry.Worker != null ? System.Text.RegularExpressions.Regex.Replace(entry.Worker.Role.ToString(), "([a-z])([A-Z])", "$1 $2") : "",
|
||||
worker = entry.UserDisplayName ?? "Unknown",
|
||||
hours = entry.HoursWorked,
|
||||
rate = Math.Round(rate, 2),
|
||||
total = Math.Round(lineCost, 2),
|
||||
usingFallback = entry.Worker == null || !roleCostMap.ContainsKey(entry.Worker.Role),
|
||||
usingFallback = !usingPerUser,
|
||||
stage = entry.Stage,
|
||||
workDate = entry.WorkDate.ToString("MM/dd/yyyy")
|
||||
});
|
||||
@@ -3949,7 +3972,7 @@ public class JobsController : Controller
|
||||
grossMargin,
|
||||
quotedMargin,
|
||||
quotedPrice = Math.Round(job.QuotedPrice, 2),
|
||||
fallbackLaborRate,
|
||||
companyLaborCostRate,
|
||||
powderLines,
|
||||
laborLines,
|
||||
hasPowderData = powderLines.Count > 0,
|
||||
|
||||
Reference in New Issue
Block a user