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.Departments; using F9WebAiTest.Domain.Enums.SYS.Employees; using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments; using SysEmp = F9WebAiTest.Domain.Entities.SYS.Employees; namespace F9WebAiTest.Application.Services.Prod; /// /// 看台排班(生产-前缫):车头工 / 挡车工 / 值班长三类排班的查询与维护。 /// 对应 F9 WinClient\F9.Business.Prod\QS\KTPB 下的 Page_KTPB 与三个编辑页。 /// public interface IKtpbService { /// 车间下拉(职责类型=前缫) Task> GetQianxianDepsAsync(); /// 指定车间的机组下拉(含车台范围,来源于机组车台) Task> GetJizuOptionsAsync(long depId); // 车头工 Task> GetCtgPagedAsync(long depId, Prod_Banci_Enum? banci, int? jizu, string keyword, int pageIndex, int pageSize); Task GetCtgByIdAsync(long id); Task CreateCtgAsync(KtpbctgReadDto_View dto); Task UpdateCtgAsync(long id, KtpbctgReadDto_View dto); Task DeleteCtgAsync(long id); // 挡车工 Task> GetDcgPagedAsync(long depId, Prod_Banci_Enum? banci, int? jizu, string keyword, int pageIndex, int pageSize); /// 取某个「车间+班次+机组」下的整组排班(批量编辑弹窗用) Task> GetDcgGroupAsync(long depId, Prod_Banci_Enum banci, int jizu); /// 整组替换:先删除该组旧排班,再按提交内容重建(挡车数由服务端重算) Task SaveDcgBatchAsync(KtpbdcgBatchDto dto); Task DeleteDcgAsync(long id); // 值班长 Task> GetZbzPagedAsync(long depId, Prod_Banci_Enum? banci, string keyword, int pageIndex, int pageSize); Task GetZbzByIdAsync(long id); Task CreateZbzAsync(KtpbzbzReadDto_View dto); Task UpdateZbzAsync(long id, KtpbzbzReadDto_View dto); Task DeleteZbzAsync(long id); } public class KtpbService : IKtpbService { private readonly IRepository _ctgRepo; private readonly IRepository _dcgRepo; private readonly IRepository _zbzRepo; private readonly IRepository _jzctRepo; private readonly IRepository _deptRepo; private readonly IRepository _empRepo; public KtpbService(IRepository ctgRepo, IRepository dcgRepo, IRepository zbzRepo, IRepository jzctRepo, IRepository deptRepo, IRepository empRepo) { _ctgRepo = ctgRepo; _dcgRepo = dcgRepo; _zbzRepo = zbzRepo; _jzctRepo = jzctRepo; _deptRepo = deptRepo; _empRepo = empRepo; } // ==================== 下拉 ==================== public async Task> GetQianxianDepsAsync() { var depts = await _deptRepo.GetAllAsync(); return depts .Where(d => d.DutyType == Department_DutyType_Enum.前缫) .OrderBy(d => d.SortOrder).ThenBy(d => d.Id) .Select(d => new ChejianbanciDepOptionDto { Id = d.Id, Name = d.Name }) .ToList(); } public async Task> GetJizuOptionsAsync(long depId) { var list = await _jzctRepo.FindAsync(x => x.DepartmentId == depId); return list.OrderBy(x => x.Jizu) .Select(x => new KtpbJizuOptionDto { Jizu = x.Jizu, ChetaiStart = x.ChetaiStart, ChetaiEnd = x.ChetaiEnd }) .ToList(); } // ==================== 车头工 ==================== public async Task> GetCtgPagedAsync(long depId, Prod_Banci_Enum? banci, int? jizu, string keyword, int pageIndex, int pageSize) { if (pageIndex <= 0) pageIndex = 1; if (pageSize <= 0) pageSize = 20; var deptDict = await GetDeptDictAsync(); var empDict = await GetEmpDictAsync(); var all = await _ctgRepo.GetAllAsync(); var q = all.AsEnumerable(); if (depId > 0) q = q.Where(x => x.DeptId == depId); if (banci.HasValue) q = q.Where(x => x.Banci == banci.Value); if (jizu.HasValue && jizu.Value > 0) q = q.Where(x => x.Jizu == jizu.Value); var views = q.OrderBy(x => x.DeptId).ThenBy(x => x.Banci).ThenBy(x => x.Jizu) .Select(x => ToCtgView(x, deptDict, empDict)).ToList(); if (!string.IsNullOrWhiteSpace(keyword)) { var kw = keyword.Trim(); views = views.Where(x => x.DeptName.Contains(kw) || x.BanciName.Contains(kw) || x.Jizu.ToString().Contains(kw) || x.Ygid1Name.Contains(kw) || x.Ygid2Name.Contains(kw)).ToList(); } return Paged(views, pageIndex, pageSize); } public async Task GetCtgByIdAsync(long id) { var e = await _ctgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404); return ToCtgView(e, await GetDeptDictAsync(), await GetEmpDictAsync()); } public async Task CreateCtgAsync(KtpbctgReadDto_View dto) { await ValidateGroupAsync(dto.DepId, dto.Banci, dto.Jizu, 0); if (dto.Ygid1 <= 0) throw new ServiceException("请选择车头工1"); await _ctgRepo.AddAsync(new KtpbCtg { DeptId = dto.DepId, Banci = dto.Banci, Jizu = dto.Jizu, Ygid1 = dto.Ygid1, Ygid2 = dto.Ygid2 }); await _ctgRepo.SaveChangesAsync(); } public async Task UpdateCtgAsync(long id, KtpbctgReadDto_View dto) { var e = await _ctgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404); await ValidateGroupAsync(dto.DepId, dto.Banci, dto.Jizu, id); if (dto.Ygid1 <= 0) throw new ServiceException("请选择车头工1"); e.DeptId = dto.DepId; e.Banci = dto.Banci; e.Jizu = dto.Jizu; e.Ygid1 = dto.Ygid1; e.Ygid2 = dto.Ygid2; await _ctgRepo.UpdateAsync(e); await _ctgRepo.SaveChangesAsync(); } public async Task DeleteCtgAsync(long id) { var e = await _ctgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404); await _ctgRepo.DeleteAsync(e); await _ctgRepo.SaveChangesAsync(); } // ==================== 挡车工 ==================== public async Task> GetDcgPagedAsync(long depId, Prod_Banci_Enum? banci, int? jizu, string keyword, int pageIndex, int pageSize) { if (pageIndex <= 0) pageIndex = 1; if (pageSize <= 0) pageSize = 20; var deptDict = await GetDeptDictAsync(); var empDict = await GetEmpDictAsync(); var all = await _dcgRepo.GetAllAsync(); var q = all.AsEnumerable(); if (depId > 0) q = q.Where(x => x.DeptId == depId); if (banci.HasValue) q = q.Where(x => x.Banci == banci.Value); if (jizu.HasValue && jizu.Value > 0) q = q.Where(x => x.Jizu == jizu.Value); var views = q.OrderBy(x => x.DeptId).ThenBy(x => x.Banci).ThenBy(x => x.Jizu) .ThenBy(x => x.ChetaiStart) .Select(x => ToDcgView(x, deptDict, empDict)).ToList(); if (!string.IsNullOrWhiteSpace(keyword)) { var kw = keyword.Trim(); views = views.Where(x => x.DeptName.Contains(kw) || x.BanciName.Contains(kw) || x.YgName.Contains(kw) || x.Jizu.ToString().Contains(kw) || x.ChetaiStart.ToString().Contains(kw) || x.ChetaiEnd.ToString().Contains(kw)).ToList(); } return Paged(views, pageIndex, pageSize); } public async Task> GetDcgGroupAsync(long depId, Prod_Banci_Enum banci, int jizu) { if (depId <= 0) throw new ServiceException("请选择车间"); var deptDict = await GetDeptDictAsync(); var empDict = await GetEmpDictAsync(); var list = await _dcgRepo.FindAsync(x => x.DeptId == depId && x.Banci == banci && x.Jizu == jizu); return list.OrderBy(x => x.ChetaiStart).ThenBy(x => x.ChetaiEnd) .Select(x => ToDcgView(x, deptDict, empDict)).ToList(); } public async Task SaveDcgBatchAsync(KtpbdcgBatchDto dto) { if (dto.DepId <= 0) throw new ServiceException("请选择车间"); if (dto.Jizu <= 0) throw new ServiceException("请选择机组"); var items = dto.Items ?? new List(); foreach (var it in items) { if (it.YgId <= 0) throw new ServiceException("存在未选择挡车工的记录"); if (it.ChetaiStart <= 0) throw new ServiceException($"挡车工「{it.YgName}」的起始车台必须大于 0"); if (it.ChetaiEnd < it.ChetaiStart) throw new ServiceException($"挡车工「{it.YgName}」的结束车台不能小于起始车台"); } var jzct = await _jzctRepo.FirstOrDefaultAsync(x => x.DepartmentId == dto.DepId && x.Jizu == dto.Jizu); if (jzct != null) { foreach (var it in items) { if (it.ChetaiStart < jzct.ChetaiStart || it.ChetaiEnd > jzct.ChetaiEnd) throw new ServiceException( $"挡车工「{it.YgName}」的车台范围 {it.ChetaiStart}-{it.ChetaiEnd} 超出该机组车台范围 " + $"{jzct.ChetaiStart}-{jzct.ChetaiEnd}"); } } // 整组替换:先删后建(对齐 F9 BatchReplaceKtpbdcgCommand) var olds = await _dcgRepo.FindAsync(x => x.DeptId == dto.DepId && x.Banci == dto.Banci && x.Jizu == dto.Jizu); if (olds.Count > 0) await _dcgRepo.HardDeleteRangeAsync(olds); var dangche = RecalcDangcheshu(items); foreach (var it in items) { await _dcgRepo.AddAsync(new KtpbDcg { DeptId = dto.DepId, Banci = dto.Banci, Jizu = dto.Jizu, ChetaiStart = it.ChetaiStart, ChetaiEnd = it.ChetaiEnd, YgId = it.YgId, Dangcheshu = dangche.GetValueOrDefault(BuildDcgKey(it.ChetaiStart, it.ChetaiEnd, it.YgId), 0) }); } await _dcgRepo.SaveChangesAsync(); return items.Count; } public async Task DeleteDcgAsync(long id) { var e = await _dcgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404); await _dcgRepo.DeleteAsync(e); await _dcgRepo.SaveChangesAsync(); } /// /// 挡车数计算(移植 F9 Page_KTPBDCG_Edit.CalculateSingleDangcheshu): /// 每个车台默认权重 1;若该车台被多条记录共享,且同时是某记录的起始点与某记录的结束点(接壤车台),权重取 0.5。 /// private static Dictionary RecalcDangcheshu(List records) { var result = new Dictionary(); if (records.Count == 0) return result; var counts = new Dictionary(); var starts = new HashSet(); var ends = new HashSet(); foreach (var r in records) { starts.Add(r.ChetaiStart); ends.Add(r.ChetaiEnd); for (var i = r.ChetaiStart; i <= r.ChetaiEnd; i++) counts[i] = counts.GetValueOrDefault(i) + 1; } foreach (var r in records) { double total = 0; for (var i = r.ChetaiStart; i <= r.ChetaiEnd; i++) { double weight = 1; if (counts.GetValueOrDefault(i) > 1 && starts.Contains(i) && ends.Contains(i)) weight = 0.5; total += weight; } result[BuildDcgKey(r.ChetaiStart, r.ChetaiEnd, r.YgId)] = Math.Round(total, 2); } return result; } private static string BuildDcgKey(int start, int end, long ygId) => $"{start}-{end}-{ygId}"; // ==================== 值班长 ==================== public async Task> GetZbzPagedAsync(long depId, Prod_Banci_Enum? banci, string keyword, int pageIndex, int pageSize) { if (pageIndex <= 0) pageIndex = 1; if (pageSize <= 0) pageSize = 20; var deptDict = await GetDeptDictAsync(); var empDict = await GetEmpDictAsync(); var all = await _zbzRepo.GetAllAsync(); var q = all.AsEnumerable(); if (depId > 0) q = q.Where(x => x.DeptId == depId); if (banci.HasValue) q = q.Where(x => x.Banci == banci.Value); var views = q.OrderBy(x => x.DeptId).ThenBy(x => x.Banci).ThenBy(x => x.Id) .Select(x => ToZbzView(x, deptDict, empDict)).ToList(); if (!string.IsNullOrWhiteSpace(keyword)) { var kw = keyword.Trim(); views = views.Where(x => x.DeptName.Contains(kw) || x.BanciName.Contains(kw) || x.YgName.Contains(kw)) .ToList(); } return Paged(views, pageIndex, pageSize); } public async Task GetZbzByIdAsync(long id) { var e = await _zbzRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404); return ToZbzView(e, await GetDeptDictAsync(), await GetEmpDictAsync()); } public async Task CreateZbzAsync(KtpbzbzReadDto_View dto) { if (dto.DepId <= 0) throw new ServiceException("请选择车间"); if (dto.YgId <= 0) throw new ServiceException("请选择值班长"); var dup = await _zbzRepo.AnyAsync(x => x.DeptId == dto.DepId && x.Banci == dto.Banci && x.YgId == dto.YgId); if (dup) throw new ServiceException("该车间该班次已存在此值班长排班"); await _zbzRepo.AddAsync(new KtpbZbz { DeptId = dto.DepId, Banci = dto.Banci, YgId = dto.YgId }); await _zbzRepo.SaveChangesAsync(); } public async Task UpdateZbzAsync(long id, KtpbzbzReadDto_View dto) { var e = await _zbzRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404); if (dto.DepId <= 0) throw new ServiceException("请选择车间"); if (dto.YgId <= 0) throw new ServiceException("请选择值班长"); var dup = await _zbzRepo.AnyAsync(x => x.Id != id && x.DeptId == dto.DepId && x.Banci == dto.Banci && x.YgId == dto.YgId); if (dup) throw new ServiceException("该车间该班次已存在此值班长排班"); e.DeptId = dto.DepId; e.Banci = dto.Banci; e.YgId = dto.YgId; await _zbzRepo.UpdateAsync(e); await _zbzRepo.SaveChangesAsync(); } public async Task DeleteZbzAsync(long id) { var e = await _zbzRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404); await _zbzRepo.DeleteAsync(e); await _zbzRepo.SaveChangesAsync(); } // ==================== 公共 ==================== /// 车头工:同一「车间+班次+机组」只能有一条排班 private async Task ValidateGroupAsync(long depId, Prod_Banci_Enum banci, int jizu, long excludeId) { if (depId <= 0) throw new ServiceException("请选择车间"); if (jizu <= 0) throw new ServiceException("请选择机组"); var dup = await _ctgRepo.AnyAsync(x => x.Id != excludeId && x.DeptId == depId && x.Banci == banci && x.Jizu == jizu); if (dup) throw new ServiceException("该车间该班次该机组已有车头工排班"); } private async Task> GetDeptDictAsync() => (await _deptRepo.GetAllAsync()).ToDictionary(d => d.Id, d => d.Name); private async Task> GetEmpDictAsync() => (await _empRepo.GetAllAsync()).ToDictionary(e => e.Id, e => e.Name); private static KtpbctgReadDto_View ToCtgView(KtpbCtg x, Dictionary deptDict, Dictionary empDict) => new() { Id = x.Id, DepId = x.DeptId, DeptName = deptDict.GetValueOrDefault(x.DeptId, ""), Banci = x.Banci, BanciName = x.Banci.ToString(), Jizu = x.Jizu, Ygid1 = x.Ygid1, Ygid1Name = empDict.GetValueOrDefault(x.Ygid1, ""), Ygid2 = x.Ygid2, Ygid2Name = empDict.GetValueOrDefault(x.Ygid2, "") }; private static KtpbdcgReadDto_View ToDcgView(KtpbDcg x, Dictionary deptDict, Dictionary empDict) => new() { Id = x.Id, DepId = x.DeptId, DeptName = deptDict.GetValueOrDefault(x.DeptId, ""), Banci = x.Banci, BanciName = x.Banci.ToString(), Jizu = x.Jizu, ChetaiStart = x.ChetaiStart, ChetaiEnd = x.ChetaiEnd, YgId = x.YgId, YgName = empDict.GetValueOrDefault(x.YgId, ""), Dangcheshu = x.Dangcheshu }; private static KtpbzbzReadDto_View ToZbzView(KtpbZbz x, Dictionary deptDict, Dictionary empDict) => new() { Id = x.Id, DepId = x.DeptId, DeptName = deptDict.GetValueOrDefault(x.DeptId, ""), Banci = x.Banci, BanciName = x.Banci.ToString(), YgId = x.YgId, YgName = empDict.GetValueOrDefault(x.YgId, "") }; private static PageResult Paged(List all, int pageIndex, int pageSize) => new() { Items = all.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(), Total = all.Count, PageIndex = pageIndex, PageSize = pageSize }; }