refactor(flow): 优化流程配置及相关测试
- 在测试代码中统一使用 setPreviousAction 与 setDuration 方法,规范方法调用 - VirtualContextBuilder 允许 setDuration 接收 null,增强灵活性 - VirtualityFlowProcessor 计算步骤持续时间时改进判断逻辑,避免错误持续时间值 - 增加日志记录上一步骤开始时间,方便调试流程状态 - 自定义流程配置文件大幅重构,新增多种晨洗模式配置 - 新增机洗单步骤流程、无晨洗流程、每日首次晨洗、存储超时晨洗等多种流程样例 - 自定义流程支持 override_steps 为 true/false 的不同合并策略说明与示例 - 详细补充配置节点说明,增强配置文档准确性和用户理解 - 自定义语音模板扩展,支持多种语音播报及错误提示文本 - 更新时间验证配置,统一各流程中各步骤时间要求 - 补充“配置自定义节点流程指南”文档,详细说明配置原则、合并策略与使用建议
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,60 +1,111 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程配置示例文件
|
* 流程配置文件
|
||||||
* 演示如何灵活配置不同医院的业务流程
|
*
|
||||||
|
* 使用说明:
|
||||||
|
* 1. 在 .env 中设置 FLOW_PROCESS_CONFIG_KEY 指定使用的配置键名
|
||||||
|
* 2. 每个配置包含:晨洗模式、步骤列表、时间验证、语音模板等
|
||||||
|
* 3. steps 中的 code 必须与 ProcessNode 的 getCode() 返回值一致
|
||||||
|
*
|
||||||
|
* @see \app\flow\config\ProcessConfig 加载和处理逻辑
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// ============================================
|
// ============================================
|
||||||
// 示例1: 标准完整流程(默认配置)
|
// 1. 机洗单步骤流程
|
||||||
// 晨洗→清洗→漂洗→消毒→终末漂洗→干燥→结束
|
// ============================================
|
||||||
|
// 场景:只刷机洗读卡器,自动完成后续步骤
|
||||||
|
// 流程:机洗 → 终末漂洗 → 干燥 → 结束
|
||||||
|
// 特点:跳过手工清洗的漂洗和消毒步骤
|
||||||
|
// ============================================
|
||||||
|
'machine_wash' => [
|
||||||
|
'name' => '机洗单步骤流程',
|
||||||
|
'description' => '只刷机洗后自动完成终末漂洗、干燥、结束',
|
||||||
|
'override_steps' => true,
|
||||||
|
|
||||||
|
// 晨洗配置:禁用
|
||||||
|
'morning_wash' => [
|
||||||
|
'mode' => 'none', // none | all | daily_first | storage_time | specific_types
|
||||||
|
],
|
||||||
|
|
||||||
|
// 步骤配置
|
||||||
|
'steps' => [
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true, 'required' => ['结束']],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true, 'required' => ['清洗']],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true, 'required' => ['漂洗']],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true, 'required' => ['清洗', '漂洗', '消毒']],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true, 'required' => ['机洗']],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true, 'required' => ['终末漂洗']],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥']],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 2. 标准完整流程
|
||||||
|
// ============================================
|
||||||
|
// 场景:完整的七步洗消流程
|
||||||
|
// 流程:晨洗 → 清洗 → 漂洗 → 消毒 → 终末漂洗 → 干燥 → 结束
|
||||||
|
// 特点:符合 WS 507-2016 规范,包含所有必需步骤
|
||||||
// ============================================
|
// ============================================
|
||||||
'standard' => [
|
'standard' => [
|
||||||
'name' => '标准完整流程',
|
'name' => '标准完整流程',
|
||||||
'description' => '包含所有步骤的完整清洗流程',
|
'description' => '包含晨洗在内的完整七步洗消流程',
|
||||||
// 覆盖 steps
|
'override_steps' => true,
|
||||||
'override_steps' => false,
|
|
||||||
|
// 晨洗配置:每天第一次需要晨洗
|
||||||
'morning_wash' => [
|
'morning_wash' => [
|
||||||
'mode' => 'daily_first', // 每天第一次需要晨洗
|
'mode' => 'daily_first', // 每天第一次洗消需要晨洗
|
||||||
'storage_threshold' => 4,
|
'storage_threshold' => 4, // 存储时间阈值(小时)
|
||||||
'morning_start_time' => '00:00:00',
|
'morning_start_time' => '00:00:00', // 每日开始时间
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 步骤配置
|
||||||
'steps' => [
|
'steps' => [
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true,'required' => ['结束','晨洗']],
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true,'required' => ['结束']],
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
||||||
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 默认时间验证配置(秒)
|
||||||
'time_validation' => [
|
'time_validation' => [
|
||||||
'手工洗' => [
|
'durations' => [
|
||||||
'清洗' => 120,
|
'清洗' => 120, // 2 分钟
|
||||||
'漂洗' => 60,
|
'漂洗' => 60, // 1 分钟
|
||||||
'消毒' => 300,
|
'消毒' => 300, // 5 分钟
|
||||||
'终末漂洗' => 120,
|
'终末漂洗' => 120, // 2 分钟
|
||||||
'干燥' => 30,
|
'干燥' => 30, // 30 秒
|
||||||
'机洗' => 360,
|
'机洗' => 360, // 6 分钟
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 示例2: 无晨洗流程
|
// 3. 无晨洗流程
|
||||||
// 医院不需要晨洗功能
|
// ============================================
|
||||||
// 清洗→漂洗→消毒→终末漂洗→干燥→结束
|
// 场景:不需要晨洗功能的医院
|
||||||
|
// 流程:清洗 → 漂洗 → 消毒 → 终末漂洗 → 干燥 → 结束
|
||||||
|
// 特点:完全禁用晨洗节点
|
||||||
// ============================================
|
// ============================================
|
||||||
'no_morning_wash' => [
|
'no_morning_wash' => [
|
||||||
'name' => '无晨洗流程',
|
'name' => '无晨洗流程',
|
||||||
'description' => '医院不需要晨洗功能',
|
'description' => '不包含晨洗步骤的标准洗消流程',
|
||||||
|
'override_steps' => true,
|
||||||
|
|
||||||
|
// 晨洗配置:完全禁用
|
||||||
'morning_wash' => [
|
'morning_wash' => [
|
||||||
'mode' => 'none', // 完全禁用晨洗
|
'mode' => 'none',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 步骤配置
|
||||||
'steps' => [
|
'steps' => [
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false], // 禁用晨洗节点
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
@@ -66,15 +117,46 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 示例3: 部分镜子需要晨洗(义乌模式)
|
// 4. 晨洗配置(多种模式)
|
||||||
// 根据存储时间判断:超过4小时需要晨洗
|
|
||||||
// ============================================
|
// ============================================
|
||||||
'partial_morning_wash' => [
|
// 场景:根据不同医院政策配置晨洗规则
|
||||||
'name' => '部分镜子晨洗(义乌模式)',
|
// 子配置:
|
||||||
'description' => '普通镜柜超过4小时需要晨洗,无菌镜柜免晨消',
|
// - morning_wash_daily: 每天第一次
|
||||||
|
// - morning_wash_storage: 存储超时
|
||||||
|
// - morning_wash_all: 每次都晨洗
|
||||||
|
// - morning_wash_specific: 特定类型镜子
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// 4.1 每天第一次晨洗(忠县模式)
|
||||||
|
'morning_wash_daily' => [
|
||||||
|
'name' => '每天第一次晨洗',
|
||||||
|
'description' => '每日首次洗消需要晨洗',
|
||||||
|
'override_steps' => true,
|
||||||
'morning_wash' => [
|
'morning_wash' => [
|
||||||
'mode' => 'storage_time', // 根据存储时间判断
|
'mode' => 'daily_first',
|
||||||
'storage_threshold' => 4, // 4小时阈值
|
'storage_threshold' => 4,
|
||||||
|
'morning_start_time' => '00:00:00',
|
||||||
|
],
|
||||||
|
'steps' => [
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// 4.2 存储超时晨洗(义乌模式)
|
||||||
|
'morning_wash_storage' => [
|
||||||
|
'name' => '存储超时晨洗(义乌模式)',
|
||||||
|
'description' => '普通镜柜超过阈值小时需要晨洗,无菌镜柜免晨消',
|
||||||
|
'override_steps' => true,
|
||||||
|
'morning_wash' => [
|
||||||
|
'mode' => 'storage_time',
|
||||||
|
'storage_threshold' => 4, // 4 小时阈值
|
||||||
'morning_start_time' => '06:00:00',
|
'morning_start_time' => '06:00:00',
|
||||||
],
|
],
|
||||||
'steps' => [
|
'steps' => [
|
||||||
@@ -89,106 +171,13 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
// ============================================
|
// 4.3 每次都晨洗(高感染风险区)
|
||||||
// 示例4: 无干燥流程
|
'morning_wash_all' => [
|
||||||
// 医院不需要干燥步骤,消毒后直接结束
|
'name' => '每次都晨洗',
|
||||||
// 清洗→漂洗→消毒→结束
|
'description' => '每次洗消都需要先进行晨洗',
|
||||||
// ============================================
|
'override_steps' => true,
|
||||||
'no_dry' => [
|
|
||||||
'name' => '无干燥流程',
|
|
||||||
'description' => '医院不需要干燥步骤',
|
|
||||||
'morning_wash' => [
|
'morning_wash' => [
|
||||||
'mode' => 'none',
|
'mode' => 'all',
|
||||||
],
|
|
||||||
'steps' => [
|
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
|
||||||
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
|
||||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
|
||||||
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => false], // 跳过终末漂洗
|
|
||||||
['code' => '干燥', 'class' => 'DryNode', 'enabled' => false], // 禁用干燥
|
|
||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 示例5: 仅干燥流程
|
|
||||||
// 医院只有干燥步骤,没有完整清洗流程
|
|
||||||
// 干燥→结束
|
|
||||||
// ============================================
|
|
||||||
'dry_only' => [
|
|
||||||
'name' => '仅干燥流程',
|
|
||||||
'description' => '医院只有干燥步骤',
|
|
||||||
'morning_wash' => [
|
|
||||||
'mode' => 'none',
|
|
||||||
],
|
|
||||||
'steps' => [
|
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => false],
|
|
||||||
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => false],
|
|
||||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => false],
|
|
||||||
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => false],
|
|
||||||
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
|
||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => false],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 示例6: 机洗流程
|
|
||||||
// 清洗→机洗→终末漂洗→干燥→结束
|
|
||||||
// ============================================
|
|
||||||
'machine_wash' => [
|
|
||||||
'name' => '机洗流程',
|
|
||||||
'description' => '刷完机洗后接终末漂洗再干燥结束',
|
|
||||||
'morning_wash' => [
|
|
||||||
'mode' => 'none',
|
|
||||||
],
|
|
||||||
'steps' => [
|
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
|
||||||
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => false], // 跳过硬洗漂洗
|
|
||||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => false], // 跳过硬洗消毒
|
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
|
||||||
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
|
||||||
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
|
||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 示例7: 简化流程
|
|
||||||
// 只刷一个清洗就结束
|
|
||||||
// 清洗→结束
|
|
||||||
// ============================================
|
|
||||||
'simple' => [
|
|
||||||
'name' => '简化流程',
|
|
||||||
'description' => '只刷一个清洗就结束',
|
|
||||||
'morning_wash' => [
|
|
||||||
'mode' => 'none',
|
|
||||||
],
|
|
||||||
'steps' => [
|
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
|
||||||
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => false],
|
|
||||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => false],
|
|
||||||
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => false],
|
|
||||||
['code' => '干燥', 'class' => 'DryNode', 'enabled' => false],
|
|
||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => false],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 示例8: 自定义语音流程
|
|
||||||
// 针对某个流程特殊定制语音
|
|
||||||
// ============================================
|
|
||||||
'custom_voice' => [
|
|
||||||
'name' => '自定义语音流程',
|
|
||||||
'description' => '针对流程特殊定制语音',
|
|
||||||
'morning_wash' => [
|
|
||||||
'mode' => 'daily_first',
|
|
||||||
],
|
],
|
||||||
'steps' => [
|
'steps' => [
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
||||||
@@ -200,54 +189,118 @@ return [
|
|||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
],
|
],
|
||||||
'voice_templates' => [
|
],
|
||||||
// 与 VoiceGenerationStrategy 中默认配置进行合并
|
|
||||||
'normal_wash' => [
|
// 4.4 特定类型镜子晨洗
|
||||||
'清洗' => '第一步清洗开始,请认真清洗',
|
'morning_wash_specific' => [
|
||||||
'漂洗' => '第二步漂洗开始',
|
'name' => '特定类型镜子晨洗',
|
||||||
'消毒' => '第三步消毒开始,请确保消毒时间',
|
'description' => '只有胃镜、十二指肠镜需要晨洗',
|
||||||
'终末漂洗' => '第四步终末漂洗开始',
|
'override_steps' => true,
|
||||||
'干燥' => '最后一步干燥开始',
|
'morning_wash' => [
|
||||||
'结束' => '清洗流程全部完成,请妥善保管内镜',
|
'mode' => 'specific_types',
|
||||||
|
'specific_types' => ['胃镜', '十二指肠镜'],
|
||||||
|
],
|
||||||
|
'steps' => [
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 5. 有干燥流程(默认)
|
||||||
|
// ============================================
|
||||||
|
// 场景:包含干燥步骤的完整流程
|
||||||
|
// 流程:晨洗 → 清洗 → 漂洗 → 消毒 → 终末漂洗 → 干燥 → 结束
|
||||||
|
// 特点:干燥节点启用,符合规范要求
|
||||||
|
// ============================================
|
||||||
|
'with_dry' => [
|
||||||
|
'name' => '有干燥流程',
|
||||||
|
'description' => '包含干燥步骤的完整洗消流程',
|
||||||
|
'override_steps' => true,
|
||||||
|
|
||||||
|
// 晨洗配置:每天第一次
|
||||||
|
'morning_wash' => [
|
||||||
|
'mode' => 'daily_first',
|
||||||
|
],
|
||||||
|
|
||||||
|
// 步骤配置:干燥启用
|
||||||
|
'steps' => [
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true], // ✓ 启用干燥
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
|
],
|
||||||
|
|
||||||
|
// 时间验证
|
||||||
|
'time_validation' => [
|
||||||
|
'durations' => [
|
||||||
|
'清洗' => 120,
|
||||||
|
'漂洗' => 60,
|
||||||
|
'消毒' => 300,
|
||||||
|
'终末漂洗' => 120,
|
||||||
|
'干燥' => 30, // 干燥至少 30 秒
|
||||||
|
'机洗' => 360,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 示例9: 特定类型镜子晨洗
|
// 6. 无干燥流程
|
||||||
// 只有特定类型的镜子需要晨洗
|
|
||||||
// ============================================
|
// ============================================
|
||||||
'specific_type_morning_wash' => [
|
// 场景:医院不需要干燥步骤
|
||||||
'name' => '特定类型镜子晨洗',
|
// 流程:晨洗 → 清洗 → 漂洗 → 消毒 → 终末漂洗 → 结束
|
||||||
'description' => '只有胃镜需要晨洗,肠镜不需要',
|
// 特点:禁用干燥和终末漂洗
|
||||||
|
// ============================================
|
||||||
|
'no_dry' => [
|
||||||
|
'name' => '无干燥流程',
|
||||||
|
'description' => '不包含干燥步骤的洗消流程',
|
||||||
|
'override_steps' => true,
|
||||||
|
|
||||||
|
// 晨洗配置:禁用
|
||||||
'morning_wash' => [
|
'morning_wash' => [
|
||||||
'mode' => 'specific_types',
|
'mode' => 'none',
|
||||||
'specific_types' => ['胃镜', '十二指肠镜'], // 只有这些类型需要晨洗
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 步骤配置:禁用干燥和终末漂洗
|
||||||
'steps' => [
|
'steps' => [
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => false], // ✗ 禁用
|
||||||
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => false], // ✗ 禁用干燥
|
||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 示例10: 自定义时间要求
|
// 7. 自定义语音流程
|
||||||
// 不同医院对步骤时间有不同要求
|
|
||||||
// ============================================
|
// ============================================
|
||||||
'custom_duration' => [
|
// 场景:需要特殊语音播报的医院
|
||||||
'name' => '自定义时间要求',
|
// 特点:每个步骤都有自定义语音内容
|
||||||
'description' => '消毒时间要求10分钟',
|
// ============================================
|
||||||
// 覆盖 steps
|
'custom_voice' => [
|
||||||
'override_steps' => false,
|
'name' => '自定义语音流程',
|
||||||
|
'description' => '每个步骤都有特殊语音提示',
|
||||||
|
'override_steps' => true,
|
||||||
|
|
||||||
|
// 晨洗配置:每天第一次
|
||||||
'morning_wash' => [
|
'morning_wash' => [
|
||||||
'mode' => 'daily_first',
|
'mode' => 'daily_first',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 步骤配置:标准流程
|
||||||
'steps' => [
|
'steps' => [
|
||||||
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
||||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
@@ -258,13 +311,44 @@ return [
|
|||||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 自定义语音模板
|
||||||
|
'voice_templates' => [
|
||||||
|
// 正常洗消流程语音
|
||||||
|
'normal_wash' => [
|
||||||
|
'晨洗' => '晨洗开始,请进行每日首次清洗准备',
|
||||||
|
'清洗' => '第一步清洗开始,请认真清洗内镜外表面和管道',
|
||||||
|
'漂洗' => '第二步漂洗开始,用流动水冲洗残留酶液',
|
||||||
|
'消毒' => '第三步消毒开始,请确保消毒剂浓度和时间',
|
||||||
|
'终末漂洗' => '第四步终末漂洗开始,用无菌水彻底冲洗',
|
||||||
|
'干燥' => '最后一步干燥开始,用压力气枪吹干管道',
|
||||||
|
'结束' => '清洗流程全部完成,请妥善保管内镜',
|
||||||
|
'机洗' => '机洗开始,请将内镜放入清洗机',
|
||||||
|
],
|
||||||
|
|
||||||
|
// 晨洗流程语音
|
||||||
|
'morning_wash' => [
|
||||||
|
'晨洗' => '晨洗模式启动,今日首次清洗需额外注意',
|
||||||
|
'清洗' => '晨洗后的第一次清洗,请按标准流程操作',
|
||||||
|
],
|
||||||
|
|
||||||
|
// 错误提示语音
|
||||||
|
'error_messages' => [
|
||||||
|
'NOT_ENOUGH_TIME' => '{step}时间不足,还需等待{time}秒',
|
||||||
|
'PLEASE_SWIPE_OPERATOR' => '请先刷人员卡',
|
||||||
|
'PLEASE_SWIPE_ENDOSCOPE' => '请刷内镜卡',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// 时间验证配置
|
||||||
'time_validation' => [
|
'time_validation' => [
|
||||||
'durations' => [
|
'durations' => [
|
||||||
'清洗' => 300, // 5分钟
|
'清洗' => 120,
|
||||||
'漂洗' => 120, // 2分钟
|
'漂洗' => 60,
|
||||||
'消毒' => 600, // 10分钟(自定义)
|
'消毒' => 300,
|
||||||
'终末漂洗' => 180, // 3分钟(自定义)
|
'终末漂洗' => 120,
|
||||||
'干燥' => 300, // 5分钟(自定义)
|
'干燥' => 30,
|
||||||
|
'机洗' => 360,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ class VirtualContextBuilder
|
|||||||
/**
|
/**
|
||||||
* 设置操作时长(秒)
|
* 设置操作时长(秒)
|
||||||
*/
|
*/
|
||||||
public function setDuration(int $seconds): self
|
public function setDuration(?int $seconds): self
|
||||||
{
|
{
|
||||||
$this->processStatus = new ProcessStatus(
|
$this->processStatus = new ProcessStatus(
|
||||||
currentStep: $this->processStatus->currentStep,
|
currentStep: $this->processStatus->currentStep,
|
||||||
|
|||||||
@@ -164,9 +164,12 @@ class VirtualityFlowProcessor
|
|||||||
|
|
||||||
// 继承之前的流程状态
|
// 继承之前的流程状态
|
||||||
if ($currentState !== null) {
|
if ($currentState !== null) {
|
||||||
|
$st = strtotime($currentState->getActionStartTime());
|
||||||
|
$duration = time() - $st;
|
||||||
|
if (empty($st) || $duration <= 0) $duration = null;
|
||||||
$builder->currentStep($currentState->getCurrentStep())
|
$builder->currentStep($currentState->getCurrentStep())
|
||||||
->batchNo($currentState->getBatchNo())
|
->batchNo($currentState->getBatchNo())
|
||||||
->setDuration(time() - strtotime($currentState->getActionStartTime()))
|
->setDuration($duration)
|
||||||
->processType($currentState->getProcessType());
|
->processType($currentState->getProcessType());
|
||||||
|
|
||||||
// 设置 previousAction:上一步骤名称就是当前状态的 currentStep
|
// 设置 previousAction:上一步骤名称就是当前状态的 currentStep
|
||||||
@@ -231,6 +234,7 @@ class VirtualityFlowProcessor
|
|||||||
Logger::info("当前步骤:{}", [$result->getCurrentStep()]);
|
Logger::info("当前步骤:{}", [$result->getCurrentStep()]);
|
||||||
Logger::info("上一个步骤类型:{}", [$result->getPreviousAction()->action_type_name ?? "null"]);
|
Logger::info("上一个步骤类型:{}", [$result->getPreviousAction()->action_type_name ?? "null"]);
|
||||||
Logger::info("上一个步骤:{}", [$result->getPreviousAction()->process_name ?? "null"]);
|
Logger::info("上一个步骤:{}", [$result->getPreviousAction()->process_name ?? "null"]);
|
||||||
|
Logger::info("上一个步骤开始时间:{}", [$result->getPreviousAction()->op_starttime ?? "null"]);
|
||||||
Logger::info("时长:{}", [$result->getDuration() ?? "null"]);
|
Logger::info("时长:{}", [$result->getDuration() ?? "null"]);
|
||||||
Logger::info("当前语音:{$result->getFullVoice()}\n");
|
Logger::info("当前语音:{$result->getFullVoice()}\n");
|
||||||
return $result;
|
return $result;
|
||||||
|
|||||||
@@ -86,9 +86,9 @@ class BlockTest extends TestCase
|
|||||||
->reader('漂洗')
|
->reader('漂洗')
|
||||||
->operator('操作员1')
|
->operator('操作员1')
|
||||||
->currentStep('清洗')
|
->currentStep('清洗')
|
||||||
->previousAction('清洗') // 上一步也是清洗,表示重复刷同一步骤
|
->setPreviousAction('清洗') // 上一步也是清洗,表示重复刷同一步骤
|
||||||
->processType('手工洗') // 必须设置流程类型,否则 hasStep 返回 false
|
->processType('手工洗') // 必须设置流程类型,否则 hasStep 返回 false
|
||||||
->duration(5) // 只有5秒,时间不足
|
->setDuration(5) // 只有5秒,时间不足
|
||||||
->batchNo(date('Ymd') . '010001')
|
->batchNo(date('Ymd') . '010001')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
@@ -115,9 +115,9 @@ class BlockTest extends TestCase
|
|||||||
->reader('漂洗')
|
->reader('漂洗')
|
||||||
->operator('操作员1')
|
->operator('操作员1')
|
||||||
->currentStep('清洗')
|
->currentStep('清洗')
|
||||||
->previousAction('清洗') // 设置 previousAction 使时间验证生效
|
->setPreviousAction('清洗') // 设置 previousAction 使时间验证生效
|
||||||
->processType('手工洗') // 必须设置流程类型
|
->processType('手工洗') // 必须设置流程类型
|
||||||
->duration(5) // 只有5秒,时间不足
|
->setDuration(5) // 只有5秒,时间不足
|
||||||
->batchNo(date('Ymd') . '010001')
|
->batchNo(date('Ymd') . '010001')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
@@ -166,9 +166,9 @@ class BlockTest extends TestCase
|
|||||||
->reader('漂洗')
|
->reader('漂洗')
|
||||||
->operator('操作员1')
|
->operator('操作员1')
|
||||||
->currentStep('清洗')
|
->currentStep('清洗')
|
||||||
->previousAction('清洗') // 设置 previousAction 使时间验证生效
|
->setPreviousAction('清洗') // 设置 previousAction 使时间验证生效
|
||||||
->processType('手工洗') // 必须设置流程类型
|
->processType('手工洗') // 必须设置流程类型
|
||||||
->duration(120) // 120秒,时间充足
|
->setDuration(120) // 120秒,时间充足
|
||||||
->batchNo(date('Ymd') . '010001')
|
->batchNo(date('Ymd') . '010001')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class VoiceTest extends TestCase
|
|||||||
->reader('漂洗')
|
->reader('漂洗')
|
||||||
->operator('操作员1')
|
->operator('操作员1')
|
||||||
->currentStep('清洗')
|
->currentStep('清洗')
|
||||||
->duration(5) // 只有5秒,时间不足
|
->setDuration(5) // 只有5秒,时间不足
|
||||||
->batchNo(date('Ymd') . '010001')
|
->batchNo(date('Ymd') . '010001')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
|
|||||||
+443
@@ -0,0 +1,443 @@
|
|||||||
|
# 配置自定义节点流程指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本项目支持通过配置文件自定义洗消流程,可以灵活控制每个节点的前置条件、启用状态等。
|
||||||
|
|
||||||
|
## 配置文件位置
|
||||||
|
|
||||||
|
- **文件路径**:`./custom_process_config.php`
|
||||||
|
- **配置键选择**:通过环境变量 `FLOW_PROCESS_CONFIG_KEY` 指定使用的配置
|
||||||
|
- **配置键**:通过环境变量 `FLOW_USE_CUSTOM_PROCESS` 来开启允许使用自定义配置
|
||||||
|
|
||||||
|
### override_steps 参数详解 ⭐
|
||||||
|
|
||||||
|
**作用**:控制自定义步骤与默认步骤的合并策略。
|
||||||
|
|
||||||
|
#### 1. override_steps = true(完全覆盖模式)
|
||||||
|
|
||||||
|
**行为**:
|
||||||
|
- ✅ 完全使用配置文件中的 `steps` 列表
|
||||||
|
- ✅ **不会**创建配置中不存在的节点
|
||||||
|
- ✅ 如果配置为空数组,则使用默认步骤列表
|
||||||
|
- ✅ 适合:定义全新的流程、大幅简化流程
|
||||||
|
|
||||||
|
**示例**:机洗单步骤流程
|
||||||
|
```php
|
||||||
|
'machine_wash' => [
|
||||||
|
'override_steps' => true, // 完全覆盖
|
||||||
|
'steps' => [
|
||||||
|
// 只定义需要的节点,其他节点不会创建
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
// 结果:流程中只有这 4 个节点,清洗/漂洗/消毒等节点不会被创建
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- ⚠️ 必须显式列出所有需要的节点
|
||||||
|
- ⚠️ 漏掉的节点将**不存在**于流程中(不是禁用,是不存在)
|
||||||
|
- ⚠️ 如果某个节点没配置但后续逻辑需要,会导致错误
|
||||||
|
|
||||||
|
#### 2. override_steps = false(增量合并模式)
|
||||||
|
|
||||||
|
**行为**:
|
||||||
|
- ✅ 使用默认步骤列表作为基础
|
||||||
|
- ✅ 用自定义步骤**覆盖**相同 code 的默认步骤
|
||||||
|
- ✅ 自定义步骤中不存在的节点**保持默认配置**
|
||||||
|
- ✅ 适合:微调特定节点的属性(如 enabled、required)
|
||||||
|
|
||||||
|
**示例**:仅调整晨洗节点
|
||||||
|
```php
|
||||||
|
'custom_morning' => [
|
||||||
|
'override_steps' => false, // 增量合并
|
||||||
|
'steps' => [
|
||||||
|
// 只配置晨洗节点,其他节点使用默认值
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true, 'required' => []],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
// 结果:
|
||||||
|
// - 晨洗节点:使用自定义配置(enabled=true, required=[])
|
||||||
|
// - 清洗/漂洗/消毒等其他节点:使用默认配置
|
||||||
|
```
|
||||||
|
|
||||||
|
**合并逻辑**(伪代码):
|
||||||
|
```php
|
||||||
|
// 1. 将默认步骤转为「code => StepConfig」的关联数组
|
||||||
|
$defaultStepsMap = [];
|
||||||
|
foreach (self::defaultSteps() as $defaultStep) {
|
||||||
|
$defaultStepsMap[$defaultStep->code] = $defaultStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 遍历自定义步骤,覆盖默认步骤中相同 code 的元素
|
||||||
|
foreach ($customSteps as $customStep) {
|
||||||
|
$defaultStepsMap[$customStep->code] = $customStep; // 覆盖
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转回索引数组
|
||||||
|
$this->steps = array_values($defaultStepsMap);
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- ⚠️ 只能修改已存在于默认列表中的节点
|
||||||
|
- ⚠️ 无法添加默认列表中不存在的新节点(会被忽略)
|
||||||
|
- ⚠️ 适合小范围调整,不适合大规模改动
|
||||||
|
|
||||||
|
#### 3. 对比总结
|
||||||
|
|
||||||
|
| 特性 | override_steps = true | override_steps = false |
|
||||||
|
|------|----------------------|------------------------|
|
||||||
|
| 基础列表 | 自定义 steps | 默认 steps |
|
||||||
|
| 未配置的节点 | 不存在 | 使用默认配置 |
|
||||||
|
| 相同 code 的节点 | 以自定义为准 | 以自定义为准 |
|
||||||
|
| 适用场景 | 全新流程、大幅简化 | 微调特定节点 |
|
||||||
|
| 风险 | 可能遗漏必需节点 | 无法添加新节点 |
|
||||||
|
|
||||||
|
#### 4. 使用建议
|
||||||
|
|
||||||
|
**使用 override_steps = true 的场景**:
|
||||||
|
- ✅ 机洗单步骤流程(只需 4 个节点)
|
||||||
|
- ✅ 无干燥流程(去掉干燥和终末漂洗)
|
||||||
|
- ✅ 特殊医院定制的简化流程
|
||||||
|
|
||||||
|
**使用 override_steps = false 的场景**:
|
||||||
|
- ✅ 仅调整晨洗规则
|
||||||
|
- ✅ 仅修改某个节点的 enabled 状态
|
||||||
|
- ✅ 仅添加 required 约束
|
||||||
|
- ✅ 临时测试某个节点的配置变更
|
||||||
|
|
||||||
|
**默认推荐**:
|
||||||
|
- 📌 大多数情况使用 `true`,明确列出所有需要的节点
|
||||||
|
- 📌 只在确实只需要微调时使用 `false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 配置结构说明
|
||||||
|
|
||||||
|
### 完整配置示例
|
||||||
|
|
||||||
|
```php
|
||||||
|
'custom_flow' => [
|
||||||
|
'name' => '自定义流程名称',
|
||||||
|
'description' => '流程描述',
|
||||||
|
'override_steps' => true, // 是否覆盖默认步骤
|
||||||
|
|
||||||
|
// 晨洗配置
|
||||||
|
'morning_wash' => [
|
||||||
|
'mode' => 'none', // none | daily_first | all | storage_time | specific_types
|
||||||
|
'storage_threshold' => 4, // 存储时间阈值(小时)- storage_time 模式使用
|
||||||
|
'morning_start_time' => '00:00:00', // 每日开始时间 - daily_first 模式使用
|
||||||
|
'specific_types' => ['胃镜', '十二指肠镜'], // 特定类型 - specific_types 模式使用
|
||||||
|
],
|
||||||
|
|
||||||
|
// 步骤配置(核心)
|
||||||
|
'steps' => [
|
||||||
|
[
|
||||||
|
'code' => '清洗', // 节点标识码(必须与 ProcessNode 的 getCode() 一致)
|
||||||
|
'class' => 'WashNode', // 节点类名(不含命名空间)
|
||||||
|
'enabled' => true, // 是否启用该节点
|
||||||
|
'required' => ['结束', '晨洗'], // 可选:指定前置节点列表
|
||||||
|
],
|
||||||
|
// ... 其他节点
|
||||||
|
],
|
||||||
|
|
||||||
|
// 时间验证配置(秒)
|
||||||
|
'time_validation' => [
|
||||||
|
'durations' => [
|
||||||
|
'晨洗' => 60, // 晨洗至少 60 秒
|
||||||
|
'清洗' => 120, // 清洗至少 120 秒
|
||||||
|
'漂洗' => 60, // 漂洗至少 60 秒
|
||||||
|
'消毒' => 300, // 消毒至少 300 秒
|
||||||
|
'终末漂洗' => 120, // 终末漂洗至少 120 秒
|
||||||
|
'干燥' => 30, // 干燥至少 30 秒
|
||||||
|
'机洗' => 360, // 机洗至少 360 秒
|
||||||
|
],
|
||||||
|
'enabled' => true, // 是否启用时间验证
|
||||||
|
],
|
||||||
|
|
||||||
|
// 语音模板配置
|
||||||
|
'voice_templates' => [
|
||||||
|
// 正常洗消流程语音
|
||||||
|
'normal_wash' => [
|
||||||
|
'晨洗' => '晨洗开始,请进行每日首次清洗准备',
|
||||||
|
'清洗' => '第一步清洗开始,请认真清洗内镜外表面和管道',
|
||||||
|
'漂洗' => '第二步漂洗开始,用流动水冲洗残留酶液',
|
||||||
|
'消毒' => '第三步消毒开始,请确保消毒剂浓度和时间',
|
||||||
|
'终末漂洗' => '第四步终末漂洗开始,用无菌水彻底冲洗',
|
||||||
|
'干燥' => '最后一步干燥开始,用压力气枪吹干管道',
|
||||||
|
'结束' => '清洗流程全部完成,请妥善保管内镜',
|
||||||
|
'机洗' => '机洗开始,请将内镜放入清洗机',
|
||||||
|
],
|
||||||
|
|
||||||
|
// 晨洗流程语音
|
||||||
|
'morning_wash' => [
|
||||||
|
'晨洗' => '晨洗模式启动,今日首次清洗需额外注意',
|
||||||
|
'清洗' => '晨洗后的第一次清洗,请按标准流程操作',
|
||||||
|
],
|
||||||
|
|
||||||
|
// 错误提示语音
|
||||||
|
'error_messages' => [
|
||||||
|
'NOT_ENOUGH_TIME' => '{step}时间不足,还需等待{time}秒',
|
||||||
|
'PLEASE_SWIPE_OPERATOR' => '请先刷人员卡',
|
||||||
|
'PLEASE_SWIPE_ENDOSCOPE' => '请刷内镜卡',
|
||||||
|
'PLEASE_SWIPE_READER' => '请刷读卡器',
|
||||||
|
],
|
||||||
|
|
||||||
|
// 重复刷卡提示
|
||||||
|
'duplicate_check' => [
|
||||||
|
'operator' => '您已刷过人员卡,无需重复刷卡',
|
||||||
|
'endoscope' => '您已刷过内镜卡,无需重复刷卡',
|
||||||
|
'reader' => '您已刷过读卡器,无需重复刷卡',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心参数详解
|
||||||
|
|
||||||
|
### 1. steps[].code(节点标识码)
|
||||||
|
|
||||||
|
**作用**:唯一标识一个节点,必须与对应的 `ProcessNode` 子类的 `getCode()` 方法返回值一致。
|
||||||
|
|
||||||
|
**可用节点码**:
|
||||||
|
- `晨洗` - MorningWashNode
|
||||||
|
- `重复刷卡` - DuplicateCheckNode
|
||||||
|
- `清洗` - WashNode
|
||||||
|
- `漂洗` - RinseNode
|
||||||
|
- `消毒` - DisinfectNode
|
||||||
|
- `终末漂洗` - FinalRinseNode
|
||||||
|
- `干燥` - DryNode
|
||||||
|
- `结束` - EndNode
|
||||||
|
- `机洗` - MachineWashNode
|
||||||
|
- `虚拟清洗机` - VirtualWashMachineNode
|
||||||
|
- `存储` - StorageNode
|
||||||
|
- `内镜放入` - StorageInNode
|
||||||
|
- `内镜取出` - StorageOutNode
|
||||||
|
- `Close` - CloseNode
|
||||||
|
|
||||||
|
### 2. steps[].class(节点类名)
|
||||||
|
|
||||||
|
**作用**:指定处理该节点的类名(不含命名空间)。
|
||||||
|
|
||||||
|
**命名空间**:所有节点类都在 `app\flow\nodes\` 命名空间下。
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```php
|
||||||
|
'class' => 'WashNode' // 对应 \app\flow\nodes\WashNode
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. steps[].enabled(启用状态)
|
||||||
|
|
||||||
|
**作用**:控制节点是否启用。
|
||||||
|
|
||||||
|
**值**:
|
||||||
|
- `true` - 启用该节点
|
||||||
|
- `false` - 禁用/跳过该节点
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```php
|
||||||
|
// 机洗单步骤流程中,跳手工洗节点
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => false],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. steps[].required(前置节点要求)⭐
|
||||||
|
|
||||||
|
**作用**:指定当前节点的前一个节点可以是哪些。用于实现特殊流程控制。
|
||||||
|
|
||||||
|
**类型**:字符串数组(允许的前置节点 code 列表)
|
||||||
|
|
||||||
|
**工作原理**:
|
||||||
|
- 当流程执行到某个节点时,会检查上一个节点的 code 是否在 `required` 列表中
|
||||||
|
- 如果不在列表中,流程会拒绝执行并提示错误
|
||||||
|
- 如果不设置 `required`,则使用默认的配置
|
||||||
|
|
||||||
|
**使用场景**:
|
||||||
|
|
||||||
|
#### 场景 1:强制流程顺序
|
||||||
|
```php
|
||||||
|
'steps' => [
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true, 'required' => ['结束', '晨洗']],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true, 'required' => ['清洗']],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true, 'required' => ['漂洗']],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true, 'required' => ['消毒']],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true, 'required' => ['终末漂洗']],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥']],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
这样确保必须按顺序执行:清洗 → 漂洗 → 消毒 → 终末漂洗 → 干燥 → 结束
|
||||||
|
|
||||||
|
#### 场景 2:机洗介入
|
||||||
|
```php
|
||||||
|
'steps' => [
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
|
// 机洗可以在手工洗的任意阶段介入
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true, 'required' => ['清洗', '漂洗', '消毒']],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true, 'required' => ['机洗']],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true, 'required' => ['终末漂洗']],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥', '机洗']],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 场景 3:多路径汇聚
|
||||||
|
```php
|
||||||
|
'steps' => [
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
|
// 结束可以从干燥或机洗过来
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥', '机洗']],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
1. ✅ `required` 中的节点码必须在 `steps` 中存在
|
||||||
|
2. ✅ 循环依赖可能会导致死锁(如 A 要求 B,B 要求 A)
|
||||||
|
3. ✅ 如果节点设置了 `required`,则只能从指定的前驱节点过来
|
||||||
|
|
||||||
|
## 常用流程配置示例
|
||||||
|
|
||||||
|
### 1. 标准完整流程(不设置 required)
|
||||||
|
|
||||||
|
```php
|
||||||
|
'standard' => [
|
||||||
|
'name' => '标准完整流程',
|
||||||
|
'override_steps' => true,
|
||||||
|
'morning_wash' => ['mode' => 'daily_first'],
|
||||||
|
'steps' => [
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true],
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 机洗单步骤流程(设置 required)
|
||||||
|
|
||||||
|
```php
|
||||||
|
'machine_wash' => [
|
||||||
|
'name' => '机洗单步骤流程',
|
||||||
|
'override_steps' => true,
|
||||||
|
'morning_wash' => ['mode' => 'none'],
|
||||||
|
'steps' => [
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true, 'required' => ['结束']],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true, 'required' => ['清洗']],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true, 'required' => ['漂洗']],
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true, 'required' => ['清洗', '漂洗', '消毒']],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true, 'required' => ['机洗']],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true, 'required' => ['终末漂洗']],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥']],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 无干燥流程
|
||||||
|
|
||||||
|
```php
|
||||||
|
'no_dry' => [
|
||||||
|
'name' => '无干燥流程',
|
||||||
|
'override_steps' => true,
|
||||||
|
'morning_wash' => ['mode' => 'none'],
|
||||||
|
'steps' => [
|
||||||
|
['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => false],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => false],
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 调试技巧
|
||||||
|
|
||||||
|
### 1. 查看当前使用的配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在 .env 文件中查看
|
||||||
|
FLOW_PROCESS_CONFIG_KEY=standard
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 日志输出
|
||||||
|
|
||||||
|
流程引擎会在日志中输出加载的配置信息:
|
||||||
|
```
|
||||||
|
ProcessConfig 初始化中...
|
||||||
|
加载 [steps] 步骤配置,来源:自定义配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 测试配置有效性
|
||||||
|
|
||||||
|
运行单元测试验证配置:
|
||||||
|
```bash
|
||||||
|
phpunit tests/flow/node_flow/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 配置后流程不生效?
|
||||||
|
|
||||||
|
**检查清单**:
|
||||||
|
1. ✅ 确认 `.env` 中的 `FLOW_PROCESS_CONFIG_KEY` 正确
|
||||||
|
2. ✅ 确认 `steps[].code` 与节点类的 `getCode()` 一致
|
||||||
|
3. ✅ 确认 `steps[].class` 类名存在且正确
|
||||||
|
4. ✅ 清除缓存(如果有)
|
||||||
|
|
||||||
|
### Q2: required 设置后流程卡住?
|
||||||
|
|
||||||
|
**可能原因**:
|
||||||
|
- 循环依赖:A 要求 B,B 要求 A
|
||||||
|
- 前置节点不存在于 steps 中
|
||||||
|
- 前置节点被禁用(enabled=false)
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
1. 检查 `required` 数组中的节点码是否拼写正确
|
||||||
|
2. 确保前置节点在同一个配置的 `steps` 中且启用
|
||||||
|
3. 暂时移除 `required` 测试基础流程
|
||||||
|
|
||||||
|
### Q3: 如何同时支持手工洗和机洗?
|
||||||
|
|
||||||
|
**方案**:在 `steps` 中同时启用两种节点,通过 `required` 控制介入时机:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'steps' => [
|
||||||
|
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||||
|
['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
|
||||||
|
// 机洗可以在任意阶段介入
|
||||||
|
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true, 'required' => ['清洗', '漂洗', '消毒', '终末漂洗']],
|
||||||
|
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||||
|
['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
|
||||||
|
// 结束可以从干燥或机洗过来
|
||||||
|
['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥', '机洗']],
|
||||||
|
['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- **配置文件**:`app/config/custom_process_config.php`
|
||||||
|
- **配置加载类**:`app/flow/config/ProcessConfig.php`
|
||||||
|
- **步骤配置类**:`app/flow/config/StepConfig.php`
|
||||||
|
- **步骤集合类**:`app/flow/config/StepsConfig.php`
|
||||||
|
- **流程引擎**:`app/flow/ProcessEngine.php`
|
||||||
|
- **节点基类**:`app/flow/nodes/ProcessNodeInterface.php`
|
||||||
|
- **抽象节点**:`app/flow/nodes/AbstractProcessNode.php`
|
||||||
|
|
||||||
|
## 开发建议
|
||||||
|
|
||||||
|
1. ✅ **标准流程保持简单**:`standard` 配置不设置 `required`,保持灵活性
|
||||||
|
2. ✅ **特殊流程才用 required**:只在需要严格控制顺序时使用
|
||||||
|
3. ✅ **充分测试**:修改配置后运行完整的流程测试
|
||||||
|
4. ✅ **注释清晰**:为每个自定义配置添加详细的用途说明
|
||||||
|
5. ✅ **版本管理**:配置变更要在 Git 中记录清楚
|
||||||
Reference in New Issue
Block a user