..., 'code'=>...] 형태로 반환 */ protected function v(array $params, array $rules) { $v = Validator::make($params, $rules); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } return $v->validated(); } /** 목록 */ public function index(array $params) { $p = $this->v($params, [ 'q' => 'nullable|string|max:100', 'is_active' => 'nullable|in:0,1', 'page' => 'nullable|integer|min:1', 'per_page' => 'nullable|integer|min:1|max:200', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $q = Department::query(); if (isset($p['is_active'])) { $q->where('is_active', (int)$p['is_active']); } if (!empty($p['q'])) { $q->where(function ($w) use ($p) { $w->where('name', 'like', '%' . $p['q'] . '%') ->orWhere('code', 'like', '%' . $p['q'] . '%'); }); } $q->orderBy('sort_order')->orderBy('name'); $perPage = $p['per_page'] ?? 20; $page = $p['page'] ?? null; return $q->paginate($perPage, ['*'], 'page', $page); } /** 생성 */ public function store(array $params) { // 테넌트 강제가 필요하면 아래 라인 사용: // $this->tenantIdOrFail(); $p = $this->v($params, [ 'code' => 'nullable|string|max:50', 'name' => 'required|string|max:100', 'description' => 'nullable|string|max:255', 'is_active' => 'nullable|in:0,1', 'sort_order' => 'nullable|integer', 'created_by' => 'nullable|integer|min:1', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; if (!empty($p['code'])) { $exists = Department::query()->where('code', $p['code'])->exists(); if ($exists) return ['error' => '이미 존재하는 부서 코드입니다.', 'code' => 409]; } $dept = Department::create([ 'code' => $p['code'] ?? null, 'name' => $p['name'], 'description' => $p['description'] ?? null, 'is_active' => isset($p['is_active']) ? (int)$p['is_active'] : 1, 'sort_order' => $p['sort_order'] ?? 0, 'created_by' => $p['created_by'] ?? null, 'updated_by' => $p['created_by'] ?? null, ]); return $dept->fresh(); } /** 단건 */ public function show(int $id, array $params = []) { if (!$id) return ['error' => 'id가 필요합니다.', 'code' => 400]; $res = Department::query()->find($id); if (!$res) { return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404]; } return $res; } /** 수정 */ public function update(int $id, array $params) { if (!$id) return ['error' => 'id가 필요합니다.', 'code' => 400]; $p = $this->v($params, [ 'code' => 'nullable|string|max:50', 'name' => 'nullable|string|max:100', 'description' => 'nullable|string|max:255', 'is_active' => 'nullable|in:0,1', 'sort_order' => 'nullable|integer', 'updated_by' => 'nullable|integer|min:1', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $dept = Department::query()->find($id); if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404]; if (array_key_exists('code', $p) && !is_null($p['code'])) { $exists = Department::query() ->where('code', $p['code']) ->where('id', '!=', $id) ->exists(); if ($exists) return ['error' => '이미 존재하는 부서 코드입니다.', 'code' => 409]; } $dept->fill([ 'code' => array_key_exists('code', $p) ? $p['code'] : $dept->code, 'name' => $p['name'] ?? $dept->name, 'description' => $p['description'] ?? $dept->description, 'is_active' => isset($p['is_active']) ? (int)$p['is_active'] : $dept->is_active, 'sort_order' => $p['sort_order'] ?? $dept->sort_order, 'updated_by' => $p['updated_by'] ?? $dept->updated_by, ])->save(); return $dept->fresh(); } /** 삭제(soft) */ public function destroy(int $id, array $params) { if (!$id) return ['error' => 'id가 필요합니다.', 'code' => 400]; $p = $this->v($params, [ 'deleted_by' => 'nullable|integer|min:1', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $dept = Department::query()->find($id); if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404]; if (!empty($p['deleted_by'])) { $dept->deleted_by = $p['deleted_by']; $dept->save(); } $dept->delete(); return ['id' => $id, 'deleted_at' => now()->toDateTimeString()]; } /** 부서 사용자 목록 */ public function listUsers(int $deptId, array $params) { $p = $this->v($params, [ 'page' => 'nullable|integer|min:1', 'per_page' => 'nullable|integer|min:1|max:200', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $dept = Department::query()->find($deptId); if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404]; $builder = $dept->departmentUsers()->with('user') ->orderByDesc('is_primary')->orderBy('id'); $perPage = $p['per_page'] ?? 20; $page = $p['page'] ?? null; return $builder->paginate($perPage, ['*'], 'page', $page); } /** 사용자 배정 (단건) */ public function attachUser(int $deptId, array $params) { $p = $this->v($params, [ 'user_id' => 'required|integer|min:1', 'is_primary' => 'nullable|in:0,1', 'joined_at' => 'nullable|date', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $dept = Department::query()->find($deptId); if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404]; $result = DB::transaction(function () use ($dept, $p) { $du = DepartmentUser::withTrashed() ->where('department_id', $dept->id) ->where('user_id', $p['user_id']) ->first(); if ($du && is_null($du->deleted_at)) { return ['error' => '이미 배정된 사용자입니다.', 'code' => 409]; } if (!empty($p['is_primary']) && (int)$p['is_primary'] === 1) { DepartmentUser::whereNull('deleted_at') ->where('user_id', $p['user_id']) ->update(['is_primary' => 0]); } $payload = [ 'department_id' => $dept->id, 'user_id' => $p['user_id'], 'is_primary' => isset($p['is_primary']) ? (int)$p['is_primary'] : 0, 'joined_at' => !empty($p['joined_at']) ? Carbon::parse($p['joined_at']) : now(), ]; if ($du) { $du->fill($payload); $du->restore(); $du->save(); } else { DepartmentUser::create($payload); } return ['department_id' => $dept->id, 'user_id' => $p['user_id']]; }); if ($result instanceof JsonResponse) return $result; return $result; } /** 사용자 제거(soft) */ public function detachUser(int $deptId, int $userId, array $params) { $du = DepartmentUser::whereNull('deleted_at') ->where('department_id', $deptId) ->where('user_id', $userId) ->first(); if (!$du) return ['error' => '배정된 사용자를 찾을 수 없습니다.', 'code' => 404]; $du->delete(); return [ 'user_id' => $userId, 'deleted_at' => now()->toDateTimeString(), ]; } /** 주부서 설정/해제 */ public function setPrimary(int $deptId, int $userId, array $params) { $p = $this->v($params, [ 'is_primary' => 'required|in:0,1', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $result = DB::transaction(function () use ($deptId, $userId, $p) { $du = DepartmentUser::whereNull('deleted_at') ->where('department_id', $deptId) ->where('user_id', $userId) ->first(); if (!$du) { return ['error' => '배정된 사용자를 찾을 수 없습니다.', 'code' => 404]; } if ((int)$p['is_primary'] === 1) { DepartmentUser::whereNull('deleted_at') ->where('user_id', $userId) ->update(['is_primary' => 0]); } $du->is_primary = (int)$p['is_primary']; $du->save(); return ['user_id' => $userId, 'department_id' => $deptId, 'is_primary' => $du->is_primary]; }); if ($result instanceof JsonResponse) return $result; return $result; } /** 부서 권한 목록 */ public function listPermissions(int $deptId, array $params) { $p = $this->v($params, [ 'menu_id' => 'nullable|integer|min:1', 'is_allowed' => 'nullable|in:0,1', 'page' => 'nullable|integer|min:1', 'per_page' => 'nullable|integer|min:1|max:200', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $dept = Department::query()->find($deptId); if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404]; $q = DepartmentPermission::query() ->whereNull('deleted_at') ->where('department_id', $deptId); if (isset($p['menu_id'])) $q->where('menu_id', $p['menu_id']); if (isset($p['is_allowed'])) $q->where('is_allowed', (int)$p['is_allowed']); $q->orderByDesc('is_allowed')->orderBy('permission_id'); $perPage = $p['per_page'] ?? 20; $page = $p['page'] ?? null; return $q->paginate($perPage, ['*'], 'page', $page); } /** 권한 부여/차단 upsert */ public function upsertPermission(int $deptId, array $params) { $p = $this->v($params, [ 'permission_id' => 'required|integer|min:1', 'menu_id' => 'nullable|integer|min:1', 'is_allowed' => 'nullable|in:0,1', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $dept = Department::query()->find($deptId); if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404]; $payload = [ 'department_id' => $deptId, 'permission_id' => $p['permission_id'], 'menu_id' => $p['menu_id'] ?? null, ]; $model = DepartmentPermission::withTrashed()->firstOrNew($payload); $model->is_allowed = isset($p['is_allowed']) ? (int)$p['is_allowed'] : 1; $model->deleted_at = null; $model->save(); // 변경 후 목록 반환 return $this->listPermissions($deptId, []); } /** 권한 제거 (menu_id 없으면 전체 제거) */ public function revokePermission(int $deptId, int $permissionId, array $params) { $p = $this->v($params, [ 'menu_id' => 'nullable|integer|min:1', ]); if ($p instanceof JsonResponse) return $p; if (isset($p['error'])) return $p; $q = DepartmentPermission::whereNull('deleted_at') ->where('department_id', $deptId) ->where('permission_id', $permissionId); if (isset($p['menu_id'])) $q->where('menu_id', $p['menu_id']); $rows = $q->get(); if ($rows->isEmpty()) return ['error' => '대상 권한을 찾을 수 없습니다.', 'code' => 404]; foreach ($rows as $row) $row->delete(); return [ 'permission_id' => $permissionId, 'menu_id' => $p['menu_id'] ?? null, 'deleted_count' => $rows->count(), ]; } }