using Infrastructure.Attribute; using Microsoft.Extensions.Logging; using MQTTnet.Protocol; using SqlSugar; using System; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using ZR.Common.MqttHelper; using ZR.Model.Business; using ZR.Model.Dto; using ZR.Model.MES.wms; using ZR.Service.Business.IBusinessService; using ZR.Service.mqtt; namespace ZR.Service.Business { /// /// 质量BackEnd工单业务模块Service业务层处理 /// [AppService(ServiceType = typeof(IQcBackEndService), ServiceLifetime = LifeTime.Transient)] public class QcBackEndService : BaseService, IQcBackEndService { private readonly MqttService _mqttService; // 注入MqttService private readonly ILogger _logger; public QcBackEndService(MqttService mqttService, ILogger logger) { _mqttService = mqttService; _logger = logger; } public QcBackEndLabelAnalysisDto AnalyzeLabelToDto(string label, int type) { QcBackEndLabelAnalysisDto labelAnalysisDto = new() { IsOk = true, Msg = "解析成功!", LabelCode = label, }; // 判断内外箱标签 // 解析零件号 labelAnalysisDto.Partnumber = DoAnalyzePartnumber(label); // 解析数量 labelAnalysisDto.Number = DoAnalyzeQuantity(label); if (string.IsNullOrEmpty(labelAnalysisDto.Partnumber)) { labelAnalysisDto.IsOk = false; labelAnalysisDto.Msg = "标签,零件号解析异常!"; } // TYPE === 1 时,同时根据物料清单获取详细信息 if (type == 1) { string checkPartnumber = labelAnalysisDto.Partnumber; // 使用正则表达式匹配并移除特殊后缀 string processedPartnumber = Regex.Replace( checkPartnumber, @"-(FL|FR|RR|RL)$", "", RegexOptions.IgnoreCase ); WmMaterial material = Context .Queryable() .Where(it => it.Partnumber == processedPartnumber) .Where(it => it.Type == 1) .Where(it => it.Status == 1) .First(); if (material == null) { labelAnalysisDto.IsOk = false; labelAnalysisDto.Msg = "物料清单内无此零件号!请检查物料清单:" + processedPartnumber; } else { labelAnalysisDto.Color = material.Color; labelAnalysisDto.Specification = material.Specification; labelAnalysisDto.Description = !string.IsNullOrEmpty(material.Description) ? material.Description : material.ProductName; } } return labelAnalysisDto; } // 标签的零件号解析 public string DoAnalyzePartnumber(string label) { // 通用规则下的标签零件号抓取 var predicate = Expressionable .Create() .And(it => it.Code == "PartNumber") .And(it => it.Status == "1"); List analysisList = Context .Queryable() .Where(predicate.ToExpression()) .ToList(); foreach (QcBackEndBaseLabelAnalysis analysis in analysisList) { if (string.IsNullOrEmpty(analysis.Expression)) { continue; } // 零件号正则表达式 Regex pattern = new(@analysis.Expression); Match match = pattern.Match(label); if (match.Success && match.Groups.Count > 1) { return match.Groups[1].Value; } } // 非通用规则下的标签零件号抓取 // 解析T58门把手产品标签 try { // 直接从标签中截取前 15 个字符作为零件号 if (!string.IsNullOrEmpty(label) && label.Length >= 40 && label.EndsWith('$')) { return label.Substring(0, 17); } } catch (Exception) { return ""; } return ""; } // 标签的数量解析 public int DoAnalyzeQuantity(string label) { int result = 0; // 标签零件号抓取 var predicate = Expressionable .Create() .And(it => it.Code == "Quantity") .And(it => it.Status == "1"); List analysisList = Context .Queryable() .Where(predicate.ToExpression()) .ToList(); foreach (QcBackEndBaseLabelAnalysis analysis in analysisList) { if (string.IsNullOrEmpty(analysis.Expression)) { continue; } // 零件号正则表达式 Regex pattern = new(@analysis.Expression); Match match = pattern.Match(label); if (match.Success && match.Groups.Count > 1) { if (Int32.TryParse(match.Groups[1].Value, out result)) { return result; } else { return -1; } } } return -1; } // 标签的批次号解析 public string DoAnalyzeBatchCode(string label) { // 标签批次号正则抓取 var predicate = Expressionable .Create() .And(it => it.Code == "BatchCode") .And(it => it.Status == "1"); List analysisList = Context .Queryable() .Where(predicate.ToExpression()) .ToList(); foreach (QcBackEndBaseLabelAnalysis analysis in analysisList) { if (string.IsNullOrEmpty(analysis.Expression)) { continue; } // 零件号正则表达式 Regex pattern = new(@analysis.Expression); Match match = pattern.Match(label); if (match.Success && match.Groups.Count > 1) { return match.Groups[1].Value; } } return ""; } public List GetDefectInitOptions() { List defectList = new(); var predicate = Expressionable .Create() .And(it => it.Status == "1"); /*List groupList = Context .Queryable() .Where(predicate.ToExpression()) .GroupBy(it => it.Group) .Select(it => it.Group) .ToList();*/ List groupList = new() { "油漆", "设备", "毛坯", "程序", "班组操作" }; foreach (string group in groupList) { QcBackEndAlterationDefectDto defectDto = new(); defectDto.GroupName = group; List children = Context .Queryable() .Where(it => it.Group == group) .Where(predicate.ToExpression()) .Select(it => new QcBackEndChildrenDefectDto { Name = it.Name, Code = it.Code, Type = it.Type, Num = 0 }) .ToList(); defectDto.Children = children; defectList.Add(defectDto); } return defectList; } public List GetDefectTableOptions() { List defectList = new(); var predicate = Expressionable .Create() .And(it => it.Type == "打磨") .And(it => it.Status == "1"); /* List groupList = Context .Queryable() .Where(predicate.ToExpression()) .GroupBy(it => it.Group) .Select(it => it.Group) .ToList();*/ List groupList = new() { "油漆", "设备", "毛坯", "程序", "班组操作" }; foreach (string group in groupList) { QcBackEndAlterationDefectDto defectDto = new(); defectDto.GroupName = group; List children = Context .Queryable() .Where(it => it.Group == group) .Where(predicate.ToExpression()) .Select(it => new QcBackEndChildrenDefectDto { Name = it.Name, Code = SqlFunc.IIF( SqlFunc.Length(it.Code) >= 2, SqlFunc.MergeString( SqlFunc.Substring(it.Code, 0, 1), // 获取第一个字符(注意:SQL 中索引通常从1开始) SqlFunc.Substring(it.Code, SqlFunc.Length(it.Code) - 1, 1) // 获取最后一个字符 ), it.Code ), Type = it.Type, Num = 0 }) .ToList(); defectDto.Children = children; defectList.Add(defectDto); } return defectList; } public List GetGroupOptions() { var predicate = Expressionable.Create().And(it => it.Status == "1"); var response = Context .Queryable() .Where(predicate.ToExpression()) .Select(it => new QcBackEndBaseGroupDto()) .ToList(); return response; } public List GetStieOptions() { var predicate = Expressionable.Create().And(it => it.Status == "1"); var response = Context .Queryable() .Where(predicate.ToExpression()) .Select(it => new QcBackEndBaseSiteDto()) .ToList(); return response; } public QcBackEndServiceWorkorder StartBackEndWorkOrder(QcBackEndWorkorderDetailDto data) { // 检查是否是已扫过的外箱标签 QcBackEndServiceWorkorder oldWorkOrder = Context .Queryable() .Where(it => it.Label == data.Label) .First(); // 有旧记录则返回旧记录 if (oldWorkOrder != null) { return oldWorkOrder; } // 没有旧记录则开启新的记录 try { // 检查箱标签是否当内标签扫过 /*bool isInnerLabelScan = Context .Queryable() .Where(it => it.Label == data.Label) .Where(it => it.LabelType == 2) .Any(); if (isInnerLabelScan) { throw new Exception("标签异常,该标签已经当内标签扫过!"); }*/ Context.Ado.BeginTran(); DateTime nowTime = DateTime.Now; // 创建新工单号 QcBackEndWorkorderDetailDto workorderInfo = GetNewWorkOrderCreate(data); // 赋值 data.WorkOrder = workorderInfo.WorkOrder; data.SerialNumber = workorderInfo.SerialNumber; data.StartTime = nowTime; QcBackEndServiceWorkorder newModel = GetNewWorkOrderInfo(data); QcBackEndServiceWorkorder result = Context .Insertable(newModel) .ExecuteReturnEntity(); if (result == null) { Context.Ado.RollbackTran(); throw new Exception("插入新工单异常"); } // 工单开始记录 QcBackEndRecordLabelScan newScanLabelRecord = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = result.WorkOrder, PartNumber = result.PartNumber, Team = result.Team, SiteNo = result.SiteNo, ComNo = result.ComNo, Label = result.Label, LabelType = 1, LabelSort = 0, ScanTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "1", Status = "1", Remark = "新工单创建扫描箱标签", CreatedBy = "后台系统", CreatedTime = nowTime, }; int res = Context.Insertable(newScanLabelRecord).ExecuteCommand(); if (res == 0) { Context.Ado.RollbackTran(); throw new Exception("插入标签记录异常"); } QcBackEndLogWorkorder qcBackEndLog = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), Name = "工单开始", Content = $"工单:{result.WorkOrder}开始,开始时间{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "100", Status = "1", Remark = "触摸屏操作记录", CreatedBy = "系统", CreatedTime = nowTime }; Context.Insertable(qcBackEndLog).ExecuteCommand(); Context.Ado.CommitTran(); return result; } catch (Exception ex) { Context.Ado.RollbackTran(); throw new Exception(ex.Message); } } // 创建新工单号 public QcBackEndWorkorderDetailDto GetNewWorkOrderCreate(QcBackEndWorkorderDetailDto data) { QcBackEndWorkorderDetailDto result = new(); string newWorkOrder = ""; DateTime today = DateTime.Today; // 检查是否是已扫过的外箱标签 QcBackEndServiceWorkorder lastWorkOrder = Context .Queryable() .Where(it => it.CreatedTime.Value.Date == today) .OrderByDescending(it => it.SerialNumber) .First(); if (lastWorkOrder != null) { // 递增序列号 int sequenceNumber = lastWorkOrder.SerialNumber + 1; if (data.IsOnetime == 1) { newWorkOrder = $"W{today:yyyyMMdd}{sequenceNumber:D3}"; } else if (data.IsPolish == 1) { newWorkOrder = $"P{today:yyyyMMdd}{sequenceNumber:D3}"; } else { newWorkOrder = $"{today:yyyyMMdd}{sequenceNumber:D3}"; } result.SerialNumber = sequenceNumber; } else { // 如果今天还没有创建过工单,则从 "001" 开始 if (data.IsOnetime == 1) { newWorkOrder = $"W{today:yyyyMMdd}001"; } else if (data.IsPolish == 1) { newWorkOrder = $"P{today:yyyyMMdd}001"; } else { newWorkOrder = $"{today:yyyyMMdd}001"; } result.SerialNumber = 1; } result.WorkOrder = newWorkOrder; return result; } public static QcBackEndServiceWorkorder GetNewWorkOrderInfo( QcBackEndWorkorderDetailDto data ) { // 新工单 QcBackEndServiceWorkorder model = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = data.WorkOrder, SerialNumber = data.SerialNumber, PartNumber = data.PartNumber, Specification = data.Specification, Color = data.Color, Description = data.Description, Team = data.Team, SiteNo = data.SiteNo, ComNo = data.ComNo, IsOnetime = data.IsOnetime, IsPolish = data.IsPolish, IsBack = data.IsBack, IsOut = data.IsOut, StartTime = data.StartTime, EndTime = null, Label = data.Label, RequireNumber = 0, QualifiedNumber = 0, PolishNumber = 0, DamoNumber = 0, BaofeiNumber = 0, Type = "1", Status = "1", Remark = "系统新增工单", CreatedBy = data.CreatedBy, CreatedTime = data.CreatedTime, UpdatedBy = data.UpdatedBy, UpdatedTime = data.UpdatedTime }; return model; } public QcBackEndServiceWorkorder ChangeWorkOrderDefect(QcBackEndWorkorderDefectDto data) { try { if (string.IsNullOrEmpty(data.DefectCode)) { throw new Exception("缺陷项传入为空!"); } Context.Ado.BeginTran(); DateTime nowTime = DateTime.Now; // 获取缺陷信息 QcBackEndBaseDefect defect = Context .Queryable() .Where(it => it.Code == data.DefectCode && it.Status == "1") .First(); if (defect == null) { throw new Exception("缺陷项不在缺陷清单中!"); } // 工单信息修改 QcBackEndServiceWorkorder qcBackEndWorkorder = Context .Queryable() .Where(it => it.WorkOrder == data.WorkOrder) .First(); if (qcBackEndWorkorder == null) { throw new Exception("工单不存在!"); } // 获取当前工作单缺陷记录 QcBackEndRecordWorkorderDefect workOrderDefect = Context .Queryable() .Where(it => it.WorkOrder == data.WorkOrder && it.DefectCode == data.DefectCode) .First(); if (data.Type == "1") { // Type === 1 短按新增 if (workOrderDefect != null) { // 数据库中有记录,DefectNum + 1 workOrderDefect.DefectNum += 1; workOrderDefect.UpdatedTime = data.CreatedTime; workOrderDefect.UpdatedBy = data.CreatedBy; Context.Updateable(workOrderDefect).ExecuteCommand(); } else { // 数据库中无记录,新增记录并设置 DefectNum = 1 workOrderDefect = new QcBackEndRecordWorkorderDefect { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = data.WorkOrder, PartNumber = qcBackEndWorkorder.PartNumber, Team = qcBackEndWorkorder.Team, SiteNo = qcBackEndWorkorder.SiteNo, ComNo = qcBackEndWorkorder.ComNo, DefectName = defect.Name, DefectCode = data.DefectCode, DefectType = defect.Type, ClickTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "1", Status = "1", Remark = "", CreatedBy = data.CreatedBy, CreatedTime = data.CreatedTime, DefectNum = 1 }; Context.Insertable(workOrderDefect).ExecuteCommand(); } } else if (data.Type == "2") { // Type == 2 长按修改 if (workOrderDefect != null) { // 数据库中有记录,直接赋值 workOrderDefect.UpdatedTime = data.CreatedTime; workOrderDefect.UpdatedBy = data.CreatedBy; workOrderDefect.DefectNum = data.DefectNum; // 假设 data.DefectNum 存在 Context.Updateable(workOrderDefect).ExecuteCommand(); } else { // 数据库中无记录,新增记录 workOrderDefect = new QcBackEndRecordWorkorderDefect { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = data.WorkOrder, PartNumber = qcBackEndWorkorder.PartNumber, Team = qcBackEndWorkorder.Team, SiteNo = qcBackEndWorkorder.SiteNo, ComNo = qcBackEndWorkorder.ComNo, DefectName = defect.Name, DefectCode = data.DefectCode, DefectType = defect.Type, ClickTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "1", Status = "1", Remark = "", CreatedBy = data.CreatedBy, CreatedTime = data.CreatedTime, DefectNum = data.DefectNum // 假设 data.DefectNum 存在 }; Context.Insertable(workOrderDefect).ExecuteCommand(); } } UpdateWorkOrderDetail(data.WorkOrder); // 提交事务 Context.Ado.CommitTran(); return qcBackEndWorkorder; } catch (Exception ex) { // 回滚事务 Context.Ado.RollbackTran(); throw new Exception("操作失败!", ex); } } public List GetWorkOrderDefectList(string workorder) { return Context .Queryable() .Where(it => it.WorkOrder == workorder) .ToList(); } public QcBackEndServiceWorkorder UpdateWorkOrderDetail(string workorder) { QcBackEndServiceWorkorder qcBackEndWorkorder = Context .Queryable() .Where(it => it.WorkOrder == workorder) // .Where(it=>it.Status == "1") .First(); // 更新工单中的统计数据 qcBackEndWorkorder.QualifiedNumber = Context .Queryable() .Where(it => it.WorkOrder == workorder) .Where(it => it.LabelType == 2) .Count(); qcBackEndWorkorder.PolishNumber = Context .Queryable() .Where(it => it.WorkOrder == workorder && it.DefectType == "抛光") .Sum(it => it.DefectNum) ?? 0; qcBackEndWorkorder.DamoNumber = Context .Queryable() .Where(it => it.WorkOrder == workorder && it.DefectType == "打磨") .Sum(it => it.DefectNum) ?? 0; qcBackEndWorkorder.BaofeiNumber = Context .Queryable() .Where(it => it.WorkOrder == workorder && it.DefectType == "报废") .Sum(it => it.DefectNum) ?? 0; qcBackEndWorkorder.RequireNumber = qcBackEndWorkorder.PolishNumber + qcBackEndWorkorder.DamoNumber + qcBackEndWorkorder.BaofeiNumber + (qcBackEndWorkorder.QualifiedNumber ?? 0); // 更新工单统计信息到数据库 Context .Updateable(qcBackEndWorkorder) .UpdateColumns(it => new { it.RequireNumber, it.QualifiedNumber, it.PolishNumber, it.DamoNumber, it.BaofeiNumber }) .ExecuteCommand(); return qcBackEndWorkorder; } // 后道扫描箱标签 public string ScanPackageLabel(QcBackEndLabelScanDto data) { try { Context.Ado.BeginTran(); DateTime nowTime = DateTime.Now; // 标签防错 (零件号) QcBackEndServiceWorkorder workorderInfo = Context .Queryable() .Where(it => it.WorkOrder == data.WorkOrder) .First(); if (workorderInfo == null) { Context.Ado.RollbackTran(); return "工单号不存在!"; } // 内外标签零件号不一致 string partnumber = DoAnalyzePartnumber(data.Label); if (!(workorderInfo.PartNumber == partnumber)) { Context.Ado.RollbackTran(); return "箱标签零件号与工单零件号不一致!"; } bool hasAny = Context .Queryable() .Where(it => it.Label == data.Label) .Where(it => it.LabelType == 1) .Any(); if (hasAny) { Context.Ado.RollbackTran(); return "此外箱标签已扫过,禁止重复扫码!"; } // 上一个内标签流水号检查 int oldPackageLabelSort = 1; QcBackEndRecordLabelScan lastPackagelabelInfo = Context .Queryable() // TODO 加锁 .TranLock(DbLockType.Wait) .Where(it => it.WorkOrder == data.WorkOrder) .Where(it => it.LabelType == 1) .OrderByDescending(it => it.LabelSort) .Take(1) .First(); if (lastPackagelabelInfo == null) { oldPackageLabelSort = 0; } else { oldPackageLabelSort = lastPackagelabelInfo.LabelSort.Value; } // 新箱标签录入 QcBackEndRecordLabelScan newLabelScran = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = data.WorkOrder, PartNumber = data.PartNumber, Team = data.Team, SiteNo = data.SiteNo, ComNo = data.ComNo, Label = data.Label, LabelType = 1, LabelSort = oldPackageLabelSort + 1, ScanTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "1", Status = "1", Remark = "外箱标签", CreatedBy = data.CreatedBy, CreatedTime = data.CreatedTime, }; int res = Context.Insertable(newLabelScran).ExecuteCommand(); if (res == 0) { Context.Ado.RollbackTran(); return "箱标签录入系统失败!"; } Context.Ado.CommitTran(); return "ok"; } catch (Exception) { Context.Ado.RollbackTran(); return "箱标签录入系统失败!"; } } // 后道扫内标签 public string ScanInnerLabel(QcBackEndLabelScanDto data) { try { Context.Ado.BeginTran(); DateTime nowTime = DateTime.Now; // 标签防错 (零件号) QcBackEndServiceWorkorder workorderInfo = Context .Queryable() .Where(it => it.WorkOrder == data.WorkOrder) .First(); if (workorderInfo == null) { Context.Ado.RollbackTran(); return "工单号不存在!"; } // 内外标签零件号不一致 string partnumber = DoAnalyzePartnumber(data.Label); if (!(workorderInfo.PartNumber == partnumber)) { Context.Ado.RollbackTran(); return "产品标签零件号与工单零件号不一致!"; } bool hasAny = Context .Queryable() .Where(it => it.Label == data.Label) .Where(it => it.LabelType == 2) .Any(); if (hasAny) { Context.Ado.RollbackTran(); return "此内标签已扫过,禁止重复扫码!"; } bool hasAny2 = Context .Queryable() .Where(it => it.Label == data.Label) .Where(it => it.LabelType == 1) .Where(it => it.LabelSort > 0) .Any(); if (hasAny2) { Context.Ado.RollbackTran(); return "此外箱标签已扫过,禁止重复扫码!"; } // 内标签工单确认 QcBackEndServiceWorkorder workorder = Context .Queryable() .Where(it => it.WorkOrder == data.WorkOrder) .First(); if (workorder == null) { Context.Ado.RollbackTran(); return $"工单异常:工单不存在{data.WorkOrder}"; } // 打印配置确认 QcBackendBaseOutpackage packageLabelConfig = Context .Queryable() .Where(it => workorder.Description.Contains(it.CheckStr)) .First(); if (packageLabelConfig == null) { Context.Ado.RollbackTran(); return $"该产品内标签,未检测到对应打印参数:{data.PartNumber},{workorder.Description}"; } // 上一个内标签流水号检查 int oldInnerLabelSort = 0; QcBackEndRecordLabelScan labelScan = Context .Queryable() // TODO 加锁 .TranLock(DbLockType.Wait) .Where(it => it.WorkOrder == data.WorkOrder) .Where(it => it.LabelType == 2) .OrderByDescending(it => it.LabelSort) .Take(1) .First(); if (labelScan != null) { oldInnerLabelSort = labelScan.LabelSort ?? 0; } // 新内标签流水号 int newInnerLabelSort = oldInnerLabelSort + 1; // 新标签录入 QcBackEndRecordLabelScan newLabelScran = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = data.WorkOrder, PartNumber = data.PartNumber, Team = data.Team, SiteNo = data.SiteNo, ComNo = data.ComNo, Label = data.Label, LabelType = 2, LabelSort = newInnerLabelSort, ScanTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "1", Status = "1", Remark = "", CreatedBy = data.CreatedBy, CreatedTime = data.CreatedTime, }; int res2 = Context.Insertable(newLabelScran).ExecuteCommand(); if (res2 == 0) { Context.Ado.RollbackTran(); return "产标签录入系统失败!"; } // 检查是否需要打印箱标签 CheckAndPrintPackageLabel(newLabelScran, packageLabelConfig); Context.Ado.CommitTran(); return "ok"; } catch (Exception e) { Context.Ado.RollbackTran(); return $"异常:{e.Message}"; } } // 判断是否满箱并且要出箱标签 public bool CheckPackageIsFullAndNeedScanPackageLabel(string workorder) { // 判断是否需要扫箱标签 bool neeedScan = false; // 工单判断 QcBackEndServiceWorkorder workorderInfo = Context .Queryable() .Where(it => it.WorkOrder == workorder) .First(); if (workorderInfo == null) { throw new Exception($"异常:工单不存在{workorder}"); } // 打印配置确认 QcBackendBaseOutpackage packageLabelConfig = Context .Queryable() .Where(it => workorderInfo.Description.Contains(it.CheckStr)) .First(); if (packageLabelConfig == null) { throw new Exception($"异常:零件满箱配置不存在{workorder}"); } int maxPackageNum = packageLabelConfig.PackageNum ?? 0; // 内标签总数 int innerLabelCount = Context .Queryable() .Where(it => it.WorkOrder == workorder) .Where(it => it.LabelType == 2) .Count(); // 箱标签总数 int packageLabelCount = Context .Queryable() .Where(it => it.WorkOrder == workorder) .Where(it => it.LabelType == 1) .Count(); packageLabelCount -= 1; if (packageLabelCount < 0) { packageLabelCount = 0; } // 是否满箱 bool isFull = (innerLabelCount > 0) && (innerLabelCount % maxPackageNum == 0); // 是否需要扫箱标签 bool needScanPackageLabel = packageLabelCount * maxPackageNum < innerLabelCount; // TODO额外附加标签 /* Console.WriteLine($"======= CheckPackageIsFullAndNeedScanPackageLabel,maxPackageNum:{maxPackageNum},innerLabelCount:{innerLabelCount},packageLabelCount:{packageLabelCount},isFull:{isFull},needScanPackageLabel:{needScanPackageLabel}");*/ neeedScan = isFull && needScanPackageLabel; return neeedScan; } /// /// 判断是否需要自动出满箱标签 /// /// public void CheckAndPrintPackageLabel( QcBackEndRecordLabelScan newLabelScran, QcBackendBaseOutpackage packageLabelConfig ) { DateTime nowTime = DateTime.Now; // 判断是否需要自动出满箱标签 int checkSort = newLabelScran.LabelSort ?? 0; int maxPackage = packageLabelConfig.PackageNum ?? 0; if (checkSort >= maxPackage && checkSort % maxPackage == 0) { // TODO额外附加标签 _logger.LogWarning($"=======> 需要打满箱标签{nowTime.ToString()},checkSort:{checkSort},maxPackage:{maxPackage}"); // 需要打外箱标签 SendPrintPackageLabelAsync(newLabelScran, packageLabelConfig.FileUrl, maxPackage) .Wait(); } } /// /// 发送打印后道外箱标签的MQTT信息 /// /// 当输入参数为null时抛出 /// 当标签内容无效时抛出 /// 特殊标签 0-正常 1-补打 2-零头箱 public async Task SendPrintPackageLabelAsync( QcBackEndRecordLabelScan newLabelScran, string path, int maxPackage, int specialPrintType = 0 ) { // 参数验证 if (newLabelScran == null) throw new ArgumentNullException(nameof(newLabelScran), "标签扫描信息不能为空"); if (string.IsNullOrWhiteSpace(newLabelScran.Label)) throw new ArgumentException("标签内容不能为空", nameof(newLabelScran.Label)); if (maxPackage <= 0) throw new ArgumentException("包装数量必须大于0", nameof(maxPackage)); try { string topic = $"shgg_mes/backEnd/print/1站点"; QcBackEndPrintMqttEventDto mqttEventDto = CreateNewQcBackEndPrintMqttEventDto( newLabelScran, path, maxPackage, specialPrintType ); var payload = JsonSerializer.Serialize(mqttEventDto); // 添加打印记录 await AddBackendLabelPrintRecordAsync(mqttEventDto, newLabelScran.WorkOrder, maxPackage, specialPrintType); // 保持原有PublishAsync调用方式 await _mqttService.PublishAsync( topic, payload, MqttQualityOfServiceLevel.AtLeastOnce, retain: false ); _logger.LogInformation($"发送后道外箱标签打印成功:{topic}"); } catch (JsonException ex) { _logger.LogError(ex, "序列化MQTT消息失败"); throw new InvalidOperationException("MQTT消息格式错误", ex); } catch (Exception ex) { _logger.LogError(ex, $"发送后道外箱标签打印失败:{ex.Message}"); throw; } } /// /// 异步添加后道箱标签打印记录 /// 特殊标签 0-正常 1-补打 2-零头箱 /// private async Task AddBackendLabelPrintRecordAsync( QcBackEndPrintMqttEventDto labelScan, string workOrder, int maxPackage, int specialPrintType = 0 ) { try { string labelCode = labelScan.LabelCode ?? ""; string description = ""; string batchCode = ""; try { batchCode = DoAnalyzeBatchCode(labelCode); } catch (Exception ex) { _logger.LogWarning(ex, "解析批次号失败"); } // 上一个内标签流水号检查 /* int oldPackageLabelSort = 1; QcBackendRecordLabelPrint lastPackagelabelInfo = Context .Queryable() .Where(it => it.PartNumber == labelScan.PartNumber) .Where(it => it.BatchCode.Contains(batchCode)) .Where(it => it.LabelType == 1) .OrderByDescending(it => it.SerialNumber) .First(); if (lastPackagelabelInfo == null) { oldPackageLabelSort = 1; } else { oldPackageLabelSort = lastPackagelabelInfo.SerialNumber.Value; }*/ QcBackendRecordLabelPrint printRecord = new() { Id = SnowFlakeSingle.instance.NextId().ToString(), MachineCode = labelScan.SiteNo ?? "未知站点", LabelCode = labelCode, WorkOrder = workOrder ?? "未知工单", PartNumber = labelScan.PartNumber ?? "未知零件号", Description = description, Team = labelScan.Team ?? "未知班组", BatchCode = batchCode, SerialNumber = labelScan.Sort, PartNum = maxPackage, LabelType = 1, BoxMaxNum = maxPackage, IsFull = specialPrintType == 2 ? 0 : 1, IsLcl = 0, CreateBy = "后道标签打印系统", CreateTime = DateTime.Now }; // 使用异步数据库操作 await Context.Insertable(printRecord).ExecuteCommandAsync(); } catch (Exception ex) { _logger.LogError(ex, "添加打印记录失败"); throw new Exception("保存打印记录失败", ex); } } /// /// 生成打印后道外箱标签的mqtt信息 /// /// /// /// 特殊标签 0-正常 1-补打 2-零头箱 /// public QcBackEndPrintMqttEventDto CreateNewQcBackEndPrintMqttEventDto( QcBackEndRecordLabelScan newLabelScran, string path, int maxPackage, int specialPrintType = 0 ) { // 解析产品批次号,如果没有,则生成最新批次号 string batchCode = DoAnalyzeBatchCode(newLabelScran.Label); if (string.IsNullOrEmpty(batchCode)) { batchCode = DateTime.Now.ToString("yyMMdd") + "000"; } int packageSort = 1; // 使用SqlSugar的事务处理 Context.Ado.BeginTran(); try { // 查询最新流水号 QcBackendRecordLabelPrint labelPrintRecord = Context .Queryable() .Where(it => it.PartNumber == newLabelScran.PartNumber) .Where(it => it.BatchCode.Contains(batchCode)) .Where(it => it.LabelType == 1) .OrderByDescending(it => it.SerialNumber) .First(); packageSort = labelPrintRecord?.SerialNumber + 1 ?? 1; Context.Ado.CommitTran(); } catch (Exception) { Context.Ado.RollbackTran(); throw; } // 提取产品描述 string checkPartnumber = newLabelScran.PartNumber; // 使用正则表达式匹配并移除特殊后缀 string processedPartnumber = Regex.Replace( checkPartnumber, @"-(FL|FR|RR|RL)$", "", RegexOptions.IgnoreCase ); WmMaterial material = Context .Queryable() .Where(it => it.Partnumber == processedPartnumber) .Where(it => it.Type == 1) .Where(it => it.Status == 1) .First(); if (material == null) { throw new Exception("生成打印后道外箱标签的信息:此零件号不在物料清单内!" + processedPartnumber); } // 生成工单号 string workOrder = $"{batchCode}_{packageSort}"; // 是否满箱 0-不满 1-满箱 int isFull = specialPrintType == 2 ? 0 : 1; // 是否补打 0-非补打 1-补打 int isAgain = specialPrintType == 1 ? 1 : 0; string newLabelCode = $"Code=PGW{workOrder}^ItemNumber={newLabelScran.PartNumber}^Order=W{batchCode}^Qty={maxPackage}^LabelType=1^LabelBy=HD^Fu={isFull}^Ag={isAgain}"; string newPackageCode = $"BOX:PGW{workOrder}{newLabelScran.Team}1"; QcBackEndPrintMqttEventDto mqttEventDto = new() { Path = path, SiteNo = "1站点", Name = "后道外箱标签打印", PartNumber = processedPartnumber, Description = material.Description ?? "", Color = material.Color ?? "", Specification = material.Specification ?? "", WorkOrder = workOrder, PackageCode = newPackageCode, Team = newLabelScran.Team, Sort = packageSort, ProductionTime = "20" + batchCode.Substring(0, 6), BatchCode = batchCode, PackageNum = maxPackage, LabelCode = newLabelCode, LabelType = 1, CreatedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }; return mqttEventDto; } public string EndBackEndWorkOrderAndCreateStatistics(string workorder) { try { Context.Ado.BeginTran(); DateTime nowTime = DateTime.Now; // 工单信息修改 QcBackEndServiceWorkorder qcBackEndWorkorder = Context .Queryable() .Where(it => it.WorkOrder == workorder) .First(); if (qcBackEndWorkorder == null) { throw new Exception("工单不存在!"); } qcBackEndWorkorder.EndTime = nowTime; qcBackEndWorkorder.Type = "2"; if (!qcBackEndWorkorder.Remark.Contains("已生成过报表")) { qcBackEndWorkorder.Remark += "已生成过报表"; } Context.Updateable(qcBackEndWorkorder).ExecuteCommand(); // 生成报表记录 List addList = new(); string groupCode = SnowFlakeSingle.Instance.NextId().ToString(); addList.Add(CreateNewStatistics(qcBackEndWorkorder, groupCode, 1)); addList.Add(CreateNewStatistics(qcBackEndWorkorder, groupCode, 2)); addList.Add(CreateNewStatistics(qcBackEndWorkorder, groupCode, 3)); Context .Deleteable() .Where(it => it.WorkOrder == workorder) .ExecuteCommand(); Context.Insertable(addList).ExecuteCommand(); QcBackEndLogWorkorder qcBackEndLog = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), Name = "工单结束", Content = $"工单:{workorder}结束,结束时间{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "200", Status = "1", Remark = "触摸屏操作记录", CreatedBy = "系统", CreatedTime = nowTime }; Context.Insertable(qcBackEndLog).ExecuteCommand(); // 提交事务 Context.Ado.CommitTran(); return "ok"; } catch (Exception ex) { // 回滚事务 Context.Ado.RollbackTran(); return ex.Message; } } public QcBackEndServiceStatistics CreateNewStatistics( QcBackEndServiceWorkorder data, string groupCode, int groupSort ) { List defectList = Context .Queryable() .Where(it => it.WorkOrder == data.WorkOrder) .WhereIF(groupSort == 1, it => it.DefectType == "抛光") .WhereIF(groupSort == 2, it => it.DefectType == "打磨") .WhereIF(groupSort == 3, it => it.DefectType == "报废") .ToList(); string JsonString = JsonSerializer.Serialize(defectList); // 计算合格率 string qualifiedRate = CalculateQualifiedRate(data); DateTime nowTime = DateTime.Now; QcBackEndServiceStatistics workorderStatistics = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = data.WorkOrder, PartNumber = data.PartNumber, Specification = data.Specification, Color = data.Color, Description = data.Description, Team = data.Team, SiteNo = data.SiteNo, ComNo = data.ComNo, IsOnetime = data.IsOnetime, IsPolish = data.IsPolish, IsBack = data.IsBack, IsOut = data.IsOut, StartTime = data.StartTime, EndTime = data.EndTime, Label = data.Label, RequireNumber = data.RequireNumber, QualifiedNumber = data.QualifiedNumber, QualifiedRate = qualifiedRate, PolishNumber = data.PolishNumber, DamoNumber = data.DamoNumber, BaofeiNumber = data.BaofeiNumber, GroupCode = groupCode, GroupSort = groupSort, GroupDefectJson = JsonString, Type = "1", Status = "1", Remark = "结束工单系统新增质量报表", CreatedBy = "后端", CreatedTime = nowTime, }; return workorderStatistics; } public static string CalculateQualifiedRate(QcBackEndServiceWorkorder data) { if (data == null || data.RequireNumber <= 0) { return "0%"; } double qualifiedRate = (double)data.QualifiedNumber.Value / data.RequireNumber.Value * 100; return $"{qualifiedRate:F1}%"; } public QcBackEndServiceWorkorder GenerateVirtualLabel( QcBackEndWorkorderDetailDto workorderDetail ) { try { Context.Ado.BeginTran(); // 检查当前工单已扫码合格数 int qualifiedNumber = workorderDetail.QualifiedNumber ?? -1; if (qualifiedNumber < 0) { throw new ArgumentException( "传入合格数异常!", nameof(workorderDetail.QualifiedNumber) ); } int labelCount = GetLabelCountForWorkOrder(workorderDetail.WorkOrder); if (labelCount < qualifiedNumber) { GenerateVirtualLabels(workorderDetail, qualifiedNumber - labelCount); } else if (labelCount > qualifiedNumber) { DeleteExcessLabels(workorderDetail.WorkOrder, labelCount - qualifiedNumber); } Context.Ado.CommitTran(); return UpdateWorkOrderDetail(workorderDetail.WorkOrder); } catch (Exception e) { Context.Ado.RollbackTran(); throw new Exception($"生成虚拟标签时出错: {e.Message}", e); } } private int GetLabelCountForWorkOrder(string workOrder) { return Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 2) .Count(); } private void GenerateVirtualLabels( QcBackEndWorkorderDetailDto workOrderDetail, int countToGenerate ) { List virtualLabels = new List(); int nextLabelNumber = GetNextLabelNumber(workOrderDetail.WorkOrder); for (int i = 0; i < countToGenerate; i++) { string uniqueLabel = GenerateUniqueSequentialLabel( workOrderDetail.WorkOrder, nextLabelNumber++ ); virtualLabels.Add( new QcBackEndRecordLabelScan { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = workOrderDetail.WorkOrder, PartNumber = workOrderDetail.PartNumber, Team = workOrderDetail.Team, SiteNo = workOrderDetail.SiteNo, ComNo = workOrderDetail.ComNo, ScanTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"), Type = "2", Status = "1", Remark = "虚拟标签", CreatedTime = DateTime.UtcNow, CreatedBy = "系统", LabelType = 2, LabelSort = nextLabelNumber, Label = uniqueLabel } ); } Context.Insertable(virtualLabels).ExecuteCommand(); } private void DeleteExcessLabels(string workOrder, int countToDelete) { var labelsToDelete = Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 2) .OrderByDescending(it => it.LabelSort) .Take(countToDelete) .ToList(); Context.Deleteable(labelsToDelete).ExecuteCommand(); } private int GetNextLabelNumber(string workOrder) { return Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 2) .Max(it => it.LabelSort ?? 0); } private string GenerateUniqueSequentialLabel(string workOrder, int number) { const string prefix = "VIRT"; string baseLabel = $"{prefix}-{GenerateUniqueId()}-{number:D5}"; string uniqueLabel = baseLabel; while (IsLabelExists(workOrder, uniqueLabel)) { uniqueLabel = $"{baseLabel}-{GenerateUniqueId()}"; } return uniqueLabel; } private bool IsLabelExists(string workOrder, string label) { return Context .Queryable() .Any(it => it.WorkOrder == workOrder && it.LabelType == 2 && it.Label == label); } private string GenerateUniqueId() { return Guid.NewGuid().ToString("N").Substring(0, 10); // Generate a 10-character unique ID } /// /// 打印特殊包装标签 /// /// 工单信息 /// 特殊打印类别 1-补打标签 2-打印零头箱 /// /// public string PrintSpecialPacakgeLabel( QcBackEndWorkorderDetailDto workorderDetail, int specialPrintType = 1, int packageNum = 0 ) { try { Context.Ado.BeginTran(); DateTime nowTime = DateTime.Now; // 工单判断 QcBackEndServiceWorkorder workorderInfo = Context .Queryable() .Where(it => it.WorkOrder == workorderDetail.WorkOrder) .First(); if (workorderInfo == null) { Context.Ado.RollbackTran(); return $"异常:工单不存在,工单号:[{workorderDetail.WorkOrder}]"; } // 打印配置确认 QcBackendBaseOutpackage packageLabelConfig = Context .Queryable() .Where(it => workorderInfo.Description.Contains(it.CheckStr)) .First(); if (packageLabelConfig == null) { Context.Ado.RollbackTran(); return $"异常:零件满箱配置不存在,工单号[{workorderDetail.WorkOrder}]"; } string remark = specialPrintType == 1 ? "补打标签" : "零头箱"; // 上一个内标签流水号检查 int oldInnerLabelSort = 0; QcBackEndRecordLabelScan labelScan = Context .Queryable() .Where(it => it.WorkOrder == workorderDetail.WorkOrder) .Where(it => it.LabelType == 2) .OrderByDescending(it => it.LabelSort) .First(); if (labelScan != null) { oldInnerLabelSort = labelScan.LabelSort ?? 0; } // 新内标签流水号 int newSort = oldInnerLabelSort + 1; // 新标签生成 QcBackEndRecordLabelScan newLabelScran = new() { Id = SnowFlakeSingle.Instance.NextId().ToString(), WorkOrder = workorderInfo.WorkOrder, PartNumber = workorderInfo.PartNumber, Team = workorderInfo.Team, SiteNo = workorderInfo.SiteNo, ComNo = workorderInfo.ComNo, Label = workorderInfo.Label, LabelType = 1, LabelSort = newSort, ScanTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}", Type = "1", Status = "1", Remark = remark, CreatedBy = "特殊标签", CreatedTime = nowTime, }; int _pacakgeNum = packageLabelConfig.PackageNum.Value; // 1.补打标签 if (specialPrintType == 1) { SendPrintPackageLabelAsync( newLabelScran, packageLabelConfig.FileUrl, _pacakgeNum, specialPrintType ) .Wait(); } // 2.打印零头箱 if (specialPrintType == 2) { _pacakgeNum = packageNum; SendPrintPackageLabelAsync( newLabelScran, packageLabelConfig.FileUrl, _pacakgeNum, specialPrintType ) .Wait(); //Context.Insertable(newLabelScran).ExecuteCommand(); } Context.Ado.CommitTran(); return "ok"; } catch (Exception ex) { Context.Ado.RollbackTran(); return $"系统异常{ex.Message}"; } } public QcBackEndWorkorderPrintLabelDetailDto SearchWorkOrderLabelDetail(string workOrder) { // 首先检查工单是否存在 var workorderInfo = Context .Queryable() .Where(it => it.WorkOrder == workOrder) .First(); if (workorderInfo == null) { throw new Exception($"异常:工单不存在{workOrder}"); } // 检查打印配置 var packageLabelConfig = Context .Queryable() .Where(it => workorderInfo.Description.Contains(it.CheckStr)) .First(); if (packageLabelConfig == null) { throw new Exception($"异常:零件满箱配置不存在{workOrder}"); } // 验证配置有效性 int maxPackageNum = packageLabelConfig.PackageNum ?? 0; if (maxPackageNum <= 0) { throw new ArgumentException($"无效的满箱数量:{maxPackageNum}", nameof(maxPackageNum)); } QcBackEndWorkorderPrintLabelDetailDto result = new QcBackEndWorkorderPrintLabelDetailDto(); // 扫描零件计数(LabelType=2),合并查询条件 result.ScannedPartCount = Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 2) .Count(); // 扫描箱标签计数(LabelType=1且LabelSort>0),合并查询条件 result.ScannedBoxLabelCount = Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 1 && it.LabelSort > 0) .Count(); // 重新打印计数(LabelType=1且LabelCode包含IsAgain(Ag)=1),合并查询条件 result.RePrintCount = Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 1 && it.LabelCode.Contains("Ag=1")) .Count(); // 零头箱打印计数(LabelType=1且未装满),合并查询条件 result.RemainderBoxPrintCount = Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 1 && it.IsFull == 0) .Count(); // 当前箱标签最大序列号,合并查询条件并增加空值处理 result.CurrentBoxLabelSequence = Context .Queryable() .Where(it => it.WorkOrder == workOrder && it.LabelType == 1) .Max(it => (int?)it.SerialNumber) ?? 0; // 零头箱计算 result.PackageNum = result.ScannedPartCount > 0 ? result.ScannedPartCount % maxPackageNum : 0; return result; } } }