- 모든 서비스를 인스턴스구조로 변경예정 * 규모가 커질수록 → 인스턴스(=DI) 설계가 유리 * 작고 단순한 유틸/순수 함수만 스태틱으로 유지 * DI/모킹/테스트 쉬움 (서비스 교체·부분 테스트 가능) * 의존성 교체 쉬움 (Repo/캐시/로거…) * 상태·컨텍스트 주입 명확 (테넌트/유저/트랜잭션)
381 lines
13 KiB
PHP
381 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Commons\Department;
|
|
use App\Models\Commons\DepartmentPermission;
|
|
use App\Models\Commons\DepartmentUser;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
class DepartmentService extends Service
|
|
{
|
|
/**
|
|
* 공통 검증 헬퍼: 실패 시 ['error'=>..., '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(),
|
|
];
|
|
}
|
|
}
|