From 17efdf01dfeb774054dead8fc75788fad4a74de1 Mon Sep 17 00:00:00 2001
From: mzl
Date: Wed, 10 Sep 2025 13:39:06 +0800
Subject: [PATCH] Merge branch 'develop' into feature/mzl_dev
---
.../controller/ApprovalRecordsController.php | 4 +-
app/common/service/StockOrdersService.php | 5 +-
.../approval/service/ApprovalFlowService.php | 9 +-
.../service/ApprovalFlowService_bak.php | 751 ------------------
app/common/utils/pdf/template/stockOrder.stub | 58 +-
.../utils/pdf/template/stockOrder_bak.stub | 255 ++++++
app/controller/IndexController.php | 3 +
.../controller/ApprovalRecordsController.php | 4 +-
app/crm/controller/StockOrdersController.php | 2 +-
app/crm/service/StockOrdersService.php | 434 ++++++++++
10 files changed, 760 insertions(+), 765 deletions(-)
delete mode 100644 app/common/utils/approval/service/ApprovalFlowService_bak.php
create mode 100644 app/common/utils/pdf/template/stockOrder_bak.stub
create mode 100644 app/crm/service/StockOrdersService.php
diff --git a/app/admin/controller/ApprovalRecordsController.php b/app/admin/controller/ApprovalRecordsController.php
index 4e963ea..1bf914e 100644
--- a/app/admin/controller/ApprovalRecordsController.php
+++ b/app/admin/controller/ApprovalRecordsController.php
@@ -47,7 +47,9 @@ class ApprovalRecordsController extends BaseController
'step_index' => $record['step_index'],
'action' => $record['action'],
- 'status' => SysDictItem::where('item_value', $record['status'])->where('status', 1)->value('item_label'),
+ 'action_desc' => SysDictItem::where('item_value', $record['action'])->where('status', 1)->value('item_label'),
+ 'status' => $record['status'],
+ 'status_desc' => SysDictItem::where('item_value', $record['status'])->where('status', 1)->value('item_label'),
'comment' => $record['comment'],
'approved_at' => $record['approved_at'],
diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php
index 6a6d6b6..d936e5d 100644
--- a/app/common/service/StockOrdersService.php
+++ b/app/common/service/StockOrdersService.php
@@ -11,7 +11,6 @@ use app\common\model\StockOrders;
use app\common\model\SysCurrency;
use support\Request;
use app\common\model\User;
-use support\exception\BusinessException;
use support\Db;
class StockOrdersService
@@ -63,6 +62,10 @@ class StockOrdersService
}
$config['order']['id'] = 'desc';
+
+ // $config['dataLimit']['type'] = 'relation';
+ // $config['dataLimit']['userId'] = $request->user->uid;
+ // $config['dataLimit']['userField'] = 'salesperson_id';
break;
case 'print':
$config['where']['id'] = $p['id'];
diff --git a/app/common/utils/approval/service/ApprovalFlowService.php b/app/common/utils/approval/service/ApprovalFlowService.php
index b794532..9916b6e 100644
--- a/app/common/utils/approval/service/ApprovalFlowService.php
+++ b/app/common/utils/approval/service/ApprovalFlowService.php
@@ -112,7 +112,7 @@ class ApprovalFlowService
if ((int)$instance->current_step !== $idx) {
// return ['status' => 'noop', 'reason' => 'not_current_step', 'current_step' => (int)$instance->current_step];
// throw new BusinessException("非当前步骤");
- return ['status' => 'noop', 'msg' => '非当前步骤'];
+ return ['status' => 'noop', 'msg' => '非当前审批'];
}
// return ['status' => 'noop', 'reason' => 'no_pending_record'];
return ['status' => 'noop', 'msg' => '无待处理记录'];
@@ -136,7 +136,7 @@ class ApprovalFlowService
if ($outcome === 'pending') {
// return ['status' => 'step_pending'];
- return ['status' => 'step_pending', 'msg' => '步骤待定'];
+ return ['status' => 'step_pending', 'msg' => '审批待定'];
}
// 3) 有结论:关闭本步其余 pending
@@ -386,10 +386,11 @@ class ApprovalFlowService
$resolved = array_unique(array_filter(array_map('intval', $resolved)));
if ($avoidSelf) {
- $resolved = array_values(array_filter($resolved, fn($id) => $id !== (int)$instance->initiator_id));
+ // $resolved = array_values(array_filter($resolved, fn($id) => $id !== (int)$instance->initiator_id));
+ $resolved = array_values($resolved);
}
if (empty($resolved)) {
- throw new BusinessException("步骤未解析到任何审批人");
+ throw new BusinessException("未解析到任何审批人");
}
return $resolved;
}
diff --git a/app/common/utils/approval/service/ApprovalFlowService_bak.php b/app/common/utils/approval/service/ApprovalFlowService_bak.php
deleted file mode 100644
index f988a48..0000000
--- a/app/common/utils/approval/service/ApprovalFlowService_bak.php
+++ /dev/null
@@ -1,751 +0,0 @@
-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) 寻找首个有效步骤索引(考虑条件)
- $firstIndex = $this->getNextStepIndex($steps, $formData, -1);
- if ($firstIndex === null) {
- throw new BusinessException("未找到可执行的审批步骤");
- }
- $firstStep = $steps[$firstIndex];
-
- // 3) 计算实例截止时间(实例级,可扩展到步骤级)
- $deadlineAt = null;
- if (($flow->timeout_hours ?? 0) > 0) {
- $deadlineAt = date('Y-m-d H:i:s', time() + $flow->timeout_hours * 3600);
- }
-
- // 4) 创建审批实例
- $now = date('Y-m-d H:i:s');
- $instanceId = Db::table('approval_instances')->insertGetId([
- 'flow_type' => $flowType,
- 'biz_type' => $bizType,
- 'biz_id' => $bizId,
- 'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'initiator_id' => $initiatorId,
- 'status' => 'processing',
- 'current_step' => $firstIndex,
- 'deadline_at' => $deadlineAt,
- 'created_at' => $now,
- 'updated_at' => $now,
- ]);
-
- // 5) 创建首步待办
- $instance = (object)[
- 'id' => $instanceId,
- 'flow_type' => $flowType,
- 'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'initiator_id' => $initiatorId,
- 'current_step' => $firstIndex,
- 'status' => 'processing',
- ];
- $approverIds = $this->resolveApprovers($firstStep, $instance, true);
-
- foreach ($approverIds as $uid) {
- // 唯一键会防重复
- $this->insertApprovalRecord($instanceId, $firstIndex, $uid);
- }
-
- // 6) 通知审批人 & 抄送
- $this->sendNotification($approverIds, $instance, 'pending', false);
- $this->notifyCCs($firstStep, $instance, 'pending');
-
- return ['instance_id' => $instanceId];
- });
- }
-
- /**
- * 执行审批
- */
- public function approve(int $instanceId, int $approverId, string $action, string $comment = '')
- {
- if (!in_array($action, ['approve', 'reject'], true)) {
- throw new BusinessException("无效的审批操作");
- }
-
- return Db::transaction(function () use ($instanceId, $approverId, $action, $comment) {
- // 锁实例,保证并发安全
- $instance = Db::table('approval_instances')->where('id', $instanceId)->lockForUpdate()->first();
- if (!$instance) {
- throw new BusinessException("审批实例未找到");
- }
- if ($instance->status !== 'processing') {
- throw new BusinessException("流程已结束或不可审批");
- }
-
- // 读取流程与当前步
- $flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
- if (!$flow) throw new BusinessException("审批流程定义未找到");
-
- $steps = json_decode($flow->steps, true);
- $idx = (int)$instance->current_step;
- $step = $steps[$idx] ?? null;
- if (!$step) throw new BusinessException("当前步骤配置错误");
-
- // 锁当前步所有记录,避免同时推进
- $pendingRecord = Db::table('approval_records')
- ->where('instance_id', $instanceId)
- ->where('approver_id', $approverId)
- ->where('step_index', $idx)
- ->where('status', 'pending')
- ->lockForUpdate()
- ->first();
- if (!$pendingRecord) {
- throw new BusinessException("未找到待审批记录");
- }
-
- $now = date('Y-m-d H:i:s');
-
- // 1) 更新当前人的审批记录
- Db::table('approval_records')->where('id', $pendingRecord->id)->update([
- 'action' => $action,
- 'status' => ($action === 'approve') ? 'approved' : 'rejected',
- 'comment' => $comment,
- 'approved_at' => $now,
- 'updated_at' => $now,
- ]);
-
- // 2) 聚合判断本步骤结果
- $outcome = $this->getStepOutcome($instanceId, $idx); // 'pending'|'approve'|'reject'
- $type = $step['type'] ?? 'all';
-
- $stepDone = false; $finalReject = false; $finalApprove = false;
- if ($type === 'any') {
- if ($outcome === 'reject') { $stepDone = true; $finalReject = true; }
- elseif ($outcome === 'approve') { $stepDone = true; $finalApprove = true; }
- } else { // all
- if ($outcome === 'reject') { $stepDone = true; $finalReject = true; }
- elseif ($outcome === 'approve') { $stepDone = true; $finalApprove = true; }
- }
-
- if (!$stepDone) {
- return ['status' => 'step_pending'];
- }
-
- // 3) 本步已有结论:关闭其余 pending 待办为 skipped
- $this->closeOtherPending($instanceId, $idx, [$approverId]);
-
- // 4) 步骤结论为拒绝 -> 结束
- if ($finalReject) {
- Db::table('approval_instances')->where('id', $instanceId)->update([
- 'status' => 'rejected',
- 'finished_at' => $now,
- 'updated_at' => $now,
- ]);
- $this->sendNotification([$instance->initiator_id], $instance, 'reject', false);
- return ['status' => 'completed', 'result' => 'reject'];
- }
-
- // 5) 进入下一步(基于表单快照与条件继续寻找)
- $formData = json_decode($instance->form_data, true) ?: [];
- $nextIndex = $this->getNextStepIndex($steps, $formData, $idx);
- if ($nextIndex === null) {
- // 流程结束(全通过)
- Db::table('approval_instances')->where('id', $instanceId)->update([
- 'status' => 'approved',
- 'finished_at' => $now,
- 'updated_at' => $now,
- ]);
- $this->sendNotification([$instance->initiator_id], $instance, 'approve', false);
- return ['status' => 'completed', 'result' => 'approve'];
- }
-
- // 6) 推进实例 current_step(乐观条件)
- $affected = Db::table('approval_instances')
- ->where('id', $instanceId)
- ->where('current_step', $idx)
- ->where('status', 'processing')
- ->update([
- 'current_step' => $nextIndex,
- 'updated_at' => $now,
- ]);
- if (!$affected) {
- throw new BusinessException('流程已被他人推进,请刷新后重试');
- }
-
- // 7) 生成下一步待办 + 通知
- $nextStep = $steps[$nextIndex];
- $nextApprovers = $this->resolveApprovers($nextStep, $instance, true);
- foreach ($nextApprovers as $uid) {
- $this->insertApprovalRecord($instanceId, $nextIndex, $uid);
- }
- $this->sendNotification($nextApprovers, $instance, 'pending', false);
- $this->notifyCCs($nextStep, $instance, 'pending');
-
- return ['status' => 'next_step', 'step_name' => $nextStep['name'] ?? ('Step '.$nextIndex)];
- });
- }
-
- /**
- * 撤回流程(可选)
- * 仅限发起人在第一条“已处理记录”出现前撤回
- */
- public function revoke(int $instanceId, int $initiatorId, string $reason = '')
- {
- return Db::transaction(function () use ($instanceId, $initiatorId, $reason) {
- $instance = Db::table('approval_instances')->where('id', $instanceId)->lockForUpdate()->first();
- if (!$instance) throw new BusinessException('审批实例未找到');
- if ((int)$instance->initiator_id !== (int)$initiatorId) throw new BusinessException('无权撤回');
-
- if ($instance->status !== 'processing') throw new BusinessException('流程已结束或不可撤回');
-
- // 已有处理记录则不允许撤回
- $handled = Db::table('approval_records')
- ->where('instance_id', $instanceId)
- ->whereIn('status', ['approved','rejected','timeout','skipped'])
- ->count() > 0;
- if ($handled) throw new BusinessException('当前进度不可撤回');
-
- $now = date('Y-m-d H:i:s');
- Db::table('approval_instances')->where('id', $instanceId)->update([
- 'status' => 'revoked',
- 'finished_at' => $now,
- 'updated_at' => $now,
- ]);
-
- // 关闭所有 pending 待办
- Db::table('approval_records')
- ->where('instance_id', $instanceId)
- ->where('status', 'pending')
- ->update(['status' => 'skipped', 'approved_at' => $now, 'updated_at' => $now]);
-
- $this->sendNotification([$initiatorId], $instance, 'revoked', false);
- return ['status' => 'completed', 'result' => 'revoked'];
- });
- }
-
- /**
- * 我的待办
- */
- public function getPendingApprovals(int $userId): array
- {
- 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.*')
- ->distinct()
- ->get()
- ->toArray();
- }
-
- /**
- * 审批历史
- */
- public function getApprovalHistory(int $instanceId): array
- {
- return Db::table('approval_records')
- ->where('instance_id', $instanceId)
- ->orderBy('step_index')
- ->orderByRaw('COALESCE(approved_at, created_at) asc')
- ->get()
- ->toArray();
- }
-
- /**
- * 流程图数据(识别 skipped)
- */
- 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')->orderByRaw('COALESCE(approved_at, created_at) asc')
- ->get()
- ->groupBy('step_index');
-
- $formData = json_decode($instance->form_data, true) ?: [];
- $data = [];
- foreach ($steps as $i => $step) {
- $actions = ($records->get($i) ?? collect())->toArray();
-
- $status = 'pending';
- if (!empty($actions)) {
- $hasReject = false; $allApproved = true;
- foreach ($actions as $a) {
- if ($a->status === 'rejected') $hasReject = true;
- if ($a->status !== 'approved') $allApproved = false;
- }
- if ($hasReject) $status = 'rejected';
- elseif ($allApproved) $status = 'approved';
- else $status = 'processing';
- } else {
- // 无记录:可能被跳过、未到达、已过
- if ($i < (int)$instance->current_step) {
- // 如果条件不满足,认为 skipped,否则视为 approved(历史兼容)
- if (isset($step['condition']) && !$this->evaluateCondition($step['condition'], $formData)) {
- $status = 'skipped';
- } else {
- $status = 'approved';
- }
- } elseif ($i === (int)$instance->current_step && $instance->status === 'processing') {
- $status = 'processing';
- } else {
- // 还未到达
- $status = 'pending';
- }
- }
-
- $data[] = [
- 'index' => $i,
- 'name' => $step['name'] ?? ('Step '.$i),
- 'type' => $step['type'] ?? 'all',
- 'status' => $status,
- 'actions' => $actions,
- ];
- }
- return $data;
- }
-
- /**
- * 定时任务:处理超时实例(实例级)
- */
- public function handleTimeouts()
- {
- $now = date('Y-m-d H:i:s');
- $expired = Db::table('approval_instances')
- ->where('status', 'processing')
- ->whereNotNull('deadline_at')
- ->where('deadline_at', '<=', $now)
- ->get();
-
- foreach ($expired as $instance) {
- Db::transaction(function () use ($instance, $now) {
- $flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
- if (!$flow) return;
-
- $final = ($flow->timeout_action === 'auto_approve') ? 'approved' : 'rejected';
- $finalAction = ($flow->timeout_action === 'auto_approve') ? 'approve' : 'reject';
-
- // 更新实例
- Db::table('approval_instances')->where('id', $instance->id)->update([
- 'status' => ($final === 'approved') ? 'approved' : 'rejected',
- 'finished_at' => $now,
- 'updated_at' => $now,
- ]);
-
- // 关闭当前步所有 pending 记录为 timeout
- Db::table('approval_records')
- ->where('instance_id', $instance->id)
- ->where('step_index', $instance->current_step)
- ->where('status', 'pending')
- ->update(['status' => 'timeout', 'approved_at' => $now, 'updated_at' => $now]);
-
- $this->notifyInitiator($instance, $finalAction);
- });
- }
- }
-
- // === 内部工具 ===
-
- /**
- * 解析审批人
- * $avoidSelf:是否过滤发起人本人
- */
- private function resolveApprovers(array $step, object $instance, bool $avoidSelf = true): array
- {
- $resolved = [];
- $formData = json_decode($instance->form_data ?? '{}', true) ?: [];
-
- $confs = $step['approvers'] ?? [];
- foreach ($confs as $cfg) {
- $typ = $cfg['type'] ?? '';
- $val = $cfg['value'] ?? null;
- $ids = [];
-
- switch ($typ) {
- case 'role':
- // $ids = $this->getUserIdsByRole($val);
-
- // 支持:
- // - {"type":"role","value":1} // 按 role_id
- // - {"type":"role","value":"销售经理"} // 按 roles.name
- // - {"type":"role","value":"销售经理","system_id": 2} // 限定系统;默认也包含全局(system_id=0)
- $val = $cfg['value'] ?? null;
- $sys = isset($cfg['system_id']) ? (int)$cfg['system_id'] : null;
- // include_global 默认 true(把 system_id=0 的“全局角色”也算上)
- $includeGlobal = array_key_exists('include_global', $cfg) ? (bool)$cfg['include_global'] : true;
- $ids = $this->getUserIdsByRole($val, $sys, true, $includeGlobal);
-
- break;
- case 'user':
- $ids = [(int)$val];
- break;
- case 'initiator_leader':
- $leaderId = $this->getLeaderIdByUserId((int)$instance->initiator_id);
- if ($leaderId) $ids = [(int)$leaderId];
- break;
- case 'field_user':
- if (isset($formData[$val])) $ids = [(int)$formData[$val]];
- break;
- case 'dept_head':
- $head = $this->getDeptHeadId((int)$val);
- if ($head) $ids = [(int)$head];
- break;
- default:
- throw new BusinessException("不支持的审批人类型: {$typ}");
- }
- $resolved = array_merge($resolved, $ids);
- }
-
- $resolved = array_unique(array_filter(array_map('intval', $resolved)));
- if ($avoidSelf) {
- $resolved = array_values(array_filter($resolved, fn($id) => $id !== (int)$instance->initiator_id));
- }
- if (empty($resolved)) {
- throw new BusinessException("步骤未解析到任何审批人");
- }
- return $resolved;
- }
-
- /**
- * 通知抄送人
- */
- private function notifyCCs(array $step, object $instance, string $action = 'pending'): void
- {
- $ccConfig = $step['cc'] ?? [];
- if (empty($ccConfig)) return;
-
- $formData = json_decode($instance->form_data ?? '{}', true) ?: [];
- $uids = [];
-
- foreach ($ccConfig as $cc) {
- $typ = $cc['type'] ?? '';
- $val = $cc['value'] ?? null;
-
- switch ($typ) {
- case 'role':
- $uids = array_merge($uids, $this->getUserIdsByRole($val));
- break;
- case 'user':
- $uids[] = (int)$val;
- break;
- case 'initiator_leader':
- $leaderId = $this->getLeaderIdByUserId((int)$instance->initiator_id);
- if ($leaderId) $uids[] = (int)$leaderId;
- break;
- case 'field_user':
- if (isset($formData[$val])) $uids[] = (int)$formData[$val];
- break;
- case 'dept_head':
- $head = $this->getDeptHeadId((int)$val);
- if ($head) $uids[] = (int)$head;
- break;
- default:
- // ignore
- break;
- }
- }
-
- $uids = array_unique(array_filter(array_map('intval', $uids)));
- if (empty($uids)) return;
-
- $now = date('Y-m-d H:i:s');
- foreach ($uids as $uid) {
- // 唯一键防重;这里仍做一次 exists 判断
- $exists = Db::table('approval_ccs')
- ->where('instance_id', $instance->id)
- ->where('user_id', $uid)
- ->count() > 0;
- if (!$exists) {
- Db::table('approval_ccs')->insert([
- 'instance_id' => $instance->id,
- 'user_id' => $uid,
- 'notified_at' => $now,
- 'created_at' => $now,
- 'updated_at' => $now,
- ]);
- }
- }
-
- $this->sendNotification($uids, $instance, $action, true);
- }
-
- /**
- * 条件求值:支持 {field} {op} {value}
- * op: ==, !=, >, <, >=, <=
- */
- private function evaluateCondition(?string $condition, array $formData): bool
- {
- if (!$condition) return true;
-
- $pattern = '/^(\w+)\s*(==|!=|>|<|>=|<=)\s*(.+)$/';
- if (!preg_match($pattern, $condition, $m)) {
- throw new BusinessException("条件格式错误: {$condition}");
- }
-
- $field = $m[1];
- $op = $m[2];
- $value = trim($m[3], "\"'");
-
- $actual = $formData[$field] ?? null;
-
- // 数值比较
- if (is_numeric($value) && is_numeric($actual)) {
- $value = (float)$value;
- $actual = (float)$actual;
- }
-
- switch ($op) {
- case '==': return $actual == $value;
- case '!=': return $actual != $value;
- case '>': return $actual > $value;
- case '<': return $actual < $value;
- case '>=': return $actual >= $value;
- case '<=': return $actual <= $value;
- }
- throw new BusinessException("不支持的操作符: {$op}");
- }
-
- /**
- * 获取下一个有效步骤索引(考虑 condition)
- * @param int $currentStepIndex 从该索引之后开始找;如果传 -1 则从开头找“首个有效”
- */
- private function getNextStepIndex(array $steps, array $formData, int $currentStepIndex): ?int
- {
- for ($i = $currentStepIndex + 1; $i < count($steps); $i++) {
- $s = $steps[$i];
- if (!isset($s['condition']) || $this->evaluateCondition($s['condition'], $formData)) {
- return $i;
- }
- }
- return null;
- }
-
- /**
- * 聚合本步骤结论
- * 返回:'pending' | 'approve' | 'reject'
- */
- private function getStepOutcome(int $instanceId, int $stepIndex): string
- {
- $records = Db::table('approval_records')
- ->where('instance_id', $instanceId)
- ->where('step_index', $stepIndex)
- ->get();
-
- $hasPending = false; $hasReject = false; $allApproved = true;
-
- foreach ($records as $r) {
- if ($r->status === 'pending') { $hasPending = true; $allApproved = false; }
- elseif ($r->status === 'rejected') { $hasReject = true; $allApproved = false; }
- elseif ($r->status !== 'approved') { $allApproved = false; }
- }
-
- if ($hasPending) return 'pending';
- if ($hasReject) return 'reject';
- return $allApproved ? 'approve' : 'reject';
- }
-
- /**
- * 关闭本步其余待办(标记为 skipped)
- */
- private function closeOtherPending(int $instanceId, int $stepIndex, array $keepApproverIds = []): void
- {
- $now = date('Y-m-d H:i:s');
- $q = Db::table('approval_records')
- ->where('instance_id', $instanceId)
- ->where('step_index', $stepIndex)
- ->where('status', 'pending');
-
- if (!empty($keepApproverIds)) {
- $q->whereNotIn('approver_id', $keepApproverIds);
- }
-
- $q->update([
- 'status' => 'skipped',
- 'approved_at' => $now,
- 'updated_at' => $now,
- ]);
- }
-
- private function insertApprovalRecord(int $instanceId, int $stepIndex, int $approverId): void
- {
- $now = date('Y-m-d H:i:s');
-
- // 防重复:唯一键兜底,这里先查一次
- $exists = Db::table('approval_records')
- ->where('instance_id', $instanceId)
- ->where('step_index', $stepIndex)
- ->where('approver_id', $approverId)
- ->count() > 0;
-
- if (!$exists) {
- Db::table('approval_records')->insert([
- 'instance_id' => $instanceId,
- 'approver_id' => $approverId,
- 'step_index' => $stepIndex,
- 'action' => null, // 待办阶段无动作
- 'status' => 'pending', // 明确 pending
- 'comment' => null,
- 'approved_at' => null, // 未审批为 NULL
- 'created_at' => $now,
- 'updated_at' => $now,
- ]);
- }
- }
-
- private function sendNotification(array $userIds, object $instance, string $action, bool $isCC): void
- {
- $map = [
- 'pending' => '待审批',
- 'approve' => '已通过',
- 'reject' => '已拒绝',
- 'revoked' => '已撤回',
- ];
- $prefix = $isCC ? '[抄送]' : '';
- $text = $map[$action] ?? $action;
- $msg = "{$prefix}您有一条审批 {$text}:流程ID {$instance->id},类型 {$instance->flow_type}";
- // TODO: 接入你的消息系统(站内信/企微/飞书…)
- // MessageService::send($userIds, $msg);
- // echo "通知 ".implode(',', $userIds).":{$msg}\n";
- }
-
- private function notifyInitiator(object $instance, string $result): void
- {
- $this->sendNotification([(int)$instance->initiator_id], $instance, $result, false);
- }
-
- // === 数据访问 ===
-
- // private function getUserIdsByRole(string $roleCode): array
- // {
- // // 兼容 pluck 行为(不同版本可能返回 array 或 Collection)
- // $rows = Db::table('user_roles as ur')
- // ->join('roles as r', 'ur.role_id', '=', 'r.id')
- // ->where('r.code', $roleCode)
- // ->select('ur.user_id')
- // ->get();
-
- // $ids = [];
- // foreach ($rows as $row) {
- // $ids[] = (int)$row->user_id;
- // }
- // return array_values(array_unique($ids));
- // }
-
- /**
- * 从角色取用户ID列表
- *
- * @param mixed $roleIdOrName 数字 => role_id,字符串 => roles.name 精确匹配
- * @param int|null $systemId 可选:限定 roles.system_id;默认不传=不过滤
- * @param bool $onlyActive 是否仅取 status=1 的角色(默认 true)
- * @param bool $includeGlobal 是否包含全局角色(system_id=0),默认 true
- * @return int[]
- */
- private function getUserIdsByRole($roleIdOrName, ?int $systemId = null, bool $onlyActive = true, bool $includeGlobal = true): array
- {
- // 查询构造
- // 情况A:纯数字 => 当作 role_id,优先直接走 user_roles(role_id) 索引
- if (is_int($roleIdOrName) || (is_string($roleIdOrName) && ctype_digit($roleIdOrName))) {
- $roleId = (int)$roleIdOrName;
-
- // 如果不需要校验 roles 的 system/status,就不 join,性能更好
- $needJoin = $onlyActive || !is_null($systemId) || $includeGlobal === false;
-
- if (!$needJoin) {
- $rows = Db::table('user_roles')
- ->where('role_id', $roleId)
- ->select('user_id')
- ->get();
- } else {
- $q = Db::table('user_roles as ur')
- ->join('roles as r', 'ur.role_id', '=', 'r.id')
- ->where('ur.role_id', $roleId)
- ->select('ur.user_id');
-
- if ($onlyActive) {
- $q->where('r.status', 1);
- }
- if (!is_null($systemId)) {
- if ($includeGlobal) {
- $q->whereIn('r.system_id', [0, $systemId]);
- } else {
- $q->where('r.system_id', $systemId);
- }
- }
- $rows = $q->get();
- }
- }
- // 情况B:字符串 => 当作 roles.name
- else {
- $name = (string)$roleIdOrName;
- $q = Db::table('user_roles as ur')
- ->join('roles as r', 'ur.role_id', '=', 'r.id')
- ->where('r.name', $name)
- ->select('ur.user_id');
-
- if ($onlyActive) {
- $q->where('r.status', 1);
- }
- if (!is_null($systemId)) {
- if ($includeGlobal) {
- $q->whereIn('r.system_id', [0, $systemId]);
- } else {
- $q->where('r.system_id', $systemId);
- }
- }
- $rows = $q->get();
- }
-
- $ids = [];
- foreach ($rows as $row) {
- $ids[] = (int)$row->user_id;
- }
- return array_values(array_unique($ids));
- }
-
-
- private function getLeaderIdByUserId(int $userId): ?int
- {
- $id = Db::table('user')->where('id', $userId)->value('report_to');
- return $id ? (int)$id : null;
- }
-
- private function getDeptHeadId(int $deptId): ?int
- {
- $id = Db::table('departments')->where('id', $deptId)->value('head_user_id');
- return $id ? (int)$id : null;
- }
-}
\ 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 6b93ecc..850f31f 100644
--- a/app/common/utils/pdf/template/stockOrder.stub
+++ b/app/common/utils/pdf/template/stockOrder.stub
@@ -240,15 +240,61 @@
-
-
-
-
下单人:
-
; ?>)
+
+
+
+
+
+
+
+
业务员签字:
+
+
; ?>)
+
+
+
+
+
+
+
备发货负责人签字:
+
+
; ?>)
+
+
+
-
下单时间:
+
+
+
+
+
组长签字:
+
+
; ?>)
+
+
+
+
+
+
+ 日期:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 阿里订单号/PI号: |
+ 日期: |
+ 关目的国: |
+ 客户来源: |
+ 客户名称: |
+ 客户所在国: |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 编号 |
+ 车型 |
+ 数量/台 |
+ 颜色/外 |
+ 内饰 |
+ 改装 |
+ 单价 |
+ 总价 |
+ 官方指导价/二手挂牌价 |
+ 优惠/二手减价 |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+ 0): ?>
+ |
+ |
+
+ |
+ |
+
+
+
+
+
+
+ |
+ 合同汇率:
+ |
+
+ 合计到岸价:
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 清关人: |
+ 预计发运日期: |
+ 出运方式: |
+ 国内收货港: |
+ 贸易条款/清关目的港: / |
+ 最迟到港时间: |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 计划收款信息 |
+ 序号 |
+ 汇款国 |
+ 日期 |
+ 币种 |
+ 账户 |
+ 汇款人 |
+ 金额 |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
下单人:
+
; ?>)
+
+
+
下单时间:
+
+
+
+
diff --git a/app/common/utils/pdf/template/stockOrder_bak.stub b/app/common/utils/pdf/template/stockOrder_bak.stub
new file mode 100644
index 0000000..6b93ecc
--- /dev/null
+++ b/app/common/utils/pdf/template/stockOrder_bak.stub
@@ -0,0 +1,255 @@
+
+
+