221 lines
6.5 KiB
Markdown
221 lines
6.5 KiB
Markdown
|
|
# Attendance API (근태관리 API) 규칙
|
||
|
|
|
||
|
|
## 개요
|
||
|
|
|
||
|
|
근태관리 API는 테넌트 내 사용자의 출퇴근 및 근태 정보를 관리하는 API입니다.
|
||
|
|
`attendances` 테이블을 사용하며, 상세 출퇴근 정보는 `json_details` 필드에 저장합니다.
|
||
|
|
|
||
|
|
## 핵심 모델
|
||
|
|
|
||
|
|
### Attendance
|
||
|
|
|
||
|
|
- **위치**: `App\Models\Tenants\Attendance`
|
||
|
|
- **역할**: 일별 근태 기록
|
||
|
|
- **특징**:
|
||
|
|
- `BelongsToTenant` 트레이트 사용 (멀티테넌트 자동 스코핑)
|
||
|
|
- `SoftDeletes` 적용
|
||
|
|
- `json_details` 필드에 상세 출퇴근 정보 저장
|
||
|
|
|
||
|
|
## 엔드포인트
|
||
|
|
|
||
|
|
| Method | Path | 설명 |
|
||
|
|
|--------|------|------|
|
||
|
|
| GET | `/v1/attendances` | 근태 목록 조회 |
|
||
|
|
| GET | `/v1/attendances/{id}` | 근태 상세 조회 |
|
||
|
|
| POST | `/v1/attendances` | 근태 등록 |
|
||
|
|
| PATCH | `/v1/attendances/{id}` | 근태 수정 |
|
||
|
|
| DELETE | `/v1/attendances/{id}` | 근태 삭제 |
|
||
|
|
| DELETE | `/v1/attendances/bulk` | 근태 일괄 삭제 |
|
||
|
|
| POST | `/v1/attendances/check-in` | 출근 기록 |
|
||
|
|
| POST | `/v1/attendances/check-out` | 퇴근 기록 |
|
||
|
|
| GET | `/v1/attendances/monthly-stats` | 월간 통계 |
|
||
|
|
|
||
|
|
## 데이터 구조
|
||
|
|
|
||
|
|
### 기본 필드
|
||
|
|
|
||
|
|
| 필드 | 타입 | 설명 |
|
||
|
|
|------|------|------|
|
||
|
|
| `id` | int | PK |
|
||
|
|
| `tenant_id` | int | 테넌트 ID |
|
||
|
|
| `user_id` | int | 사용자 ID (FK → users) |
|
||
|
|
| `base_date` | date | 기준 일자 |
|
||
|
|
| `status` | string | 근태 상태 |
|
||
|
|
| `json_details` | json | 상세 출퇴근 정보 |
|
||
|
|
| `remarks` | string | 비고 (500자 제한) |
|
||
|
|
| `created_by` | int | 생성자 |
|
||
|
|
| `updated_by` | int | 수정자 |
|
||
|
|
| `deleted_by` | int | 삭제자 |
|
||
|
|
| `deleted_at` | timestamp | Soft Delete |
|
||
|
|
|
||
|
|
### 근태 상태 (status)
|
||
|
|
|
||
|
|
| 상태 | 설명 |
|
||
|
|
|------|------|
|
||
|
|
| `onTime` | 정상 출근 (기본값) |
|
||
|
|
| `late` | 지각 |
|
||
|
|
| `absent` | 결근 |
|
||
|
|
| `vacation` | 휴가 |
|
||
|
|
| `businessTrip` | 출장 |
|
||
|
|
| `fieldWork` | 외근 |
|
||
|
|
| `overtime` | 야근 |
|
||
|
|
| `remote` | 재택근무 |
|
||
|
|
|
||
|
|
### json_details 필드 구조
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"check_in": "09:00:00",
|
||
|
|
"check_out": "18:00:00",
|
||
|
|
"gps_data": {
|
||
|
|
"check_in": {
|
||
|
|
"lat": 37.5665,
|
||
|
|
"lng": 126.9780,
|
||
|
|
"accuracy": 10
|
||
|
|
},
|
||
|
|
"check_out": {
|
||
|
|
"lat": 37.5665,
|
||
|
|
"lng": 126.9780,
|
||
|
|
"accuracy": 10
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"external_work": {
|
||
|
|
"location": "고객사",
|
||
|
|
"purpose": "미팅",
|
||
|
|
"start_time": "14:00:00",
|
||
|
|
"end_time": "16:00:00"
|
||
|
|
},
|
||
|
|
"multiple_entries": [
|
||
|
|
{ "in": "09:00:00", "out": "12:00:00" },
|
||
|
|
{ "in": "13:00:00", "out": "18:00:00" }
|
||
|
|
],
|
||
|
|
"work_minutes": 480,
|
||
|
|
"overtime_minutes": 60,
|
||
|
|
"late_minutes": 30,
|
||
|
|
"early_leave_minutes": 0,
|
||
|
|
"vacation_type": "annual|half|sick"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 허용된 json_details 키
|
||
|
|
|
||
|
|
```php
|
||
|
|
$allowedKeys = [
|
||
|
|
'check_in', // 출근 시간 (HH:MM:SS)
|
||
|
|
'check_out', // 퇴근 시간 (HH:MM:SS)
|
||
|
|
'gps_data', // GPS 데이터 (출퇴근 위치)
|
||
|
|
'external_work', // 외근 정보
|
||
|
|
'multiple_entries', // 다중 출퇴근 기록
|
||
|
|
'work_minutes', // 총 근무 시간 (분)
|
||
|
|
'overtime_minutes', // 초과 근무 시간 (분)
|
||
|
|
'late_minutes', // 지각 시간 (분)
|
||
|
|
'early_leave_minutes',// 조퇴 시간 (분)
|
||
|
|
'vacation_type', // 휴가 유형
|
||
|
|
];
|
||
|
|
```
|
||
|
|
|
||
|
|
## 비즈니스 규칙
|
||
|
|
|
||
|
|
### 출근 기록 (check-in)
|
||
|
|
|
||
|
|
1. 오늘 기록이 있으면 업데이트, 없으면 새로 생성
|
||
|
|
2. `check_in` 시간과 GPS 데이터 저장
|
||
|
|
3. 출근 시간 기준으로 상태 자동 결정 (09:00 기준 지각 판단)
|
||
|
|
|
||
|
|
```php
|
||
|
|
// 상태 자동 결정 로직
|
||
|
|
if ($checkIn > '09:00:00') {
|
||
|
|
$status = 'late';
|
||
|
|
} else {
|
||
|
|
$status = 'onTime';
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 퇴근 기록 (check-out)
|
||
|
|
|
||
|
|
1. 오늘 출근 기록이 없으면 에러 반환
|
||
|
|
2. `check_out` 시간과 GPS 데이터 저장
|
||
|
|
3. 근무 시간(work_minutes) 자동 계산
|
||
|
|
|
||
|
|
```php
|
||
|
|
// 근무 시간 계산
|
||
|
|
$checkIn = Carbon::createFromFormat('H:i:s', $jsonDetails['check_in']);
|
||
|
|
$checkOut = Carbon::createFromFormat('H:i:s', $checkOutTime);
|
||
|
|
$jsonDetails['work_minutes'] = $checkOut->diffInMinutes($checkIn);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 근태 등록 (store)
|
||
|
|
|
||
|
|
1. 같은 날 같은 사용자 기록이 있으면 에러 반환
|
||
|
|
2. `json_details` 직접 전달 또는 개별 필드에서 구성
|
||
|
|
|
||
|
|
```php
|
||
|
|
// json_details 처리 방식
|
||
|
|
$jsonDetails = isset($data['json_details']) && is_array($data['json_details'])
|
||
|
|
? $data['json_details']
|
||
|
|
: $this->buildJsonDetails($data);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 월간 통계 (monthly-stats)
|
||
|
|
|
||
|
|
통계 항목:
|
||
|
|
- 총 근무일수
|
||
|
|
- 상태별 일수 (정상, 지각, 결근, 휴가, 출장, 외근, 야근, 재택)
|
||
|
|
- 총 근무 시간 (분)
|
||
|
|
- 총 초과 근무 시간 (분)
|
||
|
|
|
||
|
|
## 검색/필터 파라미터
|
||
|
|
|
||
|
|
| 파라미터 | 타입 | 설명 |
|
||
|
|
|----------|------|------|
|
||
|
|
| `user_id` | int | 사용자 필터 |
|
||
|
|
| `date` | date | 특정 날짜 필터 |
|
||
|
|
| `date_from` | date | 시작 날짜 |
|
||
|
|
| `date_to` | date | 종료 날짜 |
|
||
|
|
| `status` | string | 근태 상태 필터 |
|
||
|
|
| `department_id` | int | 부서 필터 (사용자의 부서) |
|
||
|
|
| `sort_by` | string | 정렬 기준 (기본: base_date) |
|
||
|
|
| `sort_dir` | string | 정렬 방향 (기본: desc) |
|
||
|
|
| `per_page` | int | 페이지당 항목 수 (기본: 20) |
|
||
|
|
|
||
|
|
## 관계 (Relationships)
|
||
|
|
|
||
|
|
```php
|
||
|
|
public function user(): BelongsTo // 사용자 정보
|
||
|
|
public function creator(): BelongsTo // 생성자
|
||
|
|
public function updater(): BelongsTo // 수정자
|
||
|
|
```
|
||
|
|
|
||
|
|
## 스코프 (Scopes)
|
||
|
|
|
||
|
|
```php
|
||
|
|
$query->onDate('2024-01-15'); // 특정 날짜
|
||
|
|
$query->betweenDates('2024-01-01', '2024-01-31'); // 날짜 범위
|
||
|
|
$query->forUser(123); // 특정 사용자
|
||
|
|
$query->withStatus('late'); // 특정 상태
|
||
|
|
```
|
||
|
|
|
||
|
|
## Accessor
|
||
|
|
|
||
|
|
```php
|
||
|
|
$attendance->check_in; // json_details['check_in']
|
||
|
|
$attendance->check_out; // json_details['check_out']
|
||
|
|
$attendance->gps_data; // json_details['gps_data']
|
||
|
|
$attendance->external_work; // json_details['external_work']
|
||
|
|
$attendance->multiple_entries; // json_details['multiple_entries']
|
||
|
|
$attendance->work_minutes; // json_details['work_minutes']
|
||
|
|
$attendance->overtime_minutes; // json_details['overtime_minutes']
|
||
|
|
$attendance->late_minutes; // json_details['late_minutes']
|
||
|
|
$attendance->early_leave_minutes;// json_details['early_leave_minutes']
|
||
|
|
$attendance->vacation_type; // json_details['vacation_type']
|
||
|
|
```
|
||
|
|
|
||
|
|
## 주의사항
|
||
|
|
|
||
|
|
1. **중복 방지**: 같은 날짜 + 같은 사용자 조합은 유일해야 함
|
||
|
|
2. **멀티테넌트**: BelongsToTenant 트레이트로 자동 스코핑
|
||
|
|
3. **Soft Delete**: deleted_by 기록 후 삭제
|
||
|
|
4. **Audit**: created_by/updated_by 자동 기록
|
||
|
|
5. **시간 형식**: check_in/check_out은 HH:MM:SS 형식
|
||
|
|
6. **표준 출근 시간**: 기본 09:00:00 (회사별 설정 필요)
|