using F9WebAiTest.Application.Common; using F9WebAiTest.Application.Dtos.Prod; using F9WebAiTest.Domain.Common; using F9WebAiTest.Domain.Entities.Base; using F9WebAiTest.Domain.Entities.Prod; using F9WebAiTest.Domain.Enums.SYS.Employees; using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments; using SysEmp = F9WebAiTest.Domain.Entities.SYS.Employees; using SysWt = F9WebAiTest.Domain.Entities.SYS.WorkTypes; namespace F9WebAiTest.Application.Services.Prod; /// /// 看台记录(生产-前缫):按「日期 + 车间 + 班次」维护车头工 / 挡车工的实际看台情况。 /// 对应 F9 Page_Ktjl(列表)、Page_Ktjl_Dcg_Edit / Page_Ktjl_Ctg_Edit(录入弹窗)与 /// Ktjl_Dcg_SaveCommand / Ktjl_Ctg_SaveCommand(整组先删后建)。 /// public interface IKtjlService { // 查询 Task> GetCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci, string like); Task> GetDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci, string like); // 保存 / 删除(整组) Task SaveCtgAsync(KtjlctgBatchDto dto); Task SaveDcgAsync(KtjldcgBatchDto dto); Task DeleteCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci); Task DeleteDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci); /// 录入(新增):按看台排班 + 该车间工种职工,带出默认记录 Task> BuildCtgDefaultsAsync(DateTime date, long depId, Prod_Banci_Enum banci); Task> BuildDcgDefaultsAsync(DateTime date, long depId, Prod_Banci_Enum banci); /// 工种下拉(用于筛选/展示) Task> GetWorktypeOptionsAsync(); } public class ProdWorktypeOptionDto { public long Id { get; set; } public string Name { get; set; } = ""; } public class KtjlService : IKtjlService { private const decimal DefaultHours = 9m; private readonly IRepository _ctgRepo; private readonly IRepository _dcgRepo; private readonly IRepository _ktpbCtgRepo; private readonly IRepository _ktpbDcgRepo; private readonly IRepository _jzctRepo; private readonly IRepository _deptRepo; private readonly IRepository _empRepo; private readonly IRepository _wtRepo; public KtjlService(IRepository ctgRepo, IRepository dcgRepo, IRepository ktpbCtgRepo, IRepository ktpbDcgRepo, IRepository jzctRepo, IRepository deptRepo, IRepository empRepo, IRepository wtRepo) { _ctgRepo = ctgRepo; _dcgRepo = dcgRepo; _ktpbCtgRepo = ktpbCtgRepo; _ktpbDcgRepo = ktpbDcgRepo; _jzctRepo = jzctRepo; _deptRepo = deptRepo; _empRepo = empRepo; _wtRepo = wtRepo; } // ==================== 查询 ==================== public async Task> GetCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci, string like) { var d = date.Date; var ctx = await BuildContextAsync(); var rows = (await _ctgRepo.FindAsync(x => x.Date == d && x.DeptId == depId && x.Banci == banci)) .OrderBy(x => x.Jizu).ThenBy(x => x.Hours).ToList(); var views = rows.Select(x => ToCtgView(x, ctx)).ToList(); return Filter(views, like, v => $"{v.EmployeeName}{v.WorktypeName}{v.EmployeeDepName}{v.Jizu}"); } public async Task> GetDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci, string like) { var d = date.Date; var ctx = await BuildContextAsync(); var rows = (await _dcgRepo.FindAsync(x => x.Date == d && x.DeptId == depId && x.Banci == banci)) .OrderBy(x => x.Jizu).ThenBy(x => x.ChetaiStart).ThenBy(x => x.Hours).ToList(); var views = rows.Select(x => ToDcgView(x, ctx)).ToList(); return Filter(views, like, v => $"{v.EmployeeName}{v.WorktypeName}{v.EmployeeDepName}{v.Jizu}{v.ChetaiStart}{v.ChetaiEnd}"); } private static List Filter(List rows, string like, Func selector) { if (string.IsNullOrWhiteSpace(like)) return rows; var kw = like.Trim(); return rows.Where(x => selector(x).Contains(kw, StringComparison.OrdinalIgnoreCase)).ToList(); } // ==================== 保存 ==================== public async Task SaveCtgAsync(KtjlctgBatchDto dto) { if (dto.DepId <= 0) throw new ServiceException("请选择车间"); var items = dto.Items ?? new List(); foreach (var it in items) { if (it.EmployeeId <= 0) throw new ServiceException("不能有空数据(存在未选择职工的记录)"); if (it.Hours < 0) throw new ServiceException($"「{it.EmployeeName}」的时长不能为负数"); } var d = dto.Date.Date; await DeleteCtgCoreAsync(d, dto.DepId, dto.Banci); foreach (var it in items) { await _ctgRepo.AddAsync(new KtjlCtg { Date = d, DeptId = dto.DepId, Banci = dto.Banci, Jizu = it.Jizu, Hours = it.Hours, EmployeeId = it.EmployeeId, EmployeeDepId = it.EmployeeDepId, WorktypeId = it.WorktypeId, WorktypeName = it.WorktypeName ?? string.Empty, NoChanliang = it.NoChanliang }); } await _ctgRepo.SaveChangesAsync(); return items.Count; } public async Task SaveDcgAsync(KtjldcgBatchDto dto) { if (dto.DepId <= 0) throw new ServiceException("请选择车间"); var items = dto.Items ?? new List(); foreach (var it in items) { if (it.EmployeeId <= 0) throw new ServiceException("不能有空数据(存在未选择职工的记录)"); if (it.ChetaiStart <= 0) throw new ServiceException($"「{it.EmployeeName}」请填写开始车位"); if (it.ChetaiEnd < it.ChetaiStart) throw new ServiceException($"「{it.EmployeeName}」结束车位不能小于开始车位"); if (it.Hours < 0) throw new ServiceException($"「{it.EmployeeName}」的时长不能为负数"); } var d = dto.Date.Date; var jzcts = (await _jzctRepo.FindAsync(x => x.DepartmentId == dto.DepId)).OrderBy(x => x.Jizu).ToList(); // 挡车数 / 机组 由服务端重算(对齐 F9 CalDangcheNum + CalJizu) foreach (var it in items) { var startShared = items.Any(x => x.EmployeeId != it.EmployeeId && x.ChetaiEnd == it.ChetaiStart); var endShared = items.Any(x => x.EmployeeId != it.EmployeeId && x.ChetaiStart == it.ChetaiEnd); it.DangcheNum = it.ChetaiEnd + 1 - it.ChetaiStart - (startShared ? 0.5m : 0m) - (endShared ? 0.5m : 0m); var jzct = jzcts.FirstOrDefault(x => x.ChetaiStart <= it.ChetaiEnd && x.ChetaiEnd >= it.ChetaiStart); if (jzct != null) it.Jizu = jzct.Jizu; } await DeleteDcgCoreAsync(d, dto.DepId, dto.Banci); foreach (var it in items) { await _dcgRepo.AddAsync(new KtjlDcg { Date = d, DeptId = dto.DepId, Banci = dto.Banci, Jizu = it.Jizu, ChetaiStart = it.ChetaiStart, ChetaiEnd = it.ChetaiEnd, DangcheNum = it.DangcheNum, Hours = it.Hours, EmployeeId = it.EmployeeId, EmployeeDepId = it.EmployeeDepId, WorktypeId = it.WorktypeId, WorktypeName = it.WorktypeName ?? string.Empty, NoChanliang = it.NoChanliang }); } await _dcgRepo.SaveChangesAsync(); return items.Count; } public async Task DeleteCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci) { if (depId <= 0) throw new ServiceException("请选择车间"); await DeleteCtgCoreAsync(date.Date, depId, banci); await _ctgRepo.SaveChangesAsync(); } public async Task DeleteDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci) { if (depId <= 0) throw new ServiceException("请选择车间"); await DeleteDcgCoreAsync(date.Date, depId, banci); await _dcgRepo.SaveChangesAsync(); } private async Task DeleteCtgCoreAsync(DateTime date, long depId, Prod_Banci_Enum banci) { var olds = await _ctgRepo.FindAsync(x => x.Date == date && x.DeptId == depId && x.Banci == banci); if (olds.Count > 0) await _ctgRepo.HardDeleteRangeAsync(olds); } private async Task DeleteDcgCoreAsync(DateTime date, long depId, Prod_Banci_Enum banci) { var olds = await _dcgRepo.FindAsync(x => x.Date == date && x.DeptId == depId && x.Banci == banci); if (olds.Count > 0) await _dcgRepo.HardDeleteRangeAsync(olds); } // ==================== 默认记录(录入(新增)) ==================== public async Task> BuildCtgDefaultsAsync(DateTime date, long depId, Prod_Banci_Enum banci) { var ctx = await BuildContextAsync(); var deptName = ctx.DeptNames.GetValueOrDefault(depId, ""); var plans = (await _ktpbCtgRepo.FindAsync(x => x.DeptId == depId && x.Banci == banci)).OrderBy(x => x.Jizu); var result = new List(); foreach (var p in plans) { foreach (var ygId in new[] { p.Ygid1, p.Ygid2 }) { if (ygId <= 0 || !ctx.Emps.TryGetValue(ygId, out var emp)) continue; result.Add(new KtjlctgReadDto_View { Date = date.Date, DepId = depId, DepName = deptName, Banci = banci, BanciName = banci.ToString(), Jizu = p.Jizu, Hours = DefaultHours, EmployeeId = emp.Id, EmployeeName = emp.Name, EmployeeDepId = emp.DepId, EmployeeDepName = ctx.DeptNames.GetValueOrDefault(emp.DepId, ""), WorktypeId = emp.WorktypeId, WorktypeName = ctx.WorktypeNames.GetValueOrDefault(emp.WorktypeId, ""), NoChanliang = !emp.IsZhuanzheng }); } } return result; } public async Task> BuildDcgDefaultsAsync(DateTime date, long depId, Prod_Banci_Enum banci) { var ctx = await BuildContextAsync(); var deptName = ctx.DeptNames.GetValueOrDefault(depId, ""); var plans = (await _ktpbDcgRepo.FindAsync(x => x.DeptId == depId && x.Banci == banci)) .OrderBy(x => x.Jizu).ThenBy(x => x.ChetaiStart); var result = new List(); foreach (var p in plans) { if (p.YgId <= 0 || !ctx.Emps.TryGetValue(p.YgId, out var emp)) continue; result.Add(new KtjldcgReadDto_View { Date = date.Date, DepId = depId, DepName = deptName, Banci = banci, BanciName = banci.ToString(), Jizu = p.Jizu, ChetaiStart = p.ChetaiStart, ChetaiEnd = p.ChetaiEnd, DangcheNum = (decimal)p.Dangcheshu, Hours = DefaultHours, EmployeeId = emp.Id, EmployeeName = emp.Name, EmployeeDepId = emp.DepId, EmployeeDepName = ctx.DeptNames.GetValueOrDefault(emp.DepId, ""), WorktypeId = emp.WorktypeId, WorktypeName = ctx.WorktypeNames.GetValueOrDefault(emp.WorktypeId, ""), NoChanliang = !emp.IsZhuanzheng }); } return result; } public async Task> GetWorktypeOptionsAsync() => (await _wtRepo.GetAllAsync()).OrderBy(x => x.SortOrder).ThenBy(x => x.Id) .Select(x => new ProdWorktypeOptionDto { Id = x.Id, Name = x.WorkName }) .ToList(); // ==================== 上下文 ==================== private sealed class Ctx { public Dictionary DeptNames { get; init; } = new(); public Dictionary WorktypeNames { get; init; } = new(); public Dictionary Emps { get; init; } = new(); } private sealed class EmpInfo { public long Id { get; init; } public string Name { get; init; } = ""; public long DepId { get; init; } public long WorktypeId { get; init; } public bool IsZhuanzheng { get; init; } } private async Task BuildContextAsync() { var depts = await _deptRepo.GetAllAsync(); var wts = await _wtRepo.GetAllAsync(); var emps = await _empRepo.GetAllAsync(); return new Ctx { DeptNames = depts.ToDictionary(d => d.Id, d => d.Name), WorktypeNames = wts.ToDictionary(w => w.Id, w => w.WorkName), Emps = emps.ToDictionary(e => e.Id, e => new EmpInfo { Id = e.Id, Name = e.Name, DepId = e.DepId, WorktypeId = e.WorktypeId, IsZhuanzheng = e.IsZhuanzheng }) }; } private static KtjlctgReadDto_View ToCtgView(KtjlCtg x, Ctx ctx) => new() { Id = x.Id, Date = x.Date, DepId = x.DeptId, DepName = ctx.DeptNames.GetValueOrDefault(x.DeptId, ""), Banci = x.Banci, BanciName = x.Banci.ToString(), Jizu = x.Jizu, Hours = x.Hours, EmployeeId = x.EmployeeId, EmployeeName = ctx.Emps.GetValueOrDefault(x.EmployeeId)?.Name ?? "", EmployeeDepId = x.EmployeeDepId, EmployeeDepName = ctx.DeptNames.GetValueOrDefault(x.EmployeeDepId, ""), WorktypeId = x.WorktypeId, WorktypeName = string.IsNullOrWhiteSpace(x.WorktypeName) ? ctx.WorktypeNames.GetValueOrDefault(x.WorktypeId, "") : x.WorktypeName, NoChanliang = x.NoChanliang }; private static KtjldcgReadDto_View ToDcgView(KtjlDcg x, Ctx ctx) => new() { Id = x.Id, Date = x.Date, DepId = x.DeptId, DepName = ctx.DeptNames.GetValueOrDefault(x.DeptId, ""), Banci = x.Banci, BanciName = x.Banci.ToString(), Jizu = x.Jizu, ChetaiStart = x.ChetaiStart, ChetaiEnd = x.ChetaiEnd, DangcheNum = x.DangcheNum, Hours = x.Hours, EmployeeId = x.EmployeeId, EmployeeName = ctx.Emps.GetValueOrDefault(x.EmployeeId)?.Name ?? "", EmployeeDepId = x.EmployeeDepId, EmployeeDepName = ctx.DeptNames.GetValueOrDefault(x.EmployeeDepId, ""), WorktypeId = x.WorktypeId, WorktypeName = string.IsNullOrWhiteSpace(x.WorktypeName) ? ctx.WorktypeNames.GetValueOrDefault(x.WorktypeId, "") : x.WorktypeName, NoChanliang = x.NoChanliang }; }