ai-refactor(flow): 调整抽象流程节点实现和依赖路径

- 修改 AbstractProcessNode 中 ProcessContext 的命名空间引用为 app\flow\context\ProcessContext
- 引入 app\flow\vo\CanHandleResult 用于节点处理结果表示
- 更新前置策略执行后对成功状态的判断,改为调用 isSuccess() 方法
- 增加日志记录细节,便于调试策略执行中断时的错误信息
- 优化代码注释,提升代码可读性和维护性
This commit is contained in:
zimoyin
2026-03-11 00:49:02 +08:00
parent e040fccba6
commit d5991813a6
14 changed files with 1755 additions and 0 deletions
+623
View File
@@ -0,0 +1,623 @@
<?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\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']
);
}
}
/**
* 生成批次号
*/
private function generateBatchNo(): string
{
$existingBatchNo = null;
if (!$this->endoscope->isEmpty()) {
$existingBatchNo = EctActionsRepository::new()->findTodayActiveBatchNo($this->config->machineId);
}
$datePart = date('Ymd');
$sequence = 1;
if (!empty($existingBatchNo)) {
$existingDatePart = substr($existingBatchNo, 0, 8);
$existingSequence = substr($existingBatchNo, 10, 4);
if ($existingDatePart === $datePart && is_numeric($existingSequence)) {
$sequence = (int)$existingSequence + 1;
}
}
$sequencePart = str_pad($sequence, 4, '0', STR_PAD_LEFT);
return $datePart . $this->config->machineId . $sequencePart;
}
// ==================== 值对象设置方法 ====================
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,
);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace app\flow\enum;
/**
* 数据库操作类型枚举
* 用于 ProcessContext 中标识需要执行的数据库操作
*/
enum DbOperationType: string
{
// 插入 actions 原始数据
case INSERT = 'insert';
// 更新内镜与 actions process_data 数据
case UPDATE = 'update';
// 不进行任何数据库操作
case NONE = 'none';
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace app\flow\enum;
/**
* 语音消息枚举
*/
enum VoiceMessage: string
{
// 自定义语音输出
case CUSTOM = 'custom';
// NONE
case NONE = '';
// 晨洗
case MORNING_WASH = '晨洗';
// 清洗
case WASH = '清洗';
// 消毒
case DISINFECT = '消毒';
// 干燥
case DRY = '干燥';
// 终末漂洗
case TERMINAL_RINSE = '终末漂洗';
// 漂洗
case RINSE = '漂洗';
// 结束
case END = '结束';
// 重复刷卡
case DUPLICATE_SWIPING = 'repeat';
case PLEASE_SWIPE_WASH = '请刷清洗';
// 请刷干燥
case PLEASE_SWIPE_DRY = '请刷干燥';
// 请刷消毒
case PLEASE_SWIPE_DISINFECT = '请刷消毒';
// 请刷漂洗
case PLEASE_SWIPE_RINSE = '请刷漂洗';
// 请刷终末漂洗
case PLEASE_SWIPE_FINAL_RINSE = '请刷终末漂洗';
// 请刷结束
case PLEASE_SWIPE_END = '请刷结束';
// 请刷机洗
case PLEASE_SWIPE_MACHINE_WASH = '请刷机洗';
// 请先进行晨洗
case PLEASE_SWIPE_MORNING_WASH = '请刷消毒或机洗,开启晨洗流程';
// 流程链未初始化
case PROCESS_CHAIN_NOT_INITIALIZED = '流程链未初始化';
case UNKNOWN_ERROR = '未知错误';
// 卡号未绑定
case CARD_NOT_BOUND = '卡号未绑定';
case READER_NOT_BOUND = '读卡器未绑定';
case ENDOSCOPE_NOT_BOUND = "内镜未绑定";
case OPERATOR_NOT_FOUND = "人员未绑定";
case PLEASE_SWIPE_OPERATOR = "请刷人员卡";
case PLEASE_SWIPE_ENDOSCOPE = "请刷内镜卡";
case NOT_ENOUGH_TIME = '{step}剩余{time}秒 '; // 刷错,当前步骤时长不足
// 存储相关
case STORAGE_IN = '内镜放入';
case STORAGE_OUT = '内镜取出';
case PLEASE_SWIPE_STORAGE_IN = '请刷入库';
case PLEASE_SWIPE_STORAGE_OUT = '请刷出库';
case ENDOSCOPE_ALREADY_IN_STORAGE = '内镜已在库中';
case ENDOSCOPE_NOT_IN_STORAGE = '内镜不在库中';
}
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace app\flow\vo;
use app\config\Config;
use app\repository\EctActionsRepository;
/**
* 批次号值对象
* 封装批次号相关操作(不可变)
*/
readonly class BatchNo
{
public function __construct(
/** 批次号值 */
public string $value = '',
) {}
/**
* 创建空批次号
*/
public static function empty(): self
{
return new self(value: '');
}
/**
* 从字符串创建
*/
public static function fromString(string $value): self
{
return new self(value: $value);
}
/**
* 生成新批次号
*/
public static function generate(?string $machineId = null): self
{
$config = Config::getInstance();
$machineId = $machineId ?? $config->machineId;
$existingBatchNo = EctActionsRepository::new()->findTodayActiveBatchNo($machineId);
$datePart = date('Ymd');
$sequence = 1;
if (!empty($existingBatchNo)) {
$existingDatePart = substr($existingBatchNo, 0, 8);
$existingSequence = substr($existingBatchNo, 10, 4);
if ($existingDatePart === $datePart && is_numeric($existingSequence)) {
$sequence = (int)$existingSequence + 1;
}
}
$sequencePart = str_pad($sequence, 4, '0', STR_PAD_LEFT);
return new self(value: $datePart . $machineId . $sequencePart);
}
/**
* 解析批次号结构
*
* @return array{date: string, machineId: string, sequence: int, dateFormatted: string}
*/
public function parse(): array
{
if (!$this->isValid()) {
return [
'date' => '',
'machineId' => '',
'sequence' => 0,
'dateFormatted' => '',
];
}
$datePart = substr($this->value, 0, 8);
$machineId = substr($this->value, 8, 2);
$sequence = (int)substr($this->value, 10, 4);
return [
'date' => $datePart,
'machineId' => $machineId,
'sequence' => $sequence,
'dateFormatted' => substr($datePart, 0, 4) . '-' . substr($datePart, 4, 2) . '-' . substr($datePart, 6, 2),
];
}
/**
* 验证批次号格式是否有效
*/
public function isValid(): bool
{
// 批次号格式: YYYYMMDD + 机器ID(2位) + 序号(4位) = 14位
if (strlen($this->value) !== 14) {
return false;
}
$datePart = substr($this->value, 0, 8);
$sequencePart = substr($this->value, 10, 4);
// 验证日期部分
if (!is_numeric($datePart)) {
return false;
}
// 验证序号部分
if (!is_numeric($sequencePart)) {
return false;
}
return true;
}
/**
* 是否为空批次号
*/
public function isEmpty(): bool
{
return empty($this->value);
}
/**
* 获取日期部分
*/
public function getDate(): string
{
$parsed = $this->parse();
return $parsed['dateFormatted'];
}
/**
* 获取机器ID
*/
public function getMachineId(): string
{
$parsed = $this->parse();
return $parsed['machineId'];
}
/**
* 获取序号
*/
public function getSequence(): int
{
$parsed = $this->parse();
return $parsed['sequence'];
}
/**
* 是否是今天的批次号
*/
public function isToday(): bool
{
if (!$this->isValid()) {
return false;
}
$datePart = substr($this->value, 0, 8);
return $datePart === date('Ymd');
}
/**
* 转换为字符串
*/
public function __toString(): string
{
return $this->value;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace app\flow\vo;
use app\flow\enum\VoiceMessage;
/**
* 节点能否处理的判断结果
* 封装 canHandle 方法的返回值(不可变)
*/
readonly class CanHandleResult
{
public function __construct(
/** 是否能处理 */
public bool $canHandle,
/** 如果不能处理,期望的下一步提示 */
public VoiceMessage $expectedNextStep = VoiceMessage::NONE,
) {}
/**
* 创建可以处理的结果
*/
public static function yes(): self
{
return new self(canHandle: true);
}
/**
* 创建不能处理的结果
*/
public static function no(?VoiceMessage $expectedNextStep = null): self
{
return new self(
canHandle: false,
expectedNextStep: $expectedNextStep ?? VoiceMessage::NONE
);
}
/**
* 是否能处理
*/
public function isCanHandle(): bool
{
return $this->canHandle;
}
/**
* 是否有期望下一步提示
*/
public function hasExpectedNextStep(): bool
{
return $this->expectedNextStep !== VoiceMessage::NONE;
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace app\flow\vo;
/**
* 内镜信息值对象
* 封装内镜的基础信息,不可变对象
*/
readonly class EndoscopeInfo
{
public function __construct(
/** 内镜ID */
public string $id = '',
/** 内镜名称 */
public string $name = '',
/** 内镜RFID卡号 */
public string $cardNo = '',
/** 内镜类型(胃镜/肠镜等) */
public string $type = '',
) {}
/**
* 创建空的内镜信息
*/
public static function empty(): self
{
return new self();
}
/**
* 判断内镜信息是否为空
*/
public function isEmpty(): bool
{
return empty($this->id);
}
/**
* 判断内镜信息是否有效
*/
public function isValid(): bool
{
return !empty($this->id) && !empty($this->cardNo);
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'cardNo' => $this->cardNo,
'type' => $this->type,
];
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
namespace app\flow\vo;
use app\flow\enum\DbOperationType;
/**
* 执行结果值对象
* 封装流程执行的结果状态(不可变)
*/
readonly class ExecutionResult
{
public function __construct(
/** 流程执行是否成功 */
public bool $success = true,
/** 是否需要操作数据库 */
public bool $needDatabaseOperation = false,
/** 数据库操作类型列表 */
public array $dbOperations = [],
/** 是否需要发送WebSocket通知 */
public bool $needWebSocketNotify = false,
/** 跳过节点数量 */
public int $skipNodeCount = 0,
) {}
/**
* 创建成功状态
*/
public static function success(): self
{
return new self(success: true);
}
/**
* 创建失败状态
*/
public static function failure(): self
{
return new self(success: false);
}
/**
* 创建需要写库的状态
*/
public static function withDbOperation(DbOperationType $operation): self
{
return new self(
success: true,
needDatabaseOperation: true,
dbOperations: [$operation]
);
}
/**
* 添加数据库操作
*/
public function addDbOperation(DbOperationType $operation): self
{
return new self(
success: $this->success,
needDatabaseOperation: true,
dbOperations: [...$this->dbOperations, $operation],
needWebSocketNotify: $this->needWebSocketNotify,
skipNodeCount: $this->skipNodeCount
);
}
/**
* 设置为失败
*/
public function fail(): self
{
return new self(
success: false,
needDatabaseOperation: $this->needDatabaseOperation,
dbOperations: $this->dbOperations,
needWebSocketNotify: $this->needWebSocketNotify,
skipNodeCount: $this->skipNodeCount
);
}
/**
* 设置需要WebSocket通知
*/
public function withWebSocketNotify(bool $need = true): self
{
return new self(
success: $this->success,
needDatabaseOperation: $this->needDatabaseOperation,
dbOperations: $this->dbOperations,
needWebSocketNotify: $need,
skipNodeCount: $this->skipNodeCount
);
}
/**
* 设置跳过节点数
*/
public function withSkipNodeCount(int $count): self
{
return new self(
success: $this->success,
needDatabaseOperation: $this->needDatabaseOperation,
dbOperations: $this->dbOperations,
needWebSocketNotify: $this->needWebSocketNotify,
skipNodeCount: $count
);
}
/**
* 是否有数据库操作
*/
public function hasDbOperations(): bool
{
return !empty($this->dbOperations);
}
/**
* 获取数据库操作类型名称列表
*/
public function getDbOperationNames(): array
{
return array_map(fn(DbOperationType $op) => $op->name, $this->dbOperations);
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'success' => $this->success,
'needDatabaseOperation' => $this->needDatabaseOperation,
'dbOperations' => $this->getDbOperationNames(),
'needWebSocketNotify' => $this->needWebSocketNotify,
'skipNodeCount' => $this->skipNodeCount,
];
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
namespace app\flow\vo;
/**
* 晨洗状态值对象
* 封装内镜的晨洗相关状态信息,不可变对象
*/
readonly class MorningWashStatus
{
public function __construct(
/** 是否需要晨洗 */
public bool $needMorningWash = false,
/** 是否已完成晨洗 */
public bool $morningWashed = false,
/** 晨洗开始时间(如 06:00:00 */
public string $startTime = '00:00:00',
/** 今天洗消记录数 */
public int $todayWashRecords = 0,
) {}
/**
* 创建默认的晨洗状态(不需要晨洗)
*/
public static function notRequired(): self
{
return new self(needMorningWash: false);
}
/**
* 创建需要晨洗的状态
*/
public static function required(string $startTime = '06:00:00', int $todayWashRecords = 0): self
{
return new self(
needMorningWash: true,
morningWashed: false,
startTime: $startTime,
todayWashRecords: $todayWashRecords
);
}
/**
* 创建已完成晨洗的状态
*/
public static function completed(string $startTime = '06:00:00'): self
{
return new self(
needMorningWash: true,
morningWashed: true,
startTime: $startTime
);
}
/**
* 判断是否需要进行晨洗(需要且未完成)
*/
public function shouldDoMorningWash(): bool
{
return $this->needMorningWash && !$this->morningWashed;
}
/**
* 判断晨洗是否已完成
*/
public function isCompleted(): bool
{
return $this->morningWashed;
}
/**
* 判断今天是否有洗消记录
*/
public function hasWashRecordsToday(): bool
{
return $this->todayWashRecords > 0;
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'needMorningWash' => $this->needMorningWash,
'morningWashed' => $this->morningWashed,
'startTime' => $this->startTime,
'todayWashRecords' => $this->todayWashRecords,
];
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace app\flow\vo;
/**
* 操作员信息值对象
* 封装操作员的基础信息,不可变对象
*/
readonly class OperatorInfo
{
public function __construct(
/** 操作员ID */
public string $id = '',
/** 操作员姓名 */
public string $name = '',
/** 操作员RFID卡号 */
public string $rfid = '',
) {}
/**
* 创建空的操作员信息
*/
public static function empty(): self
{
return new self();
}
/**
* 判断操作员信息是否为空
*/
public function isEmpty(): bool
{
return empty($this->id);
}
/**
* 判断操作员信息是否完整有效
*/
public function isValid(): bool
{
return !empty($this->id) && !empty($this->name) && !empty($this->rfid);
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'rfid' => $this->rfid,
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace app\flow\vo;
use app\model\EctActions;
/**
* 流程状态值对象
* 封装流程执行过程中的状态信息(不可变)
*/
readonly class ProcessStatus
{
public function __construct(
/** 当前操作步骤 */
public string $currentStep = '',
/** 流程类型 */
public string $processType = '',
/** 批次号 */
public string $batchNo = '',
/** 操作开始时间 */
public string $actionStartTime = '',
/** 操作时长(秒) */
public ?int $duration = null,
/** 上一个操作记录 */
public ?EctActions $previousAction = null,
) {}
/**
* 创建空状态
*/
public static function empty(): self
{
return new self();
}
/**
* 检查是否可以开始新流程
*/
public function canStartNewProcess(): bool
{
$validSteps = ['', '结束', '内镜取出', '测漏正常', '测漏异常'];
return in_array($this->currentStep, $validSteps);
}
/**
* 检查是否已完成清洗流程
*/
public function isWashProcessCompleted(): bool
{
return $this->currentStep === '结束';
}
/**
* 是否有上一个操作
*/
public function hasPreviousAction(): bool
{
return $this->previousAction !== null;
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'currentStep' => $this->currentStep,
'processType' => $this->processType,
'batchNo' => $this->batchNo,
'actionStartTime' => $this->actionStartTime,
'duration' => $this->duration,
'previousActionId' => $this->previousAction?->action_id,
];
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace app\flow\vo;
/**
* 读卡器信息值对象
* 封装读卡器的基础信息,不可变对象
*/
readonly class ReaderInfo
{
public function __construct(
/** 读卡器编号 */
public string $no = '',
/** 读卡器类型/功能(清洗/漂洗/消毒等) */
public string $type = '',
/** 读卡器ID */
public string $id = '',
) {}
/**
* 创建空的读卡器信息
*/
public static function empty(): self
{
return new self();
}
/**
* 判断读卡器信息是否为空
*/
public function isEmpty(): bool
{
return empty($this->no);
}
/**
* 判断读卡器类型是否匹配
*/
public function isType(string $type): bool
{
return $this->type === $type;
}
/**
* 判断读卡器类型是否在给定列表中
*/
public function isTypeIn(array $types): bool
{
return in_array($this->type, $types, true);
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'no' => $this->no,
'type' => $this->type,
'id' => $this->id,
];
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
namespace app\flow\vo;
/**
* 提醒状态值对象
* 封装流程中的各类提醒标记(不可变)
*/
readonly class ReminderStatus
{
public function __construct(
/** 是否需要增强洗 */
public bool $needEnhanceWash = false,
/** 是否需要测漏提醒 */
public bool $needLeakTestRemind = false,
/** 是否需要存储提醒 */
public bool $needStorageRemind = false,
/** 是否已测漏 */
public bool $leakTestDone = false,
/** 测漏结果 */
public string $leakTestResult = '',
) {}
/**
* 创建默认状态(无提醒)
*/
public static function none(): self
{
return new self();
}
/**
* 设置需要增强洗
*/
public function withEnhanceWash(bool $need = true): self
{
return new self(
needEnhanceWash: $need,
needLeakTestRemind: $this->needLeakTestRemind,
needStorageRemind: $this->needStorageRemind,
leakTestDone: $this->leakTestDone,
leakTestResult: $this->leakTestResult
);
}
/**
* 设置需要测漏提醒
*/
public function withLeakTestRemind(bool $need = true): self
{
return new self(
needEnhanceWash: $this->needEnhanceWash,
needLeakTestRemind: $need,
needStorageRemind: $this->needStorageRemind,
leakTestDone: $this->leakTestDone,
leakTestResult: $this->leakTestResult
);
}
/**
* 设置需要存储提醒
*/
public function withStorageRemind(bool $need = true): self
{
return new self(
needEnhanceWash: $this->needEnhanceWash,
needLeakTestRemind: $this->needLeakTestRemind,
needStorageRemind: $need,
leakTestDone: $this->leakTestDone,
leakTestResult: $this->leakTestResult
);
}
/**
* 设置测漏完成
*/
public function withLeakTestDone(string $result = ''): self
{
return new self(
needEnhanceWash: $this->needEnhanceWash,
needLeakTestRemind: $this->needLeakTestRemind,
needStorageRemind: $this->needStorageRemind,
leakTestDone: true,
leakTestResult: $result
);
}
/**
* 是否有任何提醒
*/
public function hasAnyRemind(): bool
{
return $this->needEnhanceWash
|| $this->needLeakTestRemind
|| $this->needStorageRemind;
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'needEnhanceWash' => $this->needEnhanceWash,
'needLeakTestRemind' => $this->needLeakTestRemind,
'needStorageRemind' => $this->needStorageRemind,
'leakTestDone' => $this->leakTestDone,
'leakTestResult' => $this->leakTestResult,
];
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
namespace app\flow\vo;
/**
* 存储状态值对象
* 封装内镜的存储柜状态信息,不可变对象
*/
readonly class StorageStatus
{
public function __construct(
/** 内镜是否在存储柜中 */
public bool $isInStorage = false,
/** 最后一次存储操作类型:内镜放入/内镜取出 */
public string $lastAction = '',
/** 存储入库时间 */
public ?string $inTime = null,
) {}
/**
* 创建默认的存储状态(不在库中)
*/
public static function notInStorage(): self
{
return new self(isInStorage: false);
}
/**
* 创建入库状态
*/
public static function inStorage(string $inTime): self
{
return new self(
isInStorage: true,
lastAction: '内镜放入',
inTime: $inTime
);
}
/**
* 创建出库状态
*/
public static function outOfStorage(): self
{
return new self(
isInStorage: false,
lastAction: '内镜取出',
inTime: null
);
}
/**
* 判断是否已入库
*/
public function isStored(): bool
{
return $this->isInStorage;
}
/**
* 判断是否已出库
*/
public function isTakenOut(): bool
{
return !$this->isInStorage && $this->lastAction === '内镜取出';
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'isInStorage' => $this->isInStorage,
'lastAction' => $this->lastAction,
'inTime' => $this->inTime,
];
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
namespace app\flow\vo;
use app\flow\enum\VoiceMessage;
/**
* 语音状态值对象
* 封装语音播报相关状态(不可变)
*/
readonly class VoiceState
{
public function __construct(
/** 语音播报内容 */
public string $message = '',
/** 错误消息枚举 */
public VoiceMessage $errorMessage = VoiceMessage::NONE,
/** 期望下一步提示 */
public VoiceMessage $expectedNextStep = VoiceMessage::NONE,
/** 语音模板参数 */
public array $templateParams = [],
) {}
/**
* 创建空语音状态
*/
public static function empty(): self
{
return new self();
}
/**
* 从消息创建
*/
public static function fromMessage(string|VoiceMessage $message): self
{
if ($message instanceof VoiceMessage) {
return new self(message: $message->value);
}
return new self(message: $message);
}
/**
* 从错误枚举创建
*/
public static function fromError(VoiceMessage $error): self
{
return new self(errorMessage: $error);
}
/**
* 设置语音消息
*/
public function withMessage(string|VoiceMessage $message): self
{
$msg = $message instanceof VoiceMessage ? $message->value : $message;
return new self(
message: $msg,
errorMessage: $this->errorMessage,
expectedNextStep: $this->expectedNextStep,
templateParams: $this->templateParams
);
}
/**
* 设置错误消息
*/
public function withError(VoiceMessage $error): self
{
return new self(
message: $this->message,
errorMessage: $error,
expectedNextStep: $this->expectedNextStep,
templateParams: $this->templateParams
);
}
/**
* 设置期望下一步
*/
public function withExpectedNextStep(VoiceMessage $expected): self
{
return new self(
message: $this->message,
errorMessage: $this->errorMessage,
expectedNextStep: $expected,
templateParams: $this->templateParams
);
}
/**
* 添加语音前缀
*/
public function prependMessage(string $prefix): self
{
return new self(
message: $prefix . $this->message,
errorMessage: $this->errorMessage,
expectedNextStep: $this->expectedNextStep,
templateParams: $this->templateParams
);
}
/**
* 设置模板参数
*/
public function withTemplateParams(array $params): self
{
return new self(
message: $this->message,
errorMessage: $this->errorMessage,
expectedNextStep: $this->expectedNextStep,
templateParams: $params
);
}
/**
* 获取完整语音(优先返回 message,否则返回 errorMessage
*/
public function getFullVoice(): string
{
if (!empty($this->message)) {
return $this->message;
}
return $this->errorMessage->value;
}
/**
* 是否有错误
*/
public function hasError(): bool
{
return $this->errorMessage !== VoiceMessage::NONE;
}
/**
* 是否有期望下一步提示
*/
public function hasExpectedNextStep(): bool
{
return $this->expectedNextStep !== VoiceMessage::NONE;
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'message' => $this->message,
'errorMessage' => $this->errorMessage->name,
'expectedNextStep' => $this->expectedNextStep->name,
'templateParams' => $this->templateParams,
];
}
}