修改审批流程模块儿

This commit is contained in:
mzl
2025-09-24 11:18:59 +08:00
parent 368bfe44ee
commit 8f2c47d8cb
10 changed files with 1825 additions and 196 deletions
@@ -128,7 +128,7 @@ class PurchaseOrdersController extends BaseController
{ {
try { try {
$raw = $this->service->serviceResubmit($request, $request->all()); $raw = $this->service->serviceResubmit($request, $request->all());
return $raw === true ? $this->success('提交成功') : $this->error('提交失败'); return $this->success($raw);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return $this->error($e->getMessage()); return $this->error($e->getMessage());
} }
+1
View File
@@ -59,6 +59,7 @@ class ApprovalInstances extends Model
'current_step', 'current_step',
'finished_at', 'finished_at',
'deadline_at', 'deadline_at',
'round'
]; ];
protected $casts = [ protected $casts = [
+1
View File
@@ -57,6 +57,7 @@ class ApprovalRecords extends Model
'status', 'status',
'comment', 'comment',
'approved_at', 'approved_at',
'round'
]; ];
protected $casts = [ protected $casts = [
+45 -19
View File
@@ -166,7 +166,6 @@ class PurchaseOrdersService
case 'index': case 'index':
$config['order']['id'] = 'desc'; $config['order']['id'] = 'desc';
break; break;
case 'show':
case 'update': case 'update':
$config['where']['id'] = $p['id'] ?? 0; $config['where']['id'] = $p['id'] ?? 0;
break; break;
@@ -214,13 +213,15 @@ class PurchaseOrdersService
*/ */
protected function afterServiceStore($res) protected function afterServiceStore($res)
{ {
if ($res->status == 1) { // if ($res->status == 1) {
$this->startApproval($res, ['resubmitted'=>0]); // $this->startApproval($res, ['resubmitted'=>0]);
} // }
$this->startApproval($res, ['resubmitted'=>0]);
return $res; return $res;
} }
public function startApproval($item, $formData) public function startApproval($item)
{ {
$appro = new \app\common\utils\approval\service\ApprovalFlowService(); $appro = new \app\common\utils\approval\service\ApprovalFlowService();
$appro->start('purchase_orders', 'purchase_orders', $item->id, ['resubmitted'=>0], $item->buyer_id); $appro->start('purchase_orders', 'purchase_orders', $item->id, ['resubmitted'=>0], $item->buyer_id);
@@ -229,20 +230,45 @@ class PurchaseOrdersService
public function serviceResubmit(Request $request, array $data = []) public function serviceResubmit(Request $request, array $data = [])
{ {
$info = $this->logicShow($this->buildQueryConfig($request, 'show')); $p = $request->all();
if ($info->status == 3) { $config = [];
$instance = \app\common\model\ApprovalInstances::where('biz_id', $info->id)->where('flow_type', 'purchase_orders')->first(); $config['model'] = $this->modelClass;
if ( $instance === null) { $config['where']['id'] = $p['id'];
new BusinessException('未找到审批实例'); $config['where']['buyer_id'] = $request->user->uid;
} $info = $this->logicShow($config);
// 审批服务 if ($info === null) {
$appro = new \app\common\utils\approval\service\ApprovalFlowService(); throw new BusinessException('未找到采购单');
// 重新提交
$appro->resubmit($instance->id, $instance->initiator_id, ['resubmitted'=>1], $data['remark']);
// 添加到讨论
$appro->postDiscussion($instance->id, $instance->initiator_id, $data['remark'], true, [], 'name');
return true;
} }
return false; if ($info->status != 3) {
throw new BusinessException('该状态不能重新提交');
}
$instance = \app\common\model\ApprovalInstances::where('biz_id', $info->id)->where('flow_type', 'purchase_orders')->first();
if ( $instance === null) {
throw new BusinessException('未找到审批实例');
}
// 审批服务
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
// 重新提交
$result = $appro->resubmit($instance->id, $instance->initiator_id, ['resubmitted'=>1], $data['remark']);
// 添加到讨论
if (!empty($data['remark'])) {
$appro->postDiscussion($instance->id, $instance->initiator_id, $data['remark'], false, [], 'name');
}
$info->status = 1;
$info->save();
return $result;
}
public function updateStatus(array $data = [])
{
$info = PurchaseOrders::where('id', $data['id'])->first();
if ($info === null) {
throw new BusinessException('未找到采购单');
}
$info->status = $data['status'];
return $info->save();
} }
} }
@@ -35,6 +35,7 @@ class ApprovalFlowService
'initiator_id' => $initiatorId, 'initiator_id' => $initiatorId,
'status' => 'processing', 'status' => 'processing',
'current_step' => $firstIndex, 'current_step' => $firstIndex,
'round' => 1,
'deadline_at' => $deadlineAt, 'deadline_at' => $deadlineAt,
'created_at' => $now, 'created_at' => $now,
'updated_at' => $now, 'updated_at' => $now,
@@ -50,16 +51,15 @@ class ApprovalFlowService
'flow_type' => $flowType, 'flow_type' => $flowType,
'biz_type' => $bizType, 'biz_type' => $bizType,
'biz_id' => $bizId, 'biz_id' => $bizId,
'round' => 1,
]; ];
// 解析首步审批人(支持 role_id 或 roles.name) // 解析首步审批人(支持 role_id 或 roles.name)
$approverIds = $this->resolveApprovers($firstStep, $instance, true); $approverIds = $this->resolveApprovers($firstStep, $instance, true);
foreach ($approverIds as $uid) { foreach ($approverIds as $uid) {
$this->insertApprovalRecord($instanceId, $firstIndex, $uid); $this->insertApprovalRecord($instance, $firstIndex, $uid);
} }
// $this->sendNotification($approverIds, $instance, 'pending', false);
$extra = []; $extra = [];
$submitNote = $formData['submit_note'] ?? ''; $submitNote = $formData['submit_note'] ?? '';
if (is_string($submitNote) && $submitNote !== '') { if (is_string($submitNote) && $submitNote !== '') {
@@ -84,8 +84,6 @@ class ApprovalFlowService
$instance = Db::table('approval_instances')->where('id', $instanceId)->lockForUpdate()->first(); $instance = Db::table('approval_instances')->where('id', $instanceId)->lockForUpdate()->first();
if (!$instance) throw new BusinessException("审批实例未找到"); if (!$instance) throw new BusinessException("审批实例未找到");
if ($instance->status !== 'processing') { if ($instance->status !== 'processing') {
// return ['status' => 'noop', 'reason' => 'instance_not_processing', 'instance_status' => $instance->status];
// throw new BusinessException("实例未处理");
return ['status' => 'noop', 'msg' => '实例未处理']; return ['status' => 'noop', 'msg' => '实例未处理'];
} }
@@ -94,12 +92,14 @@ class ApprovalFlowService
$steps = json_decode($flow->steps, true); $steps = json_decode($flow->steps, true);
$idx = (int)$instance->current_step; $idx = (int)$instance->current_step;
$round = (int)$instance->round;
$step = $steps[$idx] ?? null; $step = $steps[$idx] ?? null;
if (!$step) throw new BusinessException("当前步骤配置错误"); if (!$step) throw new BusinessException("当前步骤配置错误");
// 找到该用户在本步的 pending 记录(加锁) // 找到该用户在本步的 pending 记录(加锁)
$pendingRecord = Db::table('approval_records') $pendingRecord = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', $round)
->where('approver_id', $approverId) ->where('approver_id', $approverId)
->where('step_index', $idx) ->where('step_index', $idx)
->where('status', 'pending') ->where('status', 'pending')
@@ -110,21 +110,17 @@ class ApprovalFlowService
// 已处理过? // 已处理过?
$anyRecord = Db::table('approval_records') $anyRecord = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', $round)
->where('approver_id', $approverId) ->where('approver_id', $approverId)
->where('step_index', $idx) ->where('step_index', $idx)
->first(); ->first();
if ($anyRecord && $anyRecord->status !== 'pending') { if ($anyRecord && $anyRecord->status !== 'pending') {
// return ['status' => 'noop', 'reason' => 'already_handled', 'record_status' => $anyRecord->status];
// throw new BusinessException("已处理");
return ['status' => 'noop', 'msg' => '已处理']; return ['status' => 'noop', 'msg' => '已处理'];
} }
// 步骤已推进 // 步骤已推进
if ((int)$instance->current_step !== $idx) { 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' => '无待处理记录']; return ['status' => 'noop', 'msg' => '无待处理记录'];
} }
@@ -139,18 +135,22 @@ class ApprovalFlowService
'updated_at' => $now, 'updated_at' => $now,
]); ]);
// 异步生成pdf文件 9.11 add // 异步审批回调 9.23 add
if ($action === 'approve') { $approvalcallback = [
\Webman\RedisQueue\Redis::send('stock-order-pdf', ['id'=>$instance->biz_id]); 'biz_id' => $instance->biz_id,
} 'flow_type' => $instance->flow_type,
'instance_id' => $instance->id,
'initiator_id' => $instance->initiator_id,
'status' => $action
];
\Webman\RedisQueue\Redis::send('approval-callback', $approvalcallback);
// 2) 聚合本步结论(any / all) // 2) 聚合本步结论(any / all)
$typeRaw = strtolower($step['type'] ?? 'all'); $typeRaw = strtolower($step['type'] ?? 'all');
$type = in_array($typeRaw, ['any','all']) ? $typeRaw : 'all'; $type = in_array($typeRaw, ['any','all']) ? $typeRaw : 'all';
$outcome = $this->getStepOutcome($instanceId, $idx, $type); // 'pending' | 'approve' | 'reject' $outcome = $this->getStepOutcome($instanceId, $round, $idx, $type); // 'pending' | 'approve' | 'reject'
if ($outcome === 'pending') { if ($outcome === 'pending') {
// return ['status' => 'step_pending'];
return ['status' => 'step_pending', 'msg' => '审批待定']; return ['status' => 'step_pending', 'msg' => '审批待定'];
} }
@@ -165,8 +165,7 @@ class ApprovalFlowService
'updated_at' => $now, 'updated_at' => $now,
]); ]);
$this->sendNotification([$instance->initiator_id], $instance, 'reject', false); $this->sendNotification([$instance->initiator_id], $instance, 'reject', false);
// return ['status' => 'completed', 'result' => 'reject']; return ['status' => 'completed', 'msg' => '拒绝'];
return ['status' => 'completed', 'msg' => '已拒绝'];
} }
// 5) 通过 => 进入下一步或结束 // 5) 通过 => 进入下一步或结束
@@ -180,8 +179,7 @@ class ApprovalFlowService
'updated_at' => $now, 'updated_at' => $now,
]); ]);
$this->sendNotification([$instance->initiator_id], $instance, 'approve', false); $this->sendNotification([$instance->initiator_id], $instance, 'approve', false);
// return ['status' => 'completed', 'result' => 'approve']; return ['status' => 'completed', 'msg' => '同意'];
return ['status' => 'completed', 'msg' => '已同意'];
} }
// 乐观推进 current_step // 乐观推进 current_step
@@ -191,7 +189,6 @@ class ApprovalFlowService
->where('status', 'processing') ->where('status', 'processing')
->update(['current_step' => $nextIndex, 'updated_at' => $now]); ->update(['current_step' => $nextIndex, 'updated_at' => $now]);
if (!$affected) { if (!$affected) {
// return ['status' => 'noop', 'reason' => 'concurrent_progressed'];
return ['status' => 'noop', 'msg' => '并发进展']; return ['status' => 'noop', 'msg' => '并发进展'];
} }
@@ -199,12 +196,10 @@ class ApprovalFlowService
$nextStep = $steps[$nextIndex]; $nextStep = $steps[$nextIndex];
$nextApprovers = $this->resolveApprovers($nextStep, $instance, true); $nextApprovers = $this->resolveApprovers($nextStep, $instance, true);
foreach ($nextApprovers as $uid) { foreach ($nextApprovers as $uid) {
$this->insertApprovalRecord($instanceId, $nextIndex, $uid); $this->insertApprovalRecord($instance, $nextIndex, $uid);
} }
$this->sendNotification($nextApprovers, $instance, 'pending', false); $this->sendNotification($nextApprovers, $instance, 'pending', false);
$this->notifyCCs($nextStep, $instance, 'pending'); $this->notifyCCs($nextStep, $instance, 'pending');
// return ['status' => 'next_step', 'step_name' => $nextStep['name'] ?? ('Step '.$nextIndex)];
return ['status' => 'next_step', 'msg' => '进行下一步:'.$nextStep['name'] ?? ('Step '.$nextIndex)]; return ['status' => 'next_step', 'msg' => '进行下一步:'.$nextStep['name'] ?? ('Step '.$nextIndex)];
}); });
} }
@@ -253,6 +248,7 @@ class ApprovalFlowService
$steps = json_decode($flow->steps, true) ?: []; $steps = json_decode($flow->steps, true) ?: [];
$records = Db::table('approval_records') $records = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', (int)$instance->round)
->orderBy('step_index')->orderByRaw('COALESCE(approved_at, created_at) asc') ->orderBy('step_index')->orderByRaw('COALESCE(approved_at, created_at) asc')
->get() ->get()
->groupBy('step_index'); ->groupBy('step_index');
@@ -325,6 +321,7 @@ class ApprovalFlowService
Db::table('approval_records') Db::table('approval_records')
->where('instance_id', $instance->id) ->where('instance_id', $instance->id)
->where('round', (int)$instance->round)
->where('step_index', $instance->current_step) ->where('step_index', $instance->current_step)
->where('status', 'pending') ->where('status', 'pending')
->update(['status' => 'timeout', 'approved_at' => $now, 'updated_at' => $now]); ->update(['status' => 'timeout', 'approved_at' => $now, 'updated_at' => $now]);
@@ -482,19 +479,19 @@ class ApprovalFlowService
* any:有 reject => reject;否则有 approve => approve;否则 pending * any:有 reject => reject;否则有 approve => approve;否则 pending
* all:有 reject => reject;有 pending => pending;否则(全 approved 或 approved+skipped/timeout)=> approve * all:有 reject => reject;有 pending => pending;否则(全 approved 或 approved+skipped/timeout)=> approve
*/ */
private function getStepOutcome(int $instanceId, int $stepIndex, string $type): string private function getStepOutcome(int $instanceId, int $round, int $stepIndex, string $type): string
{ {
$records = Db::table('approval_records') $records = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', $round) // ★
->where('step_index', $stepIndex) ->where('step_index', $stepIndex)
->get(); ->get();
$pending = 0; $approved = 0; $rejected = 0; $pending = 0; $approved = 0; $rejected = 0;
foreach ($records as $r) { foreach ($records as $r) {
if ($r->status === 'pending') { $pending++; } if ($r->status === 'pending') $pending++;
elseif ($r->status === 'approved'){ $approved++; } elseif ($r->status === 'approved') $approved++;
elseif ($r->status === 'rejected'){ $rejected++; } elseif ($r->status === 'rejected') $rejected++;
// skipped/timeout 不影响聚合结论
} }
$type = strtolower($type); $type = strtolower($type);
@@ -503,18 +500,18 @@ class ApprovalFlowService
if ($approved > 0) return 'approve'; if ($approved > 0) return 'approve';
return 'pending'; return 'pending';
} }
// all
if ($rejected > 0) return 'reject'; if ($rejected > 0) return 'reject';
if ($pending > 0) return 'pending'; if ($pending > 0) return 'pending';
return $approved > 0 ? 'approve' : 'pending'; return $approved > 0 ? 'approve' : 'pending';
} }
/** 关闭本步其余待办为 skipped */ /** 关闭本步其余待办为 skipped */
private function closeOtherPending(int $instanceId, int $stepIndex, array $keepApproverIds = []): void private function closeOtherPending(int $instanceId, int $round, int $stepIndex, array $keepApproverIds = []): void
{ {
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
$q = Db::table('approval_records') $q = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', $round) // ★
->where('step_index', $stepIndex) ->where('step_index', $stepIndex)
->where('status', 'pending'); ->where('status', 'pending');
@@ -524,59 +521,32 @@ class ApprovalFlowService
} }
/** 创建待办(去重) */ /** 创建待办(去重) */
private function insertApprovalRecord(int $instanceId, int $stepIndex, int $approverId): void private function insertApprovalRecord(object $instance, int $stepIndex, int $approverId): void
{ {
$round = (int)($instance->round ?? 1);
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
$exists = Db::table('approval_records') $exists = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', (int)$instance->id)
->where('round', $round) // ★ 限当前轮
->where('step_index', $stepIndex) ->where('step_index', $stepIndex)
->where('approver_id', $approverId) ->where('approver_id', $approverId)
->count() > 0; ->where('status', 'pending')
if (!$exists) { ->exists();
Db::table('approval_records')->insert([
'instance_id' => $instanceId,
'approver_id' => $approverId,
'step_index' => $stepIndex,
'action' => null,
'status' => 'pending',
'comment' => null,
'approved_at' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
/** 发送通知(接你自己的消息系统即可) */ if ($exists) return;
private function sendNotification2(array $userIds, object $instance, string $action, bool $isCC): void
{
$map = [
'pending' => '待审批',
'approve' => '已通过',
'reject' => '已拒绝',
'revoked' => '已撤回'
];
$prefix = $isCC ? '【抄送】' : '【审批】';
$text = $map[$action] ?? $action;
$approvalType = array_flip(\app\common\service\SysDictService::pluckKeyValueByCode('approval_type')); Db::table('approval_records')->insert([
'instance_id' => (int)$instance->id,
switch ($instance->flow_type) { 'approver_id' => $approverId,
case 'stock_orders': // 备货单 'step_index' => $stepIndex,
$stockOrder = \app\common\model\StockOrders::find($instance->biz_id); 'round' => $round, // ★
$title = $prefix."备货单:{$stockOrder->order_no}"; 'action' => null,
$flowType = $approvalType[$instance->flow_type]; 'status' => 'pending',
$content = "{$prefix}你有一条审批 {$text}:类型 {$flowType},业务单号 {$stockOrder->order_no}"; 'comment' => null,
break; 'approved_at' => null,
case 'stock_orders_nullify': // 备货单作废 'created_at' => $now,
break; 'updated_at' => $now,
}
noticePush([
'title' => $title,
'content' => $content,
'recipient' => $userIds,
'email' => 1
]); ]);
} }
@@ -815,100 +785,6 @@ class ApprovalFlowService
]; ];
} }
/**
* 极简:审批实例的时间轴(只返回每一步的总状态)
*
* @param int $instanceId
* @return array<int, array{index:int,name:string,type:string,status:string}>
*/
public function getSimpleTimeline1(int $instanceId): array
{
// 基础数据
$instance = Db::table('approval_instances')->where('id', $instanceId)->first();
if (!$instance) 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) ?: [];
$formData = json_decode($instance->form_data ?? '{}', true) ?: [];
$currentStep = (int)$instance->current_step;
// 读出所有记录并分组
$records = Db::table('approval_records as ar')
->where('ar.instance_id', $instanceId)
->orderBy('ar.step_index')
->orderByRaw('COALESCE(ar.approved_at, ar.created_at) asc')
->select('ar.*')
->get()
->toArray();
$byStep = [];
foreach ($records as $r) {
$idx = (int)$r->step_index;
$byStep[$idx][] = $r;
}
$timeline = [];
foreach ($steps as $i => $meta) {
$name = $meta['name'] ?? ('Step '.$i);
$typeRaw = strtolower($meta['type'] ?? 'all');
$type = in_array($typeRaw, ['any','all']) ? $typeRaw : 'all';
$condStr = $meta['condition'] ?? null;
$condOk = $condStr ? $this->evaluateCondition($condStr, $formData) : true;
$recs = $byStep[$i] ?? [];
$hasRecs = !empty($recs);
// 默认状态
$status = 'pending';
if ($hasRecs) {
// 统计是否全部 timeout(且无 approve/reject/pending)
$cnt = ['pending'=>0,'approved'=>0,'rejected'=>0,'timeout'=>0];
foreach ($recs as $rr) {
if (isset($cnt[$rr->status])) $cnt[$rr->status]++;
}
// 用已有的聚合规则拿到结论
$outcome = $this->getStepOutcome($instanceId, $i, $type); // 'pending'|'approve'|'reject'
if ($outcome === 'reject') {
$status = 'rejected';
} elseif ($outcome === 'approve') {
$status = 'approved';
} else {
// 没有结论,再看是不是纯超时
if ($cnt['timeout'] > 0 && $cnt['approved'] === 0 && $cnt['rejected'] === 0 && $cnt['pending'] === 0) {
$status = 'timeout';
} else {
$status = 'processing';
}
}
} else {
// 无记录:根据位置与条件判断展示
if ($i < $currentStep) {
$status = $condOk ? 'approved' : 'skipped';
} elseif ($i === $currentStep && $instance->status === 'processing') {
// 理论上当前步应有记录;防御性展示为 processing
$status = 'processing';
} else {
$status = $condOk ? 'pending' : 'skipped';
}
}
$timeline[] = [
'index' => (int)$i,
'name' => $name,
'type' => $type, // any | all
'status' => $status, // approved | rejected | processing | pending | skipped | timeout
];
}
return $timeline;
}
/** /**
* 极简时间轴(含每步时间 & 申请人信息) * 极简时间轴(含每步时间 & 申请人信息)
* *
@@ -944,6 +820,7 @@ class ApprovalFlowService
$steps = json_decode($flow->steps, true) ?: []; $steps = json_decode($flow->steps, true) ?: [];
$formData = json_decode($instance->form_data ?? '{}', true) ?: []; $formData = json_decode($instance->form_data ?? '{}', true) ?: [];
$curStep = (int)$instance->current_step; $curStep = (int)$instance->current_step;
$round = (int)$instance->round;
// 申请人 // 申请人
$initiatorId = (int)$instance->initiator_id; $initiatorId = (int)$instance->initiator_id;
@@ -955,6 +832,7 @@ class ApprovalFlowService
// 审批记录 // 审批记录
$records = Db::table('approval_records as ar') $records = Db::table('approval_records as ar')
->where('ar.instance_id', $instanceId) ->where('ar.instance_id', $instanceId)
->where('ar.round', $round)
->orderBy('ar.step_index') ->orderBy('ar.step_index')
->orderByRaw('COALESCE(ar.approved_at, ar.created_at) asc') ->orderByRaw('COALESCE(ar.approved_at, ar.created_at) asc')
->select('ar.*') ->select('ar.*')
@@ -1027,7 +905,7 @@ class ApprovalFlowService
$startedAt = $minCreated; $startedAt = $minCreated;
// 总结果 // 总结果
$outcome = $this->getStepOutcome($instanceId, $i, $type); // 'pending'|'approve'|'reject' $outcome = $this->getStepOutcome($instanceId, $round, $i, $type); // 'pending'|'approve'|'reject'
if ($outcome === 'reject') { if ($outcome === 'reject') {
$status = 'rejected'; $status = 'rejected';
$finishedAt = $minRejectAt; $finishedAt = $minRejectAt;
@@ -1135,6 +1013,7 @@ class ApprovalFlowService
} }
$steps = json_decode($flow->steps, true) ?: []; $steps = json_decode($flow->steps, true) ?: [];
$idx = (int)$instance->current_step; $idx = (int)$instance->current_step;
$round = (int)$instance->round;
$meta = $steps[$idx] ?? []; $meta = $steps[$idx] ?? [];
$typeRaw = strtolower($meta['type'] ?? 'all'); $typeRaw = strtolower($meta['type'] ?? 'all');
@@ -1148,6 +1027,7 @@ class ApprovalFlowService
$pendingRows = Db::table('approval_records as ar') $pendingRows = Db::table('approval_records as ar')
->leftJoin('user as u', 'ar.approver_id', '=', 'u.id') ->leftJoin('user as u', 'ar.approver_id', '=', 'u.id')
->where('ar.instance_id', $instance->id) ->where('ar.instance_id', $instance->id)
->where('ar.round', $round)
->where('ar.step_index', $idx) ->where('ar.step_index', $idx)
->where('ar.status', 'pending') ->where('ar.status', 'pending')
->select('ar.approver_id', Db::raw("u.`{$userNameColumn}` as approver_name")) ->select('ar.approver_id', Db::raw("u.`{$userNameColumn}` as approver_name"))
@@ -1156,6 +1036,7 @@ class ApprovalFlowService
} else { } else {
$pendingRows = Db::table('approval_records') $pendingRows = Db::table('approval_records')
->where('instance_id', $instance->id) ->where('instance_id', $instance->id)
->where('round', $round)
->where('step_index', $idx) ->where('step_index', $idx)
->where('status', 'pending') ->where('status', 'pending')
->select('approver_id') ->select('approver_id')
@@ -1176,6 +1057,7 @@ class ApprovalFlowService
// 先快速统计是否纯 timeout // 先快速统计是否纯 timeout
$stats = Db::table('approval_records') $stats = Db::table('approval_records')
->where('instance_id', $instance->id) ->where('instance_id', $instance->id)
->where('round', $round)
->where('step_index', $idx) ->where('step_index', $idx)
->selectRaw("SUM(CASE WHEN status='pending' THEN 1 ELSE 0 END) as c_pending") ->selectRaw("SUM(CASE WHEN status='pending' THEN 1 ELSE 0 END) as c_pending")
->selectRaw("SUM(CASE WHEN status='approved' THEN 1 ELSE 0 END) as c_approved") ->selectRaw("SUM(CASE WHEN status='approved' THEN 1 ELSE 0 END) as c_approved")
@@ -1246,10 +1128,14 @@ class ApprovalFlowService
->where('status', 'pending') ->where('status', 'pending')
->update(['status'=>'skipped','approved_at'=>$now,'updated_at'=>$now]); ->update(['status'=>'skipped','approved_at'=>$now,'updated_at'=>$now]);
// ★ 轮次 +1
$newRound = ((int)$instance->round) + 1;
// 重新开启实例 // 重新开启实例
Db::table('approval_instances')->where('id', $instanceId)->update([ Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => 'processing', 'status' => 'processing',
'current_step' => $firstIndex, 'current_step' => $firstIndex,
'round' => $newRound,
'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'finished_at' => null, 'finished_at' => null,
'deadline_at' => $deadlineAt, 'deadline_at' => $deadlineAt,
@@ -1265,12 +1151,13 @@ class ApprovalFlowService
'initiator_id' => (int)$instance->initiator_id, 'initiator_id' => (int)$instance->initiator_id,
'biz_type' => (string)$instance->biz_type, 'biz_type' => (string)$instance->biz_type,
'biz_id' => (string)$instance->biz_id, 'biz_id' => (string)$instance->biz_id,
'round' => $newRound,
]; ];
$approvers = $this->resolveApprovers($firstStep, $instanceLite, true); $approvers = $this->resolveApprovers($firstStep, $instanceLite, true);
if (empty($approvers)) throw new BusinessException('重提后首步未解析到审批人'); if (empty($approvers)) throw new BusinessException('重提后首步未解析到审批人');
foreach ($approvers as $uid) { foreach ($approvers as $uid) {
$this->insertApprovalRecord($instanceId, $firstIndex, (int)$uid); $this->insertApprovalRecord($instanceLite, $firstIndex, (int)$uid);
} }
$extra = $appealText !== '' ? ['appeal' => $appealText] : []; $extra = $appealText !== '' ? ['appeal' => $appealText] : [];
@@ -1469,6 +1356,7 @@ class ApprovalFlowService
// 锁定来源人的 pending 记录(必须是“自己名下”的记录) // 锁定来源人的 pending 记录(必须是“自己名下”的记录)
$record = Db::table('approval_records') $record = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', (int)$instance->round)
->where('step_index', $stepIndex) ->where('step_index', $stepIndex)
->where('approver_id', $fromUserId) ->where('approver_id', $fromUserId)
->where('status', 'pending') ->where('status', 'pending')
@@ -1484,6 +1372,7 @@ class ApprovalFlowService
// 若目标人在本步已有 pending,合并:本记录设为 skipped,保留目标那条 // 若目标人在本步已有 pending,合并:本记录设为 skipped,保留目标那条
$target = Db::table('approval_records') $target = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', (int)$instance->round)
->where('step_index', $stepIndex) ->where('step_index', $stepIndex)
->where('approver_id', $toUserId) ->where('approver_id', $toUserId)
->where('status', 'pending') ->where('status', 'pending')
@@ -1583,6 +1472,7 @@ class ApprovalFlowService
// 锁定来源人的 pending 记录 // 锁定来源人的 pending 记录
$record = Db::table('approval_records') $record = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', (int)$instance->round)
->where('step_index', $stepIndex) ->where('step_index', $stepIndex)
->where('approver_id', $fromUserId) ->where('approver_id', $fromUserId)
->where('status', 'pending') ->where('status', 'pending')
@@ -1595,6 +1485,7 @@ class ApprovalFlowService
// 若目标已有 pending:合并(本记录置为 skipped) // 若目标已有 pending:合并(本记录置为 skipped)
$target = Db::table('approval_records') $target = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('round', (int)$instance->round)
->where('step_index', $stepIndex) ->where('step_index', $stepIndex)
->where('approver_id', $toUserId) ->where('approver_id', $toUserId)
->where('status', 'pending') ->where('status', 'pending')
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -101,8 +101,10 @@ class IndexController
public function test(Request $request) public function test(Request $request)
{ {
$service = new \app\crm\service\StockOrdersService();
$result = $service->createdPdfFile(35); $info = \app\common\model\PurchaseOrders::find(14);
$service = new \app\common\service\PurchaseOrdersService();
$result = $service->startApproval($info);
return json($result); return json($result);
} }
+2 -1
View File
@@ -119,7 +119,8 @@ if (!function_exists('flg')) {
if ($isFs) { if ($isFs) {
$data = [ $data = [
'content' => $eventData, 'content' => $eventData,
'time' => date('Y-m-d H:i:s', time()) 'time' => date('Y-m-d H:i:s', time()),
'source' => getenv('ENVIRONMENT')
]; ];
RedisQueueRedis::send("server", $data); RedisQueueRedis::send("server", $data);
} }
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace app\queue\redis;
use support\exception\BusinessException;
use Webman\RedisQueue\Consumer;
class ApprovalCallback implements Consumer
{
// 要消费的队列名
public $queue = 'approval-callback';
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
public $connection = 'default';
// 消费
public function consume($data)
{
try {
match ($data['flow_type']) {
'stock_orders' => $this->approvalStockOrders($data),
'purchase_orders' => $this->approvalPurchaseOrders($data),
default => throw new BusinessException('未知的审批类型,无法执行回调操作')
};
return true;
} catch (\Throwable $th) {
flg('审批回调异常:'.$th->getFile()."|".$th->getLine()."|".$th->getMessage(), false);
throw new BusinessException($th->getFile()."|".$th->getLine()."|".$th->getMessage());
}
}
// 消费失败回调
/*
$package = [
'id' => 1357277951, // 消息ID
'time' => 1709170510, // 消息时间
'delay' => 0, // 延迟时间
'attempts' => 2, // 消费次数
'queue' => 'send-mail', // 队列名
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
'max_attempts' => 5, // 最大重试次数
'error' => '错误信息' // 错误信息
]
*/
public function onConsumeFailure(\Throwable $e, $package)
{
echo "consume failure\n";
echo $e->getMessage() . "\n";
// 无需反序列化
var_export($package);
}
/**
* 备货单处理
*/
private function approvalStockOrders($data)
{
// 生成PDF文件
$service = new \app\crm\service\StockOrdersService();
$result = $service->createdPdfFile($data['biz_id']);
return $result;
}
/**
* 采购单处理
*/
private function approvalPurchaseOrders($data)
{
$udata['id'] = $data['biz_id'];
if ($data['status'] === 'approve') {
$udata['status'] = 2;
}
if ($data['status'] === 'reject') {
$udata['status'] = 3;
}
$service = new \app\common\service\PurchaseOrdersService();
$result = $service->updateStatus($udata);
return $result;
}
}
+2
View File
@@ -19,10 +19,12 @@ class Server implements Consumer
public function consume($data) public function consume($data)
{ {
try { try {
$source = $data['source'] === 'development' ? '-- 开发环境 --' : '** 生产环境 **';
$webhookUrl = 'https://open.feishu.cn/open-apis/bot/v2/hook/806c2e9d-6ca2-4345-a459-69bb2651706b'; $webhookUrl = 'https://open.feishu.cn/open-apis/bot/v2/hook/806c2e9d-6ca2-4345-a459-69bb2651706b';
$message = "====== ======\n"; $message = "====== ======\n";
$message .= $data['content']."\n"; $message .= $data['content']."\n";
$message .= "时间:".date('Y-m-d H:i:s', time())."\n"; $message .= "时间:".date('Y-m-d H:i:s', time())."\n";
$message .= "来源:".$source."\n";
$message .= "====== ======"; $message .= "====== ======";
sendFeishuTextMessage($webhookUrl, $message); sendFeishuTextMessage($webhookUrl, $message);
return true; return true;