using DataLinkMesWork1; using MesServerWork; using MisDataFunDll; using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; namespace MesServerFunctions { partial class FunctionMES { // OP431 手动静置出库固定配置。 private const string ManualStaticOutOpName = "OP431"; private const string ManualStaticPadTopic = "OP431PAD"; private const decimal DefaultManualStaticMinutes = 30m; private const string ManualStaticLocation = "777777"; private const string ManualStaticOperator = "PAD"; // 记录 PAD 确认时使用的最小静置分钟数,保存质量数据时沿用同一标准。 private static readonly object ManualStaticMinutesLocker = new object(); private static readonly Dictionary ManualStaticMinutesByEngine = new Dictionary(); /// /// OP431 当前产品基础信息。 /// private class ManualStaticOutProduct { public string EngineID { get; set; } public string OrderForm { get; set; } public string EngineType { get; set; } public int EngineTypeID { get; set; } } /// /// OP431 静置质量数据计算结果。 /// private class ManualStaticOutData { public string SourceOpName { get; set; } public DateTime StartTime { get; set; } public DateTime FinishTime { get; set; } public decimal Minutes { get; set; } public int QualityMask { get; set; } } /// /// 查询产品订单、型号和机型代码。 /// private bool TryGetManualStaticOutProduct(string engineID, out ManualStaticOutProduct product, out string message) { product = null; message = ""; if (string.IsNullOrWhiteSpace(engineID)) { message = "产品码为空"; return false; } // PAD 可能扫描箱体码,最终产品编号以存储过程返回的产品编号为准。 if (!TrySelectManualStaticPdcProduct(engineID, out string productCode, out string orderForm, out string engineType, out int engineTypeID, out message)) { return false; } product = new ManualStaticOutProduct { EngineID = productCode, OrderForm = orderForm, EngineType = engineType, EngineTypeID = engineTypeID }; return true; } /// /// 查询 PDC 产品执行信息,兼容产品码和箱体码输入。 /// private bool TrySelectManualStaticPdcProduct(string scanCode, out string productCode, out string orderForm, out string engineType, out int engineTypeID, out string message) { productCode = ""; orderForm = ""; engineType = ""; engineTypeID = 0; message = ""; try { SqlParameter[] sqlParameters = new SqlParameter[1]; sqlParameters[0] = new SqlParameter("@发动机号", scanCode); DataAccess.ExecuteStoredProcedure("测量数据工位_监控系统_MOBY_查询_PDC", ref sqlParameters, out DataTable dt, out string errorMessage); if (!string.IsNullOrWhiteSpace(errorMessage)) { message = "查询产品执行信息失败:" + errorMessage; return false; } if (dt == null || dt.Rows.Count == 0) { message = "未查询到产品执行信息:" + scanCode; return false; } DataRow row = dt.Rows[0]; productCode = dt.Columns.Contains("产品编号") ? Convert.ToString(row["产品编号"]) : Convert.ToString(row["发动机号"]); orderForm = Convert.ToString(row["订单号"]); engineType = Convert.ToString(row["产品型号"]); engineTypeID = Convert.ToInt32(row["机型代码"]); if (string.IsNullOrWhiteSpace(productCode) || engineTypeID <= 0 || string.IsNullOrWhiteSpace(engineType)) { message = "产品执行信息不完整:" + scanCode; return false; } productCode = productCode.Trim(); return true; } catch (Exception err) { message = "查询产品执行信息失败:" + err.Message; return false; } } /// /// 查询 OP420 记录的线下手动静置开始时间。 /// private bool TryGetManualStaticStartTime(string engineID, out string sourceOpName, out DateTime startTime, out string message) { sourceOpName = ""; startTime = DateTime.MinValue; message = ""; try { // OP431 手动出库以 OP420 记录的线下静置开始时间为准。 string procedureName = "线下手动静置_记录_查询"; SqlParameter[] sqlParameters = new SqlParameter[1]; sqlParameters[0] = new SqlParameter("@产品编号", engineID); DataAccess.ExecuteStoredProcedure(procedureName, ref sqlParameters, out DataTable dt, out string errorMessage); if (!string.IsNullOrWhiteSpace(errorMessage)) { message = "查询线下手动静置开始时间失败:" + errorMessage; return false; } if (dt == null || dt.Rows.Count == 0) { message = "未查询到线下手动静置开始记录:" + engineID; return false; } sourceOpName = "OP420"; startTime = Convert.ToDateTime(dt.Rows[0]["静置开始时间"]); return true; } catch (Exception err) { message = "查询线下手动静置开始时间失败:" + err.Message; return false; } } /// /// OP431 保存时回写线下手动静置结束时间和静置时长。 /// private bool FinishManualStaticRecord(string engineID, DateTime finishTime, decimal minutes, out string message) { message = ""; SqlParameter[] sqlParameters = new SqlParameter[] { new SqlParameter("@产品编号", engineID), new SqlParameter("@静置结束时间", finishTime), new SqlParameter("@静置时长", minutes) }; DataAccess.ExecuteStoredProcedure("线下手动静置_记录_结束", ref sqlParameters, out string errorMessage); if (!string.IsNullOrWhiteSpace(errorMessage)) { message = "回写线下手动静置结束时间失败:" + errorMessage; return false; } return true; } /// /// 生成 OP431 保存时需要的静置时间和质量标识。 /// private bool TryBuildManualStaticOutData(string engineID, int qualityMask, out ManualStaticOutData staticOutData, out string message) { staticOutData = null; message = ""; if (!TryGetManualStaticStartTime(engineID, out string sourceOpName, out DateTime startTime, out message)) { return false; } // PLC 允许保存到达时的服务器时间作为静置完成时间。 DateTime finishTime = DateTime.Now; decimal minutes = Math.Round(Convert.ToDecimal((finishTime - startTime).TotalMinutes), 2); staticOutData = new ManualStaticOutData { SourceOpName = sourceOpName, StartTime = startTime, FinishTime = finishTime, Minutes = minutes, QualityMask = NormalizeManualStaticQualityMask(qualityMask) }; return true; } /// /// 标准化质量标识:1 为合格,其余均按不合格处理。 /// private int NormalizeManualStaticQualityMask(int qualityMask) { return qualityMask == 1 ? 1 : 2; } /// /// 判断静置时长是否达到 PAD 配置的最小分钟数。 /// private bool IsManualStaticTimeEnough(ManualStaticOutData staticOutData, decimal minStaticMinutes, out string message) { message = ""; if (staticOutData.Minutes < minStaticMinutes) { message = "静置时间不足 " + minStaticMinutes.ToString("0.##") + " 分钟,当前已静置 " + staticOutData.Minutes + " 分钟"; return false; } return true; } /// /// 直接写入 OP431 四项静置质量数据。 /// private bool WriteManualStaticOutQualityData(string opName, string engineID, ManualStaticOutData data, out string message) { message = ""; int idTagCf = 0; if (!TryGetManualStaticMinutesForProduct(engineID, out decimal rememberedMinStaticMinutes)) { message = "未找到PAD确认的最小静置标准,请在PAD重新确认上线后再保存:" + engineID; return false; } // 先写质量索引,取返回的 Id_tagCF 作为明细关联键。 string minStaticMinutes = rememberedMinStaticMinutes.ToString("0.##"); // OP431 的静置数据由 Function 直接落库,不再通过 OnlineCheckData 做 HTTP 自调用。 SqlParameter[] indexParams = new SqlParameter[6]; indexParams[0] = new SqlParameter("@EngineID", engineID); indexParams[1] = new SqlParameter("@工位号", opName); indexParams[2] = new SqlParameter("@操作者工号", ManualStaticOperator); indexParams[3] = new SqlParameter("@扫码时间", data.FinishTime); indexParams[4] = new SqlParameter("@合格标志", data.QualityMask); indexParams[5] = new SqlParameter("@Id_tagCF", idTagCf); indexParams[5].Direction = ParameterDirection.Output; DataAccess.ExecuteStoredProcedure("测量数据合格索引_增加_测试设备", ref indexParams, out string errorMessage); if (!string.IsNullOrWhiteSpace(errorMessage)) { message = "写入静置质量索引失败:" + errorMessage; return false; } idTagCf = Convert.ToInt32(indexParams[5].Value.ToString()); if (idTagCf <= 0) { message = "写入静置质量索引失败:未生成 Id_tagCF"; return false; } // 四条质量明细必须与 MOM 数据收集组配置的 TagID 保持一致。 object[,] detailRows = new object[,] { { "OP431_M0001_001", "开始静置时间", "min", "1", "60", minStaticMinutes, data.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), data.QualityMask }, { "OP431_M0001_002", "静置完成时间", "min", "1", "60", minStaticMinutes, data.FinishTime.ToString("yyyy-MM-dd HH:mm:ss"), data.QualityMask }, { "OP431_M0001_003", "静置时间差", "min", "1", "60", minStaticMinutes, data.Minutes.ToString("0.##"), data.QualityMask }, { "OP431_M0001_004", "静置库位号", "静置库位", "-1", "-1", "-1", ManualStaticLocation, data.QualityMask } }; for (int i = 0; i < detailRows.GetLength(0); i++) { SqlParameter[] detailParams = new SqlParameter[] { new SqlParameter("@Id_tagCF", idTagCf), new SqlParameter("@EngineID", engineID), new SqlParameter("@TagID", detailRows[i, 0]), new SqlParameter("@TagValue", detailRows[i, 6]), new SqlParameter("@IsGoodBad", detailRows[i, 7]), new SqlParameter("@工位号", opName), new SqlParameter("@理论值", detailRows[i, 3]), new SqlParameter("@上限值", detailRows[i, 4]), new SqlParameter("@下限值", detailRows[i, 5]), new SqlParameter("@测量项目", detailRows[i, 1]), new SqlParameter("@测量单位", detailRows[i, 2]) }; DataAccess.ExecuteStoredProcedure("测量数据合格_增加_测试设备", ref detailParams, out errorMessage); if (!string.IsNullOrWhiteSpace(errorMessage)) { message = "写入静置质量明细失败:" + errorMessage; return false; } } return true; } /// /// 确认上线前校验托盘到位和 PLC 申请上线状态。 /// private bool CheckManualStaticPadPrecondition(string opName, out string message) { message = ""; MIS_BASE.Read_WorkStart(opName, out bool workStart); if (!workStart) { message = "当前无托盘到位信号,不允许扫码确认"; return false; } MIS_BASE.Read_PartLoad(opName, out bool partLoad); if (!partLoad) { message = "PLC尚未申请上线,不允许扫码确认"; return false; } return true; } /// /// PAD 确认上线:校验静置条件,通过后写入 PLC 产品区。 /// private bool ConfirmManualStaticOutProduct(string opName, string engineID, decimal minStaticMinutes, out string message) { message = ""; // PAD 确认上线时必须重新校验托盘和 PLC 申请状态,不能复用查询时的旧结果。 if (!CheckManualStaticPadPrecondition(opName, out message)) { return false; } if (!TryGetManualStaticOutProduct(engineID, out ManualStaticOutProduct product, out message)) { return false; } if (!TryBuildManualStaticOutData(product.EngineID, 1, out ManualStaticOutData staticOutData, out message)) { return false; } // 二次校验静置时间,最小分钟数来自 PAD 当前配置,默认 30 分钟。 if (!IsManualStaticTimeEnough(staticOutData, minStaticMinutes, out message)) { return false; } MIS_BASE.Read_PalletCode(opName, out int palletCode); MisDataFun.OpName_MIS_MOBY_Insert(opName, product.EngineTypeID, product.EngineType, product.EngineID, palletCode, product.OrderForm, 0, 0, ""); // 写入 MES->PLC 产品区,PLC 收到 EngineTypeGetOver_MES 后再回传 44 做进站校验。 MIS_BASE.Write_EngineTypeID_MES(opName, product.EngineTypeID); MIS_BASE.Write_EngineID_MES(opName, product.EngineID); MIS_BASE.Write_EngineType_MES(opName, product.EngineType); MIS_BASE.Write_OrderForm_MES(opName, product.OrderForm); MIS_BASE.Write_EngineTypeGetOver_MES(opName, true); RememberManualStaticMinutesForProduct(product.EngineID, minStaticMinutes); message = "PAD确认上线成功:" + product.EngineID; return true; } /// /// PAD 扫码查询:返回产品、静置时间和是否允许确认。 /// private bool QueryManualStaticOutProduct(string opName, string engineID, decimal minStaticMinutes, out string resultMessage) { resultMessage = ""; // 扫码查询只返回产品与静置信息,不写 PLC、不调用进站,也不依赖托盘到位/PLC申请上线实时消息。 // PAD 页面可能不是一直打开,托盘到位等实时 MQTT 可能丢失,最终状态由确认上线时后端再次校验。 if (!TryGetManualStaticOutProduct(engineID, out ManualStaticOutProduct product, out resultMessage)) { return false; } if (!TryBuildManualStaticOutData(product.EngineID, 1, out ManualStaticOutData staticOutData, out resultMessage)) { return false; } bool timeEnough = IsManualStaticTimeEnough(staticOutData, minStaticMinutes, out string timeMessage); MIS_BASE.Read_PartLoad(opName, out bool partLoad); // PAD 直接使用 canConfirm 控制按钮,不再自行组合判断条件。 bool canConfirm = timeEnough && partLoad; string confirmMessage = canConfirm ? "允许确认上线" : !timeEnough ? timeMessage : "PLC尚未申请上线,不允许确认上线"; resultMessage = string.Join("^", new string[] { product.EngineID, product.OrderForm, product.EngineType, product.EngineTypeID.ToString(), staticOutData.SourceOpName, staticOutData.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), staticOutData.Minutes.ToString("0.##"), minStaticMinutes.ToString("0.##"), partLoad ? "1" : "0", canConfirm ? "1" : "0", confirmMessage }); return true; } /// /// 将 PAD 业务结果同时发送到 PAD 主题和工位主题。 /// private void SendManualStaticOutResult(string opName, string command, bool success, string message) { string value = (success ? "1" : "0") + "^" + message; SendManualStaticPadMqttMessage(command, opName, value); // 同步给工位主题,保留现有页面状态监听能力;PAD 以 OP431PAD 主题为准。 SendMessageFun.SendInfomation(command, value, opName); if (!success) { SendMessage.AsyncSendMessage_Notice(opName, "【" + opName + "】" + message); } } /// /// 向 OP431 PAD 专用主题发送 MQTT 回包。 /// private void SendManualStaticPadMqttMessage(string command, string opName, string value) { SendMessage.AsyncSendNotOpMessage(ManualStaticPadTopic, "MES|" + command + "|" + opName + "|" + value); } /// /// 记录产品确认上线时使用的最小静置标准。 /// private void RememberManualStaticMinutesForProduct(string engineID, decimal minStaticMinutes) { lock (ManualStaticMinutesLocker) { ManualStaticMinutesByEngine[engineID] = minStaticMinutes; } } /// /// 获取产品确认上线时记录的最小静置标准。 /// private bool TryGetManualStaticMinutesForProduct(string engineID, out decimal minStaticMinutes) { lock (ManualStaticMinutesLocker) { if (ManualStaticMinutesByEngine.ContainsKey(engineID)) { minStaticMinutes = ManualStaticMinutesByEngine[engineID]; return true; } } minStaticMinutes = DefaultManualStaticMinutes; return false; } } }