Files
x-server-mobile/backend/app/service/BusinessTripService.php
T
root 09b2902d2b fix: 修复审批详情页行程时间不显示问题
从模型的 start_time 和 end_time 字段读取数据,而不是错误的 start_date 和 end_date。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:43:55 +08:00

974 lines
40 KiB
PHP

<?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) {
// 处理空字符串和空值
$startTime = !empty($itinerary['start_time']) ? $itinerary['start_time'] : null;
$endTime = !empty($itinerary['end_time']) ? $itinerary['end_time'] : null;
BusinessTripItinerary::create([
'business_trip_id' => $businessTrip->id,
'start_time' => $startTime,
'end_time' => $endTime,
'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' => [], // 模板节点不存储具体审批人
]);
}
// 创建审批单
$itinerariesData = $businessTrip->itineraries->map(function ($item) {
return [
'destination' => $item->destination,
'start_date' => $item->start_time,
'end_date' => $item->end_time,
'estimated_cost' => $item->estimated_cost ?? 0,
];
})->toArray();
error_log("🔍 itineraries 数据: " . json_encode($itinerariesData));
$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,
'estimated_cost' => $businessTrip->estimated_cost,
'itineraries' => $itinerariesData,
'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, $node);
return $businessTrip->fresh(['itineraries', 'approvalOrder']);
});
}
/**
* 发送审批通知(飞书消息)
*/
private function sendApprovalNotification(BusinessTrip $businessTrip, array $approverIds, array $ccUserIds, ApprovalNode $node): 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;
}
// 构建审批链接(统一使用审批详情页)
$approvalUrl = "https://uniservice-mobile.hnxinyouhe.com/approval/detail/{$approvalOrder->id}";
$detailUrl = $approvalUrl; // 用于抄送人和其他通知
// 构建内容
$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,
'applicant_id' => $approvalOrder->applicant_id, // 添加申请人ID用于自审批检测
'title' => '新的出差审批申请',
'applicant' => $businessTrip->user->name,
'type' => '出差申请',
'content' => $content,
'url' => $approvalUrl,
'order_id' => $approvalOrder->id,
'node_id' => $node->id, // 添加节点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();
// 获取关联的审批订单
$approvalOrder = \app\model\ApprovalOrder::where('business_id', $businessTrip->id)
->where('business_type', 'business_trip')
->first();
// 统一使用审批详情页
$detailUrl = $approvalOrder
? "https://uniservice-mobile.hnxinyouhe.com/approval/detail/{$approvalOrder->id}"
: "https://uniservice-mobile.hnxinyouhe.com/approval/list";
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) {
// 处理空字符串和空值
$startTime = !empty($itinerary['start_time']) ? $itinerary['start_time'] : null;
$endTime = !empty($itinerary['end_time']) ? $itinerary['end_time'] : null;
BusinessTripItinerary::create([
'business_trip_id' => $businessTrip->id,
'start_time' => $startTime,
'end_time' => $endTime,
'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://uniservice-mobile.hnxinyouhe.com/approval/detail/{$approvalOrder->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,
];
}
}