# 근태관리 기획서 > **작성일**: 2026-02-26 > **상태**: 1차 구현 완료 / 2차 고도화 예정 > **담당**: MNG 인사관리 모듈 --- ## 1. 개요 ### 1.1 목적 사원의 일별 출퇴근 및 근태 상태를 체계적으로 관리하여, 급여 산정·인사 평가·법정 근로시간 준수의 기초 데이터를 확보한다. ### 1.2 핵심 원칙 | 원칙 | 설명 | |------|------| | **정확성** | 출퇴근 시간·상태를 실시간으로 정확히 기록 | | **자동화** | 지각/정시 자동 판정, 근무시간 자동 계산 | | **유연성** | 휴가·출장·외근·재택 등 다양한 근무 형태 지원 | | **감사 추적** | 모든 등록·수정·삭제에 작업자 기록 | --- ## 2. 화면 구성 ### 2.1 메뉴 위치 ``` 인사관리 ├── 사원관리 ├── 근태관리 ← 현재 문서 ├── 달력 관리 └── (향후 확장) ``` ### 2.2 메인 화면 레이아웃 ``` ┌─────────────────────────────────────────────────────────────┐ │ 근태현황 [+ 근태 등록] │ │ 2026년 2월 현재 │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐│ │ │ 정시출근 │ │ 지각 │ │ 결근 │ │ 휴가 │ │ 기타 ││ │ │ 42건 │ │ 3건 │ │ 0건 │ │ 5건 │ │ 2건 ││ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └────────┘│ ├─────────────────────────────────────────────────────────────┤ │ [검색: 사원명] [부서 ▼] [상태 ▼] [시작일] [종료일] [검색] │ ├─────────────────────────────────────────────────────────────┤ │ 날짜 │ 사원 │ 부서 │ 상태 │ 출근 │ 퇴근 │ 비고 │ │──────────┼────────┼────────┼────────┼───────┼───────┼──────│ │ 02-26 │ 홍길동 │ 개발팀 │ 정시 │ 08:55 │ 18:10 │ │ │ 02-26 │ 김철수 │ 영업팀 │ 지각 │ 09:15 │ 18:30 │ │ │ 02-25 │ 이영희 │ 경영 │ 휴가 │ - │ - │ 연차 │ ├─────────────────────────────────────────────────────────────┤ │ ◀ 1 2 3 4 5 ▶ │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.3 근태 등록/수정 모달 ``` ┌──────────────── 근태 등록 ─────────────────┐ │ │ │ 사원 [홍길동 ▼] │ │ 날짜 [2026-02-26] │ │ 상태 [정시출근 ▼] │ │ 출근시간 [09:00] │ │ 퇴근시간 [18:00] │ │ 비고 [ ] │ │ │ │ [취소] [저장] │ └─────────────────────────────────────────────┘ ``` --- ## 3. 근태 상태 유형 | 상태 | 코드 | 색상 | 설명 | |------|------|------|------| | 정시출근 | `onTime` | 🟢 emerald | 정해진 출근 시간 내 출근 | | 지각 | `late` | 🟡 amber | 출근 시간 초과 후 출근 | | 결근 | `absent` | 🔴 red | 무단 미출근 | | 휴가 | `vacation` | 🔵 blue | 연차·반차·병가 등 | | 출장 | `businessTrip` | 🟣 purple | 사외 업무 수행 | | 외근 | `fieldWork` | 🟤 indigo | 외부 근무 | | 야근 | `overtime` | 🟠 orange | 정규시간 초과 근무 | | 재택 | `remote` | 🟦 teal | 원격 근무 | --- ## 4. 데이터 구조 ### 4.1 attendances 테이블 | 컬럼 | 타입 | 설명 | |------|------|------| | `id` | BIGINT PK | 고유 ID | | `tenant_id` | BIGINT FK | 테넌트 ID | | `user_id` | BIGINT FK | 사용자 ID | | `base_date` | DATE | 기준 일자 | | `status` | ENUM | 근태 상태 (8종) | | `json_details` | JSON | 상세 정보 (출퇴근 시간, GPS 등) | | `remarks` | VARCHAR(500) | 비고 | | `created_by` | BIGINT | 등록자 | | `updated_by` | BIGINT | 수정자 | | `deleted_by` | BIGINT | 삭제자 | | `created_at` | TIMESTAMP | 등록일시 | | `updated_at` | TIMESTAMP | 수정일시 | | `deleted_at` | TIMESTAMP | 삭제일시 (Soft Delete) | **제약 조건**: - `UNIQUE (tenant_id, user_id, base_date)` — 일자별 사용자당 1건만 허용 ### 4.2 json_details 구조 ```json { "check_in": "09:00:00", "check_out": "18:00:00", "check_ins": [ { "time": "09:00:00", "recorded_at": "2026-02-26T09:00:00+09:00" } ], "check_outs": [ { "time": "18:00:00", "recorded_at": "2026-02-26T18:00:00+09:00" } ], "gps_data": { "check_in": { "lat": 37.5665, "lng": 126.9780, "accuracy": 10 }, "check_out": { "lat": 37.5665, "lng": 126.9780, "accuracy": 10 } }, "work_minutes": 480, "break_minutes": 60, "overtime_minutes": 0, "late_minutes": 0, "early_leave_minutes": 0, "vacation_type": "annual", "external_work": { "location": "고객사", "purpose": "미팅", "start_time": "14:00", "end_time": "16:00" } } ``` ### 4.3 attendance_settings 테이블 | 컬럼 | 타입 | 설명 | |------|------|------| | `tenant_id` | BIGINT UNIQUE | 테넌트별 1건 | | `use_gps` | BOOLEAN | GPS 출퇴근 사용 여부 | | `use_auto` | BOOLEAN | 자동 출퇴근 사용 여부 | | `allowed_radius` | INT | 허용 반경 (m, 기본 100) | | `hq_address` | VARCHAR(255) | 사업장 주소 | | `hq_latitude` | DECIMAL(10,8) | 사업장 위도 | | `hq_longitude` | DECIMAL(11,8) | 사업장 경도 | --- ## 5. 비즈니스 규칙 ### 5.1 출퇴근 기록 | 규칙 | 설명 | |------|------| | R1 | 같은 날짜 + 사용자 조합은 1건만 존재 (Upsert) | | R2 | 출근 기록 시 WorkSetting의 `start_time` 기준으로 지각/정시 자동 판정 | | R3 | 퇴근 기록 시 근무시간 자동 계산: (최초 출근 ~ 최종 퇴근) - 휴게시간 | | R4 | 다중 출퇴근 기록 지원 — `check_ins`/`check_outs` 배열로 이력 관리 | | R5 | GPS 기록 시 사업장 반경 내 여부 검증 (Haversine 공식) | ### 5.2 상태 자동 판정 ``` 출근 시간 기록 시: ├── WorkSetting.start_time 이전 → 상태: 'onTime' (정시출근) ├── WorkSetting.start_time 이후 → 상태: 'late' (지각) └── WorkSetting 미설정 → 상태: 'onTime' (기본값) ``` ### 5.3 근무시간 계산 ``` 총 근무시간 = (가장 늦은 퇴근시간 - 가장 빠른 출근시간) - 휴게시간 휴게시간 산출: ├── WorkSetting에 break_start/break_end 설정 있음 │ └── 근무시간이 휴게 시간대를 포함하면 차감 └── 미설정 → 휴게시간 0분 ``` ### 5.4 권한 | 역할 | 조회 | 등록 | 수정 | 삭제 | |------|:----:|:----:|:----:|:----:| | 관리자 (admin) | ✅ 전체 | ✅ | ✅ | ✅ | | 부서장 | ✅ 소속 부서 | ✅ | ✅ | ❌ | | 일반 사원 | ✅ 본인 | ❌ | ❌ | ❌ | --- ## 6. 기능 목록 ### 6.1 1차 구현 (완료) | 기능 | 상태 | 설명 | |------|:----:|------| | 근태 목록 조회 | ✅ | 페이지네이션, 필터링, HTMX 테이블 | | 근태 등록/수정 | ✅ | 모달 기반 CRUD | | 근태 삭제 | ✅ | Soft Delete + 확인 대화상자 | | 월별 통계 카드 | ✅ | 정시/지각/결근/휴가/기타 집계 | | 필터링 | ✅ | 사원명 검색, 부서/상태/날짜 범위 필터 | | 출근/퇴근 기록 API | ✅ | check-in/check-out 엔드포인트 | | 근무시간 자동 계산 | ✅ | 출퇴근 시간 차 - 휴게시간 | | 상태 자동 판정 | ✅ | WorkSetting 기준 지각/정시 판별 | | 엑셀 내보내기 | ✅ | 월별 데이터 Excel 다운로드 | | GPS 설정 | ✅ | 사업장 좌표, 허용 반경 설정 | | 다중 출퇴근 기록 | ✅ | check_ins/check_outs 배열 관리 | | 감사 로그 | ✅ | created_by, updated_by, deleted_by | ### 6.2 2차 고도화 (예정) | 기능 | 우선순위 | 설명 | |------|:--------:|------| | 월간 캘린더 뷰 | 🔴 높음 | 달력 형태로 사원별 근태 현황 표시 | | 일괄 등록 | 🔴 높음 | 다수 사원의 근태를 한 번에 등록 (CSV/엑셀 업로드) | | 근태 승인 워크플로우 | 🟡 중간 | 휴가/출장 신청 → 부서장 승인 → 확정 | | 초과근무 알림 | 🟡 중간 | 주 52시간 초과 시 관리자 알림 | | 사원별 월간 요약 | 🟡 중간 | 개인별 월간 근무일수, 총 근무시간, 지각 횟수 등 | | GPS 출퇴근 (모바일) | 🟢 낮음 | 모바일 앱에서 GPS 기반 자동 출퇴근 | | 자동 결근 처리 | 🟢 낮음 | 영업일에 출근 기록 없으면 자동으로 결근 표시 | | 연차 관리 연동 | 🟢 낮음 | 휴가 상태 등록 시 잔여 연차 자동 차감 | --- ## 7. API 엔드포인트 ### 7.1 MNG 내부 API (HTMX) | Method | Path | 설명 | |--------|------|------| | GET | `/api/admin/hr/attendances` | 목록 조회 (HTML/JSON) | | POST | `/api/admin/hr/attendances` | 등록 | | PUT | `/api/admin/hr/attendances/{id}` | 수정 | | DELETE | `/api/admin/hr/attendances/{id}` | 삭제 | ### 7.2 외부 API (sam/api) | Method | Path | 설명 | |--------|------|------| | GET | `/api/v1/attendances` | 목록 조회 (페이지네이션) | | GET | `/api/v1/attendances/{id}` | 상세 조회 | | POST | `/api/v1/attendances` | 등록 | | PATCH | `/api/v1/attendances/{id}` | 수정 | | DELETE | `/api/v1/attendances/{id}` | 삭제 | | POST | `/api/v1/attendances/bulk-delete` | 일괄 삭제 | | POST | `/api/v1/attendances/check-in` | 출근 기록 | | POST | `/api/v1/attendances/check-out` | 퇴근 기록 | | GET | `/api/v1/attendances/monthly-stats` | 월별 통계 | | GET | `/api/v1/attendances/export` | 엑셀 내보내기 | --- ## 8. 프로세스 흐름 ### 8.1 관리자 근태 등록 ``` 관리자 → [근태 등록] 클릭 → 모달 열림 → 사원 선택, 날짜·상태·시간 입력 → [저장] 클릭 → Fetch POST /api/admin/hr/attendances → 같은 날짜+사용자 기존 기록 있으면 Upsert → 성공 → 테이블 HTMX 새로고침 ``` ### 8.2 사원 출근 (API) ``` 사원(모바일) → [출근] 버튼 → POST /api/v1/attendances/check-in { user_id, check_in_time, gps? } → 서버: WorkSetting.start_time 기준 지각/정시 판정 → 기존 기록 없으면 신규 생성 → 기존 기록 있으면 check_ins 배열에 추가 → 응답: 근태 레코드 반환 ``` ### 8.3 사원 퇴근 (API) ``` 사원(모바일) → [퇴근] 버튼 → POST /api/v1/attendances/check-out { user_id, check_out_time, gps? } → 서버: 근무시간 자동 계산 (가장 빠른 출근 ~ 가장 늦은 퇴근) - 휴게시간 → check_outs 배열에 추가 → work_minutes, overtime_minutes 업데이트 → 응답: 근태 레코드 반환 ``` --- ## 9. 관련 파일 ### MNG | 파일 | 설명 | |------|------| | `app/Http/Controllers/HR/AttendanceController.php` | 페이지 렌더링 | | `app/Http/Controllers/Api/Admin/HR/AttendanceController.php` | HTMX API | | `app/Models/HR/Attendance.php` | 모델 (Accessor 포함) | | `app/Services/HR/AttendanceService.php` | 비즈니스 로직 | | `resources/views/hr/attendances/index.blade.php` | 메인 페이지 | | `resources/views/hr/attendances/partials/table.blade.php` | 테이블 partial | ### API | 파일 | 설명 | |------|------| | `app/Http/Controllers/Api/V1/AttendanceController.php` | RESTful API | | `app/Models/Tenants/Attendance.php` | 모델 | | `app/Models/Tenants/AttendanceSetting.php` | GPS/자동 설정 | | `app/Services/AttendanceService.php` | 서비스 (642줄) | | `database/migrations/2025_12_09_*` | 테이블 생성 | ### 문서 | 파일 | 설명 | |------|------| | `docs/rules/attendance-api.md` | API 비즈니스 규칙 | | `docs/specs/erp-analysis/03-gps-attendance.md` | GPS 출퇴근 스펙 | | `docs/specs/erp-analysis/04-hr-management.md` | HR 시스템 분석 | --- **최종 업데이트**: 2026-02-26