ai-chore(config): 调整流程配置及改进测试代码

- 将 FLOW_USE_CUSTOM_PROCESS 从 true 改为 false,禁用自定义流程
- 在 BlockTest 测试用例中改用 setBlockMode 方法设置阻断模式
- 设置统一的错误处理,将错误转为异常抛出
- 重命名 BlockTest 测试文件路径,优化测试组织结构
- 更新 IDE php include paths,调整依赖包引用顺序
- 删除无用的 tests/flow/Test.php 测试文件
- 微调 start.php、webman、windows.php 配置或代码模块
This commit is contained in:
zimoyin
2026-03-11 13:48:40 +08:00
parent 6c874221ad
commit f2ff4ae123
42 changed files with 246 additions and 158 deletions
+196
View File
@@ -0,0 +1,196 @@
<?php
namespace app\flow\context\bean;
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),
];
}
/**
* 解析批次号结构
* @deprecated
* @return array{date: string, machineId: string, sequence: int, dateFormatted: string}
*/
public static function parseBatchNo(string $batchNo): array
{
$batch = BatchNo::fromString($batchNo);
if (!$batch->isValid()) {
throw new \InvalidArgumentException('Invalid batch string: ' . $batchNo);
}
return $batch->parse();
}
/**
* 验证批次号格式是否有效
*/
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 getDateStr(): string
{
$parsed = $this->parse();
return $parsed['dateFormatted'];
}
/**
* 获取时间戳
* @return int|false
*/
public function getTimestamp(): int|false
{
$parsed = $this->parse();
return strtotime($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\context\bean;
use app\flow\enum\VoiceMessage;
/**
* 节点能否处理的判断结果
* 封装 canHandle 方法的返回值(不可变)
*/
readonly class CanHandleResult
{
public function __construct(
/** 是否能处理 */
public bool $canHandle,
/** 如果不能处理,期望的下一步提示 */
public VoiceMessage $expectedNextStep = VoiceMessage::NONE,
) {}
/**
* 创建可以处理的结果
*/
public static function canHandle(): self
{
return new self(canHandle: true);
}
/**
* 创建不能处理的结果
*/
public static function cannotHandle(?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\context\bean;
/**
* 内镜信息值对象
* 封装内镜的基础信息,不可变对象
*/
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\context\bean;
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,
];
}
}
@@ -0,0 +1,91 @@
<?php
namespace app\flow\context\bean;
/**
* 晨洗状态值对象
* 封装内镜的晨洗相关状态信息,不可变对象
*/
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\context\bean;
/**
* 操作员信息值对象
* 封装操作员的基础信息,不可变对象
*/
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\context\bean;
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\context\bean;
/**
* 读卡器信息值对象
* 封装读卡器的基础信息,不可变对象
*/
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\context\bean;
/**
* 提醒状态值对象
* 封装流程中的各类提醒标记(不可变)
*/
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\context\bean;
/**
* 存储状态值对象
* 封装内镜的存储柜状态信息,不可变对象
*/
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\context\bean;
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,
];
}
}