using DataLinkMesWork1; using MesServerWork; using MisDataFunDll; using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; namespace MesServerFunctions { partial class FunctionMES { 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"; private static readonly object ManualStaticMinutesLocker = new object(); private static readonly Dictionary ManualStaticMinutesByEngine = new Dictionary(); private class ManualStaticOutProduct { public string EngineID { get; set; } public string OrderForm { get; set; } public string EngineType { get; set; } public int EngineTypeID { get; set; } } 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; } MisDataFun.Moby_Select_PDC(engineID, out string orderForm, out string engineType, out int engineTypeID); if (engineTypeID <= 0 || string.IsNullOrWhiteSpace(engineType)) { message = "未查询到产品执行信息:" + engineID; return false; } product = new ManualStaticOutProduct { EngineID = engineID.Trim(), OrderForm = orderForm, EngineType = engineType, EngineTypeID = engineTypeID }; return true; } private bool TryGetManualStaticStartTime(string engineID, out string sourceOpName, out DateTime startTime, out string message) { sourceOpName = ""; startTime = DateTime.MinValue; message = ""; try { // 取 OP400/OP410/OP420 最后一条有效离开记录作为常温静置开始时间。 string procedureName = "OP431手动静置出库_前序离开时间_查询"; 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 = "未查询到 OP400/OP410/OP420 前序出站记录"; return false; } sourceOpName = Convert.ToString(dt.Rows[0]["工位号"]); startTime = Convert.ToDateTime(dt.Rows[0]["开始静置时间"]); return true; } catch (Exception err) { message = "查询前序出站时间失败:" + err.Message; return false; } } 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; } 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; } private int NormalizeManualStaticQualityMask(int qualityMask) { return qualityMask == 1 ? 1 : 2; } 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; } 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; } 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; } 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; } 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; } 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; } 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); 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; } 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); } } 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; } } }