diff --git a/app/admin/controller/UserController.php b/app/admin/controller/UserController.php index 25f2385..8c24c6c 100644 --- a/app/admin/controller/UserController.php +++ b/app/admin/controller/UserController.php @@ -154,4 +154,16 @@ class UserController extends BaseController return $this->error($e->getMessage()); } } + + + public function updateSign(Request $request) + { + try { + $data = $request->post(); + $this->service->serviceUpdateSign($request, $data); + return $this->success([], '签名成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/admin/service/SysDictService.php b/app/admin/service/SysDictService.php index 859628c..2d29b21 100644 --- a/app/admin/service/SysDictService.php +++ b/app/admin/service/SysDictService.php @@ -80,8 +80,17 @@ class SysDictService break; case 'scope': $config['order']['id'] = 'desc'; + // 决定查id还是code + $scopeType = 0; + if (isset($p['scope_type']) && $p['scope_type'] > 0) { + $scopeType = 1; + } if (isset($p['scope']) && !empty($p['scope'])) { - $config['where']['id'] = ['in', explode(',', $p['scope'])]; + if ($scopeType == 1) { + $config['where']['dict_code'] = ['in', explode(',', $p['scope'])]; + } else { + $config['where']['id'] = ['in', explode(',', $p['scope'])]; + } } $config['with']['sysDictItem'][] = 'dict_id'; $config['with']['sysDictItem'][] = 'item_label'; @@ -95,7 +104,7 @@ class SysDictService $config['with']['sysDictItem'][] = 'item_value'; break; } - + return $config; } diff --git a/app/admin/service/UserService.php b/app/admin/service/UserService.php index 6675a89..4484552 100644 --- a/app/admin/service/UserService.php +++ b/app/admin/service/UserService.php @@ -113,6 +113,7 @@ class UserService $config['where']['id'] = $p['id']; break; case 'edit': + case 'updateSign': $config['where']['id'] = $request->user->uid; break; } @@ -154,4 +155,13 @@ class UserService $user = $this->logicUpdateBy($config,$data); return $user; } + + + + public function serviceUpdateSign(Request $request, array $data) + { + $config = $this->buildQueryConfig($request, 'updateSign'); + $user = $this->logicUpdateBy($config,$data); + return $user; + } } \ No newline at end of file diff --git a/app/common/model/StockOrderItems.php b/app/common/model/StockOrderItems.php index da57006..f45c532 100644 --- a/app/common/model/StockOrderItems.php +++ b/app/common/model/StockOrderItems.php @@ -44,7 +44,8 @@ class StockOrderItems extends Model protected $fillable = [ 'id', 'order_id', 'product_code', 'sku_id', 'sku_snapshot', 'model_snapshot', 'quantity', 'color_interior_code', 'color_interior_snapshot', - 'color_exterior_code', 'color_exterior_snapshot', 'remark', 'vin', 'created_at', 'updated_at' + 'color_exterior_code', 'color_exterior_snapshot', 'remark', 'vin', 'created_at', 'updated_at', + 'mileage', 'unique_id' ]; /** diff --git a/app/common/model/StockOrders.php b/app/common/model/StockOrders.php index 2c4c8d3..a52c791 100644 --- a/app/common/model/StockOrders.php +++ b/app/common/model/StockOrders.php @@ -87,6 +87,6 @@ class StockOrders extends Model */ public function moneyValues() { - return $this->hasMany(VehMoneyValue::class, 'object_id', 'id')->where('object_scene', 'stock_orders'); + return $this->hasOne(VehMoneyValue::class, 'object_id', 'id')->where('object_scene', 'stock_orders'); } } \ No newline at end of file diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index 17e9fbd..402937c 100644 --- a/app/common/service/StockOrdersService.php +++ b/app/common/service/StockOrdersService.php @@ -103,20 +103,65 @@ class StockOrdersService // $profit = 0; foreach ($data['veh_list'] as $veh) { $veh['order_id'] = $created->id; + + // 唯一标识 + $uniqueIdStr = ""; + $uniqueIdStr .= $veh['sku_id']; + $uniqueIdStr .= $veh['model_snapshot']; + $uniqueIdStr .= $veh['color_interior_code']; + $uniqueIdStr .= $veh['color_exterior_code']; + $uniqueIdStr .= $veh['mileage']; + $uniqueIdStr .= json($veh['modify_list']); + $veh['unique_id'] = md5($uniqueIdStr); $item = StockOrderItems::create($veh); // 表2 // 金额 // 表3 - $item->addMoney('official_price', $veh['official_price']['currency'], $veh['official_price']['amount'], $veh['official_price']['rate'], '官方指导价'); - $item->addMoney('discount_price', $veh['discount_price']['currency'], $veh['discount_price']['amount'], $veh['discount_price']['rate'], '优惠价'); - $item->addMoney('unit_price', $veh['unit_price']['currency'], $veh['unit_price']['amount'], $veh['unit_price']['rate'], '单价'); - $item->addMoney('total_price', $veh['total_price']['currency'], $veh['total_price']['amount'], $veh['total_price']['rate'], '总价'); - $item->addMoney('handling_fee', $veh['handling_fee']['currency'], $veh['handling_fee']['amount'], $veh['handling_fee']['rate'], '手续费'); - $item->addMoney('fob_transport_fee', $veh['fob_transport_fee']['currency'], $veh['fob_transport_fee']['amount'], $veh['fob_transport_fee']['rate'], 'FOB运输费'); - $item->addMoney('port_misc_fee', $veh['port_misc_fee']['currency'], $veh['port_misc_fee']['amount'], $veh['port_misc_fee']['rate'], '港杂'); - $item->addMoney('retrofit_fee', $veh['retrofit_fee']['currency'], $veh['retrofit_fee']['amount'], $veh['retrofit_fee']['rate'], '改装费'); + // $item->addMoney('official_price', $veh['official_price']['currency'], $veh['official_price']['amount'], $veh['official_price']['rate'], '官方指导价'); + // $item->addMoney('discount_price', $veh['discount_price']['currency'], $veh['discount_price']['amount'], $veh['discount_price']['rate'], '优惠价'); + if ($veh['unit_price']['amount']) { + $item->addMoney('unit_price', $veh['unit_price']['currency'], $veh['unit_price']['amount'], $veh['unit_price']['rate'], '单价'); + } + if ($veh['total_price']['amount']) { + $item->addMoney('total_price', $veh['total_price']['currency'], $veh['total_price']['amount'], $veh['total_price']['rate'], '总价'); + } + if ($veh['handling_fee']['amount']) { + $item->addMoney('handling_fee', $veh['handling_fee']['currency'], $veh['handling_fee']['amount'], $veh['handling_fee']['rate'], '手续费'); + } + if ($veh['fob_transport_fee']['amount']) { + $item->addMoney('fob_transport_fee', $veh['fob_transport_fee']['currency'], $veh['fob_transport_fee']['amount'], $veh['fob_transport_fee']['rate'], 'FOB运输费'); + } + if ($veh['port_misc_fee']['amount']) { + $item->addMoney('port_misc_fee', $veh['port_misc_fee']['currency'], $veh['port_misc_fee']['amount'], $veh['port_misc_fee']['rate'], '港杂'); + } + if ($veh['retrofit_fee']['amount']) { + $item->addMoney('retrofit_fee', $veh['retrofit_fee']['currency'], $veh['retrofit_fee']['amount'], $veh['retrofit_fee']['rate'], '改装费'); + } + + // 二手车 or 新车 + if ($veh['mileage'] > 0) { + if ($veh['listing_price']['amount']) { + $item->addMoney('listing_price', $veh['listing_price']['currency'], $veh['listing_price']['amount'], $veh['listing_price']['rate'], '二手挂牌价'); + } + if ($veh['reduction_price']['amount']) { + $item->addMoney('reduction_price', $veh['reduction_price']['currency'], $veh['reduction_price']['amount'], $veh['reduction_price']['rate'], '二手减价'); + } + + $ttPrice = ($veh['listing_price']['amount'] / $created->contract_exchange_rate); + $ddPrice = $veh['reduction_price']['amount']; + } else { + if ($veh['official_price']['amount']) { + $item->addMoney('official_price', $veh['official_price']['currency'], $veh['official_price']['amount'], $veh['official_price']['rate'], '官方指导价'); + } + if ($veh['discount_price']['amount'] ) { + $item->addMoney('discount_price', $veh['discount_price']['currency'], $veh['discount_price']['amount'], $veh['discount_price']['rate'], '优惠价'); + } + + $ttPrice = ($veh['official_price']['amount'] / $created->contract_exchange_rate); + $ddPrice = $veh['discount_price']['amount']; + } // 利润 - $profit = $veh['unit_price']['amount'] - ($veh['official_price']['amount'] / $created->contract_exchange_rate) - $veh['discount_price']['amount'] - $veh['handling_fee']['amount'] - $veh['fob_transport_fee']['amount'] - $veh['port_misc_fee']['amount'] - $veh['retrofit_fee']['amount']; + $profit = (float)$veh['unit_price']['amount'] - (float)$ttPrice - (float)$ddPrice - (float)$veh['handling_fee']['amount'] - (float)$veh['fob_transport_fee']['amount'] - (float)$veh['port_misc_fee']['amount'] - (float)$veh['retrofit_fee']['amount']; $item->addMoney('profit', $veh['unit_price']['currency'], $profit, $veh['unit_price']['rate'], '利润'); // cif_total_amount @@ -193,8 +238,8 @@ class StockOrdersService $pdfBin = $driver->htmlToPdf($html); // 存储 - $dir = public_path('pdf'); - $fileName = '/'.($pdfData['order_no'] ?? date('YmdHis')) . '.pdf'; + $dir = public_path('pdf').'/'; + $fileName = ($pdfData['order_no'] ?? date('YmdHis')) . '.pdf'; // 本地保存 $filePath = \app\common\utils\pdf\service\PdfStorage::saveBinary( @@ -209,7 +254,11 @@ class StockOrdersService // 'Content-Disposition' => 'inline; filename="stock-order.pdf"', // ], $pdfBin); - return ['file_url' => $dir.$fileName]; + $url = [ + 'file_name' => $fileName, + 'file_url' => getenv('MAIN_DOMAIN').'/pdf/'.$fileName + ]; + return $url; } @@ -219,10 +268,10 @@ class StockOrdersService $index = 0; foreach ($item->items as $k => $v) { // 存在则数量加1 - if (count($vehList) > 0 && isset($vehList[$v->sku_id])) { - $vehList[$v->sku_id]['num'] += 1; - $vehList[$v->sku_id]['total_price'] += $vehList[$v->sku_id]['unit_price']; - $vehList[$v->sku_id]['total_price_text'] = $vehList[$v->sku_id]['total_price_symbol'].number_format($vehList[$v->sku_id]['total_price'], 2, '.', ','); + if (count($vehList) > 0 && isset($vehList[$v->unique_id])) { + $vehList[$v->unique_id]['num'] += 1; + $vehList[$v->unique_id]['total_price'] += $vehList[$v->unique_id]['unit_price']; + $vehList[$v->unique_id]['total_price_text'] = $vehList[$v->unique_id]['total_price_symbol'].number_format($vehList[$v->unique_id]['total_price'], 2, '.', ','); continue; } $index += 1; @@ -232,7 +281,7 @@ class StockOrdersService $mv[$money->field] = [ 'currency' => $money->currency, 'amount' => $money->amount, - 'rate' => $money->rate, + 'rate' => $money->exchange_rate, 'currency_symbol' => SysCurrency::where('currency_code', $money->currency)->value('currency_symbol'), ]; } @@ -243,29 +292,42 @@ class StockOrdersService } // 处理结果集 - $vehList[$v->sku_id] = [ + $vehList[$v->unique_id] = [ 'index' => $index, 'model_snapshot' => $v->model_snapshot, 'color_interior_snapshot' => $v->color_interior_snapshot, 'color_exterior_snapshot' => $v->color_exterior_snapshot, 'num' => 1, 'modify' => $mfy, - + 'mileage' => $v->mileage, 'unit_price' => $mv['unit_price']['amount'], 'total_price' => $mv['total_price']['amount'], - 'official_price' => $mv['official_price']['amount'], - 'discount_price' => $mv['discount_price']['amount'], - 'unit_price_symbol' => $mv['unit_price']['currency_symbol'], 'total_price_symbol' => $mv['total_price']['currency_symbol'], - 'official_price_symbol' => $mv['official_price']['currency_symbol'], - 'discount_price_symbol' => $mv['discount_price']['currency_symbol'], - 'unit_price_text' => $mv['unit_price']['currency_symbol'].number_format($mv['unit_price']['amount'], 2, '.', ','), - 'total_price_text' => $mv['total_price']['currency_symbol'].number_format($mv['total_price']['amount'], 2, '.', ','), - 'official_price_text' => $mv['official_price']['currency_symbol'].number_format($mv['official_price']['amount'], 2, '.', ','), - 'discount_price_text' => $mv['discount_price']['currency_symbol'].number_format($mv['discount_price']['amount'], 2, '.', ','), + 'total_price_text' => $mv['total_price']['currency_symbol'].number_format($mv['total_price']['amount'], 2, '.', ',') ]; + + // 根据里程处理 新车or二手车 + if ($v->mileage > 0) { + if (isset($mv['reduction_price'])) { + $vehList[$v->unique_id]['reduction_price'] = $mv['reduction_price']['amount']; + $vehList[$v->unique_id]['reduction_price_symbol'] = $mv['reduction_price']['currency_symbol']; + $vehList[$v->unique_id]['reduction_price_text'] = $mv['reduction_price']['currency_symbol'].number_format($mv['reduction_price']['amount'], 2, '.', ','); + } + $vehList[$v->unique_id]['listing_price'] = $mv['listing_price']['amount']; + $vehList[$v->unique_id]['listing_price_symbol'] = $mv['listing_price']['currency_symbol']; + $vehList[$v->unique_id]['listing_price_text'] = $mv['listing_price']['currency_symbol'].number_format($mv['listing_price']['amount'], 2, '.', ','); + } else { + if (isset($mv['discount_price'])) { + $vehList[$v->unique_id]['discount_price'] = $mv['discount_price']['amount']; + $vehList[$v->unique_id]['discount_price_symbol'] = $mv['discount_price']['currency_symbol']; + $vehList[$v->unique_id]['discount_price_text'] = $mv['discount_price']['currency_symbol'].number_format($mv['discount_price']['amount'], 2, '.', ','); + } + $vehList[$v->unique_id]['official_price'] = $mv['official_price']['amount']; + $vehList[$v->unique_id]['official_price_symbol'] = $mv['official_price']['currency_symbol']; + $vehList[$v->unique_id]['official_price_text'] = $mv['official_price']['currency_symbol'].number_format($mv['official_price']['amount'], 2, '.', ','); + } } $accList = []; @@ -283,6 +345,12 @@ class StockOrdersService $accIndex += 1; } + $cifTotalAmount = 0.00; + if ($item->moneyValues) { + $cifTotalAmountSysboml = SysCurrency::where('currency_code', $item->moneyValues->currency)->value('currency_symbol'); + $cifTotalAmount = $cifTotalAmountSysboml.sprintf("%.2f", $item->moneyValues->amount, $item->moneyValues->exchange_rate); + } + $veh = [ 'order_no' => $item->order_no, 'salesperson_snapshot' => $item->salesperson_snapshot, @@ -310,7 +378,8 @@ class StockOrdersService // 账户 'account_list' => $accList, - 'acc_index' => $accIndex + 'acc_index' => $accIndex, + 'cif_total_amount' => $cifTotalAmount ]; return $veh; diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 53507b2..74c12c4 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -52,6 +52,9 @@ class UserService 'sn' => $user->sn, 'corp_id' => $user->corp_id, 'corp_name' => $corp_name, + 'report_to' => $user->report_to, + 'sign_img' => $user->sign_img, + 'sign_img_url' => (!empty($user->sign_img))?(new \app\common\service\OssService)->generatePrivateUrl($user->sign_img):'', ], // 'menu' => $menuTree, // 'permissions' => $permissionCodes @@ -84,7 +87,9 @@ class UserService 'email' => $user->email, 'sex' => $user->sex, 'status' => $user->status, - 'roles' => $user->roles->pluck('name') + 'roles' => $user->roles->pluck('name'), + 'sign_img' => $user->sign_img, + 'sign_img_url' => (!empty($user->sign_img))?$ossservice->generatePrivateUrl($user->sign_img):'', ]; } diff --git a/app/common/utils/approval/service/ApprovalFlowService.php b/app/common/utils/approval/service/ApprovalFlowService.php index 100cdec..a44e1c4 100644 --- a/app/common/utils/approval/service/ApprovalFlowService.php +++ b/app/common/utils/approval/service/ApprovalFlowService.php @@ -34,36 +34,56 @@ class ApprovalFlowService throw new BusinessException("未找到可执行的审批步骤"); } - // 3. 创建审批实例 + // 3. 计算截止时间 + $deadlineAt = null; + if ($flow->timeout_hours > 0) { + $deadlineAt = date('Y-m-d H:i:s', time() + $flow->timeout_hours * 3600); + } + + // 4. 创建审批实例 $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', + 'status' => 'processing', // processing 表示正在进行中 'current_step' => 0, + 'deadline_at' => $deadlineAt, '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]); + // 5. 创建审批记录(待办) + $approverIds = $this->resolveApprovers($firstStep, (object)[ + 'initiator_id' => $initiatorId, + 'form_data' => json_encode($formData) + ]); + foreach ($approverIds as $approverId) { Db::table('approval_records')->insert([ 'instance_id' => $instanceId, - 'step_name' => $firstStep['name'], 'approver_id' => $approverId, + 'step_index' => 0, + 'action' => '', // 未操作时为空 'status' => 'pending', + 'approved_at' => date('Y-m-d H:i:s', 0), // 未审批时设置为初始时间 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), ]); } - // 5. 通知审批人 - $this->sendNotification($approverIds, (object)['id' => $instanceId, 'flow_type' => $flowType], 'pending', false); + // 6. 通知审批人 + $instance = (object)[ + 'id' => $instanceId, + 'flow_type' => $flowType, + 'form_data' => json_encode($formData), + 'initiator_id' => $initiatorId + ]; + $this->sendNotification($approverIds, $instance, 'pending', false); - // 6. 通知抄送人(当前步骤) - $this->notifyCCs($firstStep, (object)['id' => $instanceId, 'flow_type' => $flowType, 'form_data' => json_encode($formData)], 'pending'); + // 7. 通知抄送人(当前步骤) + $this->notifyCCs($firstStep, $instance, 'pending'); return ['instance_id' => $instanceId]; } @@ -73,13 +93,28 @@ class ApprovalFlowService */ public function approve(int $instanceId, int $approverId, string $action, string $comment = '') { + // 验证操作是否有效 + if (!in_array($action, ['approve', 'reject'])) { + throw new BusinessException("无效的审批操作"); + } + $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; + // 获取流程定义 + $flow = Db::table('approval_flows') + ->where('type', $instance->flow_type) + ->first(); + + if (!$flow) { + throw new BusinessException("审批流程定义未找到"); + } + + $steps = json_decode($flow->steps, true); + $currentStepIndex = $instance->current_step; + $currentStep = $steps[$currentStepIndex] ?? null; if (!$currentStep) { throw new BusinessException("当前步骤配置错误"); @@ -90,20 +125,33 @@ class ApprovalFlowService throw new BusinessException("您无权审批此步骤"); } + // 检查用户是否已经审批过 + $existingRecord = Db::table('approval_records') + ->where('instance_id', $instanceId) + ->where('approver_id', $approverId) + ->where('step_index', $currentStepIndex) + ->first(); + + if ($existingRecord && $existingRecord->status !== 'pending') { + throw new BusinessException("您已经审批过此步骤"); + } + // 更新审批记录 Db::table('approval_records') ->where('instance_id', $instanceId) ->where('approver_id', $approverId) + ->where('step_index', $currentStepIndex) ->where('status', 'pending') ->update([ - 'status' => $action, + 'action' => $action, + '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']); + // 检查当前步骤是否完成(根据 type: any, all) + $stepCompleted = $this->isStepCompleted($instanceId, $currentStepIndex, $currentStep['type']); if (!$stepCompleted) { // 步骤未完成,不进入下一步 @@ -111,12 +159,13 @@ class ApprovalFlowService } // 步骤已完成,进入下一步或结束 - $nextStep = $this->getNextValidStep($steps, json_decode($instance->form_data, true), $instance->current_step + 1); + $nextStepIndex = $this->getNextStepIndex($steps, json_decode($instance->form_data, true), $currentStepIndex); - if ($nextStep === null) { + if ($nextStepIndex === null) { // 流程结束 Db::table('approval_instances')->where('id', $instanceId)->update([ - 'status' => $action === 'approve' ? 'approved' : 'rejected', + 'status' => $action, // 最终状态与最后一次操作一致 + 'finished_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); @@ -127,21 +176,26 @@ class ApprovalFlowService } // 进入下一步 - $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'), ]); + // 获取下一步信息 + $nextStep = $steps[$nextStepIndex]; + // 创建下一步的审批记录 $nextApprovers = $this->resolveApprovers($nextStep, $instance); foreach ($nextApprovers as $approverId) { Db::table('approval_records')->insert([ 'instance_id' => $instanceId, - 'step_name' => $nextStep['name'], 'approver_id' => $approverId, + 'step_index' => $nextStepIndex, + 'action' => '', 'status' => 'pending', + 'approved_at' => date('Y-m-d H:i:s', 0), 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), ]); } @@ -233,6 +287,26 @@ class ApprovalFlowService $ccUserIds = array_unique(array_filter($ccUserIds)); if (!empty($ccUserIds)) { + // 记录抄送信息到数据库 + foreach ($ccUserIds as $userId) { + // 检查是否已存在抄送记录 + $exists = Db::table('approval_ccs') + ->where('instance_id', $instance->id) + ->where('user_id', $userId) + ->exists(); + + if (!$exists) { + Db::table('approval_ccs')->insert([ + 'instance_id' => $instance->id, + 'user_id' => $userId, + 'notified_at' => date('Y-m-d H:i:s'), + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + } + } + + // 发送通知 $this->sendNotification($ccUserIds, $instance, $action, true); } } @@ -264,7 +338,7 @@ class ApprovalFlowService case '==': return $actualValue == $value; case '!=': return $actualValue != $value; case '>': return $actualValue > $value; - case '<': $actualValue < $value; + case '<': return $actualValue < $value; case '>=': return $actualValue >= $value; case '<=': return $actualValue <= $value; default: throw new BusinessException("不支持的操作符: {$op}"); @@ -272,14 +346,14 @@ class ApprovalFlowService } /** - * 获取下一个有效步骤(考虑 condition) + * 获取下一个有效步骤索引(考虑 condition) */ - private function getNextValidStep(array $steps, array $formData, int $startIndex = 0): ?array + private function getNextStepIndex(array $steps, array $formData, int $currentStepIndex): ?int { - for ($i = $startIndex; $i < count($steps); $i++) { + for ($i = $currentStepIndex + 1; $i < count($steps); $i++) { $step = $steps[$i]; if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) { - return $step; + return $i; } } return null; @@ -299,20 +373,38 @@ class ApprovalFlowService */ private function isStepCompleted(int $instanceId, int $stepIndex, string $type): bool { - $pendingCount = Db::table('approval_records') + // 获取当前步骤的所有审批记录 + $records = Db::table('approval_records') ->where('instance_id', $instanceId) - ->where('status', 'pending') - ->count(); + ->where('step_index', $stepIndex) + ->get(); - if ($type === 'any') { - return $pendingCount === 0; // 任一通过即可 + if ($records->isEmpty()) { + return false; } - if ($type === 'all' || $type === 'sequential') { - return $pendingCount === 0; // 所有人都必须审批 + switch ($type) { + case 'any': + // 任意一人审批即可(approve或reject都算完成) + foreach ($records as $record) { + if ($record->status !== 'pending') { + return true; + } + } + return false; + + case 'all': + // 所有人都必须审批 + foreach ($records as $record) { + if ($record->status === 'pending') { + return false; + } + } + return true; + + default: + return false; } - - return false; } /** @@ -341,21 +433,12 @@ class ApprovalFlowService */ 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') + return Db::table('approval_records as ar') + ->join('approval_instances as ai', 'ar.instance_id', '=', 'ai.id') + ->where('ar.approver_id', $userId) + ->where('ar.status', 'pending') + ->where('ai.status', 'processing') + ->select('ai.*') ->get() ->toArray(); } @@ -406,12 +489,7 @@ class ApprovalFlowService if ($hasReject) { $status = 'rejected'; } else { - if ($step['type'] === 'parallel') { - // 简化处理 - $status = 'approved'; - } else { - $status = 'approved'; - } + $status = 'approved'; } } elseif ($index < $instance->current_step) { $status = 'approved'; @@ -449,172 +527,40 @@ class ApprovalFlowService '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'); + $updateData['status'] = 'reject'; } elseif ($flow->timeout_action === 'auto_approve') { - $updateData['status'] = 'approved'; - Db::table('approval_instances')->where('id', $instance->id)->update($updateData); - $this->notifyInitiator($instance, 'approved_timeout'); + $updateData['status'] = 'approve'; } + + Db::table('approval_instances')->where('id', $instance->id)->update($updateData); + $this->notifyInitiator($instance, $flow->timeout_action); } } } // --- 辅助方法 --- - // 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 字段 + ->where('r.code', $roleCode) ->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"; - } } \ No newline at end of file diff --git a/app/common/utils/approval/service/ApprovalFlowService_bak2.php b/app/common/utils/approval/service/ApprovalFlowService_bak2.php new file mode 100644 index 0000000..9b6babd --- /dev/null +++ b/app/common/utils/approval/service/ApprovalFlowService_bak2.php @@ -0,0 +1,620 @@ +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"; + } +} \ No newline at end of file diff --git a/app/common/utils/pdf/template/stockOrder.stub b/app/common/utils/pdf/template/stockOrder.stub index a6aa700..df20d14 100644 --- a/app/common/utils/pdf/template/stockOrder.stub +++ b/app/common/utils/pdf/template/stockOrder.stub @@ -140,10 +140,10 @@ 颜色/外 内饰 改装 - 单价(美金) - 总价(美金) - 官方指导价 - 优惠 + 单价 + 总价 + 官方指导价/二手挂牌价 + 优惠/二手减价 @@ -157,8 +157,15 @@ - - + + 0): ?> + + + + + + + @@ -232,6 +239,17 @@ + +
+
+
+ 下单人: + +
+
+
下单时间:2025-09-08
+
+ \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 8f6428f..8039205 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -107,13 +107,23 @@ class IndexController public function test() { - return json([ + + + $flowType = 'stock_order_flow'; + $bizType = 'stock_orders'; + $bizId = 32; + $formData = []; + $initiatorId = 74; + + $appro = new \app\common\utils\approval\service\ApprovalFlowService(); + // $sp = $appro->start($flowType, $bizType, $bizId, $formData, $initiatorId); + + $sp = $appro->approve(1, 1, 'approve'); + var_dump($sp); + + return json([ 'code' => 0, - 'msg' => 'success', - 'data' => [ - 'time' => time(), - 'date' => date('Y-m-d H:i:s') - ] + 'msg' => 'success' ]); } } \ No newline at end of file diff --git a/app/crm/controller/StockOrdersController.php b/app/crm/controller/StockOrdersController.php index f32f03f..0930a1a 100644 --- a/app/crm/controller/StockOrdersController.php +++ b/app/crm/controller/StockOrdersController.php @@ -51,16 +51,13 @@ class StockOrdersController extends BaseController } if (count($data['moneyValues']) > 0) { - foreach ($data['moneyValues'] as $mvs) { - $data['cif_total_amount'] = [ - 'exchange_rate' => $mvs['exchange_rate'], - 'amount' => $mvs['amount'], - 'currency' => $mvs['currency'], - 'id' => $mvs['id'] - ]; - } + $data['cif_total_amount'] = [ + 'exchange_rate' => $data['moneyValues']['exchange_rate'], + 'amount' => $data['moneyValues']['amount'], + 'currency' => $data['moneyValues']['currency'], + 'id' => $data['moneyValues']['id'] + ]; } - unset($data['moneyValues']); foreach ($data['items'] as &$item) { foreach ($item['moneyValues'] as $mv) { diff --git a/config/route.php b/config/route.php index cd2c03c..a46227c 100644 --- a/config/route.php +++ b/config/route.php @@ -153,6 +153,7 @@ Route::group('/admin', function () { Route::any('/authorize', [app\admin\controller\UserController::class, 'authorize']); Route::any('/set/resources', [app\admin\controller\UserController::class, 'updateResources']); Route::any('/update/password', [app\admin\controller\UserController::class, 'savePassword']); + Route::any('/update/sign', [app\admin\controller\UserController::class, 'updateSign']); }); // 询单 diff --git a/public/01.jpg b/public/01.jpg new file mode 100644 index 0000000..d646fb2 Binary files /dev/null and b/public/01.jpg differ