Files
x-server-mobile/backend/app/service/BusinessTripService.php
T
root 68e0c261d8 feat: 完善业务审批消息链路
修改内容:
1. TestController - 添加 real 参数支持发送真实业务通知
2. BusinessTripService.sendApprovalNotification() - 传递 order_id

业务流程:
1. 用户创建申请 → 审批人收到带按钮的通知
2. 点击"通过"/"拒绝"按钮 → 跳转到审批详情页
3. 自动弹出确认框 → 完成审批
4. 通知下一节点审批人/申请人

按钮链接:
- 通过/拒绝:/approval/detail/{order_id}?action=approve|reject
- 查看详情:/business-trip/detail/{business_id}

测试命令:
- 测试消息: /api/v1/test/notification?user_id=1&order_id=64
- 真实业务: /api/v1/test/notification?user_id=1&order_id=64&real=1

相关文件:
- backend/app/controller/TestController.php
- backend/app/service/BusinessTripService.php

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:10:51 +08:00

942 lines
38 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\service;
use app\model\BusinessTrip;
use app\model\BusinessTripItinerary;
use app\model\ApprovalOrder;
use app\model\ApprovalFlow;
use app\model\ApprovalNode;
use app\model\ApprovalRecord;
use app\model\User;
use support\Db;
use Illuminate\Database\Connection;
class BusinessTripService
{
private Connection $db;
public function __construct()
{
$this->db = Db::connection();
}
/**
* 创建出差申请(草稿)
*/
public function createDraft(int $userId, array $data): BusinessTrip
{
return $this->db->transaction(function () use ($userId, $data) {
try {
// 计算最早和最晚时间
$itineraries = $data['itineraries'] ?? [];
if (empty($itineraries)) {
throw new \Exception('请至少添加一条行程');
}
$startDate = null;
$endDate = null;
$destinations = [];
foreach ($itineraries as $itinerary) {
if ($itinerary['start_time']) {
if (!$startDate || $itinerary['start_time'] < $startDate) {
$startDate = $itinerary['start_time'];
}
}
if ($itinerary['end_time']) {
if (!$endDate || $itinerary['end_time'] > $endDate) {
$endDate = $itinerary['end_time'];
}
}
if (!empty($itinerary['destination'])) {
$destinations[] = $itinerary['destination'];
}
}
// 创建出差申请
$businessTrip = BusinessTrip::create([
'user_id' => $userId,
'reason' => $data['reason'] ?? '',
'start_date' => $startDate ? date('Y-m-d', strtotime($startDate)) : null,
'end_date' => $endDate ? date('Y-m-d', strtotime($endDate)) : null,
'destination' => implode(', ', array_unique($destinations)),
'estimated_cost' => $data['estimated_cost'] ?? 0,
'status' => BusinessTrip::STATUS_DRAFT,
]);
// 创建行程明细
foreach ($itineraries as $itinerary) {
BusinessTripItinerary::create([
'business_trip_id' => $businessTrip->id,
'start_time' => $itinerary['start_time'],
'end_time' => $itinerary['end_time'] ?? null,
'departure' => $itinerary['departure'] ?? '',
'destination' => $itinerary['destination'] ?? '',
'transport' => $itinerary['transport'] ?? '',
'trip_type' => $itinerary['trip_type'] ?? 'oneway',
'remark' => $itinerary['remark'] ?? '',
]);
}
return $businessTrip->load('itineraries');
} catch (\Exception $e) {
error_log('BusinessTripService::createDraft error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
throw $e;
}
});
}
/**
* 提交出差申请
*/
public function submit(int $userId, int $businessTripId, array $approvers, array $ccUsers = []): BusinessTrip
{
return $this->db->transaction(function () use ($userId, $businessTripId, $approvers, $ccUsers) {
$businessTrip = BusinessTrip::with('itineraries')->findOrFail($businessTripId);
if ($businessTrip->user_id !== $userId) {
throw new \Exception('无权操作此申请');
}
if (!$businessTrip->canSubmit()) {
throw new \Exception('当前状态无法提交');
}
// 验证审批人和抄送人不能重复
// 兼容两种格式:对象数组 [{id:1, name:'xxx'}] 或纯ID数组 [1, 2, 3]
if (!empty($approvers) && isset($approvers[0]['id'])) {
$approverIds = array_column($approvers, 'id');
} else {
$approverIds = $approvers;
}
if (!empty($ccUsers) && isset($ccUsers[0]['id'])) {
$ccUserIds = array_column($ccUsers, 'id');
} else {
$ccUserIds = $ccUsers;
}
// 检查审批人列表中是否有重复
if (count($approverIds) !== count(array_unique($approverIds))) {
throw new \Exception('审批人列表中有重复用户');
}
// 检查抄送人列表中是否有重复
if (count($ccUserIds) !== count(array_unique($ccUserIds))) {
throw new \Exception('抄送人列表中有重复用户');
}
// 检查审批人和抄送人是否有重叠
$overlap = array_intersect($approverIds, $ccUserIds);
if (!empty($overlap)) {
throw new \Exception('审批人和抄送人不能是同一个人');
}
// 获取审批流程
$flow = ApprovalFlow::where('business_type', 'business_trip')
->where('status', 1)
->first();
if (!$flow) {
// 创建默认流程
$flow = ApprovalFlow::create([
'business_type' => 'business_trip',
'name' => '出差审批',
'description' => '出差申请审批流程',
'status' => 1,
]);
}
// approval_node 是流程模板,不应在提交时修改
// 实际的审批人存储在 approval_order.form_data 中
// 获取或创建第一个审批节点(仅用于流程结构)
$node = ApprovalNode::where('flow_id', $flow->id)
->where('order', 0)
->first();
if (!$node) {
$node = ApprovalNode::create([
'flow_id' => $flow->id,
'type' => 'approver',
'name' => '审批',
'order' => 0,
'approvers' => [], // 模板节点不存储具体审批人
]);
}
// 创建审批单
$approvalOrder = ApprovalOrder::create([
'business_type' => 'business_trip',
'business_id' => $businessTrip->id,
'title' => '出差申请 - ' . $businessTrip->user->name,
'applicant_id' => $userId,
'status' => ApprovalOrder::STATUS_PENDING,
'current_nodes' => [$node->id], // 使用创建的节点 ID
'flow_id' => $flow->id,
'form_data' => [
'reason' => $businessTrip->reason,
'start_date' => $businessTrip->start_date,
'end_date' => $businessTrip->end_date,
'destination' => $businessTrip->destination,
'approvers' => $approvers,
'cc_users' => $ccUsers,
],
]);
// 更新出差申请状态
$businessTrip->status = BusinessTrip::STATUS_PENDING;
$businessTrip->approval_order_id = $approvalOrder->id;
$businessTrip->save();
// 记录提交操作
ApprovalRecord::create([
'order_id' => $approvalOrder->id,
'node_id' => $node->id,
'node_type' => $node->type,
'approver_id' => $userId,
'action' => 'submit',
'comment' => null,
]);
// 如果发起人在审批人列表中,自动标记为已审批
if (in_array($userId, $approverIds)) {
ApprovalRecord::create([
'order_id' => $approvalOrder->id,
'node_id' => $node->id,
'node_type' => $node->type,
'approver_id' => $userId,
'action' => 'approve',
'comment' => '发起人自动审批通过',
]);
// 判断节点是否完成
$nodeCompleted = false;
if ($node->type === 'approver') {
// 单人审批:发起人即完成
$nodeCompleted = true;
} elseif ($node->type === 'or_approve') {
// 或签:任一人通过即可
$nodeCompleted = true;
} elseif ($node->type === 'and_approve') {
// 会签:需要所有人通过
$approvedCount = ApprovalRecord::where('order_id', $approvalOrder->id)
->where('node_id', $node->id)
->where('action', 'approve')
->count();
if ($approvedCount >= count($approverIds)) {
$nodeCompleted = true;
}
}
// 如果节点完成,移动到下一节点
if ($nodeCompleted) {
// 获取当前节点的 order 值
$currentOrder = $node->order ?? 0;
// 查找 order 比当前节点大的后续节点
$nextNodes = ApprovalNode::where('flow_id', $flow->id)
->where('order', '>', $currentOrder)
->orderBy('order')
->get();
if ($nextNodes->isEmpty()) {
// 没有后续节点,审批完成
$approvalOrder->status = ApprovalOrder::STATUS_APPROVED;
$approvalOrder->current_nodes = [];
$businessTrip->status = BusinessTrip::STATUS_APPROVED;
} else {
// 检查第一个后续节点
$firstNextNode = $nextNodes->first();
if ($firstNextNode && $firstNextNode->type === 'notify') {
// 下一个是抄送节点,跳过并完成流程
$approvalOrder->status = ApprovalOrder::STATUS_APPROVED;
$approvalOrder->current_nodes = [];
$businessTrip->status = BusinessTrip::STATUS_APPROVED;
} else {
// 移动到下一节点
$approvalOrder->current_nodes = $nextNodes->pluck('id')->toArray();
}
}
$approvalOrder->save();
$businessTrip->save();
}
}
// 发送飞书通知给审批人
error_log("🔔 准备发送审批通知,调用 sendApprovalNotification 方法");
error_log("📋 传递的参数 - approverIds: " . json_encode($approverIds) . ", ccUserIds: " . json_encode($ccUserIds));
$this->sendApprovalNotification($businessTrip, $approverIds, $ccUserIds);
return $businessTrip->fresh(['itineraries', 'approvalOrder']);
});
}
/**
* 发送审批通知(飞书消息)
*/
private function sendApprovalNotification(BusinessTrip $businessTrip, array $approverIds, array $ccUserIds): void
{
try {
error_log("🔔 准备发送审批通知: trip_id={$businessTrip->id}, approvers=" . json_encode($approverIds) . ", cc=" . json_encode($ccUserIds));
$notificationService = new \app\service\FeishuNotificationService();
// 获取 approval_order_id
$approvalOrder = \app\model\ApprovalOrder::where('business_type', 'business_trip')
->where('business_id', $businessTrip->id)
->first();
if (!$approvalOrder) {
error_log("⚠️ 未找到审批单,无法发送通知");
return;
}
// 构建审批链接(使用 approval_order 的 ID)
$approvalUrl = "https://xsm.dev.hnxinyouhe.com/approval/detail/{$approvalOrder->id}";
// 构建业务详情链接(用于查看详情按钮)
$detailUrl = "https://xsm.dev.hnxinyouhe.com/business-trip/detail/{$businessTrip->id}";
// 构建内容
$content = "**出差原因**: {$businessTrip->reason}\n";
$content .= "**目的地**: {$businessTrip->destination}\n";
$content .= "**时间**: {$businessTrip->start_date} 至 {$businessTrip->end_date}";
// 通知审批人(带审批按钮)
foreach ($approverIds as $approverId) {
try {
error_log("📧 通知审批人: approver_id={$approverId}");
$notificationService->setAppByUser($approverId)
->sendApprovalNotification([
'user_id' => $approverId,
'title' => '新的出差审批申请',
'applicant' => $businessTrip->user->name,
'type' => '出差申请',
'content' => $content,
'url' => $detailUrl,
'order_id' => $approvalOrder->id, // 添加 order_id 以显示审批按钮
]);
error_log("✅ 审批人通知成功: approver_id={$approverId}");
} catch (\Exception $e) {
error_log("❌ 发送审批通知失败: approver_id={$approverId}, error=" . $e->getMessage());
}
}
// 通知抄送人(不带审批按钮)
foreach ($ccUserIds as $ccUserId) {
try {
error_log("📧 通知抄送人: cc_user_id={$ccUserId}");
$notificationService->setAppByUser($ccUserId)
->sendSystemNotification([
'user_ids' => [$ccUserId],
'title' => '出差申请抄送',
'content' => "**申请人**: {$businessTrip->user->name}\n" . $content,
'url' => $detailUrl,
]);
error_log("✅ 抄送人通知成功: cc_user_id={$ccUserId}");
} catch (\Exception $e) {
error_log("❌ 发送抄送通知失败: cc_user_id={$ccUserId}, error=" . $e->getMessage());
}
}
error_log("✅ 审批通知发送完成");
} catch (\Exception $e) {
error_log("❌ 发送飞书通知异常: " . $e->getMessage());
}
}
/**
* 发送审批结果通知(飞书消息)
*/
private function sendApprovalResultNotification(BusinessTrip $businessTrip, string $action, string $comment = ''): void
{
try {
$notificationService = new \app\service\FeishuNotificationService();
$detailUrl = "https://xsm.dev.hnxinyouhe.com/business-trip/detail/{$businessTrip->id}";
if ($action === 'approve') {
$title = '出差申请已通过';
$content = "您的出差申请已通过审批。\n";
$content .= "**申请原因**: {$businessTrip->reason}\n";
if ($comment) {
$content .= "**审批意见**: {$comment}";
}
} else {
$title = '出差申请已拒绝';
$content = "您的出差申请被拒绝。\n";
$content .= "**申请原因**: {$businessTrip->reason}\n";
if ($comment) {
$content .= "**拒绝原因**: {$comment}";
}
}
$notificationService->setAppByUser($businessTrip->user_id)
->sendSystemNotification([
'user_ids' => [$businessTrip->user_id],
'title' => $title,
'content' => $content,
'url' => $detailUrl,
]);
} catch (\Exception $e) {
error_log("发送审批结果通知失败: " . $e->getMessage());
}
}
/**
* 获取出差申请列表
*/
public function getList(int $userId, string $type = 'my', int $page = 1, int $pageSize = 20): array
{
// 先不加 approvalOrder 关系,避免 PHP 8.5 兼容性问题
$query = BusinessTrip::with(['user', 'itineraries']);
switch ($type) {
case 'my':
// 我发起的:当前用户创建的申请
$query->where('user_id', $userId);
break;
case 'pending':
// 待我审批:需要查询当前用户是审批人且还未审批的申请
$allPendingOrders = ApprovalOrder::where('status', ApprovalOrder::STATUS_PENDING)
->where('business_type', 'business_trip')
->get();
error_log("🔍 Pending query result: count=" . count($allPendingOrders));
$pendingOrderIds = $allPendingOrders->pluck('id')->toArray();
if (empty($pendingOrderIds)) {
$query->whereRaw('1 = 0'); // 返回空结果
} else {
// 获取这些审批单的当前节点
$pendingOrders = ApprovalOrder::whereIn('id', $pendingOrderIds)->get();
$validOrderIds = [];
foreach ($pendingOrders as $order) {
error_log("🔍 Checking order {$order->id}: form_data=" . json_encode($order->form_data));
// 从 form_data 获取实际的审批人列表
$formData = $order->form_data ?? [];
$approvers = $formData['approvers'] ?? [];
// 兼容两种格式:[1,2,3] 或 [{id:1},{id:2},{id:3}]
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;
}
} else {
$approverIds = [];
}
error_log("🔍 Order {$order->id}: approverIds=" . json_encode($approverIds) . ", userId={$userId}");
// 检查用户是否在审批人列表中
$isApprover = in_array($userId, $approverIds);
error_log("🔍 Order {$order->id}: isApprover=" . ($isApprover ? 'true' : 'false'));
// 如果用户是当前节点审批人,且未审批过,则加入列表
if ($isApprover) {
$hasApproved = ApprovalRecord::where('order_id', $order->id)
->where('approver_id', $userId)
->whereIn('action', ['approve', 'reject'])
->exists();
if (!$hasApproved) {
$validOrderIds[] = $order->id;
}
}
}
if (empty($validOrderIds)) {
$query->whereRaw('1 = 0');
} else {
$query->whereIn('approval_order_id', $validOrderIds);
}
}
break;
case 'approved':
// 我已审批:当前用户已经审批过的申请
$approvedOrderIds = ApprovalRecord::where('approver_id', $userId)
->whereIn('action', ['approve', 'reject'])
->pluck('order_id')
->toArray();
error_log("🔍 'approved' query: userId={$userId}, approvedOrderIds=" . json_encode($approvedOrderIds));
if (empty($approvedOrderIds)) {
$query->whereRaw('1 = 0'); // 返回空结果
} else {
// 先检查是否有匹配的 business_trip(不加载关系)
$checkCount = BusinessTrip::whereIn('approval_order_id', $approvedOrderIds)->count();
error_log("🔍 Matching business_trips count (without relations): {$checkCount}");
// 获取匹配的 business_trip IDs
$matchingTrips = BusinessTrip::whereIn('approval_order_id', $approvedOrderIds)
->pluck('id')
->toArray();
error_log("🔍 Matching business_trip IDs: " . json_encode($matchingTrips));
$query->whereIn('approval_order_id', $approvedOrderIds);
}
break;
}
$total = $query->count();
$list = $query->orderBy('created_at', 'desc')
->offset(($page - 1) * $pageSize)
->limit($pageSize)
->get();
return [
'list' => $list,
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
];
}
/**
* 获取出差申请详情
*/
public function getDetail(int $id): array
{
$businessTrip = BusinessTrip::with(['user', 'itineraries'])
->findOrFail($id);
$result = $businessTrip->toArray();
// 获取审批单信息
if ($businessTrip->approval_order_id) {
$approvalOrder = ApprovalOrder::with(['records.approver'])
->find($businessTrip->approval_order_id);
if ($approvalOrder) {
$result['approval_order'] = $approvalOrder->toArray();
// 获取审批人和抄送人信息
$formData = $approvalOrder->form_data ?? [];
$result['approvers'] = $formData['approvers'] ?? [];
$result['cc_users'] = $formData['cc_users'] ?? [];
// 获取审批记录
$result['approval_records'] = $approvalOrder->records->toArray();
}
}
return $result;
}
/**
* 更新出差申请
*/
public function update(int $userId, int $id, array $data): BusinessTrip
{
$businessTrip = BusinessTrip::findOrFail($id);
if ($businessTrip->user_id !== $userId) {
throw new \Exception('无权操作此申请');
}
if (!$businessTrip->canEdit()) {
throw new \Exception('当前状态无法编辑');
}
return $this->db->transaction(function () use ($businessTrip, $data) {
// 更新基本信息
if (isset($data['reason'])) {
$businessTrip->reason = $data['reason'];
}
// 更新行程
if (isset($data['itineraries'])) {
// 删除旧行程
BusinessTripItinerary::where('business_trip_id', $businessTrip->id)->delete();
// 创建新行程
$startDate = null;
$endDate = null;
$destinations = [];
foreach ($data['itineraries'] as $itinerary) {
BusinessTripItinerary::create([
'business_trip_id' => $businessTrip->id,
'start_time' => $itinerary['start_time'],
'end_time' => $itinerary['end_time'] ?? null,
'departure' => $itinerary['departure'] ?? '',
'destination' => $itinerary['destination'] ?? '',
'transport' => $itinerary['transport'] ?? '',
'trip_type' => $itinerary['trip_type'] ?? 'oneway',
'remark' => $itinerary['remark'] ?? '',
]);
if ($itinerary['start_time']) {
if (!$startDate || $itinerary['start_time'] < $startDate) {
$startDate = $itinerary['start_time'];
}
}
if ($itinerary['end_time']) {
if (!$endDate || $itinerary['end_time'] > $endDate) {
$endDate = $itinerary['end_time'];
}
}
if (!empty($itinerary['destination'])) {
$destinations[] = $itinerary['destination'];
}
}
// 更新日期和目的地
$businessTrip->start_date = $startDate ? date('Y-m-d', strtotime($startDate)) : null;
$businessTrip->end_date = $endDate ? date('Y-m-d', strtotime($endDate)) : null;
$businessTrip->destination = implode(', ', array_unique($destinations));
}
$businessTrip->save();
return $businessTrip->fresh('itineraries');
});
}
/**
* 取消出差申请
*/
public function cancel(int $userId, int $id): BusinessTrip
{
$businessTrip = BusinessTrip::findOrFail($id);
if ($businessTrip->user_id !== $userId) {
throw new \Exception('无权操作此申请');
}
if (!$businessTrip->canCancel()) {
throw new \Exception('当前状态无法取消');
}
$businessTrip->status = BusinessTrip::STATUS_CANCELLED;
$businessTrip->save();
// 同时取消审批单
if ($businessTrip->approval_order_id) {
$approvalOrder = ApprovalOrder::find($businessTrip->approval_order_id);
if ($approvalOrder) {
$approvalOrder->status = ApprovalOrder::STATUS_CANCELLED;
$approvalOrder->current_nodes = [];
$approvalOrder->save();
// 发送取消通知给审批人和抄送人
$this->sendCancelNotification($businessTrip, $approvalOrder);
}
}
return $businessTrip->fresh();
}
/**
* 发送取消通知(飞书消息)
*/
private function sendCancelNotification(BusinessTrip $businessTrip, ApprovalOrder $approvalOrder): void
{
try {
$notificationService = new \app\service\FeishuNotificationService();
$detailUrl = "https://xsm.dev.hnxinyouhe.com/business-trip/detail/{$businessTrip->id}";
// 获取审批人和抄送人
$formData = $approvalOrder->form_data ?? [];
$approvers = $formData['approvers'] ?? [];
$ccUsers = $formData['cc_users'] ?? [];
$content = "**申请人**: {$businessTrip->user->name}\n";
$content .= "**出差原因**: {$businessTrip->reason}\n";
$content .= "**目的地**: {$businessTrip->destination}";
// 合并所有需要通知的用户ID
$allUserIds = array_unique(array_merge(
array_column($approvers, 'id'),
array_column($ccUsers, 'id')
));
foreach ($allUserIds as $notifyUserId) {
try {
$notificationService->setAppByUser($notifyUserId)
->sendSystemNotification([
'user_ids' => [$notifyUserId],
'title' => '出差申请已取消',
'content' => $content,
'url' => $detailUrl,
]);
} catch (\Exception $e) {
error_log("发送取消通知失败: user_id={$notifyUserId}, error=" . $e->getMessage());
}
}
} catch (\Exception $e) {
error_log("发送取消通知异常: " . $e->getMessage());
}
}
/**
* 删除草稿
*/
public function deleteDraft(int $userId, int $id): bool
{
$businessTrip = BusinessTrip::findOrFail($id);
if ($businessTrip->user_id !== $userId) {
throw new \Exception('无权操作此申请');
}
if ($businessTrip->status !== BusinessTrip::STATUS_DRAFT) {
throw new \Exception('只能删除草稿');
}
return $this->db->transaction(function () use ($businessTrip) {
// 删除行程明细
BusinessTripItinerary::where('business_trip_id', $businessTrip->id)->delete();
// 删除申请
return $businessTrip->delete();
});
}
/**
* 审批操作
*/
public function approve(int $userId, int $businessTripId, string $action, string $comment = ''): BusinessTrip
{
error_log("📝 approve() 开始: userId={$userId}, businessTripId={$businessTripId}, action={$action}");
try {
$result = $this->db->transaction(function () use ($userId, $businessTripId, $action, $comment) {
error_log("🔄 Transaction 开始");
$businessTrip = BusinessTrip::with(['itineraries', 'approvalOrder'])->findOrFail($businessTripId);
if (!$businessTrip->approval_order_id) {
throw new \Exception('未找到审批单');
}
$approvalOrder = $businessTrip->approvalOrder;
if ($approvalOrder->status !== ApprovalOrder::STATUS_PENDING) {
throw new \Exception('当前状态无法审批');
}
// 检查用户是否是当前审批人
// 从 form_data 获取实际的审批人列表
$formData = $approvalOrder->form_data ?? [];
$approvers = $formData['approvers'] ?? [];
// 兼容两种格式:[1,2,3] 或 [{id:1},{id:2},{id:3}]
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;
}
} else {
$approverIds = [];
}
if (!in_array($userId, $approverIds)) {
throw new \Exception('您不是当前审批人');
}
// 检查是否已经审批过
$existingRecord = ApprovalRecord::where('order_id', $approvalOrder->id)
->where('approver_id', $userId)
->whereIn('action', ['approve', 'reject'])
->first();
if ($existingRecord) {
throw new \Exception('您已经审批过了');
}
// 获取当前节点ID
$currentNodes = $approvalOrder->current_nodes ?? [];
$currentNodeId = $currentNodes[0] ?? null;
// 记录审批操作
ApprovalRecord::create([
'order_id' => $approvalOrder->id,
'node_id' => $currentNodeId,
'node_type' => 'approver',
'approver_id' => $userId,
'action' => $action,
'comment' => $comment,
]);
// 更新审批单状态
if ($action === 'reject') {
// 拒绝:直接结束流程
error_log("❌ 审批拒绝,结束流程");
$approvalOrder->status = ApprovalOrder::STATUS_REJECTED;
$approvalOrder->current_nodes = [];
$businessTrip->status = BusinessTrip::STATUS_REJECTED;
} else {
error_log("✅ 审批同意,检查节点完成状态");
// 同意:检查当前节点是否完成,并处理流程流转
$nodeCompleted = false;
if ($currentNodeId) {
$node = ApprovalNode::find($currentNodeId);
error_log("🔍 当前节点: id={$currentNodeId}, type=" . ($node ? $node->type : 'null'));
if ($node) {
// 根据节点类型判断是否完成
if ($node->type === 'approver') {
// 单人审批:一人通过即完成
error_log("✅ 单人审批节点,一人通过即完成");
$nodeCompleted = true;
} elseif ($node->type === 'or_approve') {
// 或签:一人通过即完成
error_log("✅ 或签节点,一人通过即完成");
$nodeCompleted = true;
} elseif ($node->type === 'and_approve') {
// 会签:需要所有人都通过
$approvedCount = ApprovalRecord::where('order_id', $approvalOrder->id)
->where('node_id', $currentNodeId)
->where('action', 'approve')
->count();
error_log("🔍 会签节点: approvedCount={$approvedCount}, totalApprovers=" . count($approverIds));
if ($approvedCount >= count($approverIds)) {
$nodeCompleted = true;
}
}
}
}
error_log("🔍 nodeCompleted: " . ($nodeCompleted ? 'true' : 'false'));
if ($nodeCompleted) {
// 当前节点完成,查找下一个节点
error_log("✅ 节点 {$currentNodeId} 已完成,查找下一节点");
// 获取当前节点的 order 值
$currentOrder = $node->order ?? 0;
error_log("📍 当前节点 order: {$currentOrder}");
// 查找 order 比当前节点大的下一个节点
$nextNode = ApprovalNode::where('flow_id', $approvalOrder->flow_id)
->where('order', '>', $currentOrder)
->orderBy('order')
->first();
error_log("🔍 下一节点: " . ($nextNode ? "id={$nextNode->id}, type={$nextNode->type}, name={$nextNode->name}, order={$nextNode->order}" : "null"));
if ($nextNode) {
// 有下一个节点
if ($nextNode->type === 'notify') {
// 下一个是抄送节点,跳过并完成流程
error_log("⏭️ 跳过抄送节点,完成流程");
$approvalOrder->status = ApprovalOrder::STATUS_APPROVED;
$approvalOrder->current_nodes = [];
$businessTrip->status = BusinessTrip::STATUS_APPROVED;
} else {
// 下一个是审批节点,移动到该节点
error_log("➡️ 移动到下一节点: {$nextNode->id}");
$approvalOrder->current_nodes = [$nextNode->id];
}
} else {
// 没有下一个节点,流程完成
error_log("✅ 没有下一节点,流程完成");
$approvalOrder->status = ApprovalOrder::STATUS_APPROVED;
$approvalOrder->current_nodes = [];
$businessTrip->status = BusinessTrip::STATUS_APPROVED;
}
}
}
error_log("💾 准备保存: order_status={$approvalOrder->status}, trip_status={$businessTrip->status}");
$approvalOrder->save();
$businessTrip->save();
error_log("✅ 保存完成: order_id={$approvalOrder->id}, trip_id={$businessTrip->id}");
// 发送审批结果通知
$this->sendApprovalResultNotification($businessTrip, $action, $comment);
error_log("✅ Transaction 成功,返回结果");
return $businessTrip->fresh(['itineraries', 'approvalOrder']);
});
error_log("📝 approve() 完成: result_id=" . $result->id);
return $result;
} catch (\Exception $e) {
error_log("❌ approve() 异常: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
throw $e;
}
}
/**
* 获取默认审批人和抄送人
* 从审批流程配置表中获取
*/
public function getDefaultApprovers(int $userId): array
{
// 获取出差审批流程
$flow = ApprovalFlow::where('business_type', 'business_trip')
->where('status', 1)
->first();
if (!$flow) {
return [
'approvers' => [],
'cc_users' => [],
];
}
// 获取流程节点
$nodes = ApprovalNode::where('flow_id', $flow->id)
->orderBy('order')
->get();
$approvers = [];
$ccUsers = [];
foreach ($nodes as $node) {
$nodeApprovers = $node->approvers ?? [];
if (empty($nodeApprovers)) {
continue;
}
// 根据节点类型获取用户信息
$users = User::whereIn('id', $nodeApprovers)
->where('status', 1)
->get();
$userList = [];
foreach ($nodeApprovers as $userId) {
$user = $users->firstWhere('id', $userId);
if ($user) {
$userList[] = [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
];
}
}
// 审批节点(approver, and_approve, or_approve)作为审批人
if ($node->isApprovalNode()) {
$approvers = array_merge($approvers, $userList);
}
// 通知节点(notify)作为抄送人
elseif ($node->type === ApprovalNode::TYPE_NOTIFY) {
$ccUsers = array_merge($ccUsers, $userList);
}
}
return [
'approvers' => $approvers,
'cc_users' => $ccUsers,
];
}
}