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.Entities.SYS.Registries; using F9WebAiTest.Domain.Enums.SYS.Employees; using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments; namespace F9WebAiTest.Application.Services.Prod; /// /// 煮茧日报(生产-前缫):某日某车间某庄口某规格一条日报,含「机组」「班次」「机组班次」三层明细。 /// 对应 F9 Page_Zjrb(列表)、Page_Zjrb_EditV2(录入)、Zjrb_SaveCommand / Zjrb_DelCommand / /// CalculateJizuSaosiHoursCommand(计算缫丝时长)。 /// /// 计算口径(与 F9 前端 Page_Zjrb_EditV2 一致,服务端保存时重算): /// 机组.今缫桶数 = 甲班 + 乙班 /// 机组.今存桶数 = 上存 + 今缫 − 今落(今落为 0 且余量够一个标准桶时自动补「工艺落丝桶数」) /// 班次.今缫重量 = 该班次机组今缫桶数 × 每桶茧量 ÷ 1000 /// 班次.领入/退回重量 = 包数 × 联统茧标重(标重未配置则保留录入值) /// 班次.今缫包数 = 今缫重量 ÷ 联统茧标重(四舍五入取整) /// 班次.今存 = 上存 + 领入 − 今缫 − 退回 /// 主表 = 甲班 + 乙班 /// public interface IZjrbService { /// 日报列表(账期起止 + 车间 + 关键字) Task> GetPagedAsync(DateTime dateFrom, DateTime dateTo, long depId, string like, int pageIndex, int pageSize); /// 某条日报的机组明细(列表页下半表) Task> GetJizuListAsync(long zjrbId); /// 修改回填:主表 + 班次 + 机组 + 机组班次 Task GetForEditAsync(long id); /// 新增:按上一日结存带出上存、每桶茧量、工艺落丝桶数,并按机组车台生成机组行 Task BuildDefaultsAsync(DateTime date, long depId, long gyzkId, string guige); /// 保存(整条替换:主表 upsert + 三层明细先删后建) Task SaveAsync(ZjrbSaveDto dto); /// 删除(四张表级联物理删除) Task DeleteAsync(long id); /// 计算缫丝时长(整日整车间重算:单班 9 小时 / 双班 18 小时,按茧量比例分配) Task CalculateJizuSaosiHoursAsync(DateTime date, long depId); } public class ZjrbService : IZjrbService { /// 单班 / 双班的机组标准时长 private const decimal SingleBanciHours = 9m; private const decimal TwoBanciHours = 18m; /// 联统茧标重的参数配置位置(与 F9 一致) private const string RegistryCategory = @"生产\前缫\煮茧"; private const string RegistryKey = "联统茧标重"; private readonly IRepository _zjrbRepo; private readonly IRepository _jizuRepo; private readonly IRepository _banciRepo; private readonly IRepository _jizuBanciRepo; private readonly IRepository _jzctRepo; private readonly IRepository _cjbcRepo; private readonly IRepository _gyzkRepo; private readonly IRepository _registryRepo; private readonly IRepository _deptRepo; public ZjrbService(IRepository zjrbRepo, IRepository jizuRepo, IRepository banciRepo, IRepository jizuBanciRepo, IRepository jzctRepo, IRepository cjbcRepo, IRepository gyzkRepo, IRepository registryRepo, IRepository deptRepo) { _zjrbRepo = zjrbRepo; _jizuRepo = jizuRepo; _banciRepo = banciRepo; _jizuBanciRepo = jizuBanciRepo; _jzctRepo = jzctRepo; _cjbcRepo = cjbcRepo; _gyzkRepo = gyzkRepo; _registryRepo = registryRepo; _deptRepo = deptRepo; } // ==================== 查询 ==================== public async Task> GetPagedAsync(DateTime dateFrom, DateTime dateTo, long depId, string like, int pageIndex, int pageSize) { if (pageIndex <= 0) pageIndex = 1; if (pageSize <= 0) pageSize = 20; var from = dateFrom.Date; var to = dateTo.Date; if (to < from) (from, to) = (to, from); var ctx = await BuildContextAsync(); var rows = await _zjrbRepo.FindAsync(x => x.Date >= from && x.Date <= to); if (depId > 0) rows = rows.Where(x => x.DeptId == depId).ToList(); if (!string.IsNullOrWhiteSpace(like)) { var kw = like.Trim(); rows = rows.Where(x => ctx.GyzkNames.GetValueOrDefault(x.GyzkId, "").Contains(kw, StringComparison.OrdinalIgnoreCase) || (x.Guige ?? "").Contains(kw, StringComparison.OrdinalIgnoreCase)) .ToList(); } var total = rows.Count; var pageRows = rows .OrderByDescending(x => x.Date) .ThenBy(x => ctx.DeptNames.GetValueOrDefault(x.DeptId, "")) .ThenBy(x => x.GyzkId) .ThenBy(x => x.Guige) .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(); var ids = pageRows.Select(x => x.Id).ToList(); var jizus = ids.Count == 0 ? new List() : await _jizuRepo.FindAsync(x => ids.Contains(x.ZjrbId)); var items = pageRows.Select(x => { var jz = jizus.Where(j => j.ZjrbId == x.Id).ToList(); return new ZjrbReadDto_View { Id = x.Id, Date = x.Date, DepId = x.DeptId, DepName = ctx.DeptNames.GetValueOrDefault(x.DeptId, ""), GyzkId = x.GyzkId, GyzkName = ctx.GyzkNames.GetValueOrDefault(x.GyzkId, ""), Guige = x.Guige ?? "", LastDate = x.LastDate, StandardLuositongshu = x.StandardLuositongshu, MeitongJianliang = x.MeitongJianliang, BaoshuShangcun = x.BaoshuShangcun, BaoshuLingru = x.BaoshuLingru, BaoshuSaosi = x.BaoshuSaosi, BaoshuTuihui = x.BaoshuTuihui, BaoshuJincun = x.BaoshuJincun, JianliangShangcun = x.JianliangShangcun, JianliangLingru = x.JianliangLingru, JianliangSaosi = x.JianliangSaosi, JianliangTuihui = x.JianliangTuihui, JianliangJincun = x.JianliangJincun, TongshuShangcun = jz.Sum(j => j.TongshuShangcun), TongshuJinsao = jz.Sum(j => j.TongshuJinsao), TongshuJinluo = jz.Sum(j => j.TongshuJinluo), TongshuJincun = jz.Sum(j => j.TongshuJincun), JizuSaosiHours = jz.Sum(j => j.JizuSaosiHours) }; }).ToList(); return new PageResult { Items = items, Total = total, PageIndex = pageIndex, PageSize = pageSize }; } public async Task> GetJizuListAsync(long zjrbId) { var jizus = (await _jizuRepo.FindAsync(x => x.ZjrbId == zjrbId)).OrderBy(x => x.Jizu).ToList(); var jb = await _jizuBanciRepo.FindAsync(x => x.ZjrbId == zjrbId); return jizus.Select(j => ToJizuDto(j, jb)).ToList(); } public async Task GetForEditAsync(long id) { var master = await _zjrbRepo.GetByIdAsync(id); if (master == null) throw new ServiceException("煮茧日报不存在或已删除"); var ctx = await BuildContextAsync(); var banciRows = (await _banciRepo.FindAsync(x => x.ZjrbId == id)).OrderBy(x => x.Banci).ToList(); var jizuRows = (await _jizuRepo.FindAsync(x => x.ZjrbId == id)).OrderBy(x => x.Jizu).ToList(); var jbRows = await _jizuBanciRepo.FindAsync(x => x.ZjrbId == id); var dto = new ZjrbSaveDto { Id = master.Id, Date = master.Date, DepId = master.DeptId, DepName = ctx.DeptNames.GetValueOrDefault(master.DeptId, ""), GyzkId = master.GyzkId, GyzkName = ctx.GyzkNames.GetValueOrDefault(master.GyzkId, ""), Guige = master.Guige ?? "", LastDate = master.LastDate, StandardLuositongshu = master.StandardLuositongshu, MeitongJianliang = master.MeitongJianliang, LiantongBiaozhong = await GetLiantongBiaozhongAsync(), BaoshuShangcun = master.BaoshuShangcun, BaoshuLingru = master.BaoshuLingru, BaoshuSaosi = master.BaoshuSaosi, BaoshuTuihui = master.BaoshuTuihui, BaoshuJincun = master.BaoshuJincun, JianliangShangcun = master.JianliangShangcun, JianliangLingru = master.JianliangLingru, JianliangSaosi = master.JianliangSaosi, JianliangTuihui = master.JianliangTuihui, JianliangJincun = master.JianliangJincun, Bancis = banciRows.Select(ToBanciDto).ToList(), Jizus = jizuRows.Select(j => ToJizuDto(j, jbRows)).ToList() }; // 历史数据可能缺班次行(或车间班次配置调整过),按配置补齐甲乙班 var need = (await GetBanciKindsAsync(master.DeptId)); foreach (var b in need) { if (dto.Bancis.All(x => x.Banci != b)) dto.Bancis.Add(new ZjrbBanciDto { Banci = b, BanciName = b.ToString() }); } dto.Bancis = dto.Bancis.OrderBy(x => x.Banci).ToList(); return dto; } public async Task BuildDefaultsAsync(DateTime date, long depId, long gyzkId, string guige) { if (depId <= 0) throw new ServiceException("请选择车间"); var ctx = await BuildContextAsync(); var d = date.Date; var g = (guige ?? string.Empty).Trim(); // 上一日:同车间 + 同庄口(+ 同规格)的最近一条日报 var candidates = await _zjrbRepo.FindAsync(x => x.Date < d && x.DeptId == depId && x.GyzkId == gyzkId); var last = candidates .Where(x => string.IsNullOrEmpty(g) || (x.Guige ?? "") == g) .OrderByDescending(x => x.Date) .FirstOrDefault(); var dto = new ZjrbSaveDto { Date = d, DepId = depId, DepName = ctx.DeptNames.GetValueOrDefault(depId, ""), GyzkId = gyzkId, GyzkName = ctx.GyzkNames.GetValueOrDefault(gyzkId, ""), Guige = g, LiantongBiaozhong = await GetLiantongBiaozhongAsync() }; List lastBancis = new(); List lastJizus = new(); if (last != null) { dto.LastDate = last.Date; dto.StandardLuositongshu = last.StandardLuositongshu; dto.MeitongJianliang = last.MeitongJianliang; lastBancis = await _banciRepo.FindAsync(x => x.ZjrbId == last.Id); lastJizus = await _jizuRepo.FindAsync(x => x.ZjrbId == last.Id); } // 班次行:按车间班次配置;未配置则固定甲乙两班 foreach (var b in await GetBanciKindsAsync(depId)) { var prev = lastBancis.FirstOrDefault(x => x.Banci == b); dto.Bancis.Add(new ZjrbBanciDto { Banci = b, BanciName = b.ToString(), BaoshuShangcun = prev?.BaoshuJincun ?? 0, JianliangShangcun = prev?.JianliangJincun ?? 0 }); } // 机组行:按机组车台配置;上存桶数取上一日该机组今存桶数 var jizus = (await _jzctRepo.FindAsync(x => x.DepartmentId == depId)) .Select(x => x.Jizu).Distinct().OrderBy(x => x).ToList(); foreach (var jz in jizus) { var prev = lastJizus.FirstOrDefault(x => x.Jizu == jz); dto.Jizus.Add(new ZjrbJizuDto { Jizu = jz, TongshuShangcun = prev?.TongshuJincun ?? 0 }); } return dto; } // ==================== 保存 / 删除 ==================== public async Task SaveAsync(ZjrbSaveDto dto) { if (dto == null) throw new ServiceException("参数不能为空"); if (dto.Date == default) throw new ServiceException("请选择日期"); if (dto.DepId <= 0) throw new ServiceException("请选择车间"); if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口"); if (dto.MeitongJianliang <= 0) throw new ServiceException("每桶茧量必须大于 0"); if (dto.StandardLuositongshu <= 0) throw new ServiceException("工艺落丝桶数必须大于 0"); if (dto.Bancis == null || dto.Bancis.Count == 0) throw new ServiceException("请至少保留一个班次行"); var d = dto.Date.Date; var guige = (dto.Guige ?? string.Empty).Trim(); // 唯一性:同 日期 + 车间 + 庄口 + 规格 var exists = await _zjrbRepo.AnyAsync(x => x.Id != dto.Id && x.Date == d && x.DeptId == dto.DepId && x.GyzkId == dto.GyzkId && x.Guige == guige); if (exists) throw new ServiceException("已存在相同的煮茧日报!"); Normalize(dto, guige); long id = dto.Id; if (id > 0) { var master = await _zjrbRepo.GetByIdAsync(id); if (master == null) throw new ServiceException("煮茧日报不存在或已删除"); CopyMaster(dto, master); await _zjrbRepo.UpdateAsync(master); await DeleteChildrenAsync(id); } else { var master = new ZjrbRecord { LastDate = dto.LastDate }; CopyMaster(dto, master); await _zjrbRepo.AddAsync(master); id = master.Id; } // 班次 foreach (var b in dto.Bancis) { await _banciRepo.AddAsync(new ZjrbBanci { ZjrbId = id, Banci = b.Banci, BaoshuShangcun = b.BaoshuShangcun, BaoshuLingru = b.BaoshuLingru, BaoshuSaosi = b.BaoshuSaosi, BaoshuTuihui = b.BaoshuTuihui, BaoshuJincun = b.BaoshuJincun, JianliangShangcun = b.JianliangShangcun, JianliangLingru = b.JianliangLingru, JianliangSaosi = b.JianliangSaosi, JianliangTuihui = b.JianliangTuihui, JianliangJincun = b.JianliangJincun }); } // 机组 + 机组班次(机组班次挂在刚生成的机组 Id 上) foreach (var j in dto.Jizus) { var jizu = await _jizuRepo.AddAsync(new ZjrbJizu { ZjrbId = id, Jizu = j.Jizu, JizuSaosiHours = j.JizuSaosiHours, TongshuShangcun = j.TongshuShangcun, TongshuJinsao = j.TongshuJinsaoJiaban + j.TongshuJinsaoYiban, TongshuJinluo = j.TongshuJinluo, TongshuJincun = j.TongshuJincun }); foreach (var b in dto.Bancis) { await _jizuBanciRepo.AddAsync(new ZjrbJizuBanci { ZjrbId = id, ZjrbJizuId = jizu.Id, Jizu = j.Jizu, Banci = b.Banci, TongshuJinsao = b.Banci == Prod_Banci_Enum.甲 ? j.TongshuJinsaoJiaban : j.TongshuJinsaoYiban }); } } await _zjrbRepo.SaveChangesAsync(); return id; } public async Task DeleteAsync(long id) { var master = await _zjrbRepo.GetByIdAsync(id); if (master == null) throw new ServiceException("煮茧日报不存在或已删除"); await DeleteChildrenAsync(id); await _zjrbRepo.HardDeleteAsync(master); } private async Task DeleteChildrenAsync(long zjrbId) { var jizuRows = await _jizuRepo.FindAsync(x => x.ZjrbId == zjrbId); if (jizuRows.Count > 0) await _jizuRepo.HardDeleteRangeAsync(jizuRows); var banciRows = await _banciRepo.FindAsync(x => x.ZjrbId == zjrbId); if (banciRows.Count > 0) await _banciRepo.HardDeleteRangeAsync(banciRows); var jbRows = await _jizuBanciRepo.FindAsync(x => x.ZjrbId == zjrbId); if (jbRows.Count > 0) await _jizuBanciRepo.HardDeleteRangeAsync(jbRows); } // ==================== 计算缫丝时长 ==================== public async Task CalculateJizuSaosiHoursAsync(DateTime date, long depId) { if (depId <= 0) throw new ServiceException("请选择车间"); var d = date.Date; var reports = await _zjrbRepo.FindAsync(x => x.Date == d && x.DeptId == depId); if (reports.Count == 0) throw new ServiceException("该日该车间没有煮茧日报,无法计算缫丝时长"); var reportMap = reports.ToDictionary(x => x.Id); var ids = reportMap.Keys.ToList(); var jizus = await _jizuRepo.FindAsync(x => ids.Contains(x.ZjrbId)); if (jizus.Count == 0) throw new ServiceException("该日该车间没有机组明细,无法计算缫丝时长"); var jbRows = await _jizuBanciRepo.FindAsync(x => ids.Contains(x.ZjrbId)); // 双班判据:该机组「今缫桶数 > 0」的班次个数为 2 → 18 小时,否则 9 小时 var hasTwoBanci = jizus.Select(x => x.Jizu).Distinct().ToDictionary( jz => jz, jz => jbRows.Where(x => x.Jizu == jz && x.TongshuJinsao > 0) .Select(x => x.Banci).Distinct().Count() == 2); // 机组总茧量 = Σ(每桶茧量 × 机组今缫桶数) var totalJianliang = jizus.GroupBy(x => x.Jizu).ToDictionary( g => g.Key, g => g.Sum(x => (decimal)reportMap[x.ZjrbId].MeitongJianliang * x.TongshuJinsao)); var changed = 0; var groups = jizus.GroupBy(x => new { reportMap[x.ZjrbId].GyzkId, Guige = reportMap[x.ZjrbId].Guige ?? "", x.Jizu }); foreach (var group in groups) { var jz = group.Key.Jizu; var totalHours = hasTwoBanci.GetValueOrDefault(jz) ? TwoBanciHours : SingleBanciHours; var jizuTotal = totalJianliang.GetValueOrDefault(jz); var current = group.Sum(x => (decimal)reportMap[x.ZjrbId].MeitongJianliang * x.TongshuJinsao); var ratio = jizuTotal > 0 ? current / jizuTotal : 0m; var newHours = Math.Round(ratio * totalHours, 1, MidpointRounding.AwayFromZero); foreach (var item in group) { if (item.JizuSaosiHours == newHours) continue; item.JizuSaosiHours = newHours; await _jizuRepo.UpdateAsync(item); changed++; } } return changed; } // ==================== 内部:口径重算 / 上下文 ==================== /// 按 F9 编辑页口径重算派生字段(机组今存、班次重量与今存、主表合计) private static void Normalize(ZjrbSaveDto dto, string guige) { dto.Guige = guige; foreach (var j in dto.Jizus) { if (j.JizuSaosiHours < 0) throw new ServiceException($"机组{j.Jizu}的缫丝时长不能为负数"); if (j.JizuSaosiHours > 20) throw new ServiceException($"机组{j.Jizu}缫丝时长不能大于 20"); j.TongshuJinsao = j.TongshuJinsaoJiaban + j.TongshuJinsaoYiban; if (j.TongshuJinsao > 0 && j.JizuSaosiHours <= 0) throw new ServiceException($"机组{j.Jizu}今缫桶数大于 0 时,请输入缫丝时长"); var pre = j.TongshuShangcun + j.TongshuJinsao - j.TongshuJinluo; if (j.TongshuJinluo == 0 && dto.StandardLuositongshu > 0 && pre >= dto.StandardLuositongshu) { j.TongshuJinluo = dto.StandardLuositongshu; pre -= dto.StandardLuositongshu; } j.TongshuJincun = pre; } foreach (var b in dto.Bancis) { if (b.BaoshuShangcun > 0 && b.JianliangShangcun <= 0) throw new ServiceException($"{b.Banci}班上存包数大于 0 时,请填写上存重量"); var tongshu = dto.Jizus.Sum(j => b.Banci == Prod_Banci_Enum.甲 ? j.TongshuJinsaoJiaban : j.TongshuJinsaoYiban); b.JianliangSaosi = Math.Round(tongshu * (decimal)dto.MeitongJianliang / 1000m, 1, MidpointRounding.AwayFromZero); if (dto.LiantongBiaozhong > 0) { b.JianliangLingru = Math.Round(b.BaoshuLingru * dto.LiantongBiaozhong, 1, MidpointRounding.AwayFromZero); b.JianliangTuihui = Math.Round(b.BaoshuTuihui * dto.LiantongBiaozhong, 1, MidpointRounding.AwayFromZero); b.BaoshuSaosi = (int)Math.Round(b.JianliangSaosi / dto.LiantongBiaozhong, 0, MidpointRounding.AwayFromZero); } b.JianliangJincun = b.JianliangShangcun + b.JianliangLingru - b.JianliangSaosi - b.JianliangTuihui; b.BaoshuJincun = b.BaoshuShangcun + b.BaoshuLingru - b.BaoshuSaosi - b.BaoshuTuihui; } // 主表 = 甲班 + 乙班 dto.BaoshuShangcun = dto.Bancis.Sum(x => x.BaoshuShangcun); dto.BaoshuLingru = dto.Bancis.Sum(x => x.BaoshuLingru); dto.BaoshuSaosi = dto.Bancis.Sum(x => x.BaoshuSaosi); dto.BaoshuTuihui = dto.Bancis.Sum(x => x.BaoshuTuihui); dto.BaoshuJincun = dto.Bancis.Sum(x => x.BaoshuJincun); dto.JianliangShangcun = dto.Bancis.Sum(x => x.JianliangShangcun); dto.JianliangLingru = dto.Bancis.Sum(x => x.JianliangLingru); dto.JianliangSaosi = dto.Bancis.Sum(x => x.JianliangSaosi); dto.JianliangTuihui = dto.Bancis.Sum(x => x.JianliangTuihui); dto.JianliangJincun = dto.Bancis.Sum(x => x.JianliangJincun); } private static void CopyMaster(ZjrbSaveDto dto, ZjrbRecord master) { master.Date = dto.Date.Date; master.DeptId = dto.DepId; master.GyzkId = dto.GyzkId; master.Guige = dto.Guige ?? ""; master.LastDate = dto.LastDate; master.StandardLuositongshu = dto.StandardLuositongshu; master.MeitongJianliang = dto.MeitongJianliang; master.BaoshuShangcun = dto.BaoshuShangcun; master.BaoshuLingru = dto.BaoshuLingru; master.BaoshuSaosi = dto.BaoshuSaosi; master.BaoshuTuihui = dto.BaoshuTuihui; master.BaoshuJincun = dto.BaoshuJincun; master.JianliangShangcun = dto.JianliangShangcun; master.JianliangLingru = dto.JianliangLingru; master.JianliangSaosi = dto.JianliangSaosi; master.JianliangTuihui = dto.JianliangTuihui; master.JianliangJincun = dto.JianliangJincun; } private static ZjrbBanciDto ToBanciDto(ZjrbBanci x) => new() { Id = x.Id, ZjrbId = x.ZjrbId, Banci = x.Banci, BanciName = x.Banci.ToString(), BaoshuShangcun = x.BaoshuShangcun, BaoshuLingru = x.BaoshuLingru, BaoshuSaosi = x.BaoshuSaosi, BaoshuTuihui = x.BaoshuTuihui, BaoshuJincun = x.BaoshuJincun, JianliangShangcun = x.JianliangShangcun, JianliangLingru = x.JianliangLingru, JianliangSaosi = x.JianliangSaosi, JianliangTuihui = x.JianliangTuihui, JianliangJincun = x.JianliangJincun }; private static ZjrbJizuDto ToJizuDto(ZjrbJizu x, List jbRows) => new() { Id = x.Id, ZjrbId = x.ZjrbId, Jizu = x.Jizu, JizuSaosiHours = x.JizuSaosiHours, TongshuShangcun = x.TongshuShangcun, TongshuJinsaoJiaban = jbRows.FirstOrDefault(b => b.ZjrbJizuId == x.Id && b.Banci == Prod_Banci_Enum.甲) ?.TongshuJinsao ?? 0, TongshuJinsaoYiban = jbRows.FirstOrDefault(b => b.ZjrbJizuId == x.Id && b.Banci == Prod_Banci_Enum.乙) ?.TongshuJinsao ?? 0, TongshuJinsao = x.TongshuJinsao, TongshuJinluo = x.TongshuJinluo, TongshuJincun = x.TongshuJincun }; /// 车间班次配置;未配置时固定甲乙两班 private async Task> GetBanciKindsAsync(long depId) { var cfgs = await _cjbcRepo.FindAsync(x => x.DeptId == depId); if (cfgs.Count == 0) return new List { Prod_Banci_Enum.甲, Prod_Banci_Enum.乙 }; return cfgs.Select(x => x.Banci).Distinct().OrderBy(x => x).ToList(); } private async Task GetLiantongBiaozhongAsync() { var row = await _registryRepo.FirstOrDefaultAsync(x => x.Category == RegistryCategory && x.KeyName == RegistryKey); if (row == null || string.IsNullOrWhiteSpace(row.Value)) return 0m; return decimal.TryParse(row.Value, out var v) ? v : 0m; } private sealed class Ctx { public Dictionary DeptNames { get; init; } = new(); public Dictionary GyzkNames { get; init; } = new(); } private async Task BuildContextAsync() { var depts = await _deptRepo.GetAllAsync(); var gyzks = await _gyzkRepo.GetAllAsync(); return new Ctx { DeptNames = depts.ToDictionary(d => d.Id, d => d.Name), GyzkNames = gyzks.ToDictionary(z => z.Id, z => z.ProductionDisplayName is { Length: > 0 } n ? n : z.ZhuangKouName) }; } }