where('user_id', $user->id) ->where('is_active', true) ->with(['position.department', 'position.appointablePositions']) ->first(); } /** * 校验操作人是否有权将目标用户任命到指定职务 * * id=1 超级管理员绕过所有要目校验,可直接任命任意职务。 * * @return array{ok: bool, message: string} */ public function validateAppoint(User $operator, User $target, Position $targetPosition): array { // 超级管理员(id=1)特权:跳过职务和白名单校验,只检查被任命人是否已有职务 if ($operator->id === 1) { $existingPosition = $this->getActivePosition($target); if ($existingPosition) { $currentName = $existingPosition->position->name; return ['ok' => false, 'message' => "【{$target->username}】当前已担任【{$currentName}】,请先撤销其职务再重新任命。"]; } return ['ok' => true, 'message' => '超级管理员直接授权']; } // 操作人必须有在职职务 $operatorPosition = $this->getActivePosition($operator); if (! $operatorPosition) { return ['ok' => false, 'message' => '您当前无在职职务,无法进行任命操作。']; } // 校验任命白名单:目标职务是否在操作人职务的可任命列表内 $isAllowed = $operatorPosition->position ->appointablePositions() ->where('positions.id', $targetPosition->id) ->exists(); if (! $isAllowed) { return ['ok' => false, 'message' => "您的职务无权任命【{$targetPosition->name}】职位。"]; } // 检查目标职务是否已满员 if ($targetPosition->isFull()) { return ['ok' => false, 'message' => "【{$targetPosition->name}】职位人数已满,无法继续任命。"]; } // 检查被任命人是否已有在职职务 $existingPosition = $this->getActivePosition($target); if ($existingPosition) { $currentName = $existingPosition->position->name; return ['ok' => false, 'message' => "【{$target->username}】当前已担任【{$currentName}】,请先撤销其职务再重新任命。"]; } return ['ok' => true, 'message' => '校验通过']; } /** * 执行任命操作 * 任命成功后自动同步 user_level 并写入权限日志 * * @return array{ok: bool, message: string, userPosition?: UserPosition} */ public function appoint(User $operator, User $target, Position $targetPosition, ?string $remark = null): array { // 权限校验 $validation = $this->validateAppoint($operator, $target, $targetPosition); if (! $validation['ok']) { return $validation; } // id=1 超级管理员无需在职职务,直接任命 $operatorPosition = $operator->id === 1 ? null : $this->getActivePosition($operator); DB::transaction(function () use ($operator, $target, $targetPosition, $remark, $operatorPosition, &$userPosition) { // 创建任职记录 $userPosition = UserPosition::create([ 'user_id' => $target->id, 'position_id' => $targetPosition->id, 'appointed_by_user_id' => $operator->id, 'appointed_at' => now(), 'remark' => $remark, 'is_active' => true, ]); // 同步 user_level $target->update(['user_level' => $targetPosition->level]); // 写入权限操作日志 $this->logAuthority( operator: $operator, operatorPosition: $operatorPosition, actionType: 'appoint', target: $target, targetPosition: $targetPosition, remark: $remark ); }); return [ 'ok' => true, 'message' => "已成功将【{$target->username}】任命为【{$targetPosition->name}】。", 'userPosition' => $userPosition, ]; } /** * 校验操作人是否有权撤销目标用户的职务 * * id=1 超级管理员可直接撤销任意职务。 * * @return array{ok: bool, message: string} */ public function validateRevoke(User $operator, User $target): array { // 超级管理员(id=1)特权:跳过白名单校验,直接撤销任意职务 if ($operator->id === 1) { $targetPosition = $this->getActivePosition($target); if (! $targetPosition) { return ['ok' => false, 'message' => "【{$target->username}】当前没有在职职务。"]; } return ['ok' => true, 'message' => '超级管理员直接授权']; } // 操作人必须有在职职务 $operatorPosition = $this->getActivePosition($operator); if (! $operatorPosition) { return ['ok' => false, 'message' => '您当前无在职职务,无法进行撤职操作。']; } // 被撤销人必须有在职职务 $targetPosition = $this->getActivePosition($target); if (! $targetPosition) { return ['ok' => false, 'message' => "【{$target->username}】当前没有在职职务。"]; } // 操作人不能撤销自己 if ($operator->id === $target->id) { return ['ok' => false, 'message' => '不能撤销自己的职务。']; } // 操作人的任命白名单中必须包含目标职务(即有权任命该职务,也就有权撤销) $isAllowed = $operatorPosition->position ->appointablePositions() ->where('positions.id', $targetPosition->position_id) ->exists(); if (! $isAllowed) { return ['ok' => false, 'message' => "您的职务无权撤销【{$targetPosition->position->name}】职位的人员。"]; } return ['ok' => true, 'message' => '校验通过']; } /** * 执行撤销职务操作 * 撤销后 user_level 归 1,并写入权限日志 * * @return array{ok: bool, message: string} */ public function revoke(User $operator, User $target, ?string $remark = null): array { // 权限校验 $validation = $this->validateRevoke($operator, $target); if (! $validation['ok']) { return $validation; } $operatorPosition = $this->getActivePosition($operator); $targetUP = $this->getActivePosition($target); DB::transaction(function () use ($operator, $target, $remark, $operatorPosition, $targetUP) { // 撤销在职记录 $targetUP->update([ 'is_active' => false, 'revoked_at' => now(), 'revoked_by_user_id' => $operator->id, ]); // 关闭尚未结束的 duty_log(只结算今日,历史遗留置0) $target->activePosition?->dutyLogs() ->whereNull('logout_at') ->whereDate('login_at', today()) ->update([ 'logout_at' => now(), 'duration_seconds' => DB::raw('TIMESTAMPDIFF(SECOND, login_at, NOW())'), ]); // 历史遗留未关闭日志直接清零 $target->activePosition?->dutyLogs() ->whereNull('logout_at') ->whereDate('login_at', '<', today()) ->update([ 'logout_at' => DB::raw('login_at'), 'duration_seconds' => 0, ]); // 撤职后:按当前经验值重新计算等级(不再无条件归 1) // 这样用户撤职后能保留正常的经验升级成果 $recalcLevel = \App\Models\Sysparam::calculateLevel($target->exp_num ?? 0); $superLevel = (int) \App\Models\Sysparam::getValue('superlevel', '100'); // 不超过满级阈值 $safeLevel = max(1, min($recalcLevel, $superLevel - 1)); $target->update(['user_level' => $safeLevel]); // 写入权限操作日志 $this->logAuthority( operator: $operator, operatorPosition: $operatorPosition, actionType: 'revoke', target: $target, targetPosition: $targetUP->position, remark: $remark ); }); return [ 'ok' => true, 'message' => "已成功撤销【{$target->username}】的【{$targetUP->position->name}】职务,其等级已归 1。", ]; } /** * 记录权限操作日志(各类管理操作公共调用) * * @param string $actionType 操作类型(appoint/revoke/reward/warn/kick/mute/banip/other) */ public function logAuthority( User $operator, ?UserPosition $operatorPosition, string $actionType, User $target, ?Position $targetPosition = null, ?int $amount = null, ?string $remark = null, ): void { // 无在职职务的操作不记录(普通管理员通过 user_level 操作不进此表) if (! $operatorPosition) { return; } PositionAuthorityLog::create([ 'user_id' => $operator->id, 'user_position_id' => $operatorPosition->id, 'action_type' => $actionType, 'target_user_id' => $target->id, 'target_position_id' => $targetPosition?->id, 'amount' => $amount, 'remark' => $remark, ]); } /** * 获取视图用:操作人有权任命的职务列表(用于后台/弹窗任命下拉选择) * * @return \Illuminate\Database\Eloquent\Collection */ public function getAppointablePositions(User $operator) { $operatorPosition = $this->getActivePosition($operator); if (! $operatorPosition) { return collect(); } return $operatorPosition->position ->appointablePositions() ->with('department') ->orderByDesc('rank') ->get(); } }