diff --git a/app/flow/DbOperationType.php b/app/flow/DbOperationType.php deleted file mode 100644 index e2c4654..0000000 --- a/app/flow/DbOperationType.php +++ /dev/null @@ -1,17 +0,0 @@ -$this->engine->getConfig()]); + $context = ProcessContextBuilder::fromPacketContext($packetContext, ["engineConfig" => $this->engine->getConfig()])->build(); Logger::debug("[{}] card: {} reader({}): {} {}: {} step: {}", [ - $context->isOperatorCard ? '人员卡' : '内镜卡', - $context->cardNo, - empty($context->readerNo) ? '(未绑定)' : $context->readerNo, - $context->readerNo, - $context->isOperatorCard ? 'user' : 'endoscope', - $context->isOperatorCard ? $context->operatorName : ($context->endoscopeName ?: $context->endoscopeId ?: '(未绑定)'), - $context->currentStep ?: '(新流程)', + $context->isOperatorCard() ? '人员卡' : '内镜卡', + $context->isOperatorCard() ? $context->getOperator()->rfid : $context->getEndoscope()->cardNo, + empty($context->getReader()->no) ? '(未绑定)' : $context->getReader()->no, + $context->getReader()->no, + $context->isOperatorCard() ? 'user' : 'endoscope', + $context->isOperatorCard() ? $context->getOperator()->name : ($context->getEndoscope()->name ?: $context->getEndoscope()->id ?: '(未绑定)'), + $context->getCurrentStep() ?: '(新流程)', ]); // 读卡器未绑定 - if (empty($context->readerId)) { - Logger::error('读卡器未绑定,放弃处理 card={}', [$context->cardNo]); - $context->setError(VoiceMessage::READER_NOT_BOUND); + if (empty($context->getReader()->id)) { + Logger::error('读卡器未绑定,放弃处理 card={}', [$context->isOperatorCard() ? $context->getOperator()->rfid : $context->getEndoscope()->cardNo]); + $context = $context->builder()->error(VoiceMessage::READER_NOT_BOUND)->build(); $this->sendVoice($context); return $context; } // 如果是人员卡,记录操作员信息后直接返回,不走流程链 - if ($context->isOperatorCard) { + if ($context->isOperatorCard()) { $mgr = OperatorSessionManager::getInstance(); $mgr->setOperator( - $context->readerId, - $context->operatorId, - $context->operatorName, - $context->operatorRfid + $context->getReader()->id, + $context->getOperator()->id, + $context->getOperator()->name, + $context->getOperator()->rfid ); - $context->setError(VoiceMessage::PLEASE_SWIPE_ENDOSCOPE); + $context = $context->builder()->error(VoiceMessage::PLEASE_SWIPE_ENDOSCOPE)->build(); $this->sendVoice($context); return $context; } // 如果内镜未绑定,返回错误 - if (empty($context->endoscopeId)) { - Logger::error('内镜未绑定,放弃处理 card={}', [$context->cardNo]); - $context->setError(VoiceMessage::CARD_NOT_BOUND); + if ($context->getEndoscope()->isEmpty()) { + Logger::error('内镜未绑定,放弃处理 card={}', [$context->getEndoscope()->cardNo]); + $context = $context->builder()->error(VoiceMessage::CARD_NOT_BOUND)->build(); $this->sendVoice($context); return $context; } // 内镜卡:从 OperatorSessionManager 补充操作员信息 $mgr = OperatorSessionManager::getInstance(); - if ($mgr->hasOperator($context->readerId) || $context->hasOperator()) { - if ($mgr->hasOperator($context->readerId)) { - $op = $mgr->getOperator($context->readerId, $context->readerType); - $context->operatorId = $op['id']; - $context->operatorName = $op['name']; - $context->operatorRfid = $op['rfid']; + if ($mgr->hasOperator($context->getReader()->id) || $context->hasOperator()) { + if ($mgr->hasOperator($context->getReader()->id)) { + $op = $mgr->getOperator($context->getReader()->id, $context->getReader()->type); + $context = $context->builder()->withOperator(new \app\flow\vo\OperatorInfo( + id: $op['id'], + name: $op['name'], + rfid: $op['rfid'] + ))->build(); } } else { // 未刷人员卡 Logger::warn('未刷人员卡 reader={} card={}', [ - $context->readerId, - $context->cardNo, + $context->getReader()->id, + $context->getEndoscope()->cardNo, ]); // 处理这个错误 - $context->setError(VoiceMessage::PLEASE_SWIPE_OPERATOR); + $context = $context->builder()->error(VoiceMessage::PLEASE_SWIPE_OPERATOR)->build(); $this->handleResult($context); return $context; } @@ -114,7 +119,7 @@ class FlowProcessor if ($isExecuteHandleResult) $this->handleResult($result); // 判断人员是否没有 - if (empty($result->operatorId)) { + if (!$result->getOperator()->isValid()) { Logger::error('[FlowProcessor] 结果集中人员不存在'); } @@ -126,14 +131,14 @@ class FlowProcessor */ protected function handleResult(ProcessContext $context): void { - if ($context->success) { + if ($context->isSuccess()) { $repo = EctActionsRepository::new(); // 更新上一次的操作结束时间 $this->updateLastOperationEndTime($context, $repo); // 数据库操作 - if ($context->needDatabaseOperation) { + if ($context->needDatabaseOperation()) { $this->saveToDatabase($context, $repo); } @@ -142,10 +147,10 @@ class FlowProcessor $this->sendVoice($context); // WebSocket通知 - if ($context->needWebSocketNotify) { + if ($context->needWebSocketNotify()) { Logger::debug('[FlowProcessor] 发送 WebSocket 通知 endoscope={} step={}', [ - $context->endoscopeName, - $context->currentStep, + $context->getEndoscope()->name, + $context->getCurrentStep(), ]); $this->sendWebSocketNotification($context); } @@ -153,7 +158,7 @@ class FlowProcessor // 执行失败,播报错误信息 Logger::warn('流程失败,播报错误 voice={} error={}', [ $context->getFullVoice(), - $context->errorMessage, + $context->getVoice()->errorMessage, ]); $this->sendVoice($context); } @@ -168,11 +173,11 @@ class FlowProcessor */ protected function updateLastOperationEndTime(ProcessContext $context, EctActionsRepository $actionsRepo): void { - $lastAction = $context->previousAction; + $lastAction = $context->getPreviousAction(); $oldActionStartTime = $lastAction->op_starttime; // 仅当旧批次未结束且流程类型不一致时更新 - if ($lastAction->op_endtime === null && $lastAction->process_name != $context->readerType) { + if ($lastAction->op_endtime === null && $lastAction->process_name != $context->getReader()->type) { $oldBatchNo = $lastAction->op_batchno; $oldActionEndTime = date('Y-m-d H:i:s'); $oldDuration = strtotime($oldActionEndTime) - strtotime($oldActionStartTime); @@ -189,12 +194,12 @@ class FlowProcessor */ protected function saveToDatabase(ProcessContext $context, EctActionsRepository $actionsRepo): void { - $opuserType = $this->getOpusesType($context->operatorId); + $opuserType = $this->getOpusesType($context->getOperator()->id); Logger::debug('[FlowProcessor] 写库 op={} step={} batch={}', [ - $context->dbOperation, - $context->currentStep, - $context->batchNo, + $context->getDbOperations(), + $context->getCurrentStep(), + $context->getBatchNo(), ]); try { diff --git a/app/flow/ProcessContext.php b/app/flow/ProcessContext.php deleted file mode 100644 index bdb365e..0000000 --- a/app/flow/ProcessContext.php +++ /dev/null @@ -1,757 +0,0 @@ - $this->endoscopeName; - } - - /** - * 内镜RFID卡号 - */ - public string $cardNo = ''; - - /** - * 内镜类型(胃镜/肠镜等) - */ - public string $endoscopeType = ''; - - // ==================== 读卡器信息 ==================== - /** - * 读卡器编号 - */ - public string $readerNo = ''; - - /** - * 当前读卡器类型/功能 - */ - public string $readerType = '' { - get => $this->readerType; - } - - /** - * 读卡器ID - */ - public string $readerId = ''; - - // ==================== 流程状态 ==================== - /** - * 当前操作步骤 - */ - public string $currentStep = '' { - get => $this->currentStep; - set => $this->currentStep = $value; - } - - - /** - * @var EctActions 上一个操作步骤 - */ - public EctActions $previousAction { - get => $this->previousAction; - } - - /** - * 流程类型: 手工洗 / 机洗 / 测漏 / 存储 / 手工洗(晨洗)/ 机洗(晨洗) - */ - public string $processType = '' { - get => $this->processType; - set => $this->processType = $value; - } - - /** - * 批次号 - */ - public string $batchNo = '' { - get => $this->batchNo; - } - - /** - * 操作开始时间 - */ - public string $actionStartTime = ''; - - - /** - * @var int|null 操作时长(秒) - * 除非处理中有节点主动设置,否则一直是 null - */ - public ?int $duration = null; - - // ==================== 晨洗相关 ==================== - /** - * 是否需要晨洗 - */ - public bool $needMorningWash = false { - get => $this->needMorningWash; - } - - /** - * 是否已完成晨洗 - */ - public bool $morningWashed = false { - get => $this->morningWashed; - set => $this->morningWashed = $value; - } - - /** - * 存储入库时间(用于晨洗判断) - */ - public ?string $storageInTime = null { - get => $this->storageInTime; - set => $this->storageInTime = $value; - } - - /** - * 今天洗消记录数 - */ - public int $todayWashRecords = 0; - - // ==================== 存储相关 ==================== - /** - * 内镜是否在存储柜中 - */ - public bool $isInStorage = false { - get => $this->isInStorage; - set => $this->isInStorage = $value; - } - - /** - * 最后一次存储操作类型:入库/出库 - */ - public string $lastStorageAction = ''; - - /** - * 晨洗开始时间 - */ - public string $morningStartTime = '00:00:00'; - - // ==================== 步骤时间记录 ==================== - - /** - * 各步骤时长要求(从数据库获取)[步骤编码 => 秒数] - */ - public array $stepDurations = []; - - // ==================== 操作员信息 ==================== - /** - * 操作员ID(人员卡) - */ - public string $operatorId = ''; - - /** - * 操作员姓名 - */ - public string $operatorName = ''; - - /** - * 操作员RFID - */ - public string $operatorRfid = ''; - - // ==================== 提醒标记 ==================== - /** - * 是否需要测漏提醒 - */ - public bool $needLeakTestRemind = false; - - /** - * 是否需要存储提醒 - */ - public bool $needStorageRemind = false; - - /** - * 是否已测漏 - */ - public bool $leakTestDone = false; - - /** - * 测漏结果 - */ - public string $leakTestResult = ''; // '正常' / '异常' / '' - - // ==================== 语音输出 ==================== - /** - * 语音播报内容 - */ - public string $voiceMessage = '' { - get => $this->voiceMessage; - } - - // ==================== 执行结果 ==================== - /** - * 流程执行结果 - */ - public bool $success = true { - get => $this->success; - } - - /** - * 错误信息 - */ - public VoiceMessage $errorMessage = VoiceMessage::NONE; - - // ==================== 原始数据 ==================== - /** - * 原始刷卡数据(PacketContext) - */ - public ?PacketContext $packetContext = null; - - /** - * 原始刷卡数据数组(兼容旧代码) - */ - public array $rawData = []; - - // ==================== 数据库相关标记 ==================== - /** - * 是否需要操作数据库 - */ - public bool $needDatabaseOperation = false { - get => $this->needDatabaseOperation; - set => $this->needDatabaseOperation = $value; - } - - /** - * 数据库操作类型 insert / update - * DbOperationType - */ - public array $dbOperation = [] { - get => $this->dbOperation; - - set(DbOperationType|array $value) => is_array($value) ? $this->dbOperation = $value : $this->dbOperation[] = $value; - } - - /** - * 是否需要发送WebSocket通知 - */ - public bool $needWebSocketNotify = false { - get => $this->needWebSocketNotify; - set => $this->needWebSocketNotify = $value; - } - - /** - * 这张卡是否是人员卡(而非内镜卡) - * true 时 FlowProcessor 不走流程链,直接存储操作员信息 - */ - public bool $isOperatorCard = false; - - - public ?ProcessConfig $engineConfig; - - /** - * 期望下一步刷卡提示 - * 当节点识别到读卡器类型匹配但流程步骤不符时,由节点自行写入此字段 - * ProcessEngine 无节点命中时直接读取此字段作为错误语音提示 - */ - public VoiceMessage $expectedNextStep = VoiceMessage::NONE { - get => $this->expectedNextStep; - set { - // 获取调用这个方法的类名 - $callerClass = debug_backtrace()[1]['class'] ?? ''; - $callerClass = array_reverse(explode('\\', $callerClass)) [0]; - Logger::debug("[{}] 设置节点预期: {}", [$callerClass, $value]); - $this->expectedNextStep = $value; - } - } - - /** - * @var int 设置后从当前节点跳过 skipNodeCount 个节点 - */ - public int $skipNodeCount = 0 { - get => $this->skipNodeCount; - set => $this->skipNodeCount = $value; - } - - public function __construct() - { - $this->config = Config::getInstance(); - } - - // ==================== 工具方法 ==================== - - /** - * 创建上下文实例 - */ - public static function create(array $data = []): self - { - $context = new self(); - foreach ($data as $key => $value) { - if (property_exists($context, $key) && $value !== null) { - $context->$key = $value; - } - } - return $context; - } - - /** - * 从 PacketContext 创建流程上下文,并自动从数据库补全内镜、读卡器及历史记录信息 - * - * @param PacketContext $packetContext 数据包上下文 - * @param array $additionalData 额外补充/覆盖的数据 - * @return self - */ - public static function fromPacketContext(PacketContext $packetContext, array $additionalData = []): self - { - $context = new self(); - $context->packetContext = $packetContext; - $context->engineConfig = $additionalData['engineConfig'] ?? null; - - - if ($context->engineConfig !== null) { - $context->morningStartTime = $context->engineConfig->getMorningWashConfig()->morningStartTime; - } - - // 1. 初始化基础信息 - $context->cardNo = $packetContext->packet->card ?? ''; - $context->readerNo = $packetContext->packet->reader ?? ''; - $context->rawData = $packetContext->packet->toArray(); - - // 2. 加载内镜/操作员信息 - $context->loadEndoscopeOrOperatorInfo(); - - // 3. 加载读卡器信息 - $context->loadReaderInfo(); - - // 4. 加载内镜操作记录相关信息(仅当内镜ID存在时执行) - if (!empty($context->endoscopeId)) { - $context->loadEndoscopeActionInfo(); - $context->loadStorageStatus(); - } - - // 5. 合并额外数据(外部可覆盖以上任意字段) - foreach ($additionalData as $key => $value) if (property_exists($context, $key)) { - $context->$key = $value; - } - - Logger::debug("从 PacketContext 创建 ProcessContext 流程上下文"); - return $context; - } - - /** - * 加载内镜或操作员信息 - */ - private function loadEndoscopeOrOperatorInfo(): void - { - if (empty($this->cardNo)) { - return; - } - - // 优先查询内镜信息 - $endoscope = EndoscopeRepository::new()->findByCardNo($this->cardNo); - if ($endoscope !== null) { - $this->endoscopeId = (string)$endoscope->endoscope_id; - $this->endoscopeName = (string)$endoscope->endoscope_name; - $this->endoscopeType = (string)$endoscope->endoscope_type; - return; - } - - // 内镜无记录则查询人员卡信息 - try { - $user = UserRepository::new()->findByRfid($this->cardNo); - if ($user !== null) { - $this->isOperatorCard = true; - $this->operatorId = (string)$user->user_id; - $this->operatorName = (string)$user->user_name; - $this->operatorRfid = (string)$user->user_rfid; - } - } catch (Exception $e) { - Logger::error("[ProcessContext] 查询人员卡信息出错: {}", [$e->getMessage()]); - } - } - - /** - * 加载读卡器信息 - */ - private function loadReaderInfo(): void - { - if (empty($this->readerNo)) { - return; - } - - $readerInfo = ReaderRepository::new()->findReaderInfo($this->readerNo); - if ($readerInfo !== null) { - $this->readerId = $readerInfo['readerId']; - $this->readerType = $readerInfo['readerType']; - } - } - - /** - * 加载内镜操作记录相关信息 - */ - private function loadEndoscopeActionInfo(): void - { - $actionsRepo = EctActionsRepository::new(); - - // 查询最后一条操作记录 - $lastAction = $actionsRepo->findLastAction($this->endoscopeId, [0, 7, 8]); - if (empty($lastAction)) { - return; - } - $lastStepStartTime = $lastAction->op_starttime; - $this->previousAction = $lastAction; - $this->duration = time() - strtotime($lastStepStartTime); // 计算操作时长(秒) - $this->handleLastAction($lastAction, $actionsRepo); - - // 查询今日洗消记录数(晨洗判断) - $this->todayWashRecords = $actionsRepo->countTodayActions($this->endoscopeId, $this->morningStartTime, [0, 7, 8]); - } - - /** - * 加载内镜存储状态 - */ - private function loadStorageStatus(): void - { - $actionsRepo = EctActionsRepository::new(); - - // 查询最后一次存储操作记录 - $lastStorageAction = $actionsRepo->findLastStorageAction($this->endoscopeId); - if ($lastStorageAction !== null) { - $this->isInStorage = ($lastStorageAction['process_name'] === '内镜放入'); - $this->lastStorageAction = $lastStorageAction['process_name']; - if ($this->isInStorage) { - $this->storageInTime = $lastStorageAction['op_starttime']; - } - } - - // 查询最后一次存储入库时间(义乌模式晨洗判断) - $storageTime = $actionsRepo->findLastStorageTime($this->endoscopeId); - if ($storageTime !== null) { - $this->storageInTime = $storageTime; - } - } - - /** - * 处理最后一条操作记录的核心逻辑 - * - * @param EctActions $lastAction 最后一条操作记录 - * @param EctActionsRepository $actionsRepo 操作记录仓储 - */ - private function handleLastAction(EctActions $lastAction, EctActionsRepository $actionsRepo): void - { - // 设置基础操作信息 - $this->currentStep = (string)$lastAction->process_name; - $this->actionStartTime = date('Y-m-d H:i:s'); - - // 处理批次号逻辑 - // 结束步骤:创建新批次;非结束步骤:使用历史批次并处理旧批次 - if ($this->currentStep === '结束') { - $newBatchNo = $this->getOrCreateBatchNo(true); - $this->batchNo = $newBatchNo; - } else { - $this->batchNo = (string)$lastAction->op_batchno; - $this->processType = (string)$lastAction->action_type_name; - } - - // 加载批次操作员信息 - if ($this->currentStep !== '结束' && !empty($this->batchNo)) { - $this->loadBatchOperatorInfo($actionsRepo); - } - } - - - /** - * 加载批次对应的操作员信息 - * - * @param EctActionsRepository $actionsRepo 操作记录仓储 - */ - private function loadBatchOperatorInfo(EctActionsRepository $actionsRepo): void - { - $operator = $actionsRepo->findOperatorByBatchNo($this->batchNo); - if ($operator !== null) { - $this->operatorId = $operator['id']; - $this->operatorName = $operator['name']; - $this->operatorRfid = $operator['rfid']; - } - } - - - // ==================== 非静态方法 ==================== - - /** - * 设置错误状态。 - * 注:同时会将 success 字段设置为 false - * 注:设置后会阻断自动流程的 next 步骤执行 - */ - public function setError(VoiceMessage $message): self - { - $this->errorMessage = $message; - $this->success = false; - return $this; - } - - /** - * 设置错误状态。 - * 注:同时会将 success 字段设置为 false - * 注:设置后会阻断自动流程的 next 步骤执行 - * @param string $message - * @return $this - */ - public function setCustomError(string $message): self - { - $this->errorMessage = VoiceMessage::CUSTOM; - $this->voiceMessage = $message; - $this->success = false; - return $this; - } - - /** - * 设置语音消息 - */ - public function setVoice(string|VoiceMessage $message): self - { - $illegalCaller = false; - $targetIndex = 2; // 目标层级索引 - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6); - foreach ($backtrace as $index => $trace) { - if ($index === $targetIndex) { - $caller = $trace['class'] ?? ''; - if (!empty($caller) && $caller === 'VoiceGenerationStrategy') $illegalCaller = true; - break; - } - } - if ($illegalCaller) Logger::warn( - "不应该在 VoiceGenerationStrategy 之外的地方调用 setVoice 方法", - new IllegalUsageException("Not allowed to call setVoice method outside of VoiceGenerationStrategy") - ); - - - if ($message instanceof VoiceMessage) { - $this->voiceMessage = $message->value; - } else { - $this->voiceMessage = $message; - } - return $this; - } - - /** - * 添加语音前缀(内镜名称等) - */ - public function prependVoice(string $prefix): self - { - $this->voiceMessage = $prefix . $this->voiceMessage; - return $this; - } - - /** - * 获取完整语音(包含内镜名称) - */ - public function getFullVoice(): string - { -// $name = str_replace(['北院', '南院', '电子'], '', $this->endoscopeName); -// return $this->endoscopeName . $this->voiceMessage; - $message = $this->voiceMessage; - if (empty($message)) $message = $this->errorMessage->value; - return $message; - } - - // ==================== 批次号管理(分布式一致性) ==================== - - /** - * 获取或创建批次号 - * - * 分布式场景下的批次号一致性保证: - * 1. 首先尝试从数据库获取该内镜当前未完成的流程批次号 - * 2. 如果没有未完成的流程,生成新的批次号 - * 3. 批次号格式:年月日时分秒 + 内镜ID + 随机数 - * - * @param bool $forceNew 强制生成新批次号(用于开始新流程) - * @return string 批次号 - */ - public function getOrCreateBatchNo(bool $forceNew = false): string - { - // 如果已有批次号且不强制新建,直接返回 - if (!$forceNew && !empty($this->batchNo)) { - return $this->batchNo; - } - - // 从数据库查询最大的批次号 - $existingBatchNo = null; - if (!empty($this->endoscopeId)) $existingBatchNo = EctActionsRepository::new()->findTodayActiveBatchNo($this->config->machineId); - - return $this->generateBatchNo($existingBatchNo); - } - - /** - * 生成批次号 - * - * 批次号格式:YYYYMMDD + 机器ID(两位) + 顺序递增(4位) - * - * @param string|null $existingBatchNo 已存在的最大批次号(无则传null) - * @return string 批次号 - */ - public function generateBatchNo(?string $existingBatchNo = null): string - { - // 1. 生成日期部分(仅YYYYMMDD,符合需求格式) - $datePart = date('Ymd'); - - // 2. 初始化序号为1(默认初始值) - $sequence = 1; - - // 3. 如果存在已有批次号,解析并递增序号 - if (!empty($existingBatchNo)) { - // 截取批次号的日期部分(前8位) - $existingDatePart = substr($existingBatchNo, 0, 8); - // 截取批次号的序号部分(后4位) - $existingSequence = substr($existingBatchNo, 10, 4); - - // 如果已有批次号的日期和今天一致,序号递增 - if ($existingDatePart === $datePart && is_numeric($existingSequence)) { - $sequence = (int)$existingSequence + 1; - } - // 如果日期不一致,序号重置为1(无需额外处理,默认就是1) - } - - // 4. 将序号补零为4位(如1→0001,10→0010,100→0100) - $sequencePart = str_pad($sequence, 4, '0', STR_PAD_LEFT); - - // 5. 拼接并返回最终批次号 - return $datePart . $this->config->machineId . $sequencePart; - } - - /** - * 设置批次号 - */ - public function setBatchNo(string $batchNo): self - { - $this->batchNo = $batchNo; - return $this; - } - - /** - * @param string $batchNo - * @return array - */ - public static function parseBatchNo(string $batchNo): array - { - // 批次号格式:YYYYMMDD + 机器ID(两位) + 顺序递增(4位) - $datePart = substr($batchNo, 0, 8); - $machineId = substr($batchNo, 8, 2); - $sequencePart = substr($batchNo, 10, 4); - - return [ - 'date' => $datePart, - 'machineId' => $machineId, - 'sequence' => $sequencePart, - ]; - } - - // ==================== 步骤时间操作方法 ==================== - - /** - * 获取步骤时长要求(秒) - * 优先级:上下文已缓存 > 数据库(按 processType 精确匹配)> 内置 fallback - */ - public function getStepDuration(string $stepCode): int - { - // 优先使用上下文中已缓存的时长 - if (isset($this->stepDurations[$stepCode])) { - return $this->stepDurations[$stepCode]; - } - - // 从数据库按流程类型精确查询 - if (!empty($this->processType)) { - $dbValue = ProcessDurationRepository::new() - ->getDurationByProcessTypeAndName($this->processType, $stepCode); - if ($dbValue !== null) { - // 写入缓存,避免后续重复查询 - $this->stepDurations[$stepCode] = $dbValue; - return $dbValue; - } - } - - // 内置 fallback(与 ect_meta_process 表数据对齐) - $defaults = [ - '清洗' => 120, // 手工洗/机洗最短 60~120s,取保守值 - '漂洗' => 60, // 60s - '消毒' => 300, // 300s - '终末漂洗' => 120, // 120s - '干燥' => 30, // 30s - '机洗' => 360, // 机洗(晨洗)360s - ]; - - return $defaults[$stepCode] ?? 0; - } - - /** - * 设置步骤时长要求 - */ - public function setStepDuration(string $stepCode, int $seconds): self - { - $this->stepDurations[$stepCode] = $seconds; - return $this; - } - - - - // ==================== 流程状态检查 ==================== - - /** - * 检查是否可以开始新流程 - */ - public function canStartNewProcess(): bool - { - // 当前步骤为空、结束、内镜取出、测漏正常时可以开始新流程 - $validSteps = ['', '结束', '内镜取出', '测漏正常', '测漏异常']; - return in_array($this->currentStep, $validSteps); - } - - /** - * 检查是否已完成清洗流程 - */ - public function isWashProcessCompleted(): bool - { - return $this->currentStep === '结束'; - } - - public function hasOperator(): bool - { - return !empty($this->operatorRfid) && !empty($this->operatorId) && !empty($this->operatorName); - } -} diff --git a/app/flow/ProcessEngine.php b/app/flow/ProcessEngine.php index 5523360..c9d24d6 100644 --- a/app/flow/ProcessEngine.php +++ b/app/flow/ProcessEngine.php @@ -174,22 +174,22 @@ class ProcessEngine public function execute(ProcessContext $context): ProcessContext { if ($this->chainHead === null) { - return $context->setError(VoiceMessage::PROCESS_CHAIN_NOT_INITIALIZED); + return $context->builder()->error(VoiceMessage::PROCESS_CHAIN_NOT_INITIALIZED)->build(); } Logger::debug('[ProcessEngine] 开始执行流程链 readerType={} currentStep={} endoscope={}', [ - $context->readerType, - $context->currentStep ?: '(空)', - $context->endoscopeName ?: $context->endoscopeId ?: '-', + $context->getReader()->type, + $context->getCurrentStep() ?: '(空)', + $context->getEndoscope()->name ?: $context->getEndoscope()->id ?: '-', ]); $result = $this->chainHead->handle($context); Logger::debug('[ProcessEngine] 流程执行完成 endoscope={} step={} success={} error={}', [ - $result->endoscopeName, - $result->currentStep, - $result->success ? 'true' : 'false', - $result->errorMessage->value ?: '-', + $result->getEndoscope()->name, + $result->getCurrentStep(), + $result->isSuccess() ? 'true' : 'false', + $result->getVoice()->errorMessage->value ?: '-', ]); return $result; diff --git a/app/flow/VoiceMessage.php b/app/flow/VoiceMessage.php deleted file mode 100644 index 14bf4dd..0000000 --- a/app/flow/VoiceMessage.php +++ /dev/null @@ -1,62 +0,0 @@ -endoscope; + } + + public function getReader(): ReaderInfo + { + return $this->reader; + } + + public function getOperator(): OperatorInfo + { + return $this->operator; + } + + public function getStorage(): StorageStatus + { + return $this->storage; + } + + public function getMorningWash(): MorningWashStatus + { + return $this->morningWash; + } + + public function getVoice(): VoiceState + { + return $this->voice; + } + + public function getResult(): ExecutionResult + { + return $this->result; + } + + public function getProcessStatus(): ProcessStatus + { + return $this->processStatus; + } + + public function getReminder(): ReminderStatus + { + return $this->reminder; + } + + public function getPacketContext(): ?PacketContext + { + return $this->packetContext; + } + + public function getEngineConfig(): ?ProcessConfig + { + return $this->engineConfig; + } + + public function getRawData(): array + { + return $this->rawData; + } + + public function isOperatorCard(): bool + { + return $this->isOperatorCard; + } + + public function getStepDurations(): array + { + return $this->stepDurations; + } + + // ==================== 便捷查询方法 ==================== + + /** + * 获取当前步骤 + */ + public function getCurrentStep(): string + { + return $this->processStatus->currentStep; + } + + /** + * 获取流程类型 + */ + public function getProcessType(): string + { + return $this->processStatus->processType; + } + + /** + * 获取批次号 + */ + public function getBatchNo(): string + { + return $this->processStatus->batchNo; + } + + /** + * 获取操作开始时间 + */ + public function getActionStartTime(): string + { + return $this->processStatus->actionStartTime; + } + + /** + * 获取操作时长 + */ + public function getDuration(): ?int + { + return $this->processStatus->duration; + } + + /** + * 获取上一个操作记录 + */ + public function getPreviousAction(): ?\app\model\EctActions + { + return $this->processStatus->previousAction; + } + + /** + * 获取完整语音 + */ + public function getFullVoice(): string + { + return $this->voice->getFullVoice(); + } + + /** + * 流程是否成功 + */ + public function isSuccess(): bool + { + return $this->result->success; + } + + /** + * 是否需要操作数据库 + */ + public function needDatabaseOperation(): bool + { + return $this->result->needDatabaseOperation; + } + + /** + * 是否需要WebSocket通知 + */ + public function needWebSocketNotify(): bool + { + return $this->result->needWebSocketNotify; + } + + /** + * 获取数据库操作列表 + */ + public function getDbOperations(): array + { + return $this->result->dbOperations; + } + + /** + * 检查是否可以开始新流程 + */ + public function canStartNewProcess(): bool + { + return $this->processStatus->canStartNewProcess(); + } + + /** + * 检查是否已完成清洗流程 + */ + public function isWashProcessCompleted(): bool + { + return $this->processStatus->isWashProcessCompleted(); + } + + /** + * 是否有操作员 + */ + public function hasOperator(): bool + { + return $this->operator->isValid(); + } + + /** + * 获取步骤时长(从缓存或返回0) + */ + public function getStepDuration(string $stepCode): int + { + return $this->stepDurations[$stepCode] ?? 0; + } + + /** + * 是否需要增强洗 + */ + public function needEnhanceWash(): bool + { + return $this->reminder->needEnhanceWash; + } + + /** + * 是否需要测漏提醒 + */ + public function needLeakTestRemind(): bool + { + return $this->reminder->needLeakTestRemind; + } + + /** + * 是否需要存储提醒 + */ + public function needStorageRemind(): bool + { + return $this->reminder->needStorageRemind; + } + + /** + * 是否已测漏 + */ + public function isLeakTestDone(): bool + { + return $this->reminder->leakTestDone; + } + + /** + * 获取测漏结果 + */ + public function getLeakTestResult(): string + { + return $this->reminder->leakTestResult; + } + + // ==================== 序列化方法 ==================== + + /** + * 转换为数组 + */ + public function toArray(): array + { + return [ + 'endoscope' => $this->endoscope->toArray(), + 'reader' => $this->reader->toArray(), + 'operator' => $this->operator->toArray(), + 'storage' => $this->storage->toArray(), + 'morningWash' => $this->morningWash->toArray(), + 'voice' => $this->voice->toArray(), + 'result' => $this->result->toArray(), + 'processStatus' => $this->processStatus->toArray(), + 'reminder' => $this->reminder->toArray(), + 'isOperatorCard' => $this->isOperatorCard, + 'stepDurations' => $this->stepDurations, + ]; + } + + /** + * 转换为 JSON 字符串 + */ + public function toJson(int $flags = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $flags); + } +} diff --git a/app/flow/docs/01-设计思路.md b/app/flow/docs/01-设计思路.md deleted file mode 100644 index e7481ed..0000000 --- a/app/flow/docs/01-设计思路.md +++ /dev/null @@ -1,307 +0,0 @@ -# 内镜清洗流程管理系统 - 设计思路 - -## 一、项目背景 - -医院内镜清洗流程复杂多变,不同医院有不同的业务需求: -- 有的医院需要晨洗,有的不需要 -- 有的医院机洗后可以直接结束,有的必须继续手工流程 -- 有的医院对步骤时间有特殊要求 -- 流程步骤可能随时调整 - -## 二、核心问题 - -### 2.1 旧系统的问题 -1. **硬编码流程**:流程步骤写死在代码中,修改困难 -2. **状态黑盒**:使用数组存储状态,不知道在哪里被修改 -3. **无法扩展**:新增流程类型需要修改大量代码 -4. **分布式问题**:多台机器部署时,批次号无法保持一致 - -### 2.2 需要支持的灵活场景 -1. 医院A:不需要晨洗 -2. 医院B:只有部分镜子需要晨洗 -3. 医院C:机洗后不允许刷终末漂洗,只能刷干燥或结束 -4. 医院D:只需要清洗和结束两个步骤 -5. 医院E:自定义每个步骤的语音播报 - -## 三、设计方案 - -### 3.1 架构模式 - -#### 责任链模式 (Chain of Responsibility) -``` -Request → Node1 → Node2 → Node3 → ... → Response - ↓ ↓ ↓ - 处理或传递 处理或传递 处理或传递 -``` - -**优点**: -- 每个节点只关心自己能否处理 -- 节点可以动态添加/删除/禁用 -- 流程顺序可配置 - -#### 策略模式 (Strategy) -``` -Context → StrategyA - → StrategyB - → StrategyC -``` - -**优点**: -- 晨洗判断逻辑可替换 -- 时间验证规则可配置 -- 语音生成模板可定制 - -### 3.2 核心组件 - -#### ProcessContext(流程上下文) -```php -class ProcessContext -{ - // 明确字段,避免黑盒 - public string $endoscopeId = ''; - public string $currentStep = ''; - public string $batchNo = ''; - public array $stepLastTimes = []; - // ... -} -``` - -**设计原则**: -- 所有状态都是明确的属性 -- 不使用 `getExtra/setExtra` 黑盒方法 -- 支持从 PacketContext 初始化 - -#### ProcessNode(流程节点) -```php -interface ProcessNodeInterface -{ - public function canHandle(ProcessContext $context): bool; - public function handle(ProcessContext $context): ProcessContext; - public function setNext(ProcessNodeInterface $next): ProcessNodeInterface; -} -``` - -**节点列表**: -- MorningWashNode:晨洗节点 -- WashNode:清洗节点 -- RinseNode:漂洗节点 -- DisinfectNode:消毒节点 -- FinalRinseNode:终末漂洗节点 -- DryNode:干燥节点 -- EndNode:结束节点 -- MachineWashNode:机洗节点 - -#### ProcessStrategy(流程策略) -```php -interface ProcessStrategyInterface -{ - public function execute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext; -} -``` - -**策略列表**: -- MorningWashStrategy:晨洗判断(5种模式) -- TimeValidationStrategy:时间验证 -- VoiceGenerationStrategy:语音生成 - -### 3.3 分布式批次号一致性 - -**问题**:机器A处理清洗,机器B处理消毒,如何保证批次号相同? - -**解决方案**: -```php -public function getOrCreateBatchNo(bool $forceNew = false): string -{ - // 1. 首先查询数据库获取该内镜未完成的批次号 - $existingBatchNo = Database::query( - "SELECT op_batchno FROM ect_actions - WHERE endoscope_id = ? - AND process_name != '结束' - AND created_at >= CURDATE() - ORDER BY action_id DESC LIMIT 1" - ); - - // 2. 如果存在,使用已有的;如果不存在,生成新的 - if ($existingBatchNo) { - return $existingBatchNo; - } - - return $this->generateBatchNo(); -} -``` - -**批次号格式**:`YYYYMMDD + HHMMSS + 内镜ID(6位) + 随机数(4位)` -- 示例:`202603031230450000011234` -- 长度固定:24位 -- 包含时间戳,便于排序 - -## 四、流程执行流程 - -### 4.1 整体流程图 - -``` -┌─────────────────┐ -│ 接收刷卡数据 │ -│ PacketContext │ -└────────┬────────┘ - │ - ▼ -┌─────────────────┐ -│ 查询数据库 │ -│ - 内镜信息 │ -│ - 历史记录 │ -│ - 当前批次号 │ -└────────┬────────┘ - │ - ▼ -┌─────────────────┐ -│ 创建ProcessContext│ -└────────┬────────┘ - │ - ▼ -┌─────────────────┐ -│ 执行责任链 │ -│ - 晨洗判断 │ -│ - 时间验证 │ -│ - 节点处理 │ -│ - 语音生成 │ -└────────┬────────┘ - │ - ▼ -┌─────────────────┐ -│ 处理结果 │ -│ - 插入数据库 │ -│ - 语音播报 │ -│ - WebSocket通知 │ -└─────────────────┘ -``` - -### 4.2 策略执行详解 - -责任链中的每个节点都可以配置多个策略,策略在节点处理前后执行: - -``` -┌─────────────────────────────────────────┐ -│ 节点处理流程 │ -├─────────────────────────────────────────┤ -│ │ -│ ┌─────────────┐ ┌─────────────┐ │ -│ │ 前置策略执行 │ → │ MorningWash │ │ -│ │ │ │ Strategy │ │ -│ └─────────────┘ └─────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────┐ ┌─────────────┐ │ -│ │ 前置策略执行 │ → │ TimeValidat │ │ -│ │ │ │ ionStrategy │ │ -│ └─────────────┘ └─────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────┐ │ -│ │ 节点核心逻辑处理 │ │ -│ │ (canHandle + handle) │ │ -│ └─────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────┐ ┌─────────────┐ │ -│ │ 后置策略执行 │ → │ VoiceGenerat│ │ -│ │ │ │ ionStrategy │ │ -│ └─────────────┘ └─────────────┘ │ -│ │ -└─────────────────────────────────────────┘ -``` - -**策略执行顺序**: - -1. **前置策略**(按配置顺序执行) - - `MorningWashStrategy`:判断是否需要晨洗 - - `TimeValidationStrategy`:验证步骤时间间隔 - -2. **节点核心逻辑** - - 调用 `canHandle()` 判断当前节点是否能处理 - - 调用 `handle()` 执行业务逻辑 - -3. **后置策略** - - `VoiceGenerationStrategy`:生成语音播报内容 - -**代码示例**: - -```php -// 在 AbstractProcessNode 中执行策略 -final public function process(ProcessContext $context): ProcessContext -{ - // 1. 执行前置策略 - foreach ($this->preStrategies as $strategy) { - $context = $strategy->execute($context, $this); - } - - // 2. 检查是否能处理 - if (!$this->canHandle($context)) { - // 传递给下一个节点 - if ($this->next !== null) { - return $this->next->process($context); - } - return $context; - } - - // 3. 执行节点核心逻辑 - $context = $this->handle($context); - - // 4. 执行后置策略 - foreach ($this->postStrategies as $strategy) { - $context = $strategy->execute($context, $this); - } - - return $context; -} -``` - -**策略配置示例**: - -```php -// 在 ProcessConfig 中配置策略 -'nodes' => [ - 'wash' => [ - 'class' => WashNode::class, - 'pre_strategies' => [ - MorningWashStrategy::class, // 晨洗判断 - TimeValidationStrategy::class, // 时间验证 - ], - 'post_strategies' => [ - VoiceGenerationStrategy::class, // 语音生成 - ], - ], -], -``` - -## 五、配置化设计 - -### 5.1 流程配置 -```php -$config = [ - 'steps' => [ - ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true], - ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true], - ['code' => '结束', 'class' => 'EndNode', 'enabled' => true], - ], - 'morning_wash' => [ - 'mode' => 'none', // 不需要晨洗 - ], -]; -``` - -### 5.2 晨洗模式 -- `none`:不需要晨洗 -- `all`:所有镜子都需要 -- `storage_time`:根据存储时间判断(义乌模式) -- `daily_first`:每天第一次(忠县模式) -- `specific_types`:特定类型镜子需要 - -### 5.3 医院特殊配置 -```php -// FinalRinseNode:机洗后不允许刷终末漂洗 -$node->setAllowAfterMachineWash(false); - -// DryNode:机洗后必须直接刷干燥 -$node->setRequireDirectAfterMachineWash(true); -``` diff --git a/app/flow/docs/02-使用配置.md b/app/flow/docs/02-使用配置.md deleted file mode 100644 index f82cc01..0000000 --- a/app/flow/docs/02-使用配置.md +++ /dev/null @@ -1,470 +0,0 @@ -# 内镜清洗流程管理系统 - 使用与配置 - -## 一、快速开始 - -### 1.1 基础使用示例 - -```php -use app\flow\FlowProcessor; -use app\flow\config\ProcessConfig; -use app\net\PacketContext; - -// 接收刷卡数据 -public function onMessage(TcpConnection $connection, $data): void -{ - // 1. 解析数据包 - $packet = PacketParserFactory::parse($data); - $packetContext = new PacketContext($connection, $packet); - - // 2. 创建流程处理器(使用标准配置) - $config = ProcessConfig::createStandard(); - $processor = new FlowProcessor($config); - - // 3. 处理流程 - $result = $processor->process($packetContext); - - // 4. 输出结果 - if ($result->success) { - echo "语音播报: " . $result->getFullVoice(); - echo "当前步骤: " . $result->currentStep; - echo "批次号: " . $result->batchNo; - } else { - echo "错误: " . $result->errorMessage; - } -} -``` - -## 二、配置方式 - -### 2.1 预置配置 - -```php -use app\flow\config\ProcessConfig; - -// 标准完整流程 -$config = ProcessConfig::createStandard(); - -// 无晨洗流程 -$config = ProcessConfig::createNoMorningWash(); - -// 简化流程(只清洗) -$config = ProcessConfig::createSimple(); - -// 机洗流程 -$config = ProcessConfig::createMachineWash(); - -// 无干燥流程 -$config = ProcessConfig::createNoDry(); - -// 仅干燥流程 -$config = ProcessConfig::createDryOnly(); -``` - -### 2.2 自定义配置 - -```php -use app\flow\config\ProcessConfig; - -$config = new ProcessConfig([ - // 定义的 steps 是带有顺序的 - // 流程步骤配置 - 'steps' => [ - ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true], - ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true], - ['code' => '结束', 'class' => 'EndNode', 'enabled' => true], - ], - - // 晨洗配置 - 'morning_wash' => [ - 'mode' => 'storage_time', // 根据存储时间判断 - 'storage_threshold' => 4, // 4小时阈值 - 'morning_start_time' => '06:00:00', - ], - - // 时间验证配置 - 'time_validation' => [ - 'durations' => [ - '清洗' => 300, // 5分钟 - '消毒' => 600, // 10分钟(自定义) - ], - ], - - // 语音模板配置 - 'voice_templates' => [ - 'custom' => [ - '清洗' => '第一步清洗开始', - '消毒' => '第三步消毒开始,请确保消毒时间', - ], - ], -]); -``` - -### 2.3 动态调整配置 - -```php -use app\flow\ProcessEngine; - -$engine = ProcessEngine::createStandard(); - -// 禁用某些步骤 -$engine->disableNode('干燥'); -$engine->disableNode('终末漂洗'); - -// 启用步骤 -$engine->enableNode('干燥'); - -// 设置自定义语音 -$engine->setStepVoice('清洗', '请开始清洗'); - -// 设置晨洗模式 -$engine->setMorningWashMode('none'); -``` - -## 三、晨洗模式配置 - -### 3.1 不需要晨洗 -```php -$config = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'none', - ], -]); -``` - -### 3.2 所有镜子都需要晨洗 -```php -$config = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'all', - ], -]); -``` - -### 3.3 根据存储时间判断(义乌模式) -```php -$config = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'storage_time', - 'storage_threshold' => 4, // 超过4小时需要晨洗 - 'morning_start_time' => '06:00:00', - ], -]); -``` - -### 3.4 每天第一次需要晨洗(忠县模式) -```php -$config = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'daily_first', - 'morning_start_time' => '06:00:00', - ], -]); -``` - -### 3.5 特定类型镜子需要晨洗 -```php -$config = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'specific_types', - 'specific_types' => ['胃镜', '十二指肠镜'], - ], -]); -``` - -## 四、医院特殊流程配置 - -### 4.1 机洗后不允许刷终末漂洗 - -```php -use app\flow\ProcessEngine; - -$engine = ProcessEngine::createStandard(); - -// 获取终末漂洗节点并配置 -$finalRinseNode = $engine->getNode('终末漂洗'); -$finalRinseNode->setAllowAfterMachineWash(false); -``` - -**效果**:机洗完成后,刷卡到终末漂洗读卡器会提示错误,只能刷干燥或结束。 - -### 4.2 机洗后必须直接刷干燥 - -```php -$dryNode = $engine->getNode('干燥'); -$dryNode->setRequireDirectAfterMachineWash(true); -``` - -**效果**:机洗完成后,必须刷干燥或结束,不能刷其他步骤。 - -### 4.3 自定义步骤时间 - -```php -use app\flow\strategies\TimeValidationStrategy; - -$strategy = new TimeValidationStrategy([ - 'durations' => [ - '清洗' => 600, // 10分钟 - '消毒' => 900, // 15分钟 - ], -]); - -$engine->addStrategy('time_validation', $strategy); -``` - -### 4.4 自定义语音模板 - -```php -$engine->setStepVoice('清洗', '第一步:请开始清洗内镜'); -$engine->setStepVoice('消毒', '第三步:消毒时间必须达到5分钟'); -$engine->setStepVoice('结束', '清洗流程完成,请妥善保管'); -``` - -## 五、从配置文件加载 - -### 5.1 创建配置文件 - -```php -// config/flow/hospital_a.php -return [ - 'name' => '医院A-无晨洗流程', - 'morning_wash' => [ - 'mode' => 'none', - ], - 'steps' => [ - ['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], - ], -]; -``` - -### 5.2 加载配置 - -```php -$config = ProcessConfig::fromFile(__DIR__ . '/config/flow/hospital_a.php'); -$processor = new FlowProcessor($config); -``` - -## 六、全局配置类(Config.php) - -### 6.1 配置类说明 - -系统提供全局配置单例类 `app\config\Config`,用于统一管理数据库配置和自定义流程配置: - -```php -use app\config\Config; - -// 获取配置实例 -$config = Config::getInstance(); - -// 获取数据库配置 -$databaseConfig = $config->database; - -// 获取自定义流程配置(从 app/config/custom_process_config.php 加载) -$customProcess = $config->customProcess; -``` - -### 6.2 配置文件位置 - -自定义流程配置文件位于:`app/config/custom_process_config.php` - -该文件返回一个数组,包含多种预设的医院流程配置: - -```php -return [ - 'standard' => [...], // 标准完整流程 - 'no_morning_wash' => [...], // 无晨洗流程 - 'partial_morning_wash' => [...], // 部分镜子晨洗(义乌模式) - 'no_dry' => [...], // 无干燥流程 - 'dry_only' => [...], // 仅干燥流程 - 'machine_wash' => [...], // 机洗流程 - 'simple' => [...], // 简化流程 - 'custom_voice' => [...], // 自定义语音流程 -]; -``` - -### 6.3 使用全局配置创建流程 - -```php -use app\config\Config; -use app\flow\ProcessConfig; -use app\flow\FlowProcessor; - -// 获取全局配置 -$globalConfig = Config::getInstance(); - -// 从全局配置中获取特定医院配置 -$hospitalConfig = $globalConfig->customProcess['no_morning_wash'] ?? null; - -if ($hospitalConfig) { - // 创建流程配置 - $processConfig = ProcessConfig::fromArray($hospitalConfig); - - // 创建流程处理器 - $processor = new FlowProcessor($processConfig); - - // 处理刷卡请求... -} -``` - -### 6.4 配置优先级 - -配置加载优先级(从高到低): -1. 代码中动态设置的配置(`$engine->setStepVoice()`) -2. 从配置文件加载的配置(`ProcessConfig::fromFile()`) -3. 全局配置类中的配置(`Config::getInstance()->customProcess`) -4. 默认配置(`ProcessConfig::createStandard()`) - -## 七、多医院配置示例 - -```php -class HospitalFlowManager -{ - protected array $processors = []; - - public function __construct() - { - // 医院A:标准流程 - $this->processors['hospital_a'] = new FlowProcessor( - ProcessConfig::createStandard() - ); - - // 医院B:无晨洗 - $this->processors['hospital_b'] = new FlowProcessor( - ProcessConfig::createNoMorningWash() - ); - - // 医院C:义乌模式 - $configC = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'storage_time', - 'storage_threshold' => 4, - ], - ]); - $this->processors['hospital_c'] = new FlowProcessor($configC); - - // 医院D:机洗流程 - $this->processors['hospital_d'] = new FlowProcessor( - ProcessConfig::createMachineWash() - ); - } - - public function process(string $hospitalId, PacketContext $packetContext): ProcessContext - { - $processor = $this->processors[$hospitalId] ?? $this->processors['hospital_a']; - return $processor->process($packetContext); - } -} -``` - -## 八、处理结果 - -### 8.1 成功结果 - -```php -$result = $processor->process($packetContext); - -if ($result->isSuccess()) { - // 基础信息 - $result->endoscopeId; // 内镜ID - $result->endoscopeName; // 内镜名称 - $result->currentStep; // 当前步骤 - $result->processType; // 流程类型 - $result->batchNo; // 批次号 - - // 语音播报 - $voice = $result->getFullVoice(); // 完整语音(含内镜名称) - $voice = $result->voiceMessage; // 仅流程语音 - - // 数据库标记 - $result->needDbInsert; // 是否需要插入数据库 - $result->dbOperation; // insert / update - - // WebSocket标记 - $result->needWebSocketNotify; // 是否需要发送通知 -} -``` - -### 8.2 失败结果 - -```php -if (!$result->isSuccess()) { - $errorMessage = $result->errorMessage; // 错误信息 - $voice = $result->voiceMessage; // 错误语音 - - // 常见错误: - // - "刷错,清洗剩余180秒"(时间未到) - // - "刷卡错误,请刷消毒"(步骤错误) - // - "内镜未绑定"(数据错误) -} -``` - -## 九、数据库表结构参考 - -### 9.1 流程配置表(ect_meta_process) - -```sql -CREATE TABLE `ect_meta_process` ( - `process_id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, - `process_name` varchar(255) COMMENT '流程名称(清洗/消毒/干燥等)', - `process_duration` int COMMENT '流程时长(秒)', - `process_type` varchar(20) COMMENT '洗消类型(手工洗/机洗等)', - `optional` int COMMENT '0=选配 1=必配', - `process_order` int COMMENT '顺序', - `status` int COMMENT '1=启用 0=禁用', - PRIMARY KEY (`process_id`) -); -``` - -### 9.2 操作记录表(ect_actions) - -```sql -CREATE TABLE `ect_actions` ( - `action_id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, - `batch_no` varchar(50) COMMENT '批次号(同一次清洗流程相同)', - `action_type` int COMMENT '操作类型', - `action_type_name` varchar(50) COMMENT '操作类型名称', - `process_name` varchar(50) COMMENT '流程名称(清洗/消毒等)', - `reader_id` bigint COMMENT '读卡器ID', - `reader_no` varchar(50) COMMENT '读卡器编号', - `endoscope_id` bigint COMMENT '内镜ID', - `endoscope_rfid` varchar(50) COMMENT '内镜RFID', - `endoscope_name` varchar(100) COMMENT '内镜名称', - `op_starttime` datetime COMMENT '操作开始时间', - `op_endtime` datetime COMMENT '操作结束时间', - `created_at` datetime, - PRIMARY KEY (`action_id`) -); -``` - -## 十、常见问题 - -### Q1: 如何跳过某个步骤? -```php -$engine->disableNode('干燥'); -``` - -### Q2: 如何修改步骤时间? -```php -$config = new ProcessConfig([ - 'time_validation' => [ - 'durations' => ['消毒' => 600], - ], -]); -``` - -### Q3: 批次号如何保证分布式一致? -系统会从数据库查询该内镜未完成的流程批次号,如果存在则使用已有批次号,不存在则生成新的。 - -### Q4: 如何添加自定义语音? -```php -$engine->setStepVoice('清洗', '自定义语音内容'); -``` - -### Q5: 如何支持新的流程类型? -1. 创建新的节点类继承 `AbstractProcessNode` -2. 在配置中添加步骤 -3. 实现 `canHandle()` 和 `doHandle()` 方法 diff --git a/app/flow/docs/03-DIY扩展.md b/app/flow/docs/03-DIY扩展.md deleted file mode 100644 index 62f2e9a..0000000 --- a/app/flow/docs/03-DIY扩展.md +++ /dev/null @@ -1,632 +0,0 @@ -# 内镜清洗流程管理系统 - DIY扩展指南 - -## 一、扩展概述 - -本系统采用责任链模式 + 策略模式设计,提供了良好的扩展性。你可以: -- 添加新的流程节点 -- 添加新的策略 -- 自定义流程配置 -- 修改现有节点行为 - -## 二、添加新的流程节点 - -### 2.1 场景示例 -假设医院需要一个"预处理"步骤,在清洗之前进行。 - -### 2.2 创建节点类 - -```php -readerType !== '预处理') { - return false; - } - - // 上一个步骤必须是空或结束 - $validSteps = ['', '结束', '内镜取出']; - return in_array($context->currentStep, $validSteps); - } - - /** - * 具体处理逻辑 - */ - protected function doHandle(ProcessContext $context): ProcessContext - { - // 更新步骤 -// $context->lastStep = $context->currentStep; - $context->currentStep = '预处理'; - - // 记录操作时间 - $context->setStepLastTime('预处理', date('Y-m-d H:i:s')); - - // TODO: 插入数据库记录 - $context->needDatabaseOperation = true; - $context->dbOperation = 'insert'; - - // TODO: 发送WebSocket通知 - $context->needWebSocketNotify = true; - - return $context; - } -} -``` - -### 2.3 注册节点 - -```php -use app\flow\ProcessEngine; -use app\flow\nodes\PreprocessNode; - -$engine = ProcessEngine::createStandard(); - -// 创建节点实例 -$preprocessNode = new PreprocessNode(); - -// 添加策略 -$preprocessNode->addStrategy($engine->getStrategy('voice_generation')); - -// 插入到责任链中(在清洗节点之前) -$washNode = $engine->getNode('清洗'); -$preprocessNode->setNext($washNode); - -// 更新链头 -// 注意:这里需要修改 ProcessEngine 的内部实现 -``` - -### 2.4 通过配置添加 - -```php -use app\flow\config\ProcessConfig; - -$config = new ProcessConfig([ - 'steps' => [ - ['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true], - ['code' => '预处理', 'class' => 'PreprocessNode', '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], - ], -]); - -$engine = new ProcessEngine($config); -``` - -## 三、添加新的策略 - -### 3.1 场景示例 -假设需要添加一个"操作员验证策略",验证是否已刷人员卡。 - -### 3.2 创建策略类 - -```php -operatorId)) { - $context->setError('请先刷人员卡'); - return $context; - } - - // 检查人员卡是否有效 - if (!$this->isValidOperator($context->operatorId)) { - $context->setError('人员卡无效'); - return $context; - } - - return $context; - } - - /** - * 验证操作员是否有效 - * TODO: 从数据库查询 - */ - protected function isValidOperator(string $operatorId): bool - { - // TODO: 查询数据库验证操作员 - // SQL: SELECT COUNT(*) FROM ect_user WHERE user_id = ? AND status = 1 - return true; - } - - /** - * 判断策略是否适用 - * 只有清洗步骤需要验证操作员 - */ - public function isApplicable(ProcessContext $context, ProcessNodeInterface $node): bool - { - return $node->getCode() === '清洗'; - } - - /** - * 获取策略名称 - */ - public function getName(): string - { - return '操作员验证策略'; - } -} -``` - -### 3.3 注册策略 - -```php -use app\flow\ProcessEngine; -use app\flow\strategies\OperatorValidationStrategy; - -$engine = ProcessEngine::createStandard(); - -// 创建策略实例 -$operatorStrategy = new OperatorValidationStrategy(); - -// 添加到引擎 -$engine->addStrategy('operator_validation', $operatorStrategy); - -// 将策略添加到指定节点 -$washNode = $engine->getNode('清洗'); -$washNode->addStrategy($operatorStrategy); -``` - -## 四、修改现有节点行为 - -### 4.1 继承并覆盖 - -```php -logCustomInfo($context); - - return $context; - } - - /** - * 自定义验证逻辑 - */ - public function canHandle(ProcessContext $context): bool - { - // 先调用父类验证 - if (!parent::canHandle($context)) { - return false; - } - - // 添加额外的验证条件 - // 例如:某些类型的内镜不能在此节点处理 - if ($context->endoscopeType === '特殊镜') { - return false; - } - - return true; - } - - /** - * 自定义日志 - */ - protected function logCustomInfo(ProcessContext $context): void - { - // 自定义日志逻辑 - error_log("[CustomWash] 内镜: {$context->endoscopeName}, 时间: " . date('Y-m-d H:i:s')); - } -} -``` - -### 4.2 使用配置替换节点 - -```php -$config = new ProcessConfig([ - 'steps' => [ - ['code' => '清洗', 'class' => 'app\flow\nodes\custom\CustomWashNode', 'enabled' => true], - // ... 其他步骤 - ], -]); -``` - -## 五、自定义流程配置加载器 - -### 5.1 从数据库加载配置 - -```php - [ - 'mode' => 'daily_first', - ], - 'steps' => self::loadStepsFromDb($hospitalId), - 'time_validation' => [ - 'durations' => self::loadDurationsFromDb($hospitalId), - ], - ]; - - return new ProcessConfig($configData); - } - - /** - * 从数据库加载步骤配置 - */ - protected static function loadStepsFromDb(string $hospitalId): array - { - // TODO: 查询数据库 - // SQL: SELECT * FROM ect_hospital_steps WHERE hospital_id = ? ORDER BY step_order - - return [ - ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true], - ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true], - ['code' => '结束', 'class' => 'EndNode', 'enabled' => true], - ]; - } - - /** - * 从数据库加载时长配置 - */ - protected static function loadDurationsFromDb(string $hospitalId): array - { - // TODO: 查询数据库 - // SQL: SELECT * FROM ect_hospital_durations WHERE hospital_id = ? - - return [ - '清洗' => 300, - '消毒' => 300, - ]; - } -} -``` - -### 5.2 使用加载器 - -```php -use app\flow\config\DatabaseConfigLoader; - -// 从数据库加载配置 -$config = DatabaseConfigLoader::load('hospital_001'); -$processor = new FlowProcessor($config); -``` - -## 六、自定义语音生成器 - -### 6.1 创建语音生成器 - -```php -processType) { - case '手工洗': - $voice = $this->generateManualWashVoice($context); - break; - case '机洗': - $voice = $this->generateMachineWashVoice($context); - break; - default: - $voice = $this->generateDefaultVoice($context); - } - - // 添加内镜名称 - $name = str_replace(['北院', '南院', '电子'], '', $context->endoscopeName); - return $name . $voice; - } - - /** - * 手工洗语音 - */ - protected function generateManualWashVoice(ProcessContext $context): string - { - $templates = [ - '清洗' => '手工清洗开始,请认真清洗', - '漂洗' => '漂洗开始', - '消毒' => '消毒开始,请确保消毒时间', - '终末漂洗' => '终末漂洗开始', - '干燥' => '干燥开始', - '结束' => '手工清洗流程结束', - ]; - - return $templates[$context->currentStep] ?? $context->currentStep . '完成'; - } - - /** - * 机洗语音 - */ - protected function generateMachineWashVoice(ProcessContext $context): string - { - $templates = [ - '机洗' => '机器清洗开始', - '结束' => '机洗流程结束', - ]; - - return $templates[$context->currentStep] ?? $context->currentStep . '完成'; - } - - /** - * 默认语音 - */ - protected function generateDefaultVoice(ProcessContext $context): string - { - return $context->currentStep . '完成'; - } -} -``` - -### 6.2 集成到策略 - -```php -// 修改 VoiceGenerationStrategy -protected function generateNormalVoice(ProcessContext $context, ProcessNodeInterface $node): string -{ - // 使用自定义语音生成器 - $generator = new CustomVoiceGenerator(); - return $generator->generate($context); -} -``` - -## 七、自定义数据库操作 - -### 7.1 创建数据库处理器 - -```php -dbOperation === 'insert') { - $this->insertAction($context); - } elseif ($context->dbOperation === 'update') { - $this->updateAction($context); - } - } - - /** - * 插入新记录 - */ - protected function insertAction(ProcessContext $context): void - { - // TODO: 实现插入逻辑 - $sql = "INSERT INTO ect_actions ( - batch_no, action_type, action_type_name, process_name, - reader_id, reader_no, - opuser_type, opuser_id, opuser_rfid, opuser_name, - endoscope_id, endoscope_rfid, endoscope_name, - created_at, op_starttime - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - $params = [ - $context->batchNo, - $this->getActionType($context->processType), - $context->processType, - $context->currentStep, - $context->readerId, - $context->readerNo, - 2, // 清洗人员 - $context->operatorId, - $context->operatorRfid, - $context->operatorName, - $context->endoscopeId, - $context->cardNo, - $context->endoscopeName, - date('Y-m-d H:i:s'), - date('Y-m-d H:i:s'), - ]; - - // Database::execute($sql, $params); - } - - /** - * 更新记录 - */ - protected function updateAction(ProcessContext $context): void - { - // TODO: 实现更新逻辑 - $sql = "UPDATE ect_actions - SET op_endtime = ? - WHERE batch_no = ? - ORDER BY action_id DESC - LIMIT 1"; - - // Database::execute($sql, [date('Y-m-d H:i:s'), $context->batchNo]); - } - - /** - * 获取操作类型编码 - */ - protected function getActionType(string $processType): int - { - $mapping = [ - '诊疗' => 0, - '手工洗' => 1, - '机洗' => 2, - '测漏' => 7, - '存储' => 8, - ]; - - return $mapping[$processType] ?? 1; - } -} -``` - -### 7.2 集成到 FlowProcessor - -```php -// 修改 FlowProcessor::saveToDatabase() -protected function saveToDatabase(ProcessContext $context): void -{ - $handler = new CustomDbHandler(); - $handler->save($context); -} -``` - -## 八、扩展最佳实践 - -### 8.1 命名规范 -- 节点类:`XxxNode`,位于 `app/flow/nodes/` -- 策略类:`XxxStrategy`,位于 `app/flow/strategies/` -- 配置类:`XxxConfig`,位于 `app/flow/config/` - -### 8.2 测试建议 -```php -// 单元测试示例 -class PreprocessNodeTest -{ - public function testCanHandle() - { - $node = new PreprocessNode(); - - $context = ProcessContext::create([ - 'readerType' => '预处理', - 'currentStep' => '', - ]); - - assert($node->canHandle($context) === true); - } -} -``` - -### 8.3 调试技巧 -```php -// 打印流程上下文 -var_dump($context); - -// 打印责任链结构 -$node = $engine->getChainHead(); -while ($node !== null) { - echo $node->getName() . ' -> '; - $node = $node->getNext(); -} -``` - -## 九、扩展示例汇总 - -| 扩展类型 | 难度 | 参考章节 | -|---------|------|---------| -| 添加新节点 | 低 | 2.1-2.4 | -| 添加新策略 | 低 | 3.1-3.3 | -| 修改节点行为 | 中 | 4.1-4.2 | -| 自定义配置加载 | 中 | 5.1-5.2 | -| 自定义语音 | 低 | 6.1-6.2 | -| 自定义数据库 | 中 | 7.1-7.2 | - -## 十、常见问题 - -### Q1: 如何调试新添加的节点? -在 `doHandle()` 方法中添加日志输出,查看上下文数据。 - -### Q2: 策略执行顺序如何控制? -策略按照添加顺序执行,可以使用 `setPhase('before'/'after')` 控制执行阶段。 - -### Q3: 如何禁用默认策略? -可以覆盖 `attachStrategies()` 方法或创建自定义引擎。 - -### Q4: 如何处理数据库事务? -在 `FlowProcessor::handleResult()` 中添加事务控制逻辑。 diff --git a/app/flow/nodes/AbstractProcessNode.php b/app/flow/nodes/AbstractProcessNode.php index 314acc1..d395ba0 100644 --- a/app/flow/nodes/AbstractProcessNode.php +++ b/app/flow/nodes/AbstractProcessNode.php @@ -3,8 +3,9 @@ namespace app\flow\nodes; use app\flow\config\StepConfig; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\strategies\ProcessStrategyInterface; +use app\flow\vo\CanHandleResult; use app\utils\Logger; /** @@ -67,26 +68,33 @@ abstract class AbstractProcessNode implements ProcessNodeInterface $context = $this->executeBeforeStrategies($context); // 如果前置策略返回错误,不再继续 - if (!$context->success) { + if (!$context->isSuccess()) { Logger::debug('[{}-Node] 前置策略拦截 error={}', [ $this->getCode(), - $context->errorMessage, + $context->getVoice()->errorMessage, ]); return $context; } // 如果不能处理当前步骤,传递给下一个节点 - if (!$this->canHandle($context)) { + $canHandleResult = $this->canHandle($context); + if (!$canHandleResult->canHandle) { Logger::debug('[{}-Node] 不能处理当前步骤,跳过', [$this->getCode()]); + // 如果有期望下一步提示,设置到上下文 + if ($canHandleResult->hasExpectedNextStep()) { + $context = $context->builder() + ->expectedNextStep($canHandleResult->expectedNextStep) + ->build(); + } return $this->passToNext($context); } // 输出当前节点 Logger::debug('[{}-Node] 开始处理 step={} batch={}', [ $this->getCode(), - $context->currentStep, - $context->batchNo ?: '-', + $context->getCurrentStep(), + $context->getBatchNo() ?: '-', ]); @@ -95,25 +103,25 @@ abstract class AbstractProcessNode implements ProcessNodeInterface Logger::debug('[{}-Node] 处理完成 step={} batch={} success={}', [ $this->getCode(), - $context->currentStep, - $context->batchNo ?: '-', - $context->success, + $context->getCurrentStep(), + $context->getBatchNo() ?: '-', + $context->isSuccess(), ]); // 执行后置策略 $context = $this->executeAfterStrategies($context); // 后置策略拦截 - if (!$context->success) { + if (!$context->isSuccess()) { Logger::debug('[{}-Node] 后置策略拦截 error={}', [ $this->getCode(), - $context->errorMessage, + $context->getVoice()->errorMessage, ]); return $context; } $nextNode = $this->getNext(); // 跳过节点逻辑 - for ($i = 0; $i < $context->skipNodeCount; $i++) { + for ($i = 0; $i < $context->getResult()->skipNodeCount; $i++) { Logger::debug('[{}-Node] 跳过节点 code={}', [$this->getCode(), $nextNode->getCode()]); $nextNode = $nextNode->getNext(); } @@ -146,7 +154,7 @@ abstract class AbstractProcessNode implements ProcessNodeInterface */ public function isMatchReaderType(ProcessContext $context): bool { - return $this->getCode() === $context->readerType; + return $this->getCode() === $context->getReader()->type; } /** @@ -165,8 +173,9 @@ abstract class AbstractProcessNode implements ProcessNodeInterface */ protected function stopNext(ProcessContext $context): ProcessContext { - $context->skipNodeCount = count($this->getRemainingNodes()); - return $context; + return $context->builder() + ->skipNodeCount(count($this->getRemainingNodes())) + ->build(); } /** @@ -214,7 +223,7 @@ abstract class AbstractProcessNode implements ProcessNodeInterface foreach ($this->strategies as $strategy) { if ($strategy->getPhase() === 'before') { $context = $strategy->execute($context, $this); - if (!$context->success) { + if (!$context->isSuccess()) { break; } } @@ -287,10 +296,15 @@ abstract class AbstractProcessNode implements ProcessNodeInterface /** * 判断当前节点是否能处理该步骤 * 默认实现:检查当前步骤是否匹配节点编码 + * + * @return CanHandleResult 包含是否能处理以及期望下一步的结果对象 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { - return $context->currentStep === $this->getCode(); + if ($context->getCurrentStep() === $this->getCode()) { + return CanHandleResult::yes(); + } + return CanHandleResult::no(); } /** diff --git a/app/flow/nodes/CloseNode.php b/app/flow/nodes/CloseNode.php index 46c7646..06c86cc 100644 --- a/app/flow/nodes/CloseNode.php +++ b/app/flow/nodes/CloseNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; use app\repository\EctActionsRepository; use app\utils\Logger; @@ -35,12 +36,12 @@ class CloseNode extends AbstractProcessNode * * 所有需要数据库记录的场景都需要经过本节点检查 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { - if (!$context->success || $context->needDatabaseOperation || !empty($context->voiceMessage)) { - return false; + if (!$context->isSuccess() || $context->needDatabaseOperation() || !empty($context->getVoice()->message)) { + return CanHandleResult::no(); } - return true; + return CanHandleResult::yes(); } /** @@ -48,21 +49,22 @@ class CloseNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - if (!$context->success || $context->needDatabaseOperation || !empty($context->voiceMessage)) return $context; + if (!$context->isSuccess() || $context->needDatabaseOperation() || !empty($context->getVoice()->message)) { + return $context; + } // 无节点命中 Logger::debug('当前刷卡无节点命中 currentStep={} readerType={} expectedNextStep={}', [ - $context->currentStep ?: '(空)', - $context->readerType, - $context->expectedNextStep + $context->getCurrentStep() ?: '(空)', + $context->getReader()->type, + $context->getVoice()->expectedNextStep ]); // 如果有预期的下一步,则返回错误 - if (!empty($context->expectedNextStep) && $context->expectedNextStep != VoiceMessage::NONE) { - Logger::debug("节点期望: {$context->expectedNextStep->value}"); - return $context->setError($context->expectedNextStep); + if ($context->getVoice()->hasExpectedNextStep()) { + Logger::debug("节点期望: {$context->getVoice()->expectedNextStep->value}"); + return $context->builder()->error($context->getVoice()->expectedNextStep)->build(); } // 异常流程 Logger::error("异常流程,所有节点处理完成,无匹配节点并且无预期的下一步"); - $context->setError(VoiceMessage::UNKNOWN_ERROR); - return $context; + return $context->builder()->error(VoiceMessage::UNKNOWN_ERROR)->build(); } } diff --git a/app/flow/nodes/DisinfectNode.php b/app/flow/nodes/DisinfectNode.php index bf7c5be..095546a 100644 --- a/app/flow/nodes/DisinfectNode.php +++ b/app/flow/nodes/DisinfectNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; /** * 消毒节点 @@ -31,27 +32,26 @@ class DisinfectNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } if (!$this->isMatchReaderType($context)) { - if ($context->currentStep === RinseNode::getName()) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_DISINFECT; + if ($context->getCurrentStep() === RinseNode::getName()) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_DISINFECT); } - return false; + return CanHandleResult::no(); } // 上一个步骤必须是漂洗 或者 晨洗 - if (!$this->isRequiredNode($context->currentStep, [RinseNode::getName(), MorningWashNode::getName()])) { - return false; + if (!$this->isRequiredNode($context->getCurrentStep(), [RinseNode::getName(), MorningWashNode::getName()])) { + return CanHandleResult::no(); } - return true; + return CanHandleResult::yes(); } /** @@ -59,14 +59,11 @@ class DisinfectNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 更新步骤 - $context->currentStep = '消毒'; - - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $context->builder() + ->withCurrentStep('消毒') + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/DryNode.php b/app/flow/nodes/DryNode.php index 7bcf60b..87044c0 100644 --- a/app/flow/nodes/DryNode.php +++ b/app/flow/nodes/DryNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; use app\utils\Logger; /** @@ -35,29 +36,27 @@ class DryNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } if (!$this->isMatchReaderType($context)) { - if ($context->currentStep === FinalRinseNode::getName()) { - if (!$context->success) Logger::debug("[DryNode] 刷卡错误,当前步骤是终末漂洗,但是刷的读卡器类型不是终末漂洗,对用户进行语音提示刷终末漂洗读卡器"); - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_DRY; + if ($context->getCurrentStep() === FinalRinseNode::getName()) { + if (!$context->isSuccess()) Logger::debug("[DryNode] 刷卡错误,当前步骤是终末漂洗,但是刷的读卡器类型不是终末漂洗,对用户进行语音提示刷终末漂洗读卡器"); + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_DRY); } - return false; + return CanHandleResult::no(); } // 上一个步骤必须是终末漂洗 - if (!$this->isRequiredNode($context->currentStep, [FinalRinseNode::getName()])) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_DISINFECT; - return false; + if (!$this->isRequiredNode($context->getCurrentStep(), [FinalRinseNode::getName()])) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_DISINFECT); } - return true; + return CanHandleResult::yes(); } /** @@ -65,13 +64,11 @@ class DryNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 更新步骤 - $context->currentStep = '干燥'; - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $context->builder() + ->withCurrentStep('干燥') + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/DuplicateCheckNode.php b/app/flow/nodes/DuplicateCheckNode.php index f236a97..ad4c23a 100644 --- a/app/flow/nodes/DuplicateCheckNode.php +++ b/app/flow/nodes/DuplicateCheckNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; use app\repository\EctActionsRepository; use app\utils\Logger; @@ -36,9 +37,12 @@ class DuplicateCheckNode extends AbstractProcessNode * * 所有需要数据库记录的场景都需要经过本节点检查 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { - return $context->previousAction->process_name === $context->readerType; + if ($context->getPreviousAction()?->process_name === $context->getReader()->type) { + return CanHandleResult::yes(); + } + return CanHandleResult::no(); } /** @@ -46,7 +50,6 @@ class DuplicateCheckNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - $context->setError(VoiceMessage::DUPLICATE_SWIPING); - return $context; + return $context->builder()->error(VoiceMessage::DUPLICATE_SWIPING)->build(); } } diff --git a/app/flow/nodes/EndNode.php b/app/flow/nodes/EndNode.php index 91536b0..c0ef3ed 100644 --- a/app/flow/nodes/EndNode.php +++ b/app/flow/nodes/EndNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; /** * 结束节点 @@ -31,31 +32,30 @@ class EndNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } if (!$this->isMatchReaderType($context)) { - if ($context->currentStep === DryNode::getName() && $context->currentStep === FinalRinseNode::getName()) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_END; + if ($context->getCurrentStep() === DryNode::getName() && $context->getCurrentStep() === FinalRinseNode::getName()) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_END); } - return false; + return CanHandleResult::no(); } // 上一个步骤必须是干燥、终末漂洗或机洗 $validSteps = ['干燥', '终末漂洗', '机洗']; - if ($this->isRequiredNode($context->currentStep, ['干燥', '终末漂洗', '机洗'])) { - if ($context->currentStep === FinalRinseNode::getName()) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_WASH; + if ($this->isRequiredNode($context->getCurrentStep(), ['干燥', '终末漂洗', '机洗'])) { + if ($context->getCurrentStep() === FinalRinseNode::getName()) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_WASH); } - return false; + return CanHandleResult::no(); } - return true; + return CanHandleResult::yes(); } /** @@ -63,13 +63,11 @@ class EndNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 更新步骤 - $context->currentStep = '结束'; - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $context->builder() + ->withCurrentStep('结束') + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/FinalRinseNode.php b/app/flow/nodes/FinalRinseNode.php index da09633..d250e00 100644 --- a/app/flow/nodes/FinalRinseNode.php +++ b/app/flow/nodes/FinalRinseNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; use app\utils\Logger; /** @@ -34,24 +35,26 @@ class FinalRinseNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } if (!$this->isMatchReaderType($context)) { - if ($context->currentStep === DisinfectNode::getName()) { - if (!$context->success) Logger::debug("[FinalRinseNode] 刷卡错误,当前步骤是消毒,但是刷的读卡器类型不是消毒,对用户进行语音提示刷消毒读卡器"); - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_FINAL_RINSE; + if ($context->getCurrentStep() === DisinfectNode::getName()) { + if (!$context->isSuccess()) Logger::debug("[FinalRinseNode] 刷卡错误,当前步骤是消毒,但是刷的读卡器类型不是消毒,对用户进行语音提示刷消毒读卡器"); + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_FINAL_RINSE); } - return false; + return CanHandleResult::no(); } // 上一个步骤必须是消毒或机洗 - return $this->isRequiredNode($context->currentStep, ['消毒', '机洗']); + if ($this->isRequiredNode($context->getCurrentStep(), ['消毒', '机洗'])) { + return CanHandleResult::yes(); + } + return CanHandleResult::no(); } /** @@ -59,14 +62,11 @@ class FinalRinseNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 更新步骤 - $context->currentStep = '终末漂洗'; - - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $context->builder() + ->withCurrentStep('终末漂洗') + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/MachineWashNode.php b/app/flow/nodes/MachineWashNode.php index b7b8522..c0bdf0a 100644 --- a/app/flow/nodes/MachineWashNode.php +++ b/app/flow/nodes/MachineWashNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; /** * 机洗节点 @@ -31,35 +32,34 @@ class MachineWashNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } if (!$this->isMatchReaderType($context)) { - if ($context->currentStep === WashNode::getName()) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_MACHINE_WASH; + if ($context->getCurrentStep() === WashNode::getName()) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_MACHINE_WASH); } - return false; + return CanHandleResult::no(); } // 需要晨洗但未完成:提示先进行晨洗 - if ($context->needMorningWash && !$context->morningWashed) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_MORNING_WASH; - return false; + if ($context->getMorningWash()->needMorningWash && !$context->getMorningWash()->morningWashed) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_MORNING_WASH); } // 机洗可以在多个步骤后执行:空步骤(新流程)、结束、内镜取出、清洗,晨洗 - if (!$this->isRequiredNode($context->currentStep, ['', '结束', '内镜取出', '清洗', MachineWashNode::getName()])) { - if ($context->currentStep === EndNode::getName()) $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_MACHINE_WASH; - else $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_END; - return false; + if (!$this->isRequiredNode($context->getCurrentStep(), ['', '结束', '内镜取出', '清洗', MachineWashNode::getName()])) { + if ($context->getCurrentStep() === EndNode::getName()) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_MACHINE_WASH); + } + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_END); } - return true; + return CanHandleResult::yes(); } /** @@ -67,21 +67,13 @@ class MachineWashNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 设置流程类型为机洗 - $context->processType = '机洗'; - - // 更新步骤 - $context->currentStep = '机洗'; - - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - // 更新批次为机洗, - $context->processType = '机洗'; - $context->dbOperation = DbOperationType::UPDATE; - - return $context; + return $context->builder() + ->withProcessType('机洗') + ->withCurrentStep('机洗') + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->dbOperation(DbOperationType::UPDATE) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/MorningWashNode.php b/app/flow/nodes/MorningWashNode.php index 5ac5d69..95efc2d 100644 --- a/app/flow/nodes/MorningWashNode.php +++ b/app/flow/nodes/MorningWashNode.php @@ -2,9 +2,11 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; +use app\flow\vo\MorningWashStatus; use app\utils\Logger; /** @@ -32,25 +34,23 @@ class MorningWashNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } // 只有需要晨洗且未完成晨洗时才处理 - if (!$context->needMorningWash || $context->morningWashed) { - return false; + if (!$context->getMorningWash()->needMorningWash || $context->getMorningWash()->morningWashed) { + return CanHandleResult::no(); } // 检查当前读卡器类型是否匹配 - if (!$this->isRequiredNode($context->readerType, ['漂洗', '机洗'])){ - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_MORNING_WASH; - return false; + if (!$this->isRequiredNode($context->getReader()->type, ['漂洗', '机洗'])){ + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_MORNING_WASH); } - return true; + return CanHandleResult::yes(); } /** @@ -59,23 +59,25 @@ class MorningWashNode extends AbstractProcessNode protected function doHandle(ProcessContext $context): ProcessContext { Logger::debug("处理晨洗节点"); + // 标记晨洗已开始 - $context->morningWashed = true; + $morningWash = new MorningWashStatus( + needMorningWash: $context->getMorningWash()->needMorningWash, + morningWashed: true, + startTime: $context->getMorningWash()->startTime, + todayWashRecords: $context->getMorningWash()->todayWashRecords + ); // 设置流程类型 - if ($context->readerType === '机洗') { - $context->processType = '机洗(晨洗)'; - } else { - $context->processType = '手工洗(晨洗)'; - } + $processType = $context->getReader()->type === '机洗' ? '机洗(晨洗)' : '手工洗(晨洗)'; - // 更新当前步骤 - $context->currentStep = self::getName(); - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $context->builder() + ->withMorningWash($morningWash) + ->withProcessType($processType) + ->withCurrentStep(self::getName()) + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/ProcessNodeInterface.php b/app/flow/nodes/ProcessNodeInterface.php index c2116b2..4ee1ecf 100644 --- a/app/flow/nodes/ProcessNodeInterface.php +++ b/app/flow/nodes/ProcessNodeInterface.php @@ -2,7 +2,8 @@ namespace app\flow\nodes; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; +use app\flow\vo\CanHandleResult; /** * 流程节点接口 @@ -45,9 +46,9 @@ interface ProcessNodeInterface /** * 判断当前节点是否能处理该步骤 * @param ProcessContext $context - * @return bool + * @return CanHandleResult 包含是否能处理以及期望下一步的结果 */ - public function canHandle(ProcessContext $context): bool; + public function canHandle(ProcessContext $context): CanHandleResult; /** * 是否启用该节点 diff --git a/app/flow/nodes/RinseNode.php b/app/flow/nodes/RinseNode.php index bb1d5a3..413c6d1 100644 --- a/app/flow/nodes/RinseNode.php +++ b/app/flow/nodes/RinseNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; /** * 漂洗节点 @@ -33,30 +34,28 @@ class RinseNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } // 期望当前读卡器为漂洗 if (!$this->isMatchReaderType($context)) { // 当前步骤是清洗且读卡器不符:说明清洗完了应该刷漂洗 - if ($context->currentStep === WashNode::getName()) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_RINSE; + if ($context->getCurrentStep() === WashNode::getName()) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_RINSE); } - return false; + return CanHandleResult::no(); } // 上一个步骤必须是清洗 - if (!$this->isRequiredNode($context->currentStep, [WashNode::getName()])) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_WASH; - return false; + if (!$this->isRequiredNode($context->getCurrentStep(), [WashNode::getName()])) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_WASH); } - return true; + return CanHandleResult::yes(); } /** @@ -64,14 +63,11 @@ class RinseNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 更新步骤 - $context->currentStep = '漂洗'; - - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $context->builder() + ->withCurrentStep('漂洗') + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/StorageInNode.php b/app/flow/nodes/StorageInNode.php index e62d19d..94f2801 100644 --- a/app/flow/nodes/StorageInNode.php +++ b/app/flow/nodes/StorageInNode.php @@ -3,9 +3,11 @@ namespace app\flow\nodes; use app\config\Config; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; +use app\flow\vo\StorageStatus; use app\utils\Logger; /** @@ -35,38 +37,38 @@ class StorageInNode extends AbstractProcessNode * 判断当前节点是否能处理该步骤 * 双读卡器模式:只在非单读卡器模式下生效 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { $config = Config::getInstance(); $singleReaderMode = $config->storageSingleReader; // 单读卡器模式不处理,由 StorageNode 统一处理 if ($singleReaderMode) { - return false; + return CanHandleResult::no(); } // 读卡器不是内镜放入类型,不处理 if (!$this->isMatchReaderType($context)) { - return false; + return CanHandleResult::no(); } // 获取内镜当前存储状态 - $isInStorage = $context->isInStorage ?? false; + $isInStorage = $context->getStorage()->isInStorage; // 如果内镜已在库中,则当前应该是出库操作,不处理 if ($isInStorage) { Logger::debug('[StorageInNode] 内镜已在库中,转由出库节点处理'); - return false; + return CanHandleResult::no(); } // 检查前置步骤要求 $validSteps = ['', '结束', '内镜取出', '测漏正常', '测漏异常']; - if (!in_array($context->currentStep, $validSteps)) { - Logger::debug('[StorageInNode] 当前步骤 {} 不符合入库条件', [$context->currentStep]); - return false; + if (!in_array($context->getCurrentStep(), $validSteps)) { + Logger::debug('[StorageInNode] 当前步骤 {} 不符合入库条件', [$context->getCurrentStep()]); + return CanHandleResult::no(); } - return true; + return CanHandleResult::yes(); } /** @@ -74,23 +76,15 @@ class StorageInNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 设置流程类型为存储 - $context->processType = '存储'; + Logger::debug('[StorageInNode] 内镜入库成功 endoscope={}', [$context->getEndoscope()->name]); - // 更新步骤 - $context->currentStep = self::getName(); - - // 标记入库状态 - $context->isInStorage = true; - $context->storageInTime = date('Y-m-d H:i:s'); - - // 设置数据库操作 - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - Logger::debug('[StorageInNode] 内镜入库成功 endoscope={}', [$context->endoscopeName]); - - return $context; + return $context->builder() + ->withProcessType('存储') + ->withCurrentStep(self::getName()) + ->withStorage(StorageStatus::inStorage(date('Y-m-d H:i:s'))) + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/StorageNode.php b/app/flow/nodes/StorageNode.php index 3389617..386877e 100644 --- a/app/flow/nodes/StorageNode.php +++ b/app/flow/nodes/StorageNode.php @@ -3,9 +3,11 @@ namespace app\flow\nodes; use app\config\Config; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; +use app\flow\vo\StorageStatus; use app\utils\Logger; /** @@ -38,40 +40,40 @@ class StorageNode extends AbstractProcessNode * * 单读卡器模式:读卡器类型是'内镜放入'或'内镜取出',根据 isInStorage 状态判断执行入库还是出库 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { $config = Config::getInstance(); $singleReaderMode = $config->storageSingleReader; // 非单读卡器模式不处理 if (!$singleReaderMode) { - return false; + return CanHandleResult::no(); } // 读卡器类型必须是'内镜放入'或'内镜取出' - if (!in_array($context->readerType, ['内镜放入', '内镜取出'])) { - return false; + if (!in_array($context->getReader()->type, ['内镜放入', '内镜取出'])) { + return CanHandleResult::no(); } - $isInStorage = $context->isInStorage ?? false; + $isInStorage = $context->getStorage()->isInStorage; if ($isInStorage) { // 内镜已在库中,执行出库 $validSteps = ['内镜放入', '结束']; - if (!in_array($context->currentStep, $validSteps)) { - Logger::debug('[StorageNode] 当前步骤 {} 不符合出库条件', [$context->currentStep]); - return false; + if (!in_array($context->getCurrentStep(), $validSteps)) { + Logger::debug('[StorageNode] 当前步骤 {} 不符合出库条件', [$context->getCurrentStep()]); + return CanHandleResult::no(); } } else { // 内镜不在库中,执行入库 $validSteps = ['', '结束', '内镜取出', '测漏正常', '测漏异常']; - if (!in_array($context->currentStep, $validSteps)) { - Logger::debug('[StorageNode] 当前步骤 {} 不符合入库条件', [$context->currentStep]); - return false; + if (!in_array($context->getCurrentStep(), $validSteps)) { + Logger::debug('[StorageNode] 当前步骤 {} 不符合入库条件', [$context->getCurrentStep()]); + return CanHandleResult::no(); } } - return true; + return CanHandleResult::yes(); } /** @@ -80,28 +82,25 @@ class StorageNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 设置流程类型为存储 - $context->processType = '存储'; + $builder = $context->builder()->withProcessType('存储'); // 根据当前状态判断执行入库还是出库(canHandle 已经验证过状态) - if (!$context->isInStorage) { + if (!$context->getStorage()->isInStorage) { // 入库操作 - $context->currentStep = '内镜放入'; - $context->isInStorage = true; - $context->storageInTime = date('Y-m-d H:i:s'); - Logger::debug('[StorageNode] 内镜入库成功 endoscope={}', [$context->endoscopeName]); + Logger::debug('[StorageNode] 内镜入库成功 endoscope={}', [$context->getEndoscope()->name]); + $builder->withCurrentStep('内镜放入') + ->withStorage(StorageStatus::inStorage(date('Y-m-d H:i:s'))); } else { // 出库操作 - $context->currentStep = '内镜取出'; - $context->isInStorage = false; - Logger::debug('[StorageNode] 内镜出库成功 endoscope={}', [$context->endoscopeName]); + Logger::debug('[StorageNode] 内镜出库成功 endoscope={}', [$context->getEndoscope()->name]); + $builder->withCurrentStep('内镜取出') + ->withStorage(StorageStatus::outOfStorage()); } - // 设置数据库操作 - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $builder + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/StorageOutNode.php b/app/flow/nodes/StorageOutNode.php index b979262..203bd82 100644 --- a/app/flow/nodes/StorageOutNode.php +++ b/app/flow/nodes/StorageOutNode.php @@ -3,9 +3,11 @@ namespace app\flow\nodes; use app\config\Config; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; +use app\flow\vo\StorageStatus; use app\utils\Logger; /** @@ -35,38 +37,38 @@ class StorageOutNode extends AbstractProcessNode * 判断当前节点是否能处理该步骤 * 双读卡器模式:只在非单读卡器模式下生效 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { $config = Config::getInstance(); $singleReaderMode = $config->storageSingleReader; // 单读卡器模式不处理,由 StorageNode 统一处理 if ($singleReaderMode) { - return false; + return CanHandleResult::no(); } // 读卡器不是内镜取出类型,不处理 if (!$this->isMatchReaderType($context)) { - return false; + return CanHandleResult::no(); } // 获取内镜当前存储状态 - $isInStorage = $context->isInStorage ?? false; + $isInStorage = $context->getStorage()->isInStorage; // 如果内镜不在库中,则当前应该是入库操作,不处理 if (!$isInStorage) { Logger::debug('[StorageOutNode] 内镜不在库中,转由入库节点处理'); - return false; + return CanHandleResult::no(); } // 检查前置步骤要求:必须在库中才能出库 $validSteps = ['内镜放入', '结束']; - if (!in_array($context->currentStep, $validSteps)) { - Logger::debug('[StorageOutNode] 当前步骤 {} 不符合出库条件', [$context->currentStep]); - return false; + if (!in_array($context->getCurrentStep(), $validSteps)) { + Logger::debug('[StorageOutNode] 当前步骤 {} 不符合出库条件', [$context->getCurrentStep()]); + return CanHandleResult::no(); } - return true; + return CanHandleResult::yes(); } /** @@ -74,22 +76,15 @@ class StorageOutNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - // 设置流程类型为存储 - $context->processType = '存储'; + Logger::debug('[StorageOutNode] 内镜出库成功 endoscope={}', [$context->getEndoscope()->name]); - // 更新步骤 - $context->currentStep = self::getName(); - - // 标记出库状态 - $context->isInStorage = false; - - // 设置数据库操作 - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - Logger::debug('[StorageOutNode] 内镜出库成功 endoscope={}', [$context->endoscopeName]); - - return $context; + return $context->builder() + ->withProcessType('存储') + ->withCurrentStep(self::getName()) + ->withStorage(StorageStatus::outOfStorage()) + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/nodes/VirtualWashMachineNode.php b/app/flow/nodes/VirtualWashMachineNode.php index e70aa7e..95e1614 100644 --- a/app/flow/nodes/VirtualWashMachineNode.php +++ b/app/flow/nodes/VirtualWashMachineNode.php @@ -3,9 +3,10 @@ namespace app\flow\nodes; use app\config\Config; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; /** * 虚拟清洗机节点 @@ -32,20 +33,22 @@ class VirtualWashMachineNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } // 如果不是机洗 - if ($context->readerType !== MachineWashNode::getName()) { - return false; + if ($context->getReader()->type !== MachineWashNode::getName()) { + return CanHandleResult::no(); } - return Config::getInstance()->enableVirtualCleanerParser; + if (Config::getInstance()->enableVirtualCleanerParser) { + return CanHandleResult::yes(); + } + return CanHandleResult::no(); } /** diff --git a/app/flow/nodes/WashNode.php b/app/flow/nodes/WashNode.php index a93715e..d30fa5a 100644 --- a/app/flow/nodes/WashNode.php +++ b/app/flow/nodes/WashNode.php @@ -2,9 +2,10 @@ namespace app\flow\nodes; -use app\flow\DbOperationType; -use app\flow\ProcessContext; -use app\flow\VoiceMessage; +use app\flow\enum\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; +use app\flow\vo\CanHandleResult; use app\utils\Logger; /** @@ -33,34 +34,31 @@ class WashNode extends AbstractProcessNode /** * 判断当前节点是否能处理该步骤 */ - public function canHandle(ProcessContext $context): bool + public function canHandle(ProcessContext $context): CanHandleResult { // 如果内镜未取出 - if ($context->isInStorage) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_STORAGE_OUT; - return false; + if ($context->getStorage()->isInStorage) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_STORAGE_OUT); } // 读卡器不是本节点,不处理 if (!$this->isMatchReaderType($context)) { - return false; + return CanHandleResult::no(); } // 需要晨洗但未完成:提示先进行晨洗 - if ($context->needMorningWash && !$context->morningWashed) { - $context->expectedNextStep = VoiceMessage::PLEASE_SWIPE_MORNING_WASH; - return false; + if ($context->getMorningWash()->needMorningWash && !$context->getMorningWash()->morningWashed) { + return CanHandleResult::no(VoiceMessage::PLEASE_SWIPE_MORNING_WASH); } $validCurrentSteps = ['', '结束', '内镜取出', '内镜放入', '测漏正常', '晨洗']; - if (!in_array($context->currentStep, $validCurrentSteps)) { + if (!in_array($context->getCurrentStep(), $validCurrentSteps)) { // 读卡器是清洗但步骤不对(如终末漂洗时刷清洗),提示应该先刷结束 -// $context->expectedNextStep = "清洗应在流程开始时刷,当前步骤为{$context->currentStep},请先刷结束卡重新开始"; - return false; + return CanHandleResult::no(); } - return true; + return CanHandleResult::yes(); } /** @@ -68,19 +66,18 @@ class WashNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { + $builder = $context->builder(); + // 设置流程类型 - if (empty($context->processType) || $context->processType === '晨洗') { - $context->processType = '手工洗'; + if (empty($context->getProcessType()) || $context->getProcessType() === '晨洗') { + $builder->withProcessType('手工洗'); } - // 更新步骤 - $context->currentStep = self::getName(); - - - $context->needDatabaseOperation = true; - $context->dbOperation = DbOperationType::INSERT; - $context->needWebSocketNotify = true; - - return $context; + return $builder + ->withCurrentStep(self::getName()) + ->needDatabaseOperation() + ->dbOperation(DbOperationType::INSERT) + ->needWebSocketNotify() + ->build(); } } diff --git a/app/flow/strategies/AbstractStrategy.php b/app/flow/strategies/AbstractStrategy.php index 9493981..36fa345 100644 --- a/app/flow/strategies/AbstractStrategy.php +++ b/app/flow/strategies/AbstractStrategy.php @@ -2,7 +2,7 @@ namespace app\flow\strategies; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\nodes\ProcessNodeInterface; /** diff --git a/app/flow/strategies/MorningWashStrategy.php b/app/flow/strategies/MorningWashStrategy.php index ca64cf5..3c5d2f6 100644 --- a/app/flow/strategies/MorningWashStrategy.php +++ b/app/flow/strategies/MorningWashStrategy.php @@ -4,8 +4,9 @@ namespace app\flow\strategies; use app\flow\config\MorningMode; use app\flow\config\MorningWashConfig; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\nodes\ProcessNodeInterface; +use app\flow\vo\MorningWashStatus; use app\utils\Logger; /** @@ -30,16 +31,17 @@ class MorningWashStrategy extends AbstractStrategy */ protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext { - $isEnd = $context->currentStep == '结束'; + $isEnd = $context->getCurrentStep() == '结束'; $needMorning = $this->checkNeedMorningWash($context) && $isEnd; - $context->needMorningWash = $needMorning; + + $morningWash = new MorningWashStatus( + needMorningWash: $needMorning, + morningWashed: !$needMorning, + startTime: $context->getMorningWash()->startTime, + todayWashRecords: $context->getMorningWash()->todayWashRecords + ); - // 如果不需要晨洗,标记为已完成 - if (!$needMorning) { - $context->morningWashed = true; - } - - return $context; + return $context->builder()->withMorningWash($morningWash)->build(); } /** @@ -62,9 +64,9 @@ class MorningWashStrategy extends AbstractStrategy */ protected function checkByStorageTime(ProcessContext $context): bool { - $storageInTime = $context->storageInTime; - $lastActionType = $context->previousAction->action_type_name; - $lastProcessName = $context->previousAction->process_name; + $storageInTime = $context->getStorage()->inTime; + $lastActionType = $context->getPreviousAction()?->action_type_name; + $lastProcessName = $context->getPreviousAction()?->process_name; // 如果最后一次操作是存储且已取出 if ($lastActionType === '存储' && $lastProcessName === '内镜取出') { @@ -94,7 +96,7 @@ class MorningWashStrategy extends AbstractStrategy */ protected function hasWashRecordToday(ProcessContext $context): bool { - return $context->todayWashRecords === 0; + return $context->getMorningWash()->todayWashRecords === 0; } /** @@ -103,7 +105,7 @@ class MorningWashStrategy extends AbstractStrategy protected function checkBySpecificTypes(ProcessContext $context): bool { $specificTypes = $this->morningWashConfig->getExpand('specific_types', []); - return in_array($context->endoscopeType, $specificTypes); + return in_array($context->getEndoscope()->type, $specificTypes); } /** diff --git a/app/flow/strategies/ProcessStrategyInterface.php b/app/flow/strategies/ProcessStrategyInterface.php index c56ed04..305342d 100644 --- a/app/flow/strategies/ProcessStrategyInterface.php +++ b/app/flow/strategies/ProcessStrategyInterface.php @@ -2,7 +2,7 @@ namespace app\flow\strategies; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\nodes\ProcessNodeInterface; /** diff --git a/app/flow/strategies/TimeValidationStrategy.php b/app/flow/strategies/TimeValidationStrategy.php index 1990247..faa9bf9 100644 --- a/app/flow/strategies/TimeValidationStrategy.php +++ b/app/flow/strategies/TimeValidationStrategy.php @@ -4,9 +4,9 @@ namespace app\flow\strategies; use app\config\Config; use app\flow\config\TimeValidationConfig; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\nodes\ProcessNodeInterface; -use app\flow\VoiceMessage; +use app\flow\enum\VoiceMessage; use app\repository\ProcessDurationRepository; use app\utils\Logger; @@ -63,8 +63,8 @@ class TimeValidationStrategy extends AbstractStrategy protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext { $stepCode = $node->getCode(); - $currentStep = $context->currentStep; - $processType = $context->processType; + $currentStep = $context->getCurrentStep(); + $processType = $context->getProcessType(); Logger::debug("开始执行时间验证策略,步骤:{$stepCode},流程类型:{$processType}"); $configDuration = $this->timeValidationConfig->getDuration($stepCode,$processType); @@ -76,9 +76,9 @@ class TimeValidationStrategy extends AbstractStrategy } else { Logger::debug("步骤:{$stepCode},流程类型:{$processType},无配置时长,从数据库查询"); // 从数据库按流程类型精确查询 - $requiredDuration = $this->getDurationFromDb($stepCode, $context->processType); + $requiredDuration = $this->getDurationFromDb($stepCode, $context->getProcessType()); if ($requiredDuration > 0) { - $context->setStepDuration($stepCode, $requiredDuration); + $context = $context->builder()->withStepDuration($stepCode, $requiredDuration)->build(); } else { // 最后使用上下文已缓存值 $requiredDuration = $context->getStepDuration($stepCode); @@ -91,7 +91,7 @@ class TimeValidationStrategy extends AbstractStrategy } // 获取上次该步骤的操作时间 - $duration = $context->duration; + $duration = $context->getDuration(); // 没有上次记录(第一次操作),允许通过 if (empty($duration)) { @@ -105,7 +105,7 @@ class TimeValidationStrategy extends AbstractStrategy $voice = VoiceMessage::NOT_ENOUGH_TIME->value; $voice = str_replace('{step}', $stepCode, $voice); $voice = str_replace('{time}', $requiredDuration - $duration, $voice); - $context->setCustomError($voice); + $context = $context->builder()->customError($voice)->build(); } return $context; } @@ -119,8 +119,8 @@ class TimeValidationStrategy extends AbstractStrategy */ public function isApplicable(ProcessContext $context, ProcessNodeInterface $node): bool { - if ($context->currentStep != $context->previousAction->process_name) return false; - if (!$this->timeValidationConfig->hasStep($node->getCode(), $context->processType)) return false; + if ($context->getCurrentStep() != $context->getPreviousAction()?->process_name) return false; + if (!$this->timeValidationConfig->hasStep($node->getCode(), $context->getProcessType())) return false; return true; } diff --git a/app/flow/strategies/VoiceGenerationStrategy.php b/app/flow/strategies/VoiceGenerationStrategy.php index e68b34f..d4a4164 100644 --- a/app/flow/strategies/VoiceGenerationStrategy.php +++ b/app/flow/strategies/VoiceGenerationStrategy.php @@ -4,7 +4,7 @@ namespace app\flow\strategies; use app\flow\config\VoiceTemplatesConfig; use app\flow\exception\IllegalUsageException; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\nodes\ProcessNodeInterface; use app\utils\Logger; @@ -31,22 +31,21 @@ class VoiceGenerationStrategy extends AbstractStrategy protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext { // 如果存在流程中自定义语音,就直接输出 - if (!empty($context->voiceMessage)) { + if (!empty($context->getVoice()->message)) { Logger::warn( "流程中存在自定义语音或存在多次设置语音,语音应该在 VoiceGenerationStrategy 策略中配置,否则不能拦截与自定义配置", new IllegalUsageException("In the existing process, there is custom voice, which should be configured in the VoiceGenerationStrategy strategy; otherwise, it cannot be intercepted and customized") ); - $context->setVoice($context->voiceMessage); - return $context; + return $context->builder()->voiceMessage($context->getVoice()->message)->build(); } // 如果已经有错误,生成错误语音 // 否则生成正常流程语音 - $voice = !$context->success ? $this->generateErrorVoice($context) : $this->generateNormalVoice($context, $node); + $voice = !$context->isSuccess() ? $this->generateErrorVoice($context) : $this->generateNormalVoice($context, $node); // 应用语音模板 - foreach ($context->voiceTemplateParams as $key => $val) { + foreach ($context->getVoice()->templateParams as $key => $val) { $replaceVal = match (true) { is_array($val) => implode(',', $val), is_bool($val) => $val ? 'true' : 'false', @@ -56,9 +55,8 @@ class VoiceGenerationStrategy extends AbstractStrategy $voice = str_replace('{' . $key . '}', $replaceVal, $voice); } Logger::debug("应用语音模板后的内容: {$voice}"); - $context->setVoice($voice); - return $context; + return $context->builder()->voiceMessage($voice)->build(); } /** @@ -68,7 +66,7 @@ class VoiceGenerationStrategy extends AbstractStrategy { $stepCode = $node->getCode(); $stepName = $node->getName(); - $processType = $context->processType; + $processType = $context->getProcessType(); // 根据流程类型选择模板 $templateKey = $this->getTemplateKey($processType, $stepCode, $context); @@ -94,12 +92,12 @@ class VoiceGenerationStrategy extends AbstractStrategy */ protected function generateErrorVoice(ProcessContext $context): string { - $errorMessage = $context->errorMessage; + $errorMessage = $context->getVoice()->errorMessage; $errorTemplates = $this->voiceTemplatesConfig->voiceMessage; $errorMsg = $errorTemplates[$errorMessage->name] ?? $errorMessage->value; if (empty($errorMsg)) { - $errorMsg = $context->voiceMessage; + $errorMsg = $context->getVoice()->message; Logger::debug("错误信息配置未命中,使用自定义语音: {$errorMsg}"); } else { Logger::debug("错误信息配置命中: {$errorMsg}"); @@ -129,7 +127,7 @@ class VoiceGenerationStrategy extends AbstractStrategy protected function getTemplateKey(string $processType, string $stepCode, ProcessContext $context): string { // 晨洗流程中的清洗/机洗步骤 - if ($context->needMorningWash) { + if ($context->getMorningWash()->needMorningWash) { return 'morning_wash'; } @@ -144,11 +142,11 @@ class VoiceGenerationStrategy extends AbstractStrategy { $reminds = []; - if ($context->needLeakTestRemind) { + if ($context->needLeakTestRemind()) { $reminds[] = ',清洗开始前,请测漏'; } - if ($context->needStorageRemind) { + if ($context->needStorageRemind()) { $reminds[] = ',未登记取出'; } diff --git a/app/repository/EctActionsRepository.php b/app/repository/EctActionsRepository.php index 0c34d88..0265076 100644 --- a/app/repository/EctActionsRepository.php +++ b/app/repository/EctActionsRepository.php @@ -2,8 +2,8 @@ namespace app\repository; -use app\flow\ProcessContext; -use app\flow\DbOperationType; +use app\flow\context\ProcessContext; +use app\flow\enum\DbOperationType; use app\model\EctActions; use app\utils\Logger; @@ -228,41 +228,41 @@ class EctActionsRepository extends BaseRepository { $op_endtime = null; $process_order = 1; - if ($context->currentStep === '结束') { + if ($context->getCurrentStep() === '结束') { $op_endtime = date('Y-m-d H:i:s'); } - if (!empty($context->previousAction)) { - $process_order = (int)($context->previousAction->process_order ?? 1); + if (!empty($context->getPreviousAction())) { + $process_order = (int)($context->getPreviousAction()->process_order ?? 1); } - if (in_array(DbOperationType::INSERT, $context->dbOperation)) { + if (in_array(DbOperationType::INSERT, $context->getDbOperations())) { return $this->insert([ - 'op_batchno' => $context->batchNo, - 'action_type' => $this->mapActionType($context->processType), - 'action_type_name' => $context->processType, - 'process_name' => $context->currentStep, + 'op_batchno' => $context->getBatchNo(), + 'action_type' => $this->mapActionType($context->getProcessType()), + 'action_type_name' => $context->getProcessType(), + 'process_name' => $context->getCurrentStep(), 'process_order' => $process_order + 1, - 'op_morning' => $context->needMorningWash ? 1 : 0, - 'op_enhance' => $context->needEnhanceWash ? 1 : 0, - 'reader_id' => (int)$context->readerId ?: null, - 'reader_no' => $context->readerNo ?: null, + 'op_morning' => $context->getMorningWash()->needMorningWash ? 1 : 0, + 'op_enhance' => $context->needEnhanceWash() ? 1 : 0, + 'reader_id' => (int)$context->getReader()->id ?: null, + 'reader_no' => $context->getReader()->no ?: null, 'opuser_type' => $opuserType, - 'opuser_id' => (int)$context->operatorId ?: null, - 'opuser_rfid' => $context->operatorRfid ?: null, - 'opuser_name' => $context->operatorName ?: null, - 'endoscope_id' => (int)$context->endoscopeId ?: null, - 'endoscope_rfid' => $context->cardNo ?: null, - 'endoscope_name' => $context->endoscopeName ?: null, - 'op_starttime' => $context->actionStartTime ?: date('Y-m-d H:i:s'), + 'opuser_id' => (int)$context->getOperator()->id ?: null, + 'opuser_rfid' => $context->getOperator()->rfid ?: null, + 'opuser_name' => $context->getOperator()->name ?: null, + 'endoscope_id' => (int)$context->getEndoscope()->id ?: null, + 'endoscope_rfid' => $context->getEndoscope()->cardNo ?: null, + 'endoscope_name' => $context->getEndoscope()->name ?: null, + 'op_starttime' => $context->getActionStartTime() ?: date('Y-m-d H:i:s'), 'op_endtime' => $op_endtime, - 'op_duration' => $context->duration ?: null, + 'op_duration' => $context->getDuration() ?: null, 'created_at' => date('Y-m-d H:i:s'), ]); - } elseif (in_array(DbOperationType::UPDATE, $context->dbOperation)) { - EctActions::where('op_batchno', $context->batchNo) - ->where('endoscope_id', $context->endoscopeId) + } elseif (in_array(DbOperationType::UPDATE, $context->getDbOperations())) { + EctActions::where('op_batchno', $context->getBatchNo()) + ->where('endoscope_id', $context->getEndoscope()->id) ->update([ - 'action_type' => $this->mapActionType($context->processType), - 'action_type_name' => $context->processType, + 'action_type' => $this->mapActionType($context->getProcessType()), + 'action_type_name' => $context->getProcessType(), ]); } return null; diff --git a/tests/flow/EdgeCaseTest.php b/tests/flow/EdgeCaseTest.php index 84f4c50..5245fb8 100644 --- a/tests/flow/EdgeCaseTest.php +++ b/tests/flow/EdgeCaseTest.php @@ -2,7 +2,7 @@ namespace tests\flow; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\ProcessEngine; use app\flow\config\ProcessConfig; diff --git a/tests/flow/FlowProcessorTest.php b/tests/flow/FlowProcessorTest.php index 9d5ff08..0d39d20 100644 --- a/tests/flow/FlowProcessorTest.php +++ b/tests/flow/FlowProcessorTest.php @@ -3,10 +3,10 @@ namespace tests\flow; use app\flow\FlowProcessor; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\ProcessEngine; use app\flow\config\ProcessConfig; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * FlowProcessor 单元测试 diff --git a/tests/flow/PerformanceTest.php b/tests/flow/PerformanceTest.php index 07c3523..8b9e40e 100644 --- a/tests/flow/PerformanceTest.php +++ b/tests/flow/PerformanceTest.php @@ -3,7 +3,7 @@ namespace tests\flow; use app\flow\ProcessEngine; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\config\ProcessConfig; /** diff --git a/tests/flow/ProcessContextTest.php b/tests/flow/ProcessContextTest.php index c230fd6..25cd1a0 100644 --- a/tests/flow/ProcessContextTest.php +++ b/tests/flow/ProcessContextTest.php @@ -2,7 +2,7 @@ namespace tests\flow; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; /** * ProcessContext 单元测试 diff --git a/tests/flow/TestCase.php b/tests/flow/TestCase.php index 875ed19..f18282a 100644 --- a/tests/flow/TestCase.php +++ b/tests/flow/TestCase.php @@ -3,7 +3,7 @@ namespace tests\flow; use PHPUnit\Framework\TestCase as BaseTestCase; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; /** * 流程测试基类 diff --git a/tests/flow/UsageExampleTest.php b/tests/flow/UsageExampleTest.php index 9f6c3bc..20232f2 100644 --- a/tests/flow/UsageExampleTest.php +++ b/tests/flow/UsageExampleTest.php @@ -4,7 +4,7 @@ namespace tests\flow; use app\config\Config; use app\flow\FlowProcessor; -use app\flow\ProcessContext; +use app\flow\context\ProcessContext; use app\flow\ProcessEngine; use app\flow\config\ProcessConfig; use app\net\PacketContext; diff --git a/tests/flow/nodes/DisinfectNodeTest.php b/tests/flow/nodes/DisinfectNodeTest.php index d5816f9..0fbe272 100644 --- a/tests/flow/nodes/DisinfectNodeTest.php +++ b/tests/flow/nodes/DisinfectNodeTest.php @@ -4,7 +4,7 @@ namespace tests\flow\nodes; use tests\flow\TestCase; use app\flow\nodes\DisinfectNode; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * 消毒节点单元测试 diff --git a/tests/flow/nodes/DryNodeTest.php b/tests/flow/nodes/DryNodeTest.php index 859f89d..c1d38be 100644 --- a/tests/flow/nodes/DryNodeTest.php +++ b/tests/flow/nodes/DryNodeTest.php @@ -4,7 +4,7 @@ namespace tests\flow\nodes; use tests\flow\TestCase; use app\flow\nodes\DryNode; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * 干燥节点单元测试 diff --git a/tests/flow/nodes/EndNodeTest.php b/tests/flow/nodes/EndNodeTest.php index 93abfce..3c05def 100644 --- a/tests/flow/nodes/EndNodeTest.php +++ b/tests/flow/nodes/EndNodeTest.php @@ -4,7 +4,7 @@ namespace tests\flow\nodes; use tests\flow\TestCase; use app\flow\nodes\EndNode; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * 结束节点单元测试 diff --git a/tests/flow/nodes/FinalRinseNodeTest.php b/tests/flow/nodes/FinalRinseNodeTest.php index f626801..5536d07 100644 --- a/tests/flow/nodes/FinalRinseNodeTest.php +++ b/tests/flow/nodes/FinalRinseNodeTest.php @@ -4,7 +4,7 @@ namespace tests\flow\nodes; use tests\flow\TestCase; use app\flow\nodes\FinalRinseNode; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * 终末漂洗节点单元测试 diff --git a/tests/flow/nodes/MachineWashNodeTest.php b/tests/flow/nodes/MachineWashNodeTest.php index 618e68d..f94977e 100644 --- a/tests/flow/nodes/MachineWashNodeTest.php +++ b/tests/flow/nodes/MachineWashNodeTest.php @@ -4,7 +4,7 @@ namespace tests\flow\nodes; use tests\flow\TestCase; use app\flow\nodes\MachineWashNode; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * 机洗节点单元测试 diff --git a/tests/flow/nodes/RinseNodeTest.php b/tests/flow/nodes/RinseNodeTest.php index 2d9a8e0..089dbac 100644 --- a/tests/flow/nodes/RinseNodeTest.php +++ b/tests/flow/nodes/RinseNodeTest.php @@ -4,7 +4,7 @@ namespace tests\flow\nodes; use tests\flow\TestCase; use app\flow\nodes\RinseNode; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * 漂洗节点单元测试 diff --git a/tests/flow/nodes/WashNodeTest.php b/tests/flow/nodes/WashNodeTest.php index ca2a4d9..4e8766b 100644 --- a/tests/flow/nodes/WashNodeTest.php +++ b/tests/flow/nodes/WashNodeTest.php @@ -4,7 +4,7 @@ namespace tests\flow\nodes; use tests\flow\TestCase; use app\flow\nodes\WashNode; -use app\flow\DbOperationType; +use app\flow\enum\DbOperationType; /** * 清洗节点单元测试