diff --git a/app/admin/controller/ApprovalRecordsController.php b/app/admin/controller/ApprovalRecordsController.php index aebbaa4..d2f4b06 100644 --- a/app/admin/controller/ApprovalRecordsController.php +++ b/app/admin/controller/ApprovalRecordsController.php @@ -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'], diff --git a/app/admin/controller/StockOrdersController.php b/app/admin/controller/StockOrdersController.php index 8383f52..1d854b1 100644 --- a/app/admin/controller/StockOrdersController.php +++ b/app/admin/controller/StockOrdersController.php @@ -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'], diff --git a/app/admin/controller/UserGroupController.php b/app/admin/controller/UserGroupController.php index 8e5b586..709c6a5 100644 --- a/app/admin/controller/UserGroupController.php +++ b/app/admin/controller/UserGroupController.php @@ -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'], diff --git a/app/common/logic/UserGroupLogic.php b/app/common/logic/UserGroupLogic.php index 006ff60..7ec6860 100644 --- a/app/common/logic/UserGroupLogic.php +++ b/app/common/logic/UserGroupLogic.php @@ -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(); // 删除所有成员 diff --git a/app/common/model/ApprovalInstances.php b/app/common/model/ApprovalInstances.php index a7a2454..e683eb4 100644 --- a/app/common/model/ApprovalInstances.php +++ b/app/common/model/ApprovalInstances.php @@ -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(); } // ====== ====== ====== 映射表业务 ====== ====== ====== diff --git a/app/common/model/StockOrders.php b/app/common/model/StockOrders.php index 0b97fe4..2ea6ded 100644 --- a/app/common/model/StockOrders.php +++ b/app/common/model/StockOrders.php @@ -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. * diff --git a/app/common/model/UserGroup.php b/app/common/model/UserGroup.php index b8f893f..02d91c0 100644 --- a/app/common/model/UserGroup.php +++ b/app/common/model/UserGroup.php @@ -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']; /** * 不允许自动填充模型字段 diff --git a/app/common/service/ApprovalRecordsService.php b/app/common/service/ApprovalRecordsService.php index b1bc4ba..3b37365 100644 --- a/app/common/service/ApprovalRecordsService.php +++ b/app/common/service/ApprovalRecordsService.php @@ -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', diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index 3ccbdf7..6eab6dd 100644 --- a/app/common/service/StockOrdersService.php +++ b/app/common/service/StockOrdersService.php @@ -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); } /** diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php index 2a65f3e..a0f9a4c 100644 --- a/app/common/utils/WsEvents.php +++ b/app/common/utils/WsEvents.php @@ -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); + } + } + /** * 系统心跳 * diff --git a/app/common/utils/approval/service/ApprovalFlowService.php b/app/common/utils/approval/service/ApprovalFlowService.php index 8853913..6151002 100644 --- a/app/common/utils/approval/service/ApprovalFlowService.php +++ b/app/common/utils/approval/service/ApprovalFlowService.php @@ -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; + } + } \ No newline at end of file diff --git a/app/crm/controller/StockOrdersController.php b/app/crm/controller/StockOrdersController.php index f8c03d4..b297b51 100644 --- a/app/crm/controller/StockOrdersController.php +++ b/app/crm/controller/StockOrdersController.php @@ -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'], diff --git a/config/plugin/webman/gateway-worker/process.php b/config/plugin/webman/gateway-worker/process.php index a429973..16180d9 100644 --- a/config/plugin/webman/gateway-worker/process.php +++ b/config/plugin/webman/gateway-worker/process.php @@ -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', diff --git a/config/route.php b/config/route.php index 5a6da1b..bcf0203 100644 --- a/config/route.php +++ b/config/route.php @@ -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']); }); // 汽车颜色 diff --git a/plugin/webman/gateway/Events.php b/plugin/webman/gateway/Events.php index d63708e..9c6573b 100644 --- a/plugin/webman/gateway/Events.php +++ b/plugin/webman/gateway/Events.php @@ -32,6 +32,7 @@ class Events public static function onClose($client_id) { + self::$wsEvents->logout($client_id); var_dump('关闭客户端ID:'.$client_id); }