738 lines
21 KiB
PHP
738 lines
21 KiB
PHP
<?php
|
||
|
||
namespace app\flow;
|
||
|
||
use app\config\Config;
|
||
use app\flow\config\AbstractConfig;
|
||
use app\flow\config\ProcessConfig;
|
||
use app\flow\exception\IllegalUsageException;
|
||
use app\model\EctActions;
|
||
use app\net\PacketContext;
|
||
use app\repository\EctActionsRepository;
|
||
use app\repository\EndoscopeRepository;
|
||
use app\repository\ProcessDurationRepository;
|
||
use app\repository\ReaderRepository;
|
||
use app\repository\UserRepository;
|
||
use app\utils\Logger;
|
||
use Exception;
|
||
|
||
/**
|
||
* 流程上下文类
|
||
* 用于在责任链节点之间传递数据
|
||
*/
|
||
class ProcessContext
|
||
{
|
||
|
||
/**
|
||
* TODO
|
||
* @var bool 是否需要增强洗
|
||
*/
|
||
public bool $needEnhanceWash = false;
|
||
|
||
/**
|
||
* 语音模板参数
|
||
* key-value
|
||
* @var array
|
||
*/
|
||
public array $voiceTemplateParams = [];
|
||
/**
|
||
* 配置类
|
||
* @var Config
|
||
*/
|
||
private Config $config;
|
||
// ==================== 基础信息 ====================
|
||
/**
|
||
* 内镜ID
|
||
*/
|
||
public string $endoscopeId = '';
|
||
|
||
/**
|
||
* 内镜名称
|
||
*/
|
||
public string $endoscopeName = '';
|
||
|
||
/**
|
||
* 内镜RFID卡号
|
||
*/
|
||
public string $cardNo = '';
|
||
|
||
/**
|
||
* 内镜类型(胃镜/肠镜等)
|
||
*/
|
||
public string $endoscopeType = '';
|
||
|
||
// ==================== 读卡器信息 ====================
|
||
/**
|
||
* 读卡器编号
|
||
*/
|
||
public string $readerNo = '';
|
||
|
||
/**
|
||
* 当前读卡器类型/功能
|
||
*/
|
||
public string $readerType = '';
|
||
|
||
/**
|
||
* 读卡器ID
|
||
*/
|
||
public string $readerId = '';
|
||
|
||
// ==================== 流程状态 ====================
|
||
/**
|
||
* 当前操作步骤
|
||
*/
|
||
public string $currentStep = '';
|
||
|
||
|
||
/**
|
||
* @var EctActions 上一个操作步骤
|
||
*/
|
||
public EctActions $previousAction {
|
||
get {
|
||
return $this->previousAction;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 流程类型: 手工洗 / 机洗 / 测漏 / 存储 / 手工洗(晨洗)/ 机洗(晨洗)
|
||
*/
|
||
public string $processType = '';
|
||
|
||
/**
|
||
* 批次号
|
||
*/
|
||
public string $batchNo = '';
|
||
|
||
/**
|
||
* 操作开始时间
|
||
*/
|
||
public string $actionStartTime = '';
|
||
|
||
|
||
/**
|
||
* @var int|null 操作时长(秒)
|
||
* 除非处理中有节点主动设置,否则一直是 null
|
||
*/
|
||
public ?int $duration = null;
|
||
|
||
// ==================== 晨洗相关 ====================
|
||
/**
|
||
* 是否需要晨洗
|
||
*/
|
||
public bool $needMorningWash = false;
|
||
|
||
/**
|
||
* 是否已完成晨洗
|
||
*/
|
||
public bool $morningWashed = false;
|
||
|
||
/**
|
||
* 存储入库时间(用于晨洗判断)
|
||
*/
|
||
public ?string $storageInTime = null;
|
||
|
||
/**
|
||
* 今天洗消记录数
|
||
*/
|
||
public int $todayWashRecords = 0;
|
||
|
||
// ==================== 存储相关 ====================
|
||
/**
|
||
* 内镜是否在存储柜中
|
||
*/
|
||
public bool $isInStorage = false;
|
||
|
||
/**
|
||
* 最后一次存储操作类型:入库/出库
|
||
*/
|
||
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 = '';
|
||
|
||
// ==================== 执行结果 ====================
|
||
/**
|
||
* 流程执行结果
|
||
*/
|
||
public bool $success = true {
|
||
get {
|
||
return $this->success;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 错误信息
|
||
*/
|
||
public VoiceMessage $errorMessage = VoiceMessage::NONE;
|
||
|
||
// ==================== 原始数据 ====================
|
||
/**
|
||
* 原始刷卡数据(PacketContext)
|
||
*/
|
||
public ?PacketContext $packetContext = null;
|
||
|
||
/**
|
||
* 原始刷卡数据数组(兼容旧代码)
|
||
*/
|
||
public array $rawData = [];
|
||
|
||
// ==================== 数据库相关标记 ====================
|
||
/**
|
||
* 是否需要操作数据库
|
||
*/
|
||
public bool $needDatabaseOperation = false;
|
||
|
||
/**
|
||
* 数据库操作类型 insert / update
|
||
* DbOperationType
|
||
*/
|
||
public array $dbOperation = [] {
|
||
get {
|
||
return $this->dbOperation;
|
||
}
|
||
|
||
set(DbOperationType|array $value) {
|
||
if (is_array($value)) {
|
||
$this->dbOperation = $value;
|
||
} else {
|
||
$this->dbOperation[] = $value;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 是否需要发送WebSocket通知
|
||
*/
|
||
public bool $needWebSocketNotify = false;
|
||
|
||
/**
|
||
* 这张卡是否是人员卡(而非内镜卡)
|
||
* true 时 FlowProcessor 不走流程链,直接存储操作员信息
|
||
*/
|
||
public bool $isOperatorCard = false;
|
||
|
||
|
||
public ?ProcessConfig $engineConfig;
|
||
|
||
/**
|
||
* 期望下一步刷卡提示
|
||
* 当节点识别到读卡器类型匹配但流程步骤不符时,由节点自行写入此字段
|
||
* ProcessEngine 无节点命中时直接读取此字段作为错误语音提示
|
||
*/
|
||
public VoiceMessage $expectedNextStep = VoiceMessage::NONE {
|
||
get {
|
||
return $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;
|
||
|
||
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);
|
||
}
|
||
}
|