3471deb3f1
- 修改 AbstractProcessNode 中 ProcessContext 的命名空间引用为 app\flow\context\ProcessContext - 引入 app\flow\vo\CanHandleResult 用于节点处理结果表示 - 更新前置策略执行后对成功状态的判断,改为调用 isSuccess() 方法 - 增加日志记录细节,便于调试策略执行中断时的错误信息 - 优化代码注释,提升代码可读性和维护性
608 lines
19 KiB
PHP
608 lines
19 KiB
PHP
<?php
|
|
|
|
namespace app\flow\context;
|
|
|
|
use app\config\Config;
|
|
use app\flow\config\ProcessConfig;
|
|
use app\flow\vo\EndoscopeInfo;
|
|
use app\flow\vo\ExecutionResult;
|
|
use app\flow\vo\MorningWashStatus;
|
|
use app\flow\vo\OperatorInfo;
|
|
use app\flow\vo\ProcessStatus;
|
|
use app\flow\vo\ReaderInfo;
|
|
use app\flow\vo\ReminderStatus;
|
|
use app\flow\vo\StorageStatus;
|
|
use app\flow\vo\VoiceState;
|
|
use app\flow\enum\DbOperationType;
|
|
use app\flow\enum\VoiceMessage;
|
|
use app\flow\vo\BatchNo;
|
|
use app\model\EctActions;
|
|
use app\net\PacketContext;
|
|
use app\repository\EctActionsRepository;
|
|
use app\repository\EndoscopeRepository;
|
|
use app\repository\ReaderRepository;
|
|
use app\repository\UserRepository;
|
|
use app\utils\Logger;
|
|
use Exception;
|
|
|
|
/**
|
|
* 流程上下文构造器
|
|
* 负责从各种数据源加载数据并构建不可变的 ProcessContext 实例
|
|
*/
|
|
class ProcessContextBuilder
|
|
{
|
|
private Config $config;
|
|
|
|
// ==================== 值对象 ====================
|
|
private EndoscopeInfo $endoscope;
|
|
private ReaderInfo $reader;
|
|
private OperatorInfo $operator;
|
|
private StorageStatus $storage;
|
|
private MorningWashStatus $morningWash;
|
|
private VoiceState $voice;
|
|
private ExecutionResult $result;
|
|
private ProcessStatus $processStatus;
|
|
private ReminderStatus $reminder;
|
|
|
|
// ==================== 原始数据 ====================
|
|
private ?PacketContext $packetContext = null;
|
|
private ?ProcessConfig $engineConfig = null;
|
|
private array $rawData = [];
|
|
|
|
// ==================== 标记 ====================
|
|
private bool $isOperatorCard = false;
|
|
private array $stepDurations = [];
|
|
|
|
// 晨洗开始时间(用于加载过程)
|
|
private string $morningStartTime = '00:00:00';
|
|
|
|
public function __construct()
|
|
{
|
|
$this->config = Config::getInstance();
|
|
// 初始化值对象为空对象
|
|
$this->endoscope = EndoscopeInfo::empty();
|
|
$this->reader = ReaderInfo::empty();
|
|
$this->operator = OperatorInfo::empty();
|
|
$this->storage = StorageStatus::notInStorage();
|
|
$this->morningWash = MorningWashStatus::notRequired();
|
|
$this->voice = VoiceState::empty();
|
|
$this->result = ExecutionResult::success();
|
|
$this->processStatus = ProcessStatus::empty();
|
|
$this->reminder = ReminderStatus::none();
|
|
}
|
|
|
|
/**
|
|
* 从 PacketContext 创建构造器
|
|
*/
|
|
public static function fromPacketContext(PacketContext $packetContext, array $additionalData = []): self
|
|
{
|
|
$builder = new self();
|
|
$builder->packetContext = $packetContext;
|
|
$builder->engineConfig = $additionalData['engineConfig'] ?? null;
|
|
|
|
// 获取原始卡号和读卡器编号
|
|
$cardNo = $packetContext->packet->card ?? '';
|
|
$readerNo = $packetContext->packet->reader ?? '';
|
|
$builder->rawData = $packetContext->packet->toArray();
|
|
|
|
// 获取晨洗开始时间
|
|
if ($builder->engineConfig !== null) {
|
|
$builder->morningStartTime = $builder->engineConfig->getMorningWashConfig()->morningStartTime;
|
|
}
|
|
|
|
// 1. 加载内镜/操作员信息
|
|
$builder->loadEndoscopeOrOperatorInfo($cardNo);
|
|
|
|
// 2. 加载读卡器信息
|
|
$builder->loadReaderInfo($readerNo);
|
|
|
|
// 3. 加载内镜操作记录相关信息(仅当内镜ID存在时执行)
|
|
if (!$builder->endoscope->isEmpty()) {
|
|
$builder->loadEndoscopeActionInfo();
|
|
$builder->loadStorageStatus();
|
|
}
|
|
|
|
// 4. 初始化晨洗状态
|
|
$builder->morningWash = new MorningWashStatus(
|
|
needMorningWash: false, // TODO: 由策略计算
|
|
morningWashed: false,
|
|
startTime: $builder->morningStartTime,
|
|
todayWashRecords: $builder->morningWash->todayWashRecords
|
|
);
|
|
|
|
// 5. 合并额外数据
|
|
foreach ($additionalData as $key => $value) {
|
|
if (property_exists($builder, $key) && $value !== null) {
|
|
$builder->$key = $value;
|
|
}
|
|
}
|
|
|
|
Logger::debug("从 PacketContext 创建 ProcessContextBuilder");
|
|
return $builder;
|
|
}
|
|
|
|
/**
|
|
* 从现有的 ProcessContext 创建构造器
|
|
* 用于基于现有上下文创建修改后的新实例
|
|
*/
|
|
public static function from(ProcessContext $context): self
|
|
{
|
|
$builder = new self();
|
|
|
|
// 复制值对象
|
|
$builder->endoscope = $context->getEndoscope();
|
|
$builder->reader = $context->getReader();
|
|
$builder->operator = $context->getOperator();
|
|
$builder->storage = $context->getStorage();
|
|
$builder->morningWash = $context->getMorningWash();
|
|
$builder->voice = $context->getVoice();
|
|
$builder->result = $context->getResult();
|
|
$builder->processStatus = $context->getProcessStatus();
|
|
$builder->reminder = $context->getReminder();
|
|
|
|
// 复制原始数据
|
|
$builder->packetContext = $context->getPacketContext();
|
|
$builder->engineConfig = $context->getEngineConfig();
|
|
$builder->rawData = $context->getRawData();
|
|
|
|
// 复制标记
|
|
$builder->isOperatorCard = $context->isOperatorCard();
|
|
$builder->stepDurations = $context->getStepDurations();
|
|
|
|
return $builder;
|
|
}
|
|
|
|
// ==================== 数据加载方法 ====================
|
|
|
|
/**
|
|
* 加载内镜或操作员信息
|
|
*/
|
|
private function loadEndoscopeOrOperatorInfo(string $cardNo): void
|
|
{
|
|
if (empty($cardNo)) {
|
|
return;
|
|
}
|
|
|
|
// 优先查询内镜信息
|
|
$endoscope = EndoscopeRepository::new()->findByCardNo($cardNo);
|
|
if ($endoscope !== null) {
|
|
$this->endoscope = new EndoscopeInfo(
|
|
id: (string)$endoscope->endoscope_id,
|
|
name: (string)$endoscope->endoscope_name,
|
|
cardNo: $cardNo,
|
|
type: (string)$endoscope->endoscope_type
|
|
);
|
|
return;
|
|
}
|
|
|
|
// 内镜无记录则查询人员卡信息
|
|
try {
|
|
$user = UserRepository::new()->findByRfid($cardNo);
|
|
if ($user !== null) {
|
|
$this->isOperatorCard = true;
|
|
$this->operator = new OperatorInfo(
|
|
id: (string)$user->user_id,
|
|
name: (string)$user->user_name,
|
|
rfid: (string)$user->user_rfid
|
|
);
|
|
}
|
|
} catch (Exception $e) {
|
|
Logger::error("[ProcessContextBuilder] 查询人员卡信息出错: {}", [$e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 加载读卡器信息
|
|
*/
|
|
private function loadReaderInfo(string $readerNo): void
|
|
{
|
|
if (empty($readerNo)) {
|
|
return;
|
|
}
|
|
|
|
$readerInfo = ReaderRepository::new()->findReaderInfo($readerNo);
|
|
if ($readerInfo !== null) {
|
|
$this->reader = new ReaderInfo(
|
|
no: $readerNo,
|
|
type: $readerInfo['readerType'],
|
|
id: $readerInfo['readerId']
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 加载内镜操作记录相关信息
|
|
*/
|
|
private function loadEndoscopeActionInfo(): void
|
|
{
|
|
$actionsRepo = EctActionsRepository::new();
|
|
|
|
// 查询最后一条操作记录
|
|
$lastAction = $actionsRepo->findLastAction($this->endoscope->id, [0, 7, 8]);
|
|
if (empty($lastAction)) {
|
|
return;
|
|
}
|
|
$lastStepStartTime = $lastAction->op_starttime;
|
|
$duration = time() - strtotime($lastStepStartTime);
|
|
$this->handleLastAction($lastAction, $actionsRepo, $duration);
|
|
|
|
// 查询今日洗消记录数(晨洗判断)
|
|
$todayWashRecords = $actionsRepo->countTodayActions($this->endoscope->id, $this->morningStartTime, [0, 7, 8]);
|
|
$this->morningWash = new MorningWashStatus(
|
|
needMorningWash: $this->morningWash->needMorningWash,
|
|
morningWashed: $this->morningWash->morningWashed,
|
|
startTime: $this->morningStartTime,
|
|
todayWashRecords: $todayWashRecords
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 加载内镜存储状态
|
|
*/
|
|
private function loadStorageStatus(): void
|
|
{
|
|
$actionsRepo = EctActionsRepository::new();
|
|
|
|
// 查询最后一次存储操作记录
|
|
$lastStorageAction = $actionsRepo->findLastStorageAction($this->endoscope->id);
|
|
if ($lastStorageAction !== null) {
|
|
$isInStorage = ($lastStorageAction['process_name'] === '内镜放入');
|
|
$inTime = $isInStorage ? $lastStorageAction['op_starttime'] : null;
|
|
$this->storage = new StorageStatus(
|
|
isInStorage: $isInStorage,
|
|
lastAction: $lastStorageAction['process_name'],
|
|
inTime: $inTime
|
|
);
|
|
}
|
|
|
|
// 查询最后一次存储入库时间(义乌模式晨洗判断)
|
|
$storageTime = $actionsRepo->findLastStorageTime($this->endoscope->id);
|
|
if ($storageTime !== null) {
|
|
$this->storage = new StorageStatus(
|
|
isInStorage: $this->storage->isInStorage,
|
|
lastAction: $this->storage->lastAction,
|
|
inTime: $storageTime
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理最后一条操作记录的核心逻辑
|
|
*/
|
|
private function handleLastAction(EctActions $lastAction, EctActionsRepository $actionsRepo, int $duration): void
|
|
{
|
|
// 设置基础操作信息
|
|
$currentStep = (string)$lastAction->process_name;
|
|
$actionStartTime = date('Y-m-d H:i:s');
|
|
$processType = '';
|
|
$batchNo = '';
|
|
|
|
// 处理批次号逻辑
|
|
if ($currentStep === '结束') {
|
|
$batchNo = $this->generateBatchNo();
|
|
} else {
|
|
$batchNo = (string)$lastAction->op_batchno;
|
|
$processType = (string)$lastAction->action_type_name;
|
|
}
|
|
|
|
$this->processStatus = new ProcessStatus(
|
|
currentStep: $currentStep,
|
|
processType: $processType,
|
|
batchNo: $batchNo,
|
|
actionStartTime: $actionStartTime,
|
|
duration: $duration,
|
|
previousAction: $lastAction
|
|
);
|
|
|
|
// 加载批次操作员信息
|
|
if ($currentStep !== '结束' && !empty($batchNo)) {
|
|
$this->loadBatchOperatorInfo($actionsRepo, $batchNo);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 加载批次对应的操作员信息
|
|
*/
|
|
private function loadBatchOperatorInfo(EctActionsRepository $actionsRepo, string $batchNo): void
|
|
{
|
|
$operator = $actionsRepo->findOperatorByBatchNo($batchNo);
|
|
if ($operator !== null) {
|
|
$this->operator = new OperatorInfo(
|
|
id: $operator['id'],
|
|
name: $operator['name'],
|
|
rfid: $operator['rfid']
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成批次号
|
|
* @return string 批次号值
|
|
*/
|
|
private function generateBatchNo(): string
|
|
{
|
|
return BatchNo::generate($this->config->machineId)->value;
|
|
}
|
|
|
|
// ==================== 值对象设置方法 ====================
|
|
|
|
public function withEndoscope(EndoscopeInfo $endoscope): self
|
|
{
|
|
$this->endoscope = $endoscope;
|
|
return $this;
|
|
}
|
|
|
|
public function withReader(ReaderInfo $reader): self
|
|
{
|
|
$this->reader = $reader;
|
|
return $this;
|
|
}
|
|
|
|
public function withOperator(OperatorInfo $operator): self
|
|
{
|
|
$this->operator = $operator;
|
|
return $this;
|
|
}
|
|
|
|
public function withStorage(StorageStatus $storage): self
|
|
{
|
|
$this->storage = $storage;
|
|
return $this;
|
|
}
|
|
|
|
public function withMorningWash(MorningWashStatus $morningWash): self
|
|
{
|
|
$this->morningWash = $morningWash;
|
|
return $this;
|
|
}
|
|
|
|
public function withVoice(VoiceState $voice): self
|
|
{
|
|
$this->voice = $voice;
|
|
return $this;
|
|
}
|
|
|
|
public function withResult(ExecutionResult $result): self
|
|
{
|
|
$this->result = $result;
|
|
return $this;
|
|
}
|
|
|
|
public function withProcessStatus(ProcessStatus $processStatus): self
|
|
{
|
|
$this->processStatus = $processStatus;
|
|
return $this;
|
|
}
|
|
|
|
public function withReminder(ReminderStatus $reminder): self
|
|
{
|
|
$this->reminder = $reminder;
|
|
return $this;
|
|
}
|
|
|
|
// ==================== 流程状态便捷设置方法 ====================
|
|
|
|
public function withCurrentStep(string $currentStep): self
|
|
{
|
|
$this->processStatus = new ProcessStatus(
|
|
currentStep: $currentStep,
|
|
processType: $this->processStatus->processType,
|
|
batchNo: $this->processStatus->batchNo,
|
|
actionStartTime: $this->processStatus->actionStartTime,
|
|
duration: $this->processStatus->duration,
|
|
previousAction: $this->processStatus->previousAction
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
public function withProcessType(string $processType): self
|
|
{
|
|
$this->processStatus = new ProcessStatus(
|
|
currentStep: $this->processStatus->currentStep,
|
|
processType: $processType,
|
|
batchNo: $this->processStatus->batchNo,
|
|
actionStartTime: $this->processStatus->actionStartTime,
|
|
duration: $this->processStatus->duration,
|
|
previousAction: $this->processStatus->previousAction
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
public function withBatchNo(string $batchNo): self
|
|
{
|
|
$this->processStatus = new ProcessStatus(
|
|
currentStep: $this->processStatus->currentStep,
|
|
processType: $this->processStatus->processType,
|
|
batchNo: $batchNo,
|
|
actionStartTime: $this->processStatus->actionStartTime,
|
|
duration: $this->processStatus->duration,
|
|
previousAction: $this->processStatus->previousAction
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
public function withDuration(?int $duration): self
|
|
{
|
|
$this->processStatus = new ProcessStatus(
|
|
currentStep: $this->processStatus->currentStep,
|
|
processType: $this->processStatus->processType,
|
|
batchNo: $this->processStatus->batchNo,
|
|
actionStartTime: $this->processStatus->actionStartTime,
|
|
duration: $duration,
|
|
previousAction: $this->processStatus->previousAction
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
public function withPreviousAction(?EctActions $action): self
|
|
{
|
|
$this->processStatus = new ProcessStatus(
|
|
currentStep: $this->processStatus->currentStep,
|
|
processType: $this->processStatus->processType,
|
|
batchNo: $this->processStatus->batchNo,
|
|
actionStartTime: $this->processStatus->actionStartTime,
|
|
duration: $this->processStatus->duration,
|
|
previousAction: $action
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
// ==================== 便捷设置方法(语义化) ====================
|
|
|
|
/**
|
|
* 设置成功状态
|
|
*/
|
|
public function success(): self
|
|
{
|
|
$this->result = new ExecutionResult(success: true);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置错误状态和消息
|
|
*/
|
|
public function error(VoiceMessage $message): self
|
|
{
|
|
$this->voice = $this->voice->withError($message);
|
|
$this->result = $this->result->fail();
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置自定义错误
|
|
*/
|
|
public function customError(string $message): self
|
|
{
|
|
$this->voice = $this->voice->withMessage($message)->withError(VoiceMessage::CUSTOM);
|
|
$this->result = $this->result->fail();
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置语音消息
|
|
*/
|
|
public function voiceMessage(string|VoiceMessage $message): self
|
|
{
|
|
$this->voice = $this->voice->withMessage($message);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置期望下一步
|
|
*/
|
|
public function expectedNextStep(VoiceMessage $expected): self
|
|
{
|
|
$this->voice = $this->voice->withExpectedNextStep($expected);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 添加数据库操作
|
|
*/
|
|
public function dbOperation(DbOperationType $operation): self
|
|
{
|
|
$this->result = $this->result->addDbOperation($operation);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置需要数据库操作
|
|
*/
|
|
public function needDatabaseOperation(bool $need = true): self
|
|
{
|
|
$this->result = new ExecutionResult(
|
|
success: $this->result->success,
|
|
needDatabaseOperation: $need,
|
|
dbOperations: $this->result->dbOperations,
|
|
needWebSocketNotify: $this->result->needWebSocketNotify,
|
|
skipNodeCount: $this->result->skipNodeCount
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置需要WebSocket通知
|
|
*/
|
|
public function needWebSocketNotify(bool $need = true): self
|
|
{
|
|
$this->result = $this->result->withWebSocketNotify($need);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置跳过节点数
|
|
*/
|
|
public function skipNodeCount(int $count): self
|
|
{
|
|
$this->result = $this->result->withSkipNodeCount($count);
|
|
return $this;
|
|
}
|
|
|
|
// ==================== 提醒状态便捷设置方法 ====================
|
|
|
|
public function withNeedEnhanceWash(bool $need): self
|
|
{
|
|
$this->reminder = $this->reminder->withEnhanceWash($need);
|
|
return $this;
|
|
}
|
|
|
|
public function withNeedLeakTestRemind(bool $need): self
|
|
{
|
|
$this->reminder = $this->reminder->withLeakTestRemind($need);
|
|
return $this;
|
|
}
|
|
|
|
public function withNeedStorageRemind(bool $need): self
|
|
{
|
|
$this->reminder = $this->reminder->withStorageRemind($need);
|
|
return $this;
|
|
}
|
|
|
|
public function withLeakTestDone(bool $done, string $result = ''): self
|
|
{
|
|
$this->reminder = $this->reminder->withLeakTestDone($result);
|
|
return $this;
|
|
}
|
|
|
|
// ==================== 标记设置方法 ====================
|
|
|
|
public function withIsOperatorCard(bool $isOperatorCard): self
|
|
{
|
|
$this->isOperatorCard = $isOperatorCard;
|
|
return $this;
|
|
}
|
|
|
|
public function withStepDuration(string $stepCode, int $duration): self
|
|
{
|
|
$this->stepDurations[$stepCode] = $duration;
|
|
return $this;
|
|
}
|
|
|
|
// ==================== 构建方法 ====================
|
|
|
|
/**
|
|
* 构建不可变的 ProcessContext 实例
|
|
*/
|
|
public function build(): ProcessContext
|
|
{
|
|
Logger::debug("ProcessContextBuilder 构建 ProcessContext 完成");
|
|
|
|
return new ProcessContext(
|
|
endoscope: $this->endoscope,
|
|
reader: $this->reader,
|
|
operator: $this->operator,
|
|
storage: $this->storage,
|
|
morningWash: $this->morningWash,
|
|
voice: $this->voice,
|
|
result: $this->result,
|
|
processStatus: $this->processStatus,
|
|
reminder: $this->reminder,
|
|
packetContext: $this->packetContext,
|
|
engineConfig: $this->engineConfig,
|
|
rawData: $this->rawData,
|
|
isOperatorCard: $this->isOperatorCard,
|
|
stepDurations: $this->stepDurations,
|
|
);
|
|
}
|
|
}
|