审批&备货单

This commit is contained in:
mzl
2025-09-09 13:42:00 +08:00
parent 5dd2925687
commit 918bac19a5
23 changed files with 1928 additions and 1427 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,620 +0,0 @@
<?php
namespace app\common\utils\approval\service;
use support\exception\BusinessException;
use support\Db;
class ApprovalFlowService_bak2
{
// --- 公共方法 ---
/**
* 启动审批流程
*/
public function start(string $flowType, string $bizType, string $bizId, array $formData, int $initiatorId): array
{
// 1. 获取流程定义
$flow = Db::table('approval_flows')
->where('type', $flowType)
->where('is_active', 1)
->first();
if (!$flow) {
throw new BusinessException("审批流程未找到或已停用: {$flowType}");
}
$steps = json_decode($flow->steps, true);
if (!is_array($steps) || empty($steps)) {
throw new BusinessException("流程步骤配置错误");
}
// 2. 查找第一个有效步骤(考虑 condition)
$firstStep = $this->getNextValidStep($steps, $formData);
if (!$firstStep) {
throw new BusinessException("未找到可执行的审批步骤");
}
// 3. 创建审批实例
$instanceId = Db::table('approval_instances')->insertGetId([
'flow_type' => $flowType,
'biz_type' => $bizType,
'biz_id' => $bizId,
'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE),
'initiator_id' => $initiatorId,
'status' => 'processing',
'current_step' => 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
// 4. 创建审批记录(待办)
$approverIds = $this->resolveApprovers($firstStep, (object)['initiator_id' => $initiatorId]);
foreach ($approverIds as $approverId) {
Db::table('approval_records')->insert([
'instance_id' => $instanceId,
'step_name' => $firstStep['name'],
'approver_id' => $approverId,
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s'),
]);
}
// 5. 通知审批人
$this->sendNotification($approverIds, (object)['id' => $instanceId, 'flow_type' => $flowType], 'pending', false);
// 6. 通知抄送人(当前步骤)
$this->notifyCCs($firstStep, (object)['id' => $instanceId, 'flow_type' => $flowType, 'form_data' => json_encode($formData)], 'pending');
return ['instance_id' => $instanceId];
}
/**
* 执行审批
*/
public function approve(int $instanceId, int $approverId, string $action, string $comment = '')
{
$instance = Db::table('approval_instances')->where('id', $instanceId)->first();
if (!$instance) {
throw new BusinessException("审批实例未找到");
}
$steps = json_decode($instance->steps, true) ?: json_decode(Db::table('approval_flows')->where('type', $instance->flow_type)->value('steps'), true);
$currentStep = $steps[$instance->current_step] ?? null;
if (!$currentStep) {
throw new BusinessException("当前步骤配置错误");
}
// 检查用户是否有权限审批
if (!$this->canUserApproveStep($currentStep, $approverId, $instance)) {
throw new BusinessException("您无权审批此步骤");
}
// 更新审批记录
Db::table('approval_records')
->where('instance_id', $instanceId)
->where('approver_id', $approverId)
->where('status', 'pending')
->update([
'status' => $action,
'comment' => $comment,
'approved_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
// 检查当前步骤是否完成(根据 type: any, all, sequential)
$stepCompleted = $this->isStepCompleted($instanceId, $instance->current_step, $currentStep['type']);
if (!$stepCompleted) {
// 步骤未完成,不进入下一步
return ['status' => 'step_pending'];
}
// 步骤已完成,进入下一步或结束
$nextStep = $this->getNextValidStep($steps, json_decode($instance->form_data, true), $instance->current_step + 1);
if ($nextStep === null) {
// 流程结束
Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => $action === 'approve' ? 'approved' : 'rejected',
'updated_at' => date('Y-m-d H:i:s'),
]);
// 通知发起人结果
$this->sendNotification([$instance->initiator_id], $instance, $action, false);
return ['status' => 'completed', 'result' => $action];
}
// 进入下一步
$nextStepIndex = array_search($nextStep, $steps);
Db::table('approval_instances')->where('id', $instanceId)->update([
'current_step' => $nextStepIndex,
'updated_at' => date('Y-m-d H:i:s'),
]);
// 创建下一步的审批记录
$nextApprovers = $this->resolveApprovers($nextStep, $instance);
foreach ($nextApprovers as $approverId) {
Db::table('approval_records')->insert([
'instance_id' => $instanceId,
'step_name' => $nextStep['name'],
'approver_id' => $approverId,
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s'),
]);
}
// 通知下一步审批人
$this->sendNotification($nextApprovers, $instance, 'pending', false);
// 通知下一步抄送人
$this->notifyCCs($nextStep, $instance, 'pending');
return ['status' => 'next_step', 'step_name' => $nextStep['name']];
}
/**
* 解析审批人
*/
private function resolveApprovers(array $step, object $instance): array
{
$resolved = [];
$formData = json_decode($instance->form_data ?? '{}', true);
foreach ($step['approvers'] as $approverConfig) {
$approverIds = [];
switch ($approverConfig['type']) {
case 'role':
$approverIds = $this->getUserIdsByRole($approverConfig['value']);
break;
case 'user':
$approverIds = [(int)$approverConfig['value']];
break;
case 'initiator_leader':
$leaderId = $this->getLeaderIdByUserId($instance->initiator_id);
if ($leaderId) $approverIds = [$leaderId];
break;
case 'field_user':
if (isset($formData[$approverConfig['value']])) {
$approverIds = [(int)$formData[$approverConfig['value']]];
}
break;
case 'dept_head':
$approverIds = $this->getDeptHeadId($approverConfig['value']);
if ($approverIds) $approverIds = [$approverIds];
break;
default:
throw new BusinessException("不支持的审批人类型: {$approverConfig['type']}");
}
$resolved = array_merge($resolved, $approverIds);
}
return array_unique(array_filter($resolved));
}
/**
* 通知抄送人
*/
private function notifyCCs(array $step, object $instance, string $action = 'pending')
{
$ccConfig = $step['cc'] ?? [];
$ccUserIds = [];
$formData = json_decode($instance->form_data ?? '{}', true);
foreach ($ccConfig as $cc) {
switch ($cc['type']) {
case 'role':
$ccUserIds = array_merge($ccUserIds, $this->getUserIdsByRole($cc['value']));
break;
case 'user':
$ccUserIds[] = (int)$cc['value'];
break;
case 'initiator_leader':
$leaderId = $this->getLeaderIdByUserId($instance->initiator_id);
if ($leaderId) $ccUserIds[] = $leaderId;
break;
case 'field_user':
if (isset($formData[$cc['value']])) {
$ccUserIds[] = (int)$formData[$cc['value']];
}
break;
case 'dept_head':
$deptHeadId = $this->getDeptHeadId($cc['value']);
if ($deptHeadId) $ccUserIds[] = $deptHeadId;
break;
default:
break;
}
}
$ccUserIds = array_unique(array_filter($ccUserIds));
if (!empty($ccUserIds)) {
$this->sendNotification($ccUserIds, $instance, $action, true);
}
}
/**
* 评估条件
*/
private function evaluateCondition(?string $condition, array $formData): bool
{
if (!$condition) return true;
$pattern = '/^(\w+)\s*(==|!=|>|<|>=|<=)\s*(.+)$/';
if (!preg_match($pattern, $condition, $matches)) {
throw new BusinessException("条件格式错误: {$condition}");
}
$field = $matches[1];
$op = $matches[2];
$value = trim($matches[3], "\"'");
$actualValue = $formData[$field] ?? null;
if (is_numeric($value) && is_numeric($actualValue)) {
$value = (float)$value;
$actualValue = (float)$actualValue;
}
switch ($op) {
case '==': return $actualValue == $value;
case '!=': return $actualValue != $value;
case '>': return $actualValue > $value;
case '<': $actualValue < $value;
case '>=': return $actualValue >= $value;
case '<=': return $actualValue <= $value;
default: throw new BusinessException("不支持的操作符: {$op}");
}
}
/**
* 获取下一个有效步骤(考虑 condition)
*/
private function getNextValidStep(array $steps, array $formData, int $startIndex = 0): ?array
{
for ($i = $startIndex; $i < count($steps); $i++) {
$step = $steps[$i];
if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) {
return $step;
}
}
return null;
}
/**
* 检查用户是否有权限审批
*/
private function canUserApproveStep(array $step, int $userId, object $instance): bool
{
$approvers = $this->resolveApprovers($step, $instance);
return in_array($userId, $approvers);
}
/**
* 检查当前步骤是否完成
*/
private function isStepCompleted(int $instanceId, int $stepIndex, string $type): bool
{
$pendingCount = Db::table('approval_records')
->where('instance_id', $instanceId)
->where('status', 'pending')
->count();
if ($type === 'any') {
return $pendingCount === 0; // 任一通过即可
}
if ($type === 'all' || $type === 'sequential') {
return $pendingCount === 0; // 所有人都必须审批
}
return false;
}
/**
* 发送通知(你可以替换为你的通知系统)
*/
private function sendNotification(array $userIds, object $instance, string $action, bool $isCC)
{
$actionText = [
'pending' => '待审批',
'approve' => '已通过',
'reject' => '已拒绝'
][$action] ?? $action;
$prefix = $isCC ? '[抄送]' : '';
$message = "{$prefix}您有一条审批 {$actionText}:流程ID {$instance->id},类型 {$instance->flow_type}";
// TODO: 调用你的消息系统,如邮件、站内信、企业微信等
// 示例:MessageService::send($userIds, $message);
echo "通知用户 " . implode(',', $userIds) . ":$message\n";
}
/**
* 获取用户待审批列表
* @param int $userId 用户ID
* @return array
*/
public function getPendingApprovals(int $userId): array
{
// 简化查询:找到用户需要审批的实例ID
// 更复杂的逻辑(如并行会签)需要更精细的查询
$instanceIds = Db::table('approval_records')
->where('approver_id', $userId)
->pluck('instance_id')
->unique()
->toArray();
if (empty($instanceIds)) {
return [];
}
return Db::table('approval_instances')
->whereIn('id', $instanceIds)
->where('status', 'processing')
->get()
->toArray();
}
/**
* 获取审批历史记录
* @param int $instanceId
* @return array
*/
public function getApprovalHistory(int $instanceId): array
{
return Db::table('approval_records')
->where('instance_id', $instanceId)
->orderBy('step_index')
->orderBy('approved_at')
->get()
->toArray();
}
/**
* 获取审批流程图数据 (简化版)
* @param int $instanceId
* @return array
*/
public function getFlowDiagramData(int $instanceId): array
{
$instance = Db::table('approval_instances')->find($instanceId);
if (!$instance) return [];
$flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
if (!$flow) return [];
$steps = json_decode($flow->steps, true);
$records = Db::table('approval_records')
->where('instance_id', $instanceId)
->orderBy('step_index')
->orderBy('approved_at')
->get()
->groupBy('step_index');
$diagramData = [];
foreach ($steps as $index => $step) {
$status = 'pending';
$actions = $records->get($index, collect())->toArray();
if (!empty($actions)) {
$hasReject = collect($actions)->contains('action', 'reject');
if ($hasReject) {
$status = 'rejected';
} else {
if ($step['type'] === 'parallel') {
// 简化处理
$status = 'approved';
} else {
$status = 'approved';
}
}
} elseif ($index < $instance->current_step) {
$status = 'approved';
} elseif ($index == $instance->current_step) {
$status = 'processing';
}
$diagramData[] = [
'index' => $index,
'name' => $step['name'],
'type' => $step['type'],
'status' => $status,
'actions' => $actions
];
}
return $diagramData;
}
/**
* 处理超时的审批实例 (定时任务调用)
*/
public function handleTimeouts()
{
$now = date('Y-m-d H:i:s');
$expiredInstances = Db::table('approval_instances')
->where('status', 'processing')
->where('deadline_at', '<=', $now)
->get();
foreach ($expiredInstances as $instance) {
$flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
if ($flow && $flow->timeout_action) {
$updateData = [
'finished_at' => $now,
'updated_at' => $now
];
if ($flow->timeout_action === 'auto_reject') {
$updateData['status'] = 'rejected';
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
$this->notifyInitiator($instance, 'rejected_timeout');
} elseif ($flow->timeout_action === 'auto_approve') {
$updateData['status'] = 'approved';
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
$this->notifyInitiator($instance, 'approved_timeout');
}
}
}
}
// --- 辅助方法 ---
// private function canUserApproveStep(array $step, int $userId, object $instance): bool
// {
// $approvers = $this->resolveApprovers($step, $instance);
// return in_array($userId, $approvers);
// }
// private function resolveApprovers(array $step, object $instance): array
// {
// $resolved = [];
// foreach ($step['approvers'] as $approver) {
// if ($approver['type'] === 'role') {
// // 示例:根据角色查找用户ID,你需要替换为实际逻辑
// if ($approver['value'] === 'admin') {
// $resolved[] = 1; // 假设 admin 角色的用户ID是1
// } elseif ($approver['value'] === 'manager') {
// $resolved[] = 2; $resolved[] = 3; // 假设有多个manager
// }
// } elseif ($approver['type'] === 'user') {
// $resolved[] = (int)$approver['value'];
// } elseif ($approver['type'] === 'initiator_leader') {
// // 示例:获取发起人的领导
// $resolved[] = 4; // 假设领导ID是4
// }
// }
// return array_unique($resolved);
// }
// private function resolveApprovers(array $step, object $instance): array
// {
// $resolved = [];
// foreach ($step['approvers'] as $approverConfig) {
// $approverIds = [];
// switch ($approverConfig['type']) {
// // 1. 按角色查找
// case 'role':
// $roleCode = $approverConfig['value'];
// // 所有拥有该角色的用户ID
// $approverIds = $this->getUserIdsByRole($roleCode);
// break;
// // 2. 按具体用户ID
// case 'user':
// $approverIds = [(int)$approverConfig['value']];
// break;
// // 3. 按发起人的直属领导 (业务一般是组长)
// case 'initiator_leader':
// // 用户表中 report_to 字段
// $approverIds = $this->getLeaderIdByUserId($instance->initiator_id);
// if ($approverIds) {
// $approverIds = [$approverIds];
// } else {
// $approverIds = []; // 没有领导,跳过
// }
// break;
// // 4. 按业务字段关联的用户 (高级功能)
// case 'field_user':
// $fieldName = $approverConfig['value']; // 如 'project_manager_id'
// $formData = json_decode($instance->form_data, true);
// if (isset($formData[$fieldName])) {
// $approverIds = [(int)$formData[$fieldName]];
// } else {
// $approverIds = [];
// }
// break;
// // 5. 按组织架构层级 (如发起人所在部门的负责人)
// case 'dept_head':
// $deptId = $approverConfig['value']; // 也可以从 instance 或 formData 中获取
// $approverIds = $this->getDeptHeadId($deptId);
// if ($approverIds) {
// $approverIds = [$approverIds];
// } else {
// $approverIds = [];
// }
// break;
// default:
// throw new BusinessException("不支持的审批人类型: {$approverConfig['type']}");
// }
// $resolved = array_merge($resolved, $approverIds);
// }
// return array_unique($resolved);
// }
// 角色层
private function getUserIdsByRole(string $roleCode): array
{
// user_roles 表和 roles 表
return Db::table('user_roles as ur')
->join('roles as r', 'ur.role_id', '=', 'r.id')
->where('r.code', $roleCode) // 假设 roles 表有 code 字段
->pluck('ur.user_id')
->toArray();
}
// 领导层
private function getLeaderIdByUserId(int $userId): ?int
{
// users 表 report_to 字段
return Db::table('user')->where('id', $userId)->value('report_to');
}
// 组织架构层
private function getDeptHeadId(int $deptId): ?int
{
// departments 表 head_user_id 字段(部门逻辑暂时不用,根据后续业务实际情况开发)
return Db::table('departments')->where('id', $deptId)->value('head_user_id');
}
private function calculateNextStepIndex(int $instanceId, int $currentStepIndex, array $currentStep): int
{
// 对于 sequential 和 any,单次 approve 即进入下一步
if ($currentStep['type'] === 'sequential' || $currentStep['type'] === 'any') {
return $currentStepIndex + 1;
} elseif ($currentStep['type'] === 'parallel') {
// 对于并行会签,需要检查是否所有审批人都已 approve
// 这里简化处理,实际需要精确计算
// 假设只要有一次 approve 就算完成(不准确,仅为示例)
// 正确做法是查询所有 approvers 是否都 approve 了
return $currentStepIndex + 1;
}
return $currentStepIndex + 1;
}
private function notifyNextApprovers(int $instanceId)
{
// 实现你的通知逻辑,如发送站内信、邮件、企业微信等
// 可以查询 instance_id 对应的当前步骤审批人
echo "通知下一个审批人 (Instance ID: {$instanceId})\n";
}
private function notifyInitiator(object $instance, string $result)
{
// 通知发起人审批结果
echo "通知发起人 {$instance->initiator_id} 审批结果: {$result}\n";
}
private function notifyAllApprovers(object $instance, string $status)
{
// 通知所有相关审批人(如撤回时)
echo "通知所有审批人,实例 {$instance->id} 状态变为: {$status}\n";
}
private function notifyCc(int $instanceId)
{
// 通知抄送人
echo "通知抄送人 (Instance ID: {$instanceId})\n";
}
}
@@ -244,10 +244,10 @@
<div style="padding-top: 6mm; padding-bottom: 6mm; display: table; margin: 0 0 0 auto;">
<div style="display: table-cell; vertical-align: middle;">
<span>下单人:</span>
<img src="https://mzl.dev.hnxinyouhe.com/01.jpg" style="width: 21mm; height: 12mm; vertical-align: middle;">
<img src="<?php echo htmlspecialchars($data['sign_img_url'] ?? ' - '); ?>" style="width: 21mm; height: 12mm; vertical-align: middle;">
</div>
</div>
<div>下单时间:2025-09-08</div>
<div>下单时间:<?php echo htmlspecialchars($data['created_at'] ?? ' - '); ?></div>
</div>
</div>