调整ä功能

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
@@ -36,7 +36,7 @@ class ApprovalRecordsController extends BaseController
'biz_id' => $record['instance']['biz_id'],
'biz_desc' => '备货单【'.$record['instance']['bizInfo']['order_no'].'】',
'biz_type' => $record['instance']['flow_type'],
'biz_type' => $record['instance']['biz_type'],
'initiator_name' => $record['instance']['initiator']['name'],
'initiator_avatar' => $record['instance']['initiator']['avatar'],
@@ -113,7 +113,7 @@ class ApprovalRecordsController extends BaseController
'biz_id' => $record['instance']['biz_id'],
'biz_desc' => '业务单号【'.$record['instance']['bizInfo']['order_no'].'】',
'biz_type' => $record['instance']['flow_type'],
'biz_type' => $record['instance']['biz_type'],
'flow_type' => $record['instance']['flow_type'],
'initiator_name' => $record['instance']['initiator']['name'],
@@ -38,7 +38,7 @@ class StockOrdersController extends BaseController
if (isset($data['id'])) {
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
$appro = $appro->getBriefStatusByBiz($data['id'], null, 'name');
$map = ['processing'=>'审批中','pending'=>'审批中','approved'=>'已通过','rejected'=>'已拒绝','revoked'=>'已撤回'];
$map = ['processing'=>'审批中','pending'=>'审批中','approved'=>'已通过','rejected'=>'已拒绝','revoked'=>'已撤回','voided'=>'已作废'];
if ($appro) {
$data['appro_status'] = [
'status' => $appro['status'],
@@ -29,6 +29,7 @@ class UserGroupController extends BaseController
$result = [
'id' => $record['id'],
'title' => $record['title'],
'scenario_code' => $record['scenario_code'],
'leader_id' => 0,
'leader_name' => '',
'sort' => $record['sort'],
+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,11 +233,14 @@ 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;
@@ -237,18 +248,12 @@ class ApprovalFlowService
}
}
break;
// case 'purchase_orders_nullify':
// ...
// break;
default:
// 非作废流程,无需处理
break;
}
} catch (\Throwable $e) {
// 避免回写异常影响审批主流程,可按需记录日志
// logger()->error('onFlowFinalized error: '.$e->getMessage());
Log::error('onFlowFinalized error: '.$e->getMessage());
}
}
@@ -362,7 +367,8 @@ class ApprovalFlowService
if (!$flow) return;
$final = ($flow->timeout_action === 'auto_approve') ? 'approved' : 'rejected';
$finalAction = ($flow->timeout_action === 'auto_approve') ? 'approve' : 'reject';
$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;
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ class StockOrdersController extends BaseController
if (isset($data['id'])) {
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
$appro = $appro->getBriefStatusByBiz($data['id'], null, 'name');
$map = ['processing'=>'审批中','pending'=>'审批中','approved'=>'已通过','rejected'=>'已拒绝','revoked'=>'已撤回'];
$map = ['processing'=>'审批中','pending'=>'审批中','approved'=>'已通过','rejected'=>'已拒绝','revoked'=>'已撤回','voided'=>'已作废'];
if ($appro) {
$data['appro_status'] = [
'status' => $appro['status'],
@@ -8,7 +8,7 @@ return [
'gateway' => [
'handler' => Gateway::class,
'listen' => 'websocket://0.0.0.0:' . getenv('GATEWAY_LISTEN'),
'count' => 2,
'count' => 3,
'reloadable' => false,
'constructor' => ['config' => [
'lanIp' => '127.0.0.1',
@@ -22,7 +22,7 @@ return [
],
'worker' => [
'handler' => BusinessWorker::class,
'count' => 6,
'count' => 2,
'constructor' => ['config' => [
'eventHandler' => plugin\webman\gateway\Events::class,
'name' => 'ChatBusinessWorker',
+4 -1
View File
@@ -330,6 +330,7 @@ Route::group('/admin', function () {
Route::any('/store', [app\admin\controller\StockOrdersController::class, 'store']);
Route::any('/print', [app\admin\controller\StockOrdersController::class, 'print']);
Route::any('/detail', [app\admin\controller\StockOrdersController::class, 'detail']);
Route::any('/destroy', [app\admin\controller\StockOrdersController::class, 'destroy']);
});
// crm
@@ -440,7 +441,7 @@ Route::group('/crm', function () {
Route::any('/user/edit', [app\common\controller\UserController::class, 'edit']);
Route::any('/user/savePassword', [app\common\controller\UserController::class, 'savePassword']);
// 基础信息
// 备货单
Route::group('/stock/order', function () {
Route::any('/list', [app\crm\controller\StockOrdersController::class, 'index']);
Route::any('/show', [app\crm\controller\StockOrdersController::class, 'show']);
@@ -452,6 +453,7 @@ Route::group('/crm', function () {
Route::any('/ultimate/approval', [app\crm\controller\StockOrdersController::class, 'ultimateApproval']);
Route::any('/nullify/approval', [app\crm\controller\StockOrdersController::class, 'approvalNullify']);
Route::any('/revoke/approval', [app\crm\controller\StockOrdersController::class, 'approvalRevoke']);
Route::any('/destroy', [app\crm\controller\StockOrdersController::class, 'destroy']);
});
// 审批记录
@@ -461,6 +463,7 @@ Route::group('/crm', function () {
Route::any('/action', [app\crm\controller\ApprovalRecordsController::class, 'approve']);
Route::any('/todo', [app\admin\controller\ApprovalRecordsController::class, 'todo']);
Route::any('/border', [app\admin\controller\ApprovalRecordsController::class, 'businessOrder']);
});
// 汽车颜色
+1
View File
@@ -32,6 +32,7 @@ class Events
public static function onClose($client_id)
{
self::$wsEvents->logout($client_id);
var_dump('关闭客户端ID:'.$client_id);
}