사용자 관리: - 사용자 등록/수정 시 테넌트별 역할/부서 선택 기능 추가 - Department, UserRole, DepartmentUser 모델 추가 - User 모델에 역할/부서 관계 및 헬퍼 메서드 추가 - syncRoles/syncDepartments 메서드 (forceDelete로 유니크 키 충돌 방지) - 체크박스 UI로 다중 선택 지원 부서 관리: - Soft Delete 필터 (정상만/전체/삭제된 항목만) - 복구(restore) 및 영구삭제(forceDelete) 기능 추가 - Department 모델에 SoftDeletes 트레이트 추가 - 삭제된 항목 빨간 배경 + "삭제됨" 배지 표시
250 lines
6.5 KiB
PHP
250 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Tenants\Department;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
|
|
class DepartmentService
|
|
{
|
|
/**
|
|
* 부서 목록 조회 (페이지네이션)
|
|
*/
|
|
public function getDepartments(array $filters = [], int $perPage = 15): LengthAwarePaginator
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Department::query()->withTrashed()->with('parent');
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId) {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
// Soft Delete 필터
|
|
if (isset($filters['trashed'])) {
|
|
if ($filters['trashed'] === 'only') {
|
|
$query->onlyTrashed();
|
|
} elseif ($filters['trashed'] === 'with') {
|
|
$query->withTrashed();
|
|
}
|
|
}
|
|
|
|
// 검색 필터
|
|
if (! empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('code', 'like', "%{$search}%")
|
|
->orWhere('description', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// 활성 상태 필터
|
|
if (isset($filters['is_active'])) {
|
|
$query->where('is_active', $filters['is_active']);
|
|
}
|
|
|
|
// 정렬
|
|
$sortBy = $filters['sort_by'] ?? 'sort_order';
|
|
$sortDirection = $filters['sort_direction'] ?? 'asc';
|
|
$query->orderBy($sortBy, $sortDirection);
|
|
|
|
return $query->paginate($perPage);
|
|
}
|
|
|
|
/**
|
|
* 특정 부서 조회
|
|
*/
|
|
public function getDepartmentById(int $id): ?Department
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Department::query()->with(['parent', 'children']);
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId) {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
return $query->find($id);
|
|
}
|
|
|
|
/**
|
|
* 부서 생성
|
|
*/
|
|
public function createDepartment(array $data): Department
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$department = Department::create([
|
|
'tenant_id' => $tenantId,
|
|
'parent_id' => $data['parent_id'] ?? null,
|
|
'code' => $data['code'],
|
|
'name' => $data['name'],
|
|
'description' => $data['description'] ?? null,
|
|
'is_active' => $data['is_active'] ?? true,
|
|
'sort_order' => $data['sort_order'] ?? 0,
|
|
'created_by' => auth()->id(),
|
|
]);
|
|
|
|
return $department->fresh(['parent']);
|
|
}
|
|
|
|
/**
|
|
* 부서 수정
|
|
*/
|
|
public function updateDepartment(int $id, array $data): bool
|
|
{
|
|
$department = $this->getDepartmentById($id);
|
|
|
|
if (! $department) {
|
|
return false;
|
|
}
|
|
|
|
// 자기 자신을 상위 부서로 설정하는 것 방지
|
|
if (isset($data['parent_id']) && $data['parent_id'] == $id) {
|
|
return false;
|
|
}
|
|
|
|
$updated = $department->update([
|
|
'parent_id' => $data['parent_id'] ?? $department->parent_id,
|
|
'code' => $data['code'] ?? $department->code,
|
|
'name' => $data['name'] ?? $department->name,
|
|
'description' => $data['description'] ?? $department->description,
|
|
'is_active' => $data['is_active'] ?? $department->is_active,
|
|
'sort_order' => $data['sort_order'] ?? $department->sort_order,
|
|
'updated_by' => auth()->id(),
|
|
]);
|
|
|
|
return $updated;
|
|
}
|
|
|
|
/**
|
|
* 부서 삭제 (Soft Delete)
|
|
*/
|
|
public function deleteDepartment(int $id): bool
|
|
{
|
|
$department = $this->getDepartmentById($id);
|
|
|
|
if (! $department) {
|
|
return false;
|
|
}
|
|
|
|
// 하위 부서가 있는 경우 삭제 불가
|
|
if ($department->children()->count() > 0) {
|
|
return false;
|
|
}
|
|
|
|
$department->deleted_by = auth()->id();
|
|
$department->save();
|
|
|
|
return $department->delete();
|
|
}
|
|
|
|
/**
|
|
* 부서 복원
|
|
*/
|
|
public function restoreDepartment(int $id): bool
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Department::onlyTrashed();
|
|
|
|
if ($tenantId) {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
$department = $query->find($id);
|
|
|
|
if (! $department) {
|
|
return false;
|
|
}
|
|
|
|
return $department->restore();
|
|
}
|
|
|
|
/**
|
|
* 부서 영구 삭제
|
|
*/
|
|
public function forceDeleteDepartment(int $id): bool
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Department::withTrashed();
|
|
|
|
if ($tenantId) {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
$department = $query->find($id);
|
|
|
|
if (! $department) {
|
|
return false;
|
|
}
|
|
|
|
// 하위 부서가 있는 경우 삭제 불가
|
|
if ($department->children()->count() > 0) {
|
|
return false;
|
|
}
|
|
|
|
return $department->forceDelete();
|
|
}
|
|
|
|
/**
|
|
* 부서 코드 중복 체크
|
|
*/
|
|
public function isCodeExists(string $code, ?int $excludeId = null): bool
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Department::where('tenant_id', $tenantId)
|
|
->where('code', $code);
|
|
|
|
if ($excludeId) {
|
|
$query->where('id', '!=', $excludeId);
|
|
}
|
|
|
|
return $query->exists();
|
|
}
|
|
|
|
/**
|
|
* 활성 부서 목록 (드롭다운용)
|
|
*/
|
|
public function getActiveDepartments(): Collection
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Department::query()->where('is_active', true);
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId) {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
return $query->orderBy('sort_order')->orderBy('name')->get(['id', 'parent_id', 'code', 'name']);
|
|
}
|
|
|
|
/**
|
|
* 부서 통계
|
|
*/
|
|
public function getDepartmentStats(): array
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$baseQuery = Department::query();
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId) {
|
|
$baseQuery->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
return [
|
|
'total' => (clone $baseQuery)->count(),
|
|
'active' => (clone $baseQuery)->where('is_active', true)->count(),
|
|
'inactive' => (clone $baseQuery)->where('is_active', false)->count(),
|
|
];
|
|
}
|
|
}
|