permissions->allows($request->user(), Permission::TeamsView), 403); $query = Team::query()->where('organization_id', $this->tenant->id()); if ($request->user()->role === UserRole::Manager) { $query->whereHas('managers', fn ($manager) => $manager->whereKey($request->user()->getKey())); } $teams = $query->withCount(['members', 'managers'])->orderBy('name')->get()->map(fn (Team $team) => $this->payload($team)); return response()->json(['data' => $teams]); } public function store(StoreTeamRequest $request): JsonResponse { $this->authorizeDesigner($request); $team = DB::transaction(function () use ($request): Team { $team = Team::query()->create([ 'organization_id' => $this->tenant->id(), 'name' => $request->validated('name'), 'description' => $request->validated('description'), 'status' => 'active', 'created_by' => $request->user()->getKey(), ]); if ($request->filled('managerId')) { $this->attachManagerAndReports($team, $this->manager($request->validated('managerId'))); } return $team->loadCount(['members', 'managers']); }); return response()->json(['data' => $this->payload($team)], 201); } public function show(Request $request, string $team): JsonResponse { abort_unless($this->permissions->allows($request->user(), Permission::TeamsView), 403); $query = Team::query()->where('organization_id', $this->tenant->id()); if ($request->user()->role === UserRole::Manager) { $query->whereHas('managers', fn ($manager) => $manager->whereKey($request->user()->getKey())); } $model = $query ->withCount(['members', 'managers']) ->with(['members:id,name,email,role,status,department,job_level,direct_manager_id', 'managers:id,name,email,role,status,department,job_level,direct_manager_id']) ->findOrFail($team); return response()->json(['data' => array_merge($this->payload($model), [ 'members' => $model->members->map(fn (User $user) => $this->userPayload($user)), 'managers' => $model->managers->map(fn (User $user) => $this->userPayload($user)), ])]); } public function update(UpdateTeamRequest $request, string $team): JsonResponse { $this->authorizeDesigner($request); $model = Team::query()->where('organization_id', $this->tenant->id())->findOrFail($team); $model->update($request->safe()->only(['name', 'description'])); return response()->json(['data' => $this->payload($model)]); } public function attachMember(AttachTeamUserRequest $request, string $team): JsonResponse { $this->authorizeDesigner($request); [$targetTeam, $user] = $this->resolveTeamAndUser($team, $request->validated('userId')); $targetTeam->members()->syncWithoutDetaching([$user->getKey()]); $this->assignmentResolver->syncOrganization($this->tenant->id()); return response()->json(['data' => ['attached' => true]]); } public function attachManager(AttachTeamUserRequest $request, string $team): JsonResponse { $this->authorizeDesigner($request); $targetTeam = Team::query()->where('organization_id', $this->tenant->id())->findOrFail($team); $this->attachManagerAndReports($targetTeam, $this->manager($request->validated('userId'))); $this->assignmentResolver->syncOrganization($this->tenant->id()); return response()->json(['data' => ['attached' => true]]); } public function detachMember(Request $request, string $team, string $user): JsonResponse { $this->authorizeDesigner($request); [$targetTeam, $targetUser] = $this->resolveTeamAndUser($team, $user); $targetTeam->members()->detach($targetUser); return response()->json(['data' => ['detached' => true]]); } public function detachManager(Request $request, string $team, string $user): JsonResponse { $this->authorizeDesigner($request); [$targetTeam, $targetUser] = $this->resolveTeamAndUser($team, $user); $targetTeam->managers()->detach($targetUser); return response()->json(['data' => ['detached' => true]]); } private function authorizeDesigner(Request $request): void { abort_unless($this->permissions->allows($request->user(), Permission::TeamsManage), 403); } private function resolveTeamAndUser(string $team, string $user): array { $targetTeam = Team::query()->where('organization_id', $this->tenant->id())->findOrFail($team); $targetUser = User::query()->where('organization_id', $this->tenant->id())->findOrFail($user); return [$targetTeam, $targetUser]; } private function manager(string $user): User { $manager = User::query()->where('organization_id', $this->tenant->id())->findOrFail($user); if ($manager->role !== UserRole::Manager) { throw ValidationException::withMessages(['userId' => ['Only a Manager may manage a team.']]); } return $manager; } private function attachManagerAndReports(Team $team, User $manager): void { $team->managers()->syncWithoutDetaching([$manager->getKey()]); $reports = User::query() ->where('organization_id', $this->tenant->id()) ->where('direct_manager_id', $manager->getKey()) ->where('status', 'active') ->pluck('id') ->all(); $team->members()->syncWithoutDetaching($reports); } private function payload(Team $team): array { return [ 'id' => $team->getKey(), 'name' => $team->name, 'description' => $team->description, 'status' => $team->status, 'memberCount' => $team->members_count ?? 0, 'managerCount' => $team->managers_count ?? 0, ]; } private function userPayload(User $user): array { return [ 'id' => $user->getKey(), 'name' => $user->name, 'email' => $user->email, 'role' => $user->role->value, 'status' => $user->status->value, 'department' => $user->department, 'jobLevel' => $user->job_level, 'directManagerId' => $user->direct_manager_id, ]; } }