Files
x-server-mobile/backend/app/service/ApprovalService.php
T

526 lines
19 KiB
PHP
Raw Normal View History

<?php
namespace app\service;
use app\model\ApprovalFlow;
use app\model\ApprovalNode;
use app\model\ApprovalOrder;
use app\model\ApprovalRecord;
use app\model\User;
use support\Db;
use Illuminate\Database\Connection;
class ApprovalService
{
private Connection $db;
2026-02-26 18:49:25 +08:00
private ?FeishuNotificationService $notificationService = null;
public function __construct()
{
$this->db = Db::connection();
}
/**
* 提交审批
*/
public function submit(array $data): ApprovalOrder
{
return $this->db->transaction(function () use ($data) {
// 1. 获取审批流程
$flow = ApprovalFlow::active()
->byBusinessType($data['business_type'])
->with('nodes')
->findOrFail($data['flow_id']);
// 2. 创建审批单
$order = ApprovalOrder::create([
'business_type' => $data['business_type'],
'business_id' => $data['business_id'],
'title' => $data['title'],
'applicant_id' => $data['applicant_id'],
'status' => ApprovalOrder::STATUS_PENDING,
'current_nodes' => $this->getFirstNodeIds($flow),
'flow_id' => $flow->id,
'form_data' => $data['form_data'] ?? [],
'attachments' => $data['attachments'] ?? [],
]);
// 3. 记录提交操作
ApprovalRecord::create([
'order_id' => $order->id,
'node_id' => $flow->nodes->first()->id ?? 0,
'node_type' => 'submit',
'approver_id' => $data['applicant_id'],
'action' => ApprovalRecord::ACTION_SUBMIT,
'comment' => $data['comment'] ?? null,
]);
return $order->load(['flow', 'applicant']);
});
}
/**
* 审批通过
*/
public function approve(int $orderId, int $approverId, ?string $comment = null): ApprovalOrder
{
return $this->db->transaction(function () use ($orderId, $approverId, $comment) {
$order = ApprovalOrder::with(['flow.nodes'])->findOrFail($orderId);
if (!$order->canApprove()) {
throw new \Exception('当前状态无法审批');
}
$currentNodeIds = $order->current_nodes ?? [];
error_log("🔍 当前节点 IDs: " . json_encode($currentNodeIds));
$currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first();
if (!$currentNode) {
error_log("❌ 未找到节点,IDs: " . json_encode($currentNodeIds));
throw new \Exception('当前节点不存在');
}
// 记录审批操作
ApprovalRecord::create([
'order_id' => $order->id,
'node_id' => $currentNode->id,
'node_type' => $currentNode->type,
'approver_id' => $approverId,
'action' => ApprovalRecord::ACTION_APPROVE,
'comment' => $comment,
]);
// 判断节点是否完成
if ($this->isNodeCompleted($order->id, $currentNode->id)) {
// 移动到下一节点
$nextNodes = $this->getNextNodes($order->flow, $currentNode->order);
if (empty($nextNodes)) {
// 没有下一节点,审批完成
$order->status = ApprovalOrder::STATUS_APPROVED;
$order->current_nodes = [];
2026-02-26 18:49:25 +08:00
// 通知申请人审批通过
$this->notifyApplicant($order, 'approve');
} else {
// 检查第一个下一节点
$firstNextNode = $nextNodes[0] ?? null;
if ($firstNextNode && $firstNextNode['type'] === 'notify') {
// 下一个是抄送节点,跳过并完成流程
$order->status = ApprovalOrder::STATUS_APPROVED;
$order->current_nodes = [];
2026-02-26 18:49:25 +08:00
// 通知申请人审批通过
$this->notifyApplicant($order, 'approve');
} else {
// 下一个是审批节点,移动到该节点
$order->current_nodes = collect($nextNodes)->pluck('id')->toArray();
2026-02-26 18:49:25 +08:00
// 通知下一节点的审批人
$this->notifyNextApprovers($order, $nextNodes);
}
}
$order->save();
}
return $order->fresh(['flow', 'applicant', 'records']);
});
}
/**
* 审批拒绝
*/
public function reject(int $orderId, int $approverId, ?string $comment = null): ApprovalOrder
{
return $this->db->transaction(function () use ($orderId, $approverId, $comment) {
$order = ApprovalOrder::findOrFail($orderId);
if (!$order->canApprove()) {
throw new \Exception('当前状态无法审批');
}
$currentNodeIds = $order->current_nodes ?? [];
$currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first();
// 记录拒绝操作
ApprovalRecord::create([
'order_id' => $order->id,
'node_id' => $currentNode->id ?? 0,
'node_type' => $currentNode->type ?? 'unknown',
'approver_id' => $approverId,
'action' => ApprovalRecord::ACTION_REJECT,
'comment' => $comment,
]);
// 更新状态为已拒绝
$order->status = ApprovalOrder::STATUS_REJECTED;
$order->current_nodes = [];
$order->save();
2026-02-26 18:49:25 +08:00
// 通知申请人审批被拒绝
$this->notifyApplicant($order, 'reject', $comment);
return $order->fresh(['flow', 'applicant', 'records']);
});
}
/**
* 撤销申请
*/
public function cancel(int $orderId, int $userId): ApprovalOrder
{
$order = ApprovalOrder::findOrFail($orderId);
if ($order->applicant_id !== $userId) {
throw new \Exception('只有申请人可以撤销');
}
if (!$order->canEdit() && !$order->canApprove()) {
throw new \Exception('当前状态无法撤销');
}
// 记录撤销操作
ApprovalRecord::create([
'order_id' => $order->id,
'node_id' => 0,
'node_type' => 'cancel',
'approver_id' => $userId,
'action' => ApprovalRecord::ACTION_CANCEL,
'comment' => '申请人撤销',
]);
$order->status = ApprovalOrder::STATUS_CANCELLED;
$order->current_nodes = [];
$order->save();
return $order->fresh(['flow', 'applicant', 'records']);
}
/**
* 获取审批列表
*/
public function getList(int $userId, string $type = 'my', int $page = 1, int $pageSize = 20): array
{
$query = ApprovalOrder::with(['flow', 'applicant']);
switch ($type) {
case 'my':
$query->where('applicant_id', $userId);
error_log("🔍 Debug 'my' query: userId={$userId}");
break;
case 'pending':
// 获取用户待审批的订单
$pendingOrderIds = $this->getPendingOrderIds($userId);
error_log("🔍 Debug 'pending' query: userId={$userId}, pendingOrderIds=" . json_encode($pendingOrderIds));
$query->whereIn('id', $pendingOrderIds);
break;
case 'approved':
2026-02-25 23:54:38 +08:00
// 获取用户已审批的订单(只显示已完成的:approved、rejected、cancelled)
$approvedOrderIds = ApprovalRecord::where('approver_id', $userId)
->where('action', '!=', ApprovalRecord::ACTION_SUBMIT)
2026-02-25 23:54:38 +08:00
->whereIn('action', [ApprovalRecord::ACTION_APPROVE, ApprovalRecord::ACTION_REJECT])
->pluck('order_id')
->unique()
->toArray();
error_log("🔍 Debug 'approved' query: userId={$userId}, approvedOrderIds=" . json_encode($approvedOrderIds));
2026-02-25 23:54:38 +08:00
$query->whereIn('id', $approvedOrderIds)
->whereIn('status', [ApprovalOrder::STATUS_APPROVED, ApprovalOrder::STATUS_REJECTED, ApprovalOrder::STATUS_CANCELLED]);
break;
case 'cc':
// 获取抄送给我的订单
$allOrders = ApprovalOrder::where('status', '!=', ApprovalOrder::STATUS_DRAFT)->get();
$ccOrderIds = [];
foreach ($allOrders as $order) {
$formData = $order->form_data ?? [];
$ccUsers = $formData['cc_users'] ?? [];
// 兼容两种格式:[{id:29, name:'...'}] 或 [29] 或 [{id:29}]
if (!empty($ccUsers)) {
if (is_array($ccUsers[0] ?? null) && isset($ccUsers[0]['id'])) {
// 对象数组格式: [{id:29, name:'...'}]
$ccUserIds = array_column($ccUsers, 'id');
} else if (is_numeric($ccUsers[0] ?? null)) {
// ID数组格式: [29, 30]
$ccUserIds = $ccUsers;
} else {
$ccUserIds = [];
}
if (in_array($userId, $ccUserIds)) {
$ccOrderIds[] = $order->id;
}
}
}
error_log("🔍 Debug 'cc' query: userId={$userId}, ccOrderIds=" . json_encode($ccOrderIds));
$query->whereIn('id', $ccOrderIds);
break;
}
$total = $query->count();
error_log("🔍 Debug total count: type={$type}, total={$total}");
$list = $query->orderBy('created_at', 'desc')
->offset(($page - 1) * $pageSize)
->limit($pageSize)
->get();
error_log("🔍 Debug list count: type={$type}, page={$page}, count=" . $list->count());
// 转换为数组避免序列化问题
$listArray = $list->map(function ($item) {
return [
'id' => $item->id,
'title' => $item->title,
'business_type' => $item->business_type,
'status' => $item->status,
'created_at' => $item->created_at ? $item->created_at->toDateTimeString() : null,
'applicant' => $item->applicant ? [
'id' => $item->applicant->id,
'name' => $item->applicant->name,
] : null,
'flow' => $item->flow ? [
'id' => $item->flow->id,
'name' => $item->flow->name,
] : null,
];
})->toArray();
return [
'list' => $listArray,
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
];
}
/**
* 获取第一个节点ID列表
*/
private function getFirstNodeIds(ApprovalFlow $flow): array
{
return $flow->nodes
->where('order', 0)
->pluck('id')
->toArray();
}
/**
* 判断节点是否完成
*/
private function isNodeCompleted(int $orderId, int $nodeId): bool
{
$node = ApprovalNode::findOrFail($nodeId);
$records = ApprovalRecord::where('order_id', $orderId)
->where('node_id', $nodeId)
2026-02-25 18:26:22 +08:00
->where('action', 'approve')
->get();
// 从 form_data 获取审批人列表
$order = ApprovalOrder::find($orderId);
$formData = $order->form_data ?? [];
$approvers = $formData['approvers'] ?? [];
// 兼容两种格式:[{id:1, name:'...'}] 或 [1]
if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
$approverIds = array_column($approvers, 'id');
} else {
$approverIds = $approvers;
}
if ($node->type === ApprovalNode::TYPE_AND_APPROVE) {
// 会签:需要所有人通过
return count($records) >= count($approverIds);
} elseif ($node->type === ApprovalNode::TYPE_OR_APPROVE) {
// 或签:任一人通过即可
return count($records) >= 1;
} else {
// 单人审批:需要一人通过
return count($records) >= 1;
}
}
/**
* 获取下一节点
*/
private function getNextNodes(ApprovalFlow $flow, int $currentOrder): array
{
return $flow->nodes
->where('order', '>', $currentOrder)
->sortBy('order')
->values()
->toArray();
}
/**
* 获取用户待审批的订单ID列表
*/
private function getPendingOrderIds(int $userId): array
{
// 获取用户参与的待审批订单
$pendingOrders = ApprovalOrder::where('status', ApprovalOrder::STATUS_PENDING)
->whereNotNull('current_nodes')
->get();
$result = [];
foreach ($pendingOrders as $order) {
// 从 form_data 获取审批人列表
$formData = $order->form_data ?? [];
$approvers = $formData['approvers'] ?? [];
// 兼容两种格式:[{id:1, name:'...'}] 或 [1]
if (!empty($approvers)) {
if (is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
// 对象数组格式: [{id:1, name:'...'}]
$approverIds = array_column($approvers, 'id');
} else {
// ID数组格式: [1, 2, 3]
$approverIds = $approvers;
}
if (in_array($userId, $approverIds)) {
// 检查是否已审批
$hasApproved = ApprovalRecord::where('order_id', $order->id)
->where('approver_id', $userId)
->whereIn('action', [ApprovalRecord::ACTION_APPROVE, ApprovalRecord::ACTION_REJECT])
->exists();
if (!$hasApproved) {
$result[] = $order->id;
}
}
}
}
return $result;
}
2026-02-26 18:49:25 +08:00
/**
* 通知申请人
*/
private function notifyApplicant(ApprovalOrder $order, string $action, ?string $comment = null): void
{
try {
if (!$this->notificationService) {
$this->notificationService = new FeishuNotificationService();
}
$applicant = User::find($order->applicant_id);
if (!$applicant) {
error_log("❌ notifyApplicant: 申请人不存在, applicant_id={$order->applicant_id}");
return;
}
// 根据业务类型获取详情链接
$detailUrl = $this->getDetailUrl($order);
// 构建消息内容
$title = $action === 'approve' ? '您的申请已通过' : '您的申请已被拒绝';
$content = "**申请标题**: {$order->title}\n";
$content .= "**申请类型**: {$this->getBusinessTypeName($order->business_type)}\n";
if ($comment) {
$content .= "**审批意见**: {$comment}\n";
}
error_log("🔔 通知申请人: applicant_id={$applicant->id}, action={$action}");
$this->notificationService->setAppByUser($applicant->id)
->sendSystemNotification([
'user_ids' => [$applicant->id],
'title' => $title,
'content' => $content,
'url' => $detailUrl,
]);
} catch (\Exception $e) {
error_log('❌ notifyApplicant 失败: ' . $e->getMessage());
}
}
/**
* 通知下一节点的审批人
*/
private function notifyNextApprovers(ApprovalOrder $order, array $nextNodes): void
{
try {
if (!$this->notificationService) {
$this->notificationService = new FeishuNotificationService();
}
// 从 form_data 获取审批人列表
$formData = $order->form_data ?? [];
$approvers = $formData['approvers'] ?? [];
// 兼容两种格式
if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
$approverIds = array_column($approvers, 'id');
} else {
$approverIds = $approvers;
}
if (empty($approverIds)) {
error_log("⚠️ notifyNextApprovers: 没有找到审批人, order_id={$order->id}");
return;
}
// 获取详情链接
$detailUrl = $this->getDetailUrl($order);
// 业务类型名称
$businessTypeName = $this->getBusinessTypeName($order->business_type);
error_log("🔔 通知下一节点审批人: order_id={$order->id}, approverIds=" . json_encode($approverIds));
// 为每个审批人发送通知
foreach ($approverIds as $approverId) {
try {
$this->notificationService->setAppByUser($approverId)
->sendApprovalNotification([
'user_id' => $approverId,
'title' => '新的审批待办',
'applicant' => $order->applicant->name,
'type' => $businessTypeName,
'content' => "**申请标题**: {$order->title}\n**申请人**: {$order->applicant->name}",
'url' => $detailUrl,
2026-02-26 18:57:59 +08:00
'order_id' => $order->id,
2026-02-26 18:49:25 +08:00
]);
error_log("✅ 审批人通知成功: approver_id={$approverId}");
} catch (\Exception $e) {
error_log("❌ 发送审批通知失败: approver_id={$approverId}, error=" . $e->getMessage());
}
}
} catch (\Exception $e) {
error_log('❌ notifyNextApprovers 失败: ' . $e->getMessage());
}
}
/**
* 获取详情链接
*/
private function getDetailUrl(ApprovalOrder $order): string
{
2026-02-27 22:12:46 +08:00
$baseUrl = config('feishu_notification.base_url') ?? request()->scheme() . '://' . request()->host();
2026-02-26 18:49:25 +08:00
// 统一使用审批详情页,不再跳转到具体业务模块的详情页
return "{$baseUrl}/approval/detail/{$order->id}";
2026-02-26 18:49:25 +08:00
}
/**
* 获取业务类型名称
*/
private function getBusinessTypeName(string $businessType): string
{
$typeNames = [
'business_trip' => '出差申请',
'expense' => '费用报销',
'leave' => '请假申请',
'purchase' => '采购申请',
];
return $typeNames[$businessType] ?? $businessType;
}
}