调整ä功能

This commit is contained in:
mzl
2025-10-11 08:41:11 +00:00
parent 11b5a694ac
commit 009312ffa3
15 changed files with 155 additions and 48 deletions
+1
View File
@@ -43,6 +43,7 @@ class UserGroupLogic
$userGroup->title = $data['title'];
$userGroup->sort = $data['sort'] ?? 0;
$userGroup->status = $data['status'];
$userGroup->scenario_code = $data['scenario_code'] ?? '';
$userGroup->save();
// 删除所有成员
+2 -1
View File
@@ -75,6 +75,7 @@ class ApprovalInstances extends Model
// ====== ====== ====== 映射业务表 ====== ====== ======
protected const BIZ_CLASS = [
'stock_orders' => \app\common\model\StockOrders::class,
'stock_orders_nullify' => \app\common\model\StockOrders::class,
'purchase_orders' => \app\common\model\PurchaseOrders::class,
];
@@ -85,7 +86,7 @@ class ApprovalInstances extends Model
public function bizInfo()
{
return $this->morphTo('bizInfo', 'biz_type', 'biz_id');
return $this->morphTo('bizInfo', 'biz_type', 'biz_id')->withTrashed();
}
// ====== ====== ====== 映射表业务 ====== ====== ======
+4
View File
@@ -4,9 +4,13 @@ namespace app\common\model;
use support\Model;
use app\common\traits\HasMoneyJournal;
use Illuminate\Database\Eloquent\SoftDeletes;
class StockOrders extends Model
{
use SoftDeletes;
/**
* The connection name for the model.
*
+1 -1
View File
@@ -40,7 +40,7 @@ class UserGroup extends Model
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'title', 'sort', 'status', 'created_at', 'updated_at'];
protected $fillable = ['id', 'title', 'scenario_code', 'sort', 'status', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
@@ -136,6 +136,7 @@ class ApprovalRecordsService
$bizInfo = [];
$service = match ($data['flow_type']) {
'stock_orders' => new \app\common\service\StockOrdersService(),
'stock_orders_nullify' => new \app\common\service\StockOrdersService(),
'purchase_orders' => new \app\common\service\PurchaseOrdersService(),
default => new \app\common\service\StockOrdersService()
};
@@ -154,6 +155,14 @@ class ApprovalRecordsService
'instance.bizInfo.moneyValues',
'instance.bizInfo.salesperson',
],
'stock_orders_nullify' => [
'instance.bizInfo.items',
'instance.bizInfo.items.options',
'instance.bizInfo.items.moneyValues',
'instance.bizInfo.accounts',
'instance.bizInfo.moneyValues',
'instance.bizInfo.salesperson',
],
'purchase_orders' => [
'instance.bizInfo.items',
'instance.bizInfo.items.stockOrder',
+1 -1
View File
@@ -410,7 +410,7 @@ class StockOrdersService
public function approvalNullify($item)
{
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
$appro->startNullify('stock_orders_nullify', 'stock_orders_nullify', $item->id, [], $item->salesperson_id);
$appro->startNullify('stock_orders_nullify', 'stock_orders', $item->id, [], $item->salesperson_id);
}
/**
+22 -10
View File
@@ -74,16 +74,16 @@ class WsEvents
}
// 连接是否满足条件
// if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') {
// flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false);
// Gateway::sendToClient($client_id, WsResponse::error(
// 'system.auth',
// 1001,
// '[x] [TOKEN认证] HTTP_ORIGIN不满足条件'
// ));
// Gateway::closeClient($client_id);
// return;
// }
if (getenv('ENVIRONMENT') === 'master' && $payload['server']['HTTP_ORIGIN'] === getenv('MAIN_DOMAIN')) {
flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false);
Gateway::sendToClient($client_id, WsResponse::error(
'system.auth',
1001,
'[x] [TOKEN认证] HTTP_ORIGIN不满足条件'
));
Gateway::closeClient($client_id);
return;
}
// 签名检查
if (!isset($payload['get']['token'])) {
@@ -99,9 +99,14 @@ class WsEvents
// 解析TOKEN
$user = (new Jwt())->verifyToken($payload['get']['token']);
// 绑定用户ID
Gateway::bindUid($client_id, $user->uid);
// 绑定SESSION
$_SESSION['uid'] = $user->uid;
$_SESSION['name'] = $user->uname;
// 响应
Gateway::sendToClient($client_id, WsResponse::success(
'system.auth',
@@ -119,6 +124,13 @@ class WsEvents
}
}
public function logout(string $client_id)
{
if ($_SESSION !== null) {
var_dump($_SESSION);
}
}
/**
* 系统心跳
*
@@ -3,6 +3,7 @@ namespace app\common\utils\approval\service;
use support\exception\BusinessException;
use support\Db;
use support\Log;
class ApprovalFlowService
{
@@ -174,14 +175,18 @@ class ApprovalFlowService
$nextIndex = $this->getNextStepIndex($steps, $formData, $idx);
if ($nextIndex === null) {
$finalStatus = $this->normalizeFinalStatus((string)$instance->flow_type, 'approved');
$notifyAction = ($finalStatus === 'voided') ? 'voided' : 'approve';
Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => 'approved',
'status' => $finalStatus,
'finished_at' => $now,
'updated_at' => $now,
]);
$this->onFlowFinalized($instance, 'approved');
$this->sendNotification([$instance->initiator_id], $instance, 'approve', false);
return ['status' => 'completed', 'msg' => '同意'];
$this->onFlowFinalized($instance, $finalStatus);
$this->sendNotification([$instance->initiator_id], $instance, $notifyAction, false);
return ['status' => 'completed', 'msg' => ($finalStatus === 'voided' ? '已作废' : '同意')];
}
// 乐观推进 current_step
@@ -206,13 +211,16 @@ class ApprovalFlowService
});
}
/** ★ 新增:流程完结后的业务回调(此处处理“作废”落库) */
private function onFlowFinalized(object $instance, string $final /* 'approved'|'rejected' */): void
// 流程完结后的业务回调(approved|rejected|revoked|timeout?)
private function onFlowFinalized(object $instance, string $final): void
{
try {
switch ($instance->flow_type) {
case 'stock_orders': {
$order = \app\common\model\StockOrders::find($instance->biz_id);
if (!$order) {
throw new BusinessException('订单不存在');
}
if ($final === 'approved') {
$order->approval_status = 2;
$order->save();
@@ -225,30 +233,27 @@ class ApprovalFlowService
}
break;
}
case 'stock_orders_nullify':
// 仅当作废审批“通过”时,真正把业务单置为作废
if ($final === 'approved') {
// 这里按你的模型更新,状态字段请替换成你系统实际枚举
$final = 'voided';
Db::table('approval_instances')->where('id', $instance->id)->update([
'status' => 'voided',
]);
}
if (in_array($final, ['voided','approved'], true)) {
$order = \app\common\model\StockOrders::find($instance->biz_id);
if ($order && $order->approval_status !== 4) {
$order->approval_status = 4;
$order->approval_status = 4;
$order->save();
}
}
break;
// case 'purchase_orders_nullify':
// ...
// break;
default:
// 非作废流程,无需处理
break;
}
} catch (\Throwable $e) {
// 避免回写异常影响审批主流程,可按需记录日志
// logger()->error('onFlowFinalized error: '.$e->getMessage());
Log::error('onFlowFinalized error: '.$e->getMessage());
}
}
@@ -361,8 +366,9 @@ class ApprovalFlowService
$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';
$final = ($flow->timeout_action === 'auto_approve') ? 'approved' : 'rejected';
$final = $this->normalizeFinalStatus((string)$instance->flow_type, $final);
$finalAction = ($final === 'voided') ? 'voided' : (($final === 'approved') ? 'approve' : 'reject');
Db::table('approval_instances')->where('id', $instance->id)->update([
'status' => $final,
@@ -490,6 +496,28 @@ class ApprovalFlowService
$head = $this->getDeptHeadId((int)$val);
if ($head) $uids[] = (int)$head;
break;
case 'group_members': {
$gid = (int)($val ?? 0);
if ($gid <= 0) throw new BusinessException('group_members 需要 group_id');
$uids = $this->getGroupMemberIds($gid); // A 方案
break;
}
case 'scenario_leader_of_initiator': {
$formData = json_decode($instance->form_data ?? '{}', true) ?: [];
$scenario = $val ?? ($formData[$cc['scenario_field']] ?? null);
if (!$scenario) throw new BusinessException('缺少业务场景(scenario 或 scenario_field)');
$mustBelong = array_key_exists('must_belong', $cc) ? (bool)$cc['must_belong'] : true;
$preferPrimary = array_key_exists('prefer_primary', $cc) ? (bool)$cc['prefer_primary'] : true;
$uids = $this->getLeadersByScenarioForInitiator(
(int)$instance->initiator_id,
(string)$scenario,
$mustBelong,
$preferPrimary
); // B 方案(按场景)
break;
}
default: break;
}
}
@@ -553,10 +581,6 @@ class ApprovalFlowService
/**
* 聚合本步骤结论(按类型)
* 返回:'pending' | 'approve' | 'reject'
*
* any:有 reject => reject;否则有 approve => approve;否则 pending
* all:有 reject => reject;有 pending => pending;否则(全 approved 或 approved+skipped/timeout)=> approve
*/
private function getStepOutcome(int $instanceId, int $round, int $stepIndex, string $type): string
{
@@ -566,22 +590,28 @@ class ApprovalFlowService
->where('step_index', $stepIndex)
->get();
$pending = 0; $approved = 0; $rejected = 0;
$pending = $approved = $rejected = $timeout = $skipped = 0;
foreach ($records as $r) {
if ($r->status === 'pending') $pending++;
elseif ($r->status === 'approved') $approved++;
elseif ($r->status === 'rejected') $rejected++;
switch ($r->status) {
case 'pending': $pending++; break;
case 'approved': $approved++; break;
case 'rejected': $rejected++; break;
case 'timeout': $timeout++; break;
case 'skipped': $skipped++; break;
}
}
$type = strtolower($type);
if ($type === 'any') {
if ($rejected > 0) return 'reject';
if ($approved > 0) return 'approve';
// 全为 skipped/timeout 且无 pending/rejected ⇒ 通过
if ($pending === 0 && $rejected === 0 && $approved === 0) return 'approve';
return 'pending';
}
if ($rejected > 0) return 'reject';
if ($pending > 0) return 'pending';
return $approved > 0 ? 'approve' : 'pending';
return 'approve';
}
/** 关闭本步其余待办为 skipped */
@@ -637,6 +667,7 @@ class ApprovalFlowService
'approve' => '已通过',
'reject' => '已拒绝',
'revoked' => '已撤回',
'voided' => '已作废',
];
$prefix = $isCC ? '【抄送】' : '【审批】';
$text = $map[$action] ?? $action;
@@ -644,6 +675,10 @@ class ApprovalFlowService
// 业务标题/内容
$approvalType = array_flip(\app\common\service\SysDictService::pluckKeyValueByCode('approval_type'));
$flowTxt = $approvalType[$instance->flow_type] ?? (string)$instance->flow_type;
$title = $prefix.'审批通知';
$content = "{$prefix}你有一条审批 {$text}:类型 {$flowTxt},业务标识 {$instance->biz_id}";
switch ($instance->flow_type) {
case 'stock_orders':
// 备货单
@@ -1589,6 +1624,7 @@ class ApprovalFlowService
->where('status', 'approved')
->orderByDesc('id')
->first();
if (!$latestApproved) {
throw new BusinessException('仅已通过的单据可发起作废');
}
@@ -1603,6 +1639,26 @@ class ApprovalFlowService
throw new BusinessException('已存在进行中的作废审批');
}
// A. 基于历史“作废流程已通过”的实例拦截
$voidApproved = Db::table('approval_instances')
->where('flow_type', $voidFlowType)
->where('biz_id', $bizId)
->whereIn('status', ['approved','voided'])
->exists();
if ($voidApproved) {
throw new BusinessException('该单据已作废,无需重复作废');
}
// B.(可选)直接查业务表状态拦截(若你用 approval_status=4 表示已作废)
/*
$orderVoided = \app\common\model\StockOrders::where('id', $bizId)
->where('approval_status', 4)
->exists();
if ($orderVoided) {
throw new BusinessException('该单据已作废,无需重复作废');
}
*/
// 4) 计算首步
// 注:作废流程可以有独立条件;若不需要条件,直接第一步
$formData = array_merge([
@@ -1735,4 +1791,23 @@ class ApprovalFlowService
return $ids;
}
//
private function isNullifyFlow(string $flowType): bool
{
$t = strtolower(trim($flowType));
return str_ends_with($t, '_nullify') || in_array($t, [
// 所有“作废”类流程的类型
'stock_orders_nullify',
], true);
}
// 放到工具方法区
private function normalizeFinalStatus(string $flowType, string $final): string
{
if ($this->isNullifyFlow($flowType) && $final === 'approved') {
return 'voided';
}
return $final;
}
}