Files
CalibratorLauncher/procedure/proceduremanager.cpp
2026-01-02 19:20:35 +09:00

656 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "proceduremanager.h"
#include "procedureparser.h"
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QCoreApplication>
ProcedureManager::ProcedureManager(QObject *parent)
: QObject(parent)
{
// 不再自动创建示例数据,等待调用 loadProcedureList
}
ProcedureManager::~ProcedureManager()
{
}
QVector<ProcedureSummary> ProcedureManager::loadProcedureList(const QString &directory)
{
m_procedureDirectory = directory;
m_procedureList.clear();
QDir dir(directory);
if (!dir.exists())
{
qWarning() << "Procedure directory does not exist:" << directory;
createSampleProcedures();
return m_procedureList;
}
// 扫描目录中的 YAML 和 JSON 文件
QStringList filters;
filters << "*.yaml" << "*.yml" << "*.json";
dir.setNameFilters(filters);
QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::Readable);
qDebug() << "Found" << files.size() << "procedure files in" << directory;
for (const QFileInfo &fileInfo : files)
{
QString filePath = fileInfo.absoluteFilePath();
qDebug() << "Scanning procedure file:" << filePath;
// 使用 ProcedureParser 解析文件
ProcedureParser parser;
if (parser.loadConfig(filePath))
{
ProcedureConfig config = parser.parseProcedureConfig();
// 创建摘要信息
ProcedureSummary summary;
summary.id = config.procedureId;
summary.name = config.procedureName;
summary.version = config.version;
summary.description = config.description;
summary.filePath = filePath;
m_procedureList.append(summary);
qDebug() << "Loaded procedure:" << summary.name
<< "ID:" << summary.id
<< "Version:" << summary.version;
}
else
{
qWarning() << "Failed to parse procedure file:" << filePath;
}
}
// 如果没有找到任何文件,创建示例数据
if (m_procedureList.isEmpty())
{
qDebug() << "No valid procedure files found, creating sample data";
createSampleProcedures();
}
else
{
// 即使找到了真实文件也添加mock异常数据用于测试
qDebug() << "Adding mock exception data for testing";
createMockExceptionData();
}
return m_procedureList;
}
QVector<ProcedureSummary> ProcedureManager::searchProcedures(const QString &keyword)
{
if (keyword.isEmpty())
{
return m_procedureList;
}
QVector<ProcedureSummary> results;
for (const auto &proc : m_procedureList)
{
if (proc.id.contains(keyword, Qt::CaseInsensitive) ||
proc.name.contains(keyword, Qt::CaseInsensitive))
{
results.append(proc);
}
}
return results;
}
ProcedureData ProcedureManager::loadProcedure(const QString &procedureId)
{
if (m_loadedProcedures.contains(procedureId))
{
return m_loadedProcedures[procedureId];
}
// 创建示例规程
ProcedureData proc;
proc.id = procedureId;
proc.workOrderId = m_workOrderId;
if (procedureId == "KMCIXRCP503")
{
proc.name = "一回路温度传感器绝缘和连续性检查:TP RCP63";
proc.version = "C4";
proc.description = "本程序适用于热停堆(热功率稳定)工况下执行RCP63程序";
// 创建任务组
TaskGroup group1;
group1.id = "preInspection";
group1.name = "5.1 试验前状态说明和检查";
// 添加步骤
StepData step1;
step1.id = "step_001";
step1.type = StepType::Manual;
step1.content = "本程序适用于热停堆(热功率稳定)工况下执行RCP63程序(K-MT-I-X-RCP-501)统一进行Sensor Check 部分使用。";
step1.status = StepStatus::Confirmed;
group1.steps.append(step1);
StepData step2;
step2.id = "step_002";
step2.type = StepType::Automatic;
step2.content = "执行前确认KIC中一回路三个环路平均温度变化量不超过0.2℃;";
step2.status = StepStatus::Passed;
step2.tableRefs.append("temperatureCheckTable");
step2.highlightFields.append("loop1Temp");
step2.highlightFields.append("loop2Temp");
step2.highlightFields.append("loop3Temp");
group1.steps.append(step2);
StepData step3;
step3.id = "step_003";
step3.type = StepType::Automatic;
step3.content = "若KIC中显示的一回路三个环路平均温度变化超过0.2℃,则采取滑动平均的计算方式记录滑动周期: (建议60s), 一环路平均温度滑动平均最大最小值之差;二环路平均温度滑动平均最大最小值之差;三环路平均温度滑动平均最大最小值之差;每个数据测量的最短时间(应大于滑动周期):。确认三个环路平均温度的滑动平均最大与最小值之差不超过0.2℃。";
step3.status = StepStatus::Pending;
group1.steps.append(step3);
StepData step4;
step4.id = "step_004";
step4.type = StepType::Manual;
step4.content = "确认 RPR、RPN上无相关的试验检修工作。";
step4.status = StepStatus::Pending;
group1.steps.append(step4);
StepData step5;
step5.id = "step_005";
step5.type = StepType::Automatic;
step5.content = "按附表1检查相关模拟量指示并记录确认结果满意。";
step5.status = StepStatus::Pending;
step5.tableRefs.append("analogReadingsTable");
group1.steps.append(step5);
StepData step6;
step6.id = "step_006";
step6.type = StepType::Automatic;
step6.content = "试验前根据附表1报警清单检查报警确认无异常报警方可进行后续检查工作";
step6.status = StepStatus::Pending;
step6.tableRefs.append("alarmCheckTable");
group1.steps.append(step6);
proc.taskGroups.append(group1);
// 创建表格
TableData tempTable;
tempTable.id = "temperatureCheckTable";
tempTable.name = "环路平均温度变化";
tempTable.description = "检验前后的环路温度数据";
TableField f1;
f1.id = "loop1Temp";
f1.name = "一环路平均温度";
f1.type = "numeric";
f1.unit = "";
TableField f2;
f2.id = "loop2Temp";
f2.name = "二环路平均温度";
f2.type = "numeric";
f2.unit = "";
TableField f3;
f3.id = "loop3Temp";
f3.name = "三环路平均温度";
f3.type = "numeric";
f3.unit = "";
tempTable.columns.append(f1);
tempTable.columns.append(f2);
tempTable.columns.append(f3);
proc.tables.append(tempTable);
TableData analogTable;
analogTable.id = "analogReadingsTable";
analogTable.name = "实验前后模拟量数据";
proc.tables.append(analogTable);
TableData alarmTable;
alarmTable.id = "alarmCheckTable";
alarmTable.name = "试验前后报警检查";
proc.tables.append(alarmTable);
// 计算步骤数
proc.totalSteps = group1.steps.size();
proc.completedSteps = 2; // 已完成2个步骤
}
else if (procedureId == "SIMPLE63")
{
proc.name = "范例 一回路温度传感器绝缘和连续性检查 TP RCP63";
proc.version = "SIMPLE";
proc.description = "范例规程";
proc.totalSteps = 6;
proc.completedSteps = 0;
}
else if (procedureId == "SIMPLE_TEST")
{
proc.name = "简单测试规程";
proc.version = "1.0";
proc.description = "这是一个简单的测试规程,用于演示 ProcedurePlayer 的基本功能。包含一个测试任务组和结果展示。";
proc.totalSteps = 4;
proc.completedSteps = 0;
}
else
{
// 尝试从 JSON 文件加载 mock 数据
proc = loadMockProcedureFromJson(procedureId);
// 如果 JSON 中没有找到,返回空数据
if (proc.name.isEmpty())
{
qWarning() << "Procedure not found:" << procedureId;
}
}
m_loadedProcedures[procedureId] = proc;
emit procedureLoaded(proc);
return proc;
}
void ProcedureManager::setWorkOrderId(const QString &workOrderId)
{
m_workOrderId = workOrderId;
}
bool ProcedureManager::confirmStep(const QString &procedureId, const QString &stepId)
{
if (!m_loadedProcedures.contains(procedureId))
{
return false;
}
auto &proc = m_loadedProcedures[procedureId];
for (auto &group : proc.taskGroups)
{
for (auto &step : group.steps)
{
if (step.id == stepId)
{
step.status = StepStatus::Confirmed;
step.executedAt = QDateTime::currentDateTime();
emit stepStatusChanged(stepId, StepStatus::Confirmed);
return true;
}
}
}
return false;
}
bool ProcedureManager::cancelStepConfirm(const QString &procedureId, const QString &stepId, const QString &reason)
{
if (!m_loadedProcedures.contains(procedureId))
{
return false;
}
auto &proc = m_loadedProcedures[procedureId];
for (auto &group : proc.taskGroups)
{
for (auto &step : group.steps)
{
if (step.id == stepId)
{
step.status = StepStatus::Pending;
step.cancelReason = reason;
emit stepStatusChanged(stepId, StepStatus::Pending);
return true;
}
}
}
return false;
}
bool ProcedureManager::executeAutoStep(const QString &procedureId, const QString &stepId)
{
// 模拟自动步骤执行
if (!m_loadedProcedures.contains(procedureId))
{
return false;
}
auto &proc = m_loadedProcedures[procedureId];
for (auto &group : proc.taskGroups)
{
for (auto &step : group.steps)
{
if (step.id == stepId && step.type == StepType::Automatic)
{
step.status = StepStatus::InProgress;
emit stepStatusChanged(stepId, StepStatus::InProgress);
// 实际应用中这里会执行真正的自动化逻辑
return true;
}
}
}
return false;
}
TableData ProcedureManager::getTableData(const QString &procedureId, const QString &tableId)
{
if (m_loadedProcedures.contains(procedureId))
{
const auto &proc = m_loadedProcedures[procedureId];
for (const auto &table : proc.tables)
{
if (table.id == tableId)
{
return table;
}
}
}
return TableData();
}
bool ProcedureManager::updateTableData(const QString &procedureId, const QString &tableId, const QVector<QVariantMap> &rows)
{
if (!m_loadedProcedures.contains(procedureId))
{
return false;
}
auto &proc = m_loadedProcedures[procedureId];
for (auto &table : proc.tables)
{
if (table.id == tableId)
{
table.rows = rows;
emit tableDataUpdated(tableId);
return true;
}
}
return false;
}
void ProcedureManager::createSampleProcedures()
{
m_procedureList.clear();
ProcedureSummary p1;
p1.id = "KMCIXRCP503";
p1.name = "一回路温度传感器绝缘和连续性检查:TP RCP63";
p1.version = "C4";
p1.description = "";
p1.filePath = "[SAMPLE]";
m_procedureList.append(p1);
ProcedureSummary p2;
p2.id = "SIMPLE63";
p2.name = "范例 一回路温度传感器绝缘和连续性检查 TP RCP63";
p2.version = "SIMPLE";
p2.description = "";
p2.filePath = "[SAMPLE]";
m_procedureList.append(p2);
ProcedureSummary p3;
p3.id = "SIMPLE_TEST";
p3.name = "简单测试规程";
p3.version = "1.0";
p3.description = "这是一个简单的测试规程,用于演示 ProcedurePlayer 的基本功能。包含一个测试任务组和结果展示。";
p3.filePath = "[SAMPLE]";
m_procedureList.append(p3);
// 也添加异常数据
createMockExceptionData();
}
void ProcedureManager::createMockExceptionData()
{
ProcedureSummary p7;
p7.id = "EMPTY_DESC";
p7.name = "空描述测试";
p7.version = "1.0";
p7.description = "";
p7.filePath = "[MOCK_EXCEPTION]";
m_procedureList.append(p7);
// Mock 异常数据 - 超长描述
ProcedureSummary p8;
p8.id = "LONG_DESC";
p8.name = "超长描述测试";
p8.version = "2.0";
p8.description = "这是一个包含超长描述的测试规程,用于验证当描述文本过长时,界面能否正确处理和显示。"
"描述内容可能包含多个段落、技术细节、注意事项等。在实际应用中,规程描述可能会非常详细,"
"包括前置条件、执行步骤概述、预期结果、风险提示、相关文档引用等信息。这样的长描述需要"
"界面能够适当地截断、折叠或提供滚动功能来确保用户体验。";
p8.filePath = "[MOCK_EXCEPTION]";
m_procedureList.append(p8);
// Mock 异常数据 - 数字和符号混合的 ID
ProcedureSummary p9;
p9.id = "TEST-2024-12-31_V1.0";
p9.name = "日期版本号混合 ID";
p9.version = "2024.12.31";
p9.description = "测试复杂的 ID 和版本号格式";
p9.filePath = "[MOCK_EXCEPTION]";
m_procedureList.append(p9);
// Mock 异常数据 - 换行符
ProcedureSummary p10;
p10.id = "MULTILINE";
p10.name = "多行\n文本\n测试";
p10.version = "1.0";
p10.description = "第一行描述\n第二行描述\n第三行描述";
p10.filePath = "[MOCK_EXCEPTION]";
m_procedureList.append(p10);
}
StepType ProcedureManager::parseStepType(const QString &typeStr)
{
if (typeStr == "Manual")
return StepType::Manual;
if (typeStr == "Automatic")
return StepType::Automatic;
return StepType::Manual;
}
StepStatus ProcedureManager::parseStepStatus(const QString &statusStr)
{
if (statusStr == "Pending")
return StepStatus::Pending;
if (statusStr == "InProgress")
return StepStatus::InProgress;
if (statusStr == "Confirmed")
return StepStatus::Confirmed;
if (statusStr == "Passed")
return StepStatus::Passed;
if (statusStr == "Failed")
return StepStatus::Failed;
if (statusStr == "Skipped")
return StepStatus::Skipped;
return StepStatus::Pending;
}
ProcedureData ProcedureManager::loadMockProcedureFromJson(const QString &procedureId)
{
ProcedureData proc;
proc.id = procedureId;
proc.workOrderId = m_workOrderId;
// 尝试多个可能的 JSON 文件路径
QStringList possiblePaths;
possiblePaths << m_procedureDirectory + "/mock_procedures.json";
possiblePaths << QDir::currentPath() + "/procedures/mock_procedures.json";
possiblePaths << QCoreApplication::applicationDirPath() + "/procedures/mock_procedures.json";
possiblePaths << QCoreApplication::applicationDirPath() + "/../../../procedures/mock_procedures.json";
QString jsonFilePath;
for (const QString &path : possiblePaths)
{
if (QFile::exists(path))
{
jsonFilePath = path;
qDebug() << "Found mock procedures JSON at:" << path;
break;
}
}
if (jsonFilePath.isEmpty())
{
qWarning() << "Mock procedures JSON file not found. Tried paths:" << possiblePaths;
return proc;
}
// 读取 JSON 文件
QFile file(jsonFilePath);
if (!file.open(QIODevice::ReadOnly))
{
qWarning() << "Failed to open mock procedures JSON:" << jsonFilePath;
return proc;
}
QByteArray jsonData = file.readAll();
file.close();
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
if (!doc.isObject())
{
qWarning() << "Invalid JSON format in mock procedures file";
return proc;
}
QJsonObject root = doc.object();
QJsonArray mockProcedures = root["mockProcedures"].toArray();
// 查找匹配的规程
for (const QJsonValue &value : mockProcedures)
{
QJsonObject procObj = value.toObject();
if (procObj["id"].toString() != procedureId)
continue;
// 解析基本信息
proc.name = procObj["name"].toString();
proc.version = procObj["version"].toString();
proc.description = procObj["description"].toString();
// 解析任务组
QJsonArray taskGroupsArray = procObj["taskGroups"].toArray();
for (const QJsonValue &groupValue : taskGroupsArray)
{
QJsonObject groupObj = groupValue.toObject();
TaskGroup group;
group.id = groupObj["id"].toString();
group.name = groupObj["name"].toString();
// 解析步骤
QJsonArray stepsArray = groupObj["steps"].toArray();
for (const QJsonValue &stepValue : stepsArray)
{
QJsonObject stepObj = stepValue.toObject();
StepData step;
step.id = stepObj["id"].toString();
step.content = stepObj["content"].toString();
step.type = parseStepType(stepObj["type"].toString());
step.status = parseStepStatus(stepObj["status"].toString());
// 解析表格引用
if (stepObj.contains("tableRefs"))
{
QJsonArray tableRefsArray = stepObj["tableRefs"].toArray();
for (const QJsonValue &refValue : tableRefsArray)
{
step.tableRefs.append(refValue.toString());
}
}
// 解析高亮字段
if (stepObj.contains("highlightFields"))
{
QJsonArray highlightArray = stepObj["highlightFields"].toArray();
for (const QJsonValue &fieldValue : highlightArray)
{
step.highlightFields.append(fieldValue.toString());
}
}
group.steps.append(step);
}
proc.taskGroups.append(group);
}
// 解析表格
if (procObj.contains("tables"))
{
QJsonArray tablesArray = procObj["tables"].toArray();
for (const QJsonValue &tableValue : tablesArray)
{
QJsonObject tableObj = tableValue.toObject();
TableData table;
table.id = tableObj["id"].toString();
table.name = tableObj["name"].toString();
if (tableObj.contains("description"))
table.description = tableObj["description"].toString();
// 解析列
if (tableObj.contains("columns"))
{
QJsonArray columnsArray = tableObj["columns"].toArray();
for (const QJsonValue &colValue : columnsArray)
{
QJsonObject colObj = colValue.toObject();
TableField field;
field.id = colObj["id"].toString();
field.name = colObj["name"].toString();
field.type = colObj["type"].toString();
if (colObj.contains("unit"))
field.unit = colObj["unit"].toString();
if (colObj.contains("isRequired"))
field.isRequired = colObj["isRequired"].toBool();
if (colObj.contains("isHighlighted"))
field.isHighlighted = colObj["isHighlighted"].toBool();
table.columns.append(field);
}
}
proc.tables.append(table);
}
}
// 计算步骤统计
if (procObj.contains("totalSteps"))
{
proc.totalSteps = procObj["totalSteps"].toInt();
}
else
{
proc.totalSteps = 0;
for (const TaskGroup &group : proc.taskGroups)
{
proc.totalSteps += group.steps.size();
}
}
if (procObj.contains("completedSteps"))
{
proc.completedSteps = procObj["completedSteps"].toInt();
}
else
{
proc.completedSteps = 0;
for (const TaskGroup &group : proc.taskGroups)
{
for (const StepData &step : group.steps)
{
if (step.status == StepStatus::Confirmed || step.status == StepStatus::Passed)
{
proc.completedSteps++;
}
}
}
}
qDebug() << "Loaded mock procedure from JSON:" << procedureId;
break;
}
return proc;
}