using ExternalDataSync;
using System;
using DC_A95;
/// <summary>
///
/// </summary>
namespace MesWork
{
    /// <summary>
    /// 处理MIS逻辑
    /// </summary>
    public partial class MesWorkForm
    {
        /// <summary>
        /// PLC信号变化处理入口
        /// 当Bool型信号值变化时由框架自动触发
        /// </summary>
        /// <param name="e"></param>
        private void MIS_Funtion(object e)
        {
            try
            {
                var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e;
                if (msgEvent.TagValue == null) return;
                var tagID = msgEvent.TagID.ToString();
                var opName = msgEvent.OpName.ToString();
                var tagTypeCodeID = (int)msgEvent.TagTypeCodeID;
                var tagTypeID = (EnumTagTypeID)msgEvent.TagTypeID;
                var isFirstValue = msgEvent.IsFirstValue.ToString().ToLower();
                var alarmLevel = msgEvent.ShaftID.ToString();
                var alarmMsg = msgEvent.ItemName.ToString();
                string tagValue;
                switch (tagTypeID)
                {
                    case EnumTagTypeID.BOOL:
                        tagValue = (Convert.ToInt32(msgEvent.TagValue)).ToString();
                        break;
                    default:
                        tagValue = msgEvent.TagValue.ToString();
                        break;
                }
                // ── 监控变量变化记录（首次值及心跳除外，不影响主流程）──
                if (!ValueT(isFirstValue) && tagTypeCodeID != 14)
                {
                    var signalName = string.IsNullOrWhiteSpace(alarmMsg) ? tagID : alarmMsg;
                    SaveSignalChangeLog(opName, tagTypeCodeID, signalName, tagValue);
                }
                // ── 报警处理（编码900~2000）──
                if (tagTypeCodeID >= 900 & tagTypeCodeID < 2000)
                {
                    if (ValueT(isFirstValue))
                    {
                        //不处理第一次变化的值
                        return;
                    }
                    var opNameNew = opName.Replace("_Alarm", "");
                    if (tagValue == "1")
                    {
                        B_DB_Opera.Event_Alarm_Start(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
                    }
                    else
                    {
                        B_DB_Opera.Event_Alarm_End(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
                    }
                }
                // ── 信号分发 ──
                switch (tagTypeCodeID)
                {
                    case 2: // 工件到位/离开
                        // WritePLC_IF(112, opName, 0); // 报警代码清空
                        ClearStationDisplay(opName);
                        break;
                    case 14: // PLC心跳 → 回写PC心跳
                        WritePLC_IF(15, opName, tagValue);
                        break;
                    case 44: // 请求工作
                        if (ValueT(isFirstValue)) return;
                        if (tagValue == "1")
                        {
                            WeighingPage?.AppendLog($"[{opName}] PLC请求工作，开始处理...");
                            Weighing_HandleRequestWork(opName, tagID);
                        }
                        else
                        {
                            WritePLC_IF(66, opName, false);  //允许工作
                        }
                        break;
                    case 19: // 请求保存
                        if (ValueT(isFirstValue)) return;
                        if (tagValue == "1")
                        {
                            CurveCollector_StopSampling(opName, true); // 先停止采样，保存完成后再关联称重记录ID
                            WeighingPage?.AppendLog($"[{opName}] PLC请求保存数据...");
                            Weighing_HandleRequestSave(opName, tagID);
                        }
                        else
                        {
                            WritePLC_IF(20, opName, false);  //保存完成
                            WritePLC_IF(21, opName, false);  //合格标志
                        }
                        break;
                    case 203: // 设备状态
                        B_DB_Opera.Event_DeviceStatus_Change(opName, tagValue);
                        break;
                    default:
                        break;
                }
            }
            catch (Exception err)
            {
                string errMsg = $"PLC信号处理异常：{err.Message}";
                B_DB_Opera.SaveLog_Request("", Log_Type.ZK_MesHandler, "MIS_Function",
                    Log_FromAndTo.ZK, Log_FromAndTo.ZK, errMsg, out long AID);
                B_DB_Opera.SaveLog_Response(errMsg, AID);
                WeighingPage?.AppendLog(errMsg);
            }
        }

        /// <summary>
        /// 记录监控变量变化，异常不影响PLC主流程。
        /// </summary>
        private void SaveSignalChangeLog(string opName, int tagTypeCodeID, string signalName, string tagValue)
        {
            try
            {
                B_DB_Opera.Event_Signal_Log(opName, tagTypeCodeID, signalName, tagValue);
            }
            catch
            {
                // 变化记录失败不干涉PLC信号主流程
            }
        }

        // ====================================================================
        // 称重交互核心方法
        // ====================================================================
        /// <summary>
        /// 获取称重类型：地面/放油前/放油后
        /// Ground模式：统一返回"地面"
        /// Hanging模式：工位1=放油前，工位2=放油后
        /// </summary>
        private string GetWeighingType(string opName)
        {
            if (WeighingType == "Ground")
                return "地面";

            if (string.Equals(opName, Station1OpName, StringComparison.OrdinalIgnoreCase))
                return "放油前";

            if (string.Equals(opName, Station2OpName, StringComparison.OrdinalIgnoreCase))
                return "放油后";

            throw new InvalidOperationException($"工位号[{opName}]未匹配到App.config中的Station1OpName/Station2OpName，请检查工位配置");
        }


        /// <summary>
        /// 清除工位显示信息（工件离开时调用）
        /// </summary>
        private void ClearStationDisplay(string opName)
        {
            try
            {
                int stIdx = WeighingPage?.GetStationIndex(opName) ?? 1;

                // 清除工件信息
                WeighingPage?.UpdateStationInfo(stIdx, "", "", "");

                // 清除油量参数信息
                WeighingPage?.UpdateOilInfo(stIdx, 0, 0, 0, 0, 0);

                // 清除结果信息
                WeighingPage?.UpdateOilResult(stIdx, 0, 0);

            }
            catch (Exception err)
            {
                WeighingPage?.AppendLog($"[{opName}] 清除显示异常：{err.Message}");
            }
        }


        /// <summary>
        /// 码块数据转换：将产品编号统一转大写，并拆分出订货号与产品编号
        /// 支持格式：
        /// 0/DHN2.5Q0219-36/D426E016767
        /// DHH02K0014-400/B726E00584555
        /// CodeBlockOrderNoReverse=0：订货号/流水号；CodeBlockOrderNoReverse=1：流水号/订货号
        /// </summary>
        private bool TryConvertCodeBlockData(string codeBlock, out string orderNo, out string productNo)
        {
            orderNo = "";
            productNo = "";
            if (string.IsNullOrWhiteSpace(codeBlock))
            {
                return false;
            }

            string normalizedCode = codeBlock.Trim().ToUpperInvariant();
            string[] parts = normalizedCode.Split('/');
            if (parts.Length >= 2)
            {
                if (CodeBlockOrderNoReverse == 1)
                {
                    productNo = parts[parts.Length - 2].Trim();
                    orderNo = parts[parts.Length - 1].Trim();
                }
                else
                {
                    orderNo = parts[parts.Length - 2].Trim();
                    productNo = parts[parts.Length - 1].Trim();
                }
            }
            else
            {
                productNo = normalizedCode;
            }

            return !string.IsNullOrWhiteSpace(productNo);
        }

        /// <summary>
        /// 业务资料异常时只记录兜底日志，PLC交互仍尽量继续，避免现场流程被MES辅助数据卡住。
        /// </summary>
        private void LogWeighingFallback(string opName, string productNo, string flowName, string reason, long AID)
        {
            string safeProductNo = string.IsNullOrWhiteSpace(productNo) ? "未知产品" : productNo;
            string msg = $"【{opName}】兜底放行：产品={safeProductNo}，流程={flowName}，原因={reason}";
            B_DB_Opera.SaveLog_Response(msg, AID);
            WeighingPage?.AppendLog($"[{opName}] {msg}");
        }

        /// <summary>
        /// 处理请求工作信号 (tagTypeCodeID=44)
        /// 流程：读取工件信息 → 查询参数 → 写过站记录 → 按类型处理称重记录 → 回写允许工作
        /// </summary>
        private void Weighing_HandleRequestWork(string opName, string tagID)
        {
            B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
                Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【44-请求工作】", out long AID);
            try
            {
                string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName));   // 原始代码串  0/dhn2.5q0219-36/d426e016767
                string modelNo = PLC_R.GetString_CleanGarbled(ReadPLC(11, opName));    // 机型号
                string orderNo = PLC_R.GetString_CleanGarbled(ReadPLC(39, opName));    // 工单号
                string palletNo = PLC_R.GetString_CleanGarbled(ReadPLC(80, opName));   // 托盘号
                string rawCodeBlock = engineNo;
                // 解析代码串格式: 0/DHN2.5Q0219-36/D426E016767 或 DHH02K0014-400/B726E00584555
                if (TryConvertCodeBlockData(engineNo, out string parsedOrderNo, out string parsedProductNo))
                {
                    if (!string.IsNullOrWhiteSpace(parsedOrderNo))
                    {
                        orderNo = parsedOrderNo;
                    }
                    engineNo = parsedProductNo;

                    int stIdx = WeighingPage?.GetStationIndex(opName) ?? 1;
                    if (!B_DB_Opera.QueryWorkpieceByOrderNo(orderNo, out string matchedModelNo, out decimal addOilQty, out decimal extractOilQty, out decimal density,
                        out decimal residualOilUpperLimit, out decimal residualOilLowerLimit)) // 根据订货号 查询基本参数
                    {
                        // WritePLC_IF(112, opName, 3); // 报警代码=3，机型错误
                        LogWeighingFallback(opName, engineNo, "请求工作", $"订货号[{orderNo}]未匹配到工件参数，使用PLC机型[{modelNo}]继续允许工作", AID);
                        // 兜底机制：参数资料缺失不再卡PLC，请求保存时若无法正常更新记录会生成异常完成记录。
                        WritePLC_IF(66, opName, true);
                        CurveCollector_Start(opName, engineNo, modelNo);
                        return;
                    }
                    modelNo = matchedModelNo;
                    B_DB_Opera.SaveLog_Response($"【{opName}】读取工件：发动机={engineNo}，机型={modelNo}，订货号={orderNo}，托盘={palletNo}", AID);
                    WeighingPage?.UpdateStationInfo(stIdx, engineNo, modelNo, orderNo);
                    WeighingPage?.AppendLog($"[{opName}] 读取工件：{engineNo}，机型={modelNo}");
                    WeighingPage?.UpdateOilInfo(stIdx, addOilQty, extractOilQty, density, 0, 0);
                    string weighType = GetWeighingType(opName); // 地面/放油前/放油后
                    string stationName = weighType; // 工位名称直接用称重类型名
                    B_DB_Opera.InsertStationRecord(engineNo, modelNo, orderNo, palletNo,
                        opName, stationName, addOilQty, extractOilQty, density, Curr_UserName); // 请求工作成功先写到站记录
                    if (weighType == "放油后")
                    {
                        // 空中放油后：查找最新放油前记录（无论是否完成，支持多次测量）
                        if (!B_DB_Opera.QueryWeighingBeforeOil(engineNo, out _, out decimal preWeight, out _, out _, out _))
                        {
                            // WritePLC_IF(112, opName, 3);
                            LogWeighingFallback(opName, engineNo, "请求工作-放油后", "未找到放油前记录，继续允许工作，保存时按异常放油后记录处理", AID);
                        }
                        else
                        {
                            B_DB_Opera.SaveLog_Response($"【{opName}】空中放油后准备完成，发动机={engineNo}，机型={modelNo}，放油前重量={preWeight}kg", AID);
                        }
                    }
                    else
                    {
                        // 地面 或 空中放油前：每次都新建称重记录（支持同一产品多次测量取最新）
                        string recordType = weighType == "地面" ? "地面" : "空中";
                        B_DB_Opera.InsertWeighingRecord(opName, recordType, engineNo, modelNo, orderNo, palletNo,
                            0, 0, addOilQty, extractOilQty, density, residualOilUpperLimit, residualOilLowerLimit, 0, 0, 0, 0,
                            opName, Curr_UserName, isComplete: 0); // 放油前/地面先占位，保存时回填重量结果
                        B_DB_Opera.SaveLog_Response($"【{opName}】{weighType}准备完成，发动机={engineNo}，机型={modelNo}，残油量范围={residualOilLowerLimit:F4}-{residualOilUpperLimit:F4}L", AID);
                    }
                    WritePLC_IF(66, opName, true);
                    CurveCollector_Start(opName, engineNo, modelNo); // 允许工作后开始记录本次曲线
                }
                else
                {
                    // WritePLC_IF(112, opName, 1); // 报警代码=1，获取数据失败
                    engineNo = string.IsNullOrWhiteSpace(rawCodeBlock) ? $"未知产品_{opName}_{DateTime.Now:yyyyMMddHHmmss}" : rawCodeBlock.Trim();
                    LogWeighingFallback(opName, engineNo, "请求工作", $"码块数据转换失败，原始码值=[{rawCodeBlock}]，仍允许工作", AID);
                    // 兜底机制：产品解析失败时仍给允许工作，后续保存阶段会按异常产品记录并判不合格。
                    WritePLC_IF(66, opName, true);
                    CurveCollector_Start(opName, engineNo, modelNo);
                }
            }
            catch (Exception err)
            {
                //WritePLC_IF(112, opName, 1); // 报警代码=1，获取数据失败
                B_DB_Opera.SaveLog_Response($"【{opName}】请求工作处理失败：{err.Message}", AID);
            }
        }
        /// <summary>
        /// 处理请求保存信号 (tagTypeCodeID=19)
        /// 流程：读取称重数据 → 更新过站记录 → 按类型处理称重记录 → 回写保存完成
        /// </summary>
        private void Weighing_HandleRequestSave(string opName, string tagID)
        {
            B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
                Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【19-请求保存】", out long AID);
            try
            {
                decimal plcWeight = Convert.ToDecimal(ReadPLC(100140, opName));   // PLC锁定重量，曲线无效时兜底使用
                decimal weight = plcWeight; // 默认使用PLC锁定重量，曲线算法成功后覆盖
                decimal waterContent = Convert.ToDecimal(ReadPLC(100180, opName)); // 水含量
                string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 原始代码串/产品编号
                string modelNo = PLC_R.GetString_CleanGarbled(ReadPLC(11, opName));  // 机型号
                string orderNo = PLC_R.GetString_CleanGarbled(ReadPLC(39, opName));  // 工单号/订货号
                string palletNo = PLC_R.GetString_CleanGarbled(ReadPLC(80, opName)); // 托盘号
                if (TryConvertCodeBlockData(engineNo, out string parsedOrderNo, out string parsedProductNo))
                {
                    if (!string.IsNullOrWhiteSpace(parsedOrderNo))
                    {
                        orderNo = parsedOrderNo;
                    }
                    engineNo = parsedProductNo;
                }
                else if (string.IsNullOrWhiteSpace(engineNo))
                {
                    engineNo = $"未知产品_{opName}_{DateTime.Now:yyyyMMddHHmmss}";
                    LogWeighingFallback(opName, engineNo, "请求保存", "保存时产品编号为空，使用临时产品号继续保存", AID);
                }

                if (CurveCollector_TryGetStableWeight(opName, out decimal curveWeight, out int curvePointCount, out string curveMsg))
                {
                    weight = curveWeight; // 曲线采样点已按工具标定公式处理，稳定重量不再二次标定
                    B_DB_Opera.SaveLog_Response($"【{opName}】标定曲线重量={curveWeight:F3}kg，PLC锁定重量={plcWeight:F3}kg，点数={curvePointCount}", AID);
                }
                else
                {
                    B_DB_Opera.SaveLog_Response($"【{opName}】曲线重量不可用，使用PLC锁定重量={plcWeight:F3}kg，原因={curveMsg}", AID);
                    decimal rawWeight = plcWeight;
                    if (!WeightCalibrationHelper.Apply(opName, rawWeight, out decimal calibratedWeight, out string calibrationFormula, out string calibrationMsg))
                    {
                        LogWeighingFallback(opName, engineNo, "请求保存", $"工具标定公式处理失败：{calibrationMsg}，使用PLC原始重量继续保存", AID);
                        calibrationFormula = "未标定兜底";
                        calibratedWeight = rawWeight;
                    }
                    weight = calibratedWeight;
                    B_DB_Opera.SaveLog_Response($"【{opName}】PLC锁定重量标定：原始重量={rawWeight:F3}kg，公式={calibrationFormula}，标定后={weight:F3}kg", AID);
                    WeighingPage?.AppendLog($"[{opName}] PLC锁定重量标定：原始={rawWeight:F3}kg，公式={calibrationFormula}，结果={weight:F3}kg");
                }

                long curveWeighingRecordId = 0;
                string weighType = GetWeighingType(opName);
                decimal addOilQty = 0;
                decimal extractOilQty = 0;
                decimal density = 0;
                decimal residualOilUpperLimit = 0;
                decimal residualOilLowerLimit = 0;
                if (!string.IsNullOrWhiteSpace(orderNo) &&
                    B_DB_Opera.QueryWorkpieceByOrderNo(orderNo, out string matchedModelNo, out decimal matchedAddOilQty,
                        out decimal matchedExtractOilQty, out decimal matchedDensity, out decimal matchedResidualOilUpperLimit,
                        out decimal matchedResidualOilLowerLimit))
                {
                    modelNo = matchedModelNo;
                    addOilQty = matchedAddOilQty;
                    extractOilQty = matchedExtractOilQty;
                    density = matchedDensity;
                    residualOilUpperLimit = matchedResidualOilUpperLimit;
                    residualOilLowerLimit = matchedResidualOilLowerLimit;
                }

                if (!B_DB_Opera.UpdateStationComplete(engineNo, opName, weight, modelNo, orderNo, palletNo, weighType,
                    addOilQty, extractOilQty, density, Curr_UserName)) // 过站记录保存最终采用重量；找不到则由SP补异常完成记录
                {
                    LogWeighingFallback(opName, engineNo, "请求保存-过站", "未找到请求工作过站记录，已补异常过站完成记录", AID);
                }
                decimal oilReleaseQty = 0;
                decimal residualOilQty = 0;
                int finalQualityFlag = 1;
                string qualityMsg = "";
                if (weighType == "地面")
                {
                    // 地面：查最新记录 → 放油量=重量/密度 → 标记完成
                    if (B_DB_Opera.QueryLatestRecord(opName, engineNo,
                        out long recordId, out _, out decimal addOil, out decimal extractOil, out decimal dens))
                    {
                        curveWeighingRecordId = recordId; // 曲线记录最终关联这条称重记录
                        oilReleaseQty = dens > 0 ? weight / dens : 0; // 地面：油桶重量/密度=放油量
                        residualOilQty = addOil - extractOil - oilReleaseQty; // 残油=加油-抽油-放油
                        B_DB_Opera.UpdateWeighingComplete(recordId, "地面", opName,
                            weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
                        B_DB_Opera.SaveLog_Response(
                            $"【{opName}】地面保存完成：发动机={engineNo}，重量={weight}kg，放油量={oilReleaseQty:F4}L，残油量={residualOilQty:F4}L，合格标志={finalQualityFlag}，{qualityMsg}", AID);
                        WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
                        WeighingPage?.AppendLog($"[{opName}] 保存完成：{engineNo}，重量={weight:F2}kg，放油量={oilReleaseQty:F4}L");
                    }
                    else
                    {
                        // 兜底机制：请求工作阶段资料异常可能没有占位记录，保存时补一条异常完成记录，避免PLC等不到保存完成。
                        curveWeighingRecordId = B_DB_Opera.InsertWeighingRecord(opName, "地面", engineNo, modelNo, orderNo, palletNo,
                            0, weight, addOilQty, extractOilQty, density, residualOilUpperLimit, residualOilLowerLimit, 0, 0, 2, waterContent,
                            opName, Curr_UserName, isComplete: 1);
                        finalQualityFlag = 2;
                        qualityMsg = "未找到请求工作占位记录，已按异常地面记录保存";
                        LogWeighingFallback(opName, engineNo, "请求保存-地面", qualityMsg, AID);
                    }
                }
                else if (weighType == "放油前")
                {
                    // 空中放油前：查最新记录 → 仅写入进站重量，不标记完成
                    if (B_DB_Opera.QueryLatestRecord(opName, engineNo,
                        out long recordId, out _, out _, out _, out _))
                    {
                        curveWeighingRecordId = recordId; // 放油前曲线也关联同一条称重记录
                        B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油前", opName,
                            weight, 0, 0, waterContent, out finalQualityFlag, out qualityMsg);
                        B_DB_Opera.SaveLog_Response(
                            $"【{opName}】空中放油前保存完成：发动机={engineNo}，进站重量={weight}kg，合格标志={finalQualityFlag}，{qualityMsg}", AID);
                        WeighingPage?.AppendLog($"[{opName}] 放油前保存：{engineNo}，重量={weight:F2}kg");
                    }
                    else
                    {
                        // 兜底机制：没有占位记录时补异常记录并完成握手，后续放油后仍可按异常链路继续。
                        curveWeighingRecordId = B_DB_Opera.InsertWeighingRecord(opName, "空中", engineNo, modelNo, orderNo, palletNo,
                            weight, 0, addOilQty, extractOilQty, density, residualOilUpperLimit, residualOilLowerLimit, 0, 0, 2, waterContent,
                            opName, Curr_UserName, isComplete: 1);
                        finalQualityFlag = 2;
                        qualityMsg = "未找到请求工作占位记录，已按异常放油前记录保存";
                        LogWeighingFallback(opName, engineNo, "请求保存-放油前", qualityMsg, AID);
                    }
                }
                else // 放油后
                {
                    // 空中放油后：查最新放油前记录（无论是否完成），计算放油量更新到该记录
                    if (B_DB_Opera.QueryWeighingBeforeOil(engineNo, out long recordId, out decimal preWeight,
                        out decimal addOil, out decimal extractOil, out decimal dens))
                    {
                        curveWeighingRecordId = recordId; // 放油后回填放油前创建的称重记录
                        oilReleaseQty = dens > 0 ? (preWeight - weight) / dens : 0; // 空中：前后重量差/密度=放油量
                        residualOilQty = addOil - extractOil - oilReleaseQty; // 残油=加油-抽油-放油
                        B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油后", opName,
                            weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
                        B_DB_Opera.SaveLog_Response(
                            $"【{opName}】空中放油后保存完成：发动机={engineNo}，放油前={preWeight}kg，放油后={weight}kg，放油量={oilReleaseQty:F4}L，残油量={residualOilQty:F4}L，合格标志={finalQualityFlag}，{qualityMsg}", AID);
                        WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
                        WeighingPage?.AppendLog($"[{opName}] 放油后保存：{engineNo}，放油量={oilReleaseQty:F4}L，残油量={residualOilQty:F4}L");
                    }
                    else
                    {
                        // 找不到放油前记录（可能直接进放油后工位）：创建一条放油后记录并标记完成
                        curveWeighingRecordId = B_DB_Opera.InsertWeighingRecord(opName, "空中放油后", engineNo, modelNo, orderNo, palletNo,
                            0, weight, addOilQty, extractOilQty, density, residualOilUpperLimit, residualOilLowerLimit, 0, 0, 2, waterContent,
                            opName, Curr_UserName, isComplete: 1); // 异常兜底记录，质量直接判不合格
                        finalQualityFlag = 2;
                        qualityMsg = "未找到放油前记录";
                        B_DB_Opera.SaveLog_Response(
                            $"【{opName}】放油后保存（无放油前记录）：发动机={engineNo}，放油后重量={weight}kg", AID);
                        WeighingPage?.AppendLog($"[{opName}] ⚠️ 放油后保存：{engineNo}（无放油前记录，已新建）");
                    }
                }
                CurveCollector_EndSegment(opName, curveWeighingRecordId); // 保存点位、最终重量和取点区间
                // TODO: MES接口数据上传（接口对接后实现）
                if (finalQualityFlag == 2 && !string.IsNullOrWhiteSpace(qualityMsg))
                {
                    WeighingPage?.AppendLog($"[{opName}] 判定不合格：{qualityMsg}");
                }
                // 4. 先回写上位机判定的合格标志，再回写保存完成
                WritePLC_IF(21, opName, finalQualityFlag); // 合格标志
                WritePLC_IF(20, opName, true); // 保存完成
            }
            catch (Exception err)
            {
                CurveCollector_EndSegment(opName, 0);
                // WritePLC_IF(112, opName, 2); // 报警代码=2，保存失败
                B_DB_Opera.SaveLog_Response($"【{opName}】保存处理失败：{err.Message}", AID);
                WeighingPage?.AppendLog($"[{opName}] ❌ 保存异常：{err.Message}");
            }
        }
        // ====================================================================
        // 扫码枪业务处理
        // ====================================================================
        /// <summary>
        /// 扫码枪扫码完成后的业务处理
        /// 将码值写入PLC地址102500，触发完成信号100016=1，1秒后复位
        /// </summary>
        /// <param name="opName">工位号，由COM口绑定决定</param>
        /// <param name="barcode">扫到的条码值</param>
        public void Barcode_HandleScan(string opName, string barcode)
        {
            B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
                Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"扫码枪触发【{opName}】码值={barcode}", out long AID);
            try
            {
                // 1. 写入码值到 PLC 地址 102500（PLC_PC_扫码枪值）
                WritePLC_IF(102500, opName, barcode);
                // 2. 写入扫码完成信号 100016 = 1（PC_PLC_扫码枪扫码完成）
                WritePLC_IF(100016, opName, true);
                // 3. 日志
                B_DB_Opera.SaveLog_Response($"【{opName}】扫码完成：码值={barcode}", AID);
                WeighingPage?.AppendLog($"[{opName}]  触发扫码 码值：{barcode}");
                // 4. 1秒后复位 100016 = 0
                System.Threading.Tasks.Task.Delay(1000).ContinueWith(_ =>
                {
                    try { WritePLC_IF(100016, opName, false); }
                    catch { }
                });
            }
            catch (Exception err)
            {
                B_DB_Opera.SaveLog_Response($"【{opName}】扫码处理失败：{err.Message}", AID);
                WeighingPage?.AppendLog($"[{opName}] ❌ 扫码处理异常：{err.Message}");
            }
        }

    }
}
