feat: [hr] 입퇴사자 현황 페이지 구현
- EmployeeService에 근속기간 조회/통계/CSV 내보내기 메서드 추가 - API 컨트롤러에 tenure/tenureExport 엔드포인트 추가 - EmployeeTenureController 뷰 컨트롤러 생성 - 통계 카드 6개 (전체/재직/퇴직/평균근속/올해입사/올해퇴사) - HTMX 테이블 (사원/부서/직책/상태/입사일/퇴사일/근속기간/근속일수) - 필터: 이름검색, 부서, 상태, 입사기간 범위, 정렬 - CSV 엑셀 다운로드 기능
This commit is contained in:
@@ -6,11 +6,13 @@
|
||||
use App\Models\Boards\File;
|
||||
use App\Services\GoogleCloudStorageService;
|
||||
use App\Services\HR\EmployeeService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class EmployeeController extends Controller
|
||||
{
|
||||
@@ -383,6 +385,119 @@ public function downloadFile(int $id, int $fileId, GoogleCloudStorageService $gc
|
||||
abort(404, '파일이 서버에 존재하지 않습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 입퇴사자 현황 목록 (HTMX → HTML / 일반 → JSON)
|
||||
*/
|
||||
public function tenure(Request $request): JsonResponse|Response
|
||||
{
|
||||
$employees = $this->employeeService->getEmployeeTenure(
|
||||
$request->all(),
|
||||
$request->integer('per_page', 50)
|
||||
);
|
||||
|
||||
// 근속기간 계산 추가
|
||||
$employees->getCollection()->each(function ($employee) {
|
||||
$hireDate = $employee->hire_date;
|
||||
if ($hireDate) {
|
||||
$hire = Carbon::parse($hireDate);
|
||||
$end = $employee->resign_date ? Carbon::parse($employee->resign_date) : today();
|
||||
$tenureDays = $hire->diffInDays($end);
|
||||
$diff = $hire->diff($end);
|
||||
|
||||
$employee->tenure_days = $tenureDays;
|
||||
$employee->tenure_label = $this->formatTenureLabel($diff);
|
||||
} else {
|
||||
$employee->tenure_days = 0;
|
||||
$employee->tenure_label = '-';
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->header('HX-Request')) {
|
||||
$stats = $this->employeeService->getTenureStats();
|
||||
|
||||
return response(view('hr.employee-tenure.partials.table', compact('employees', 'stats')));
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $employees->items(),
|
||||
'meta' => [
|
||||
'current_page' => $employees->currentPage(),
|
||||
'last_page' => $employees->lastPage(),
|
||||
'per_page' => $employees->perPage(),
|
||||
'total' => $employees->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 입퇴사자 현황 CSV 내보내기
|
||||
*/
|
||||
public function tenureExport(Request $request): StreamedResponse
|
||||
{
|
||||
$employees = $this->employeeService->getTenureExportData($request->all());
|
||||
|
||||
$filename = '입퇴사자현황_'.now()->format('Ymd').'.csv';
|
||||
|
||||
return response()->streamDownload(function () use ($employees) {
|
||||
$handle = fopen('php://output', 'w');
|
||||
|
||||
// BOM for Excel UTF-8
|
||||
fwrite($handle, "\xEF\xBB\xBF");
|
||||
|
||||
// 헤더
|
||||
fputcsv($handle, ['No.', '사원명', '부서', '직책', '상태', '입사일', '퇴사일', '근속기간', '근속일수']);
|
||||
|
||||
$index = 1;
|
||||
foreach ($employees as $employee) {
|
||||
$hireDate = $employee->hire_date;
|
||||
$tenureDays = 0;
|
||||
$tenureLabel = '-';
|
||||
|
||||
if ($hireDate) {
|
||||
$hire = Carbon::parse($hireDate);
|
||||
$end = $employee->resign_date ? Carbon::parse($employee->resign_date) : today();
|
||||
$tenureDays = $hire->diffInDays($end);
|
||||
$tenureLabel = $this->formatTenureLabel($hire->diff($end));
|
||||
}
|
||||
|
||||
$statusMap = ['active' => '재직', 'leave' => '휴직', 'resigned' => '퇴직'];
|
||||
|
||||
fputcsv($handle, [
|
||||
$index++,
|
||||
$employee->display_name ?? $employee->user?->name ?? '-',
|
||||
$employee->department?->name ?? '-',
|
||||
$employee->position_label ?? '-',
|
||||
$statusMap[$employee->employee_status] ?? $employee->employee_status,
|
||||
$employee->hire_date ?? '-',
|
||||
$employee->resign_date ?? '-',
|
||||
$tenureLabel,
|
||||
$tenureDays,
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}, $filename, [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
|
||||
private function formatTenureLabel(\DateInterval $diff): string
|
||||
{
|
||||
$parts = [];
|
||||
if ($diff->y > 0) {
|
||||
$parts[] = "{$diff->y}년";
|
||||
}
|
||||
if ($diff->m > 0) {
|
||||
$parts[] = "{$diff->m}개월";
|
||||
}
|
||||
if ($diff->d > 0 || empty($parts)) {
|
||||
$parts[] = "{$diff->d}일";
|
||||
}
|
||||
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 직급/직책 추가
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user