diff --git a/app/Http/Controllers/RdController.php b/app/Http/Controllers/RdController.php index 721693d9..2d5ddee5 100644 --- a/app/Http/Controllers/RdController.php +++ b/app/Http/Controllers/RdController.php @@ -2,8 +2,11 @@ namespace App\Http\Controllers; +use App\Models\HR\Employee; use App\Models\Rd\AiQuotation; +use App\Models\Tenants\Department; use App\Services\Rd\AiQuotationService; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\View\View; @@ -28,6 +31,114 @@ public function index(Request $request): View|\Illuminate\Http\Response return view('rd.index', compact('dashboard', 'statuses')); } + /** + * 조직도 관리 + */ + public function orgChart(Request $request): View|\Illuminate\Http\Response + { + if ($request->header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('rd.org-chart')); + } + + $tenantId = session('selected_tenant_id'); + + // 부서 트리 (parent_id=null이 최상위) + $departments = Department::where('tenant_id', $tenantId) + ->where('is_active', true) + ->orderBy('sort_order') + ->orderBy('name') + ->get(); + + // 전체 직원 (활성 상태) + $employees = Employee::withoutGlobalScopes() + ->where('tenant_id', $tenantId) + ->where('employee_status', 'active') + ->with(['user', 'department']) + ->orderBy('display_name') + ->get(); + + // 미배치 직원 (department_id가 null) + $unassigned = $employees->whereNull('department_id'); + // 배치된 직원을 부서별로 그룹핑 + $assignedByDept = $employees->whereNotNull('department_id')->groupBy('department_id'); + + return view('rd.org-chart', compact('departments', 'employees', 'unassigned', 'assignedByDept')); + } + + /** + * 조직도 - 직원 부서 배치 + */ + public function orgChartAssign(Request $request): JsonResponse + { + $request->validate([ + 'employee_id' => 'required|integer', + 'department_id' => 'required|integer', + ]); + + $tenantId = session('selected_tenant_id'); + $employee = Employee::withoutGlobalScopes() + ->where('tenant_id', $tenantId) + ->where('id', $request->employee_id) + ->first(); + + if (! $employee) { + return response()->json(['success' => false, 'message' => '직원을 찾을 수 없습니다.'], 404); + } + + $employee->department_id = $request->department_id; + $employee->save(); + + return response()->json(['success' => true]); + } + + /** + * 조직도 - 직원 부서 해제 (미배치로 이동) + */ + public function orgChartUnassign(Request $request): JsonResponse + { + $request->validate([ + 'employee_id' => 'required|integer', + ]); + + $tenantId = session('selected_tenant_id'); + $employee = Employee::withoutGlobalScopes() + ->where('tenant_id', $tenantId) + ->where('id', $request->employee_id) + ->first(); + + if (! $employee) { + return response()->json(['success' => false, 'message' => '직원을 찾을 수 없습니다.'], 404); + } + + $employee->department_id = null; + $employee->save(); + + return response()->json(['success' => true]); + } + + /** + * 조직도 - 부서 내 직원 순서/이동 일괄 처리 + */ + public function orgChartReorder(Request $request): JsonResponse + { + $request->validate([ + 'moves' => 'required|array', + 'moves.*.employee_id' => 'required|integer', + 'moves.*.department_id' => 'nullable|integer', + ]); + + $tenantId = session('selected_tenant_id'); + + foreach ($request->moves as $move) { + Employee::withoutGlobalScopes() + ->where('tenant_id', $tenantId) + ->where('id', $move['employee_id']) + ->update(['department_id' => $move['department_id']]); + } + + return response()->json(['success' => true]); + } + /** * 중대재해처벌법 실무 점검 */ diff --git a/resources/views/rd/org-chart.blade.php b/resources/views/rd/org-chart.blade.php new file mode 100644 index 00000000..ec96d422 --- /dev/null +++ b/resources/views/rd/org-chart.blade.php @@ -0,0 +1,351 @@ +@extends('layouts.app') + +@section('title', '조직도 관리') + +@section('content') +
등록된 부서가 없습니다.
+먼저 부서를 등록해주세요.
+