using F9WebAiTest.Application.Common; using F9WebAiTest.Application.Dtos; using F9WebAiTest.Application.Dtos.Base; using F9WebAiTest.Application.Dtos.Prod; using F9WebAiTest.Application.Services.Notice; using F9WebAiTest.Domain.Common; using F9WebAiTest.Domain.Entities; using F9WebAiTest.Domain.Entities.Base; using F9WebAiTest.Domain.Entities.Notice; using F9WebAiTest.Domain.Entities.Prod; using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments; using MediatR; namespace F9WebAiTest.Application.Services.Prod; public interface IXdjyService { Task> GetPagedAsync(int pageIndex, int pageSize, DateTime? date, long? deptId); Task GetEditAsync(long lsjlId); Task SaveAsync(XdjySaveDto dto); } public class XdjyService : IXdjyService { private readonly IRepository _lsjlRepo; private readonly IRepository _xdjyLsjlRepo; private readonly IRepository _xdjyRecordRepo; private readonly IRepository _guigeRepo; private readonly IRepository _deptRepo; private readonly IRepository _zkRepo; private readonly IMediator _mediator; public XdjyService( IRepository lsjlRepo, IRepository xdjyLsjlRepo, IRepository xdjyRecordRepo, IRepository guigeRepo, IRepository deptRepo, IRepository zkRepo, IMediator mediator) { _lsjlRepo = lsjlRepo; _xdjyLsjlRepo = xdjyLsjlRepo; _xdjyRecordRepo = xdjyRecordRepo; _guigeRepo = guigeRepo; _deptRepo = deptRepo; _zkRepo = zkRepo; _mediator = mediator; } public async Task> GetPagedAsync(int pageIndex, int pageSize, DateTime? date, long? deptId) { var lsjls = await _lsjlRepo.GetAllAsync(); var q = lsjls.AsQueryable(); if (date.HasValue) q = q.Where(x => x.WorkDate.Date == date.Value.Date); if (deptId.HasValue) q = q.Where(x => x.DeptId == deptId.Value); var depts = await _deptRepo.GetAllAsync(); var deptDict = depts.ToDictionary(d => d.Id, d => d.Name); var zks = await _zkRepo.GetAllAsync(); var zkDict = zks.ToDictionary(z => z.Id, z => z.ZhuangKouName); var sums = (await _xdjyLsjlRepo.GetAllAsync()).ToDictionary(x => x.LsjlId); var total = q.LongCount(); var items = q.OrderByDescending(x => x.WorkDate).ThenByDescending(x => x.Id) .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(); return new PageResult { Items = items.Select(x => { sums.TryGetValue(x.Id, out var sum); var yingcheng = x.Chetaishu * (x.Isbanche ? 2 : 4); return new XdjyListItemDto { LsjlId = x.Id, WorkDate = x.WorkDate, DeptId = x.DeptId, DeptName = deptDict.GetValueOrDefault(x.DeptId, ""), Jizu = x.Jizu, Huishu = x.Huishu, Guige = x.Guige, ZhuangKouName = zkDict.GetValueOrDefault(x.GyzkId, ""), Yanse = x.Yanse, Isbanche = x.Isbanche, IsbancheName = x.Isbanche ? "半车" : "整车", Chetaishu = x.Chetaishu, ChetaiStart = x.ChetaiStart, ChetaiEnd = x.ChetaiEnd, LuosiTime = x.LuosiTime, Yingchengnum = yingcheng, IsChecked = sum != null, Jiaoshu = sum?.Jiaoshu ?? 0, Avgxiandu = sum?.Avgxiandu ?? 0, Junfangcha = sum?.Junfangcha ?? 0, Zongcha = sum?.Zongcha ?? 0, FanxianLv = sum?.FanxianLv ?? 0, FanweiNum = sum?.FanweiNum ?? 0, XiaoyeNum = sum?.XiaoyeNum ?? 0, DayeNum = sum?.DayeNum ?? 0, TeyeNum = sum?.TeyeNum ?? 0, CenterNum = sum?.CenterNum ?? 0, Dengji = sum?.Dengji ?? "" }; }).ToList(), Total = total, PageIndex = pageIndex, PageSize = pageSize }; } public async Task GetEditAsync(long lsjlId) { var lsjl = await _lsjlRepo.GetByIdAsync(lsjlId); if (lsjl == null) throw new ServiceException("落丝记录不存在", 404); var dept = await _deptRepo.GetByIdAsync(lsjl.DeptId); var zk = await _zkRepo.GetByIdAsync(lsjl.GyzkId); var gg = await _guigeRepo.FirstOrDefaultAsync(g => g.Guige == lsjl.Guige); if (gg == null) throw new ServiceException($"未找到规格「{lsjl.Guige}」的生丝规格配置,请先维护生丝规格"); var sum = await _xdjyLsjlRepo.FirstOrDefaultAsync(x => x.LsjlId == lsjlId); var records = await _xdjyRecordRepo.FindAsync(x => x.LsjlId == lsjlId); List items; if (records.Count > 0) { // 已检验:加载已有明细 items = records.OrderBy(r => r.Chetaihao).Select(ToDto).ToList(); } else { // 首次检验:按车台范围初始化空明细 items = new List(); for (int i = lsjl.ChetaiStart; i <= lsjl.ChetaiEnd; i++) { items.Add(new XdjyRecordDto { Chetaihao = i }); } } return new XdjyEditDto { LsjlId = lsjl.Id, DeptName = dept?.Name ?? "", Jizu = lsjl.Jizu, Huishu = lsjl.Huishu, ZhuangKouName = zk?.ZhuangKouName ?? "", Yanse = lsjl.Yanse, Isbanche = lsjl.Isbanche, IsbancheName = lsjl.Isbanche ? "半车" : "整车", Guige = lsjl.Guige, ChetaiStart = lsjl.ChetaiStart, ChetaiEnd = lsjl.ChetaiEnd, Chetaishu = lsjl.Chetaishu, Yingchengnum = lsjl.Chetaishu * (lsjl.Isbanche ? 2 : 4), Dengji = sum?.Dengji ?? "", GuigeConfig = new ShengsiGuigeDto { Id = gg.Id, Guige = gg.Guige, CenterrangeLolimit = gg.CenterrangeLolimit, CenterrangeUplimit = gg.CenterrangeUplimit, CenterXiandu = gg.CenterXiandu, XiaoyeLolimit = gg.XiaoyeLolimit, XiaoyeUplimit = gg.XiaoyeUplimit, DayeLolimit = gg.DayeLolimit, DayeUplimit = gg.DayeUplimit, TeyeLolimit = gg.TeyeLolimit, TeyeUplimit = gg.TeyeUplimit }, Xdjy = items }; } public async Task SaveAsync(XdjySaveDto dto) { if (dto.LsjlId == 0) throw new ServiceException("落丝记录无效"); var lsjl = await _lsjlRepo.GetByIdAsync(dto.LsjlId); if (lsjl == null) throw new ServiceException("落丝记录不存在", 404); var gg = await _guigeRepo.FirstOrDefaultAsync(g => g.Guige == lsjl.Guige); if (gg == null) throw new ServiceException($"未找到规格「{lsjl.Guige}」的生丝规格配置"); if (dto.Items == null || dto.Items.Count == 0) throw new ServiceException("请先录入纤度数据"); // 校验车台号在范围内 var invalid = dto.Items.Where(i => i.Chetaihao < lsjl.ChetaiStart || i.Chetaihao > lsjl.ChetaiEnd).ToList(); if (invalid.Count > 0) throw new ServiceException($"车台号超出范围 [{lsjl.ChetaiStart}-{lsjl.ChetaiEnd}]"); // 计算每个车台指标 var calcItems = dto.Items.Select(i => { var r = new XdjyRecordDto { Chetaihao = i.Chetaihao, Xd1 = i.Xd1, Xd2 = i.Xd2, Xd3 = i.Xd3, Xd4 = i.Xd4 }; CalcChetai(r, gg); return r; }).ToList(); var jiaoshu = calcItems.Sum(x => x.XdNum); if (jiaoshu == 0) throw new ServiceException("请至少录入一个纤度值"); // 物理删除旧明细(避免软删除残留导致唯一索引冲突) var oldRecords = await _xdjyRecordRepo.FindAsync(x => x.LsjlId == dto.LsjlId); await _xdjyRecordRepo.HardDeleteRangeAsync(oldRecords); foreach (var item in calcItems) { await _xdjyRecordRepo.AddAsync(new XdjyRecord { LsjlId = dto.LsjlId, Chetaihao = item.Chetaihao, Xd1 = item.Xd1, Xd2 = item.Xd2, Xd3 = item.Xd3, Xd4 = item.Xd4, XdNum = item.XdNum, XdAvg = item.XdAvg, FanweiNum = item.FanweiNum, XiaoyeNum = item.XiaoyeNum, DayeNum = item.DayeNum, TeyeNum = item.TeyeNum, CenterNum = item.CenterNum }); } // 汇总指标 var sum = CalcSummary(calcItems, gg); var xdjyLsjl = await _xdjyLsjlRepo.FirstOrDefaultAsync(x => x.LsjlId == dto.LsjlId); if (xdjyLsjl == null) { xdjyLsjl = new XdjyLsjl { LsjlId = dto.LsjlId }; await _xdjyLsjlRepo.AddAsync(xdjyLsjl); } xdjyLsjl.Jiaoshu = sum.Jiaoshu; xdjyLsjl.Avgxiandu = sum.Avgxiandu; xdjyLsjl.Junfangcha = sum.Junfangcha; xdjyLsjl.Zongcha = sum.Zongcha; xdjyLsjl.FanxianLv = sum.FanxianLv; xdjyLsjl.FanweiNum = sum.FanweiNum; xdjyLsjl.XiaoyeNum = sum.XiaoyeNum; xdjyLsjl.DayeNum = sum.DayeNum; xdjyLsjl.TeyeNum = sum.TeyeNum; xdjyLsjl.CenterNum = sum.CenterNum; xdjyLsjl.Dengji = dto.Dengji ?? ""; await _xdjyLsjlRepo.UpdateAsync(xdjyLsjl); // 业务系统触发消息:发现特野时,通过 MediatR 创建"发现特野"消息,由 BackgroundService 投递 if (sum.TeyeNum > 0) { await _mediator.Send(new CreateNotificationCommand { NotificationTypeCode = "TeyeFound", BusinessType = "Xdjy", BusinessId = dto.LsjlId.ToString(), Title = $"发现特野:{lsjl.Guige} 规格", Content = $"落丝记录(机组 {lsjl.Jizu}、回数 {lsjl.Huishu})纤度检验发现 {sum.TeyeNum} 个特野值,请关注。", Priority = NotificationPriority.Urgent, CreatorId = 0, CreatorName = "系统" }); } } // ==================== 计算逻辑(与 WinForms 版保持一致) ==================== private static XdjyRecordDto ToDto(XdjyRecord r) => new() { Chetaihao = r.Chetaihao, Xd1 = r.Xd1, Xd2 = r.Xd2, Xd3 = r.Xd3, Xd4 = r.Xd4, XdNum = r.XdNum, XdAvg = r.XdAvg, FanweiNum = r.FanweiNum, XiaoyeNum = r.XiaoyeNum, DayeNum = r.DayeNum, TeyeNum = r.TeyeNum, CenterNum = r.CenterNum }; private static void CalcChetai(XdjyRecordDto item, ShengsiGuige gg) { item.XdNum = (item.Xd1 > 0 ? 1 : 0) + (item.Xd2 > 0 ? 1 : 0) + (item.Xd3 > 0 ? 1 : 0) + (item.Xd4 > 0 ? 1 : 0); if (item.XdNum > 0) { item.XdAvg = Math.Round((item.Xd1 + item.Xd2 + item.Xd3 + item.Xd4) / item.XdNum, 2, MidpointRounding.AwayFromZero); item.FanweiNum = Fanwei(item.Xd1, gg) + Fanwei(item.Xd2, gg) + Fanwei(item.Xd3, gg) + Fanwei(item.Xd4, gg); item.XiaoyeNum = Xiaoye(item.Xd1, gg) + Xiaoye(item.Xd2, gg) + Xiaoye(item.Xd3, gg) + Xiaoye(item.Xd4, gg); item.DayeNum = Daye(item.Xd1, gg) + Daye(item.Xd2, gg) + Daye(item.Xd3, gg) + Daye(item.Xd4, gg); item.TeyeNum = Teye(item.Xd1, gg) + Teye(item.Xd2, gg) + Teye(item.Xd3, gg) + Teye(item.Xd4, gg); item.CenterNum = Center(item.Xd1, gg) + Center(item.Xd2, gg) + Center(item.Xd3, gg) + Center(item.Xd4, gg); } else { item.XdAvg = 0; item.FanweiNum = 0; item.XiaoyeNum = 0; item.DayeNum = 0; item.TeyeNum = 0; item.CenterNum = 0; } } private static XdjyLsjl CalcSummary(List items, ShengsiGuige gg) { var jiaoshu = items.Sum(x => x.XdNum); var avgXiandu = jiaoshu == 0 ? 0 : items.Sum(x => x.Xd1 + x.Xd2 + x.Xd3 + x.Xd4) / jiaoshu; // 均方差:sqrt(Σ(xi-avg)² / n) double junfangcha = 0; if (jiaoshu > 0) { double sumDiff = 0; foreach (var it in items) { foreach (var xd in new[] { it.Xd1, it.Xd2, it.Xd3, it.Xd4 }) { if (xd > 0) { var d = xd - avgXiandu; sumDiff += d * d; } } } junfangcha = Math.Round(Math.Sqrt(Math.Round(sumDiff / jiaoshu, 2)), 2, MidpointRounding.AwayFromZero); } // 总差:抽样绞数 = ceil(n*0.02),最细/最粗各取,与总平均比较取最大差值 double zongcha = 0; if (jiaoshu > 0) { var xds = new List(); foreach (var it in items) { if (it.Xd1 > 0) xds.Add(it.Xd1); if (it.Xd2 > 0) xds.Add(it.Xd2); if (it.Xd3 > 0) xds.Add(it.Xd3); if (it.Xd4 > 0) xds.Add(it.Xd4); } xds.Sort(); var chouyang = Math.Max(1, (int)Math.Ceiling(xds.Count * 0.02)); var minAvg = xds.Take(chouyang).Average(); var maxAvg = xds.Skip(xds.Count - chouyang).Take(chouyang).Average(); var avgR = Math.Round(avgXiandu, 2, MidpointRounding.AwayFromZero); zongcha = Math.Round(Math.Max(Math.Abs(minAvg - avgR), Math.Abs(maxAvg - avgR)), 2, MidpointRounding.AwayFromZero); } var fanweiNum = items.Sum(x => x.FanweiNum); var fanxianLv = jiaoshu == 0 ? 0 : Math.Round((double)fanweiNum * 100 / jiaoshu, 0, MidpointRounding.AwayFromZero); return new XdjyLsjl { Jiaoshu = jiaoshu, Avgxiandu = Math.Round(avgXiandu, 2, MidpointRounding.AwayFromZero), Junfangcha = junfangcha, Zongcha = zongcha, FanxianLv = fanxianLv, FanweiNum = fanweiNum, XiaoyeNum = items.Sum(x => x.XiaoyeNum), DayeNum = items.Sum(x => x.DayeNum), TeyeNum = items.Sum(x => x.TeyeNum), CenterNum = items.Sum(x => x.CenterNum) }; } // ---- 判定(纤度 <= 0 视为未称) ---- private static int Fanwei(double xd, ShengsiGuige gg) => xd > 0 && gg.CenterrangeLolimit <= xd && gg.CenterrangeUplimit >= xd ? 1 : 0; private static int Xiaoye(double xd, ShengsiGuige gg) => xd > 0 && ((gg.XiaoyeLolimit >= xd && xd > gg.DayeLolimit) || (gg.XiaoyeUplimit <= xd && xd < gg.DayeUplimit)) ? 1 : 0; private static int Daye(double xd, ShengsiGuige gg) => xd > 0 && ((gg.DayeLolimit >= xd && xd > gg.TeyeLolimit) || (gg.DayeUplimit <= xd && xd < gg.TeyeUplimit)) ? 1 : 0; private static int Teye(double xd, ShengsiGuige gg) => xd > 0 && (gg.TeyeLolimit >= xd || gg.TeyeUplimit <= xd) ? 1 : 0; private static int Center(double xd, ShengsiGuige gg) => xd > 0 && gg.CenterXiandu == xd ? 1 : 0; }