2026-03-19 21:58:07 +09:00
|
|
|
# 근태현황 승인된 휴가 누락 버그 수정
|
2026-03-19 21:47:20 +09:00
|
|
|
|
|
|
|
|
**날짜:** 2026-03-19
|
|
|
|
|
**작업자:** Claude Code
|
|
|
|
|
|
|
|
|
|
## 변경 개요
|
|
|
|
|
|
|
|
|
|
휴가관리에서 3명의 휴가가 승인 상태인데, 근태현황에는 2명만 표시되는 버그 수정.
|
|
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
## 근본 원인
|
2026-03-19 21:47:20 +09:00
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
`Attendance` 모델의 `$fillable`에 `deleted_at`이 포함되지 않아서, `updateOrCreate()`에서 `'deleted_at' => null` 설정이 **mass assignment 보호에 의해 무시**됨.
|
2026-03-19 21:47:20 +09:00
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
### 재현 시나리오
|
|
|
|
|
|
|
|
|
|
1. 전진선이 03-18 연차를 신청 → attendance 레코드(id:380) 자동 생성
|
|
|
|
|
2. 03-05에 해당 휴가가 취소됨 → attendance 레코드 **soft-delete** (`deleted_at = 2026-03-05`)
|
|
|
|
|
3. 03-16에 동일 날짜로 다시 연차 신청 → 결재 승인 → `createAttendanceRecords()` 호출
|
|
|
|
|
4. `Attendance::withTrashed()->updateOrCreate(...)` 실행 — soft-deleted 레코드를 찾아 `deleted_at => null` 설정 시도
|
|
|
|
|
5. **`deleted_at`이 `$fillable`에 없어서 무시됨** → 레코드는 여전히 soft-deleted 상태
|
|
|
|
|
6. 근태현황 조회 시 SoftDeletes 스코프에 의해 해당 레코드 미표시
|
|
|
|
|
|
|
|
|
|
### 핵심 코드
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// LeaveService::createAttendanceRecords() — 기존 코드 (버그)
|
|
|
|
|
Attendance::withTrashed()->updateOrCreate(
|
|
|
|
|
['tenant_id' => ..., 'user_id' => ..., 'base_date' => ...],
|
|
|
|
|
['status' => 'vacation', 'deleted_at' => null] // ← $fillable에 없어서 무시됨
|
|
|
|
|
);
|
|
|
|
|
```
|
2026-03-19 21:47:20 +09:00
|
|
|
|
|
|
|
|
## 수정된 파일
|
|
|
|
|
|
|
|
|
|
| 파일 | 변경 내용 |
|
|
|
|
|
|------|----------|
|
2026-03-19 21:58:07 +09:00
|
|
|
| `app/Services/HR/LeaveService.php` | `createAttendanceRecords()` — `restore()` 메서드로 soft-delete 복원 (근본 원인 수정) |
|
|
|
|
|
| `app/Services/HR/AttendanceService.php` | `syncApprovedLeaveAttendances()` 추가 — 조회 시 누락 레코드 자동 보정 (방어 로직) |
|
2026-03-19 21:47:20 +09:00
|
|
|
|
|
|
|
|
## 상세 변경 사항
|
|
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
### 1. 근본 원인 수정 (`LeaveService`)
|
|
|
|
|
|
|
|
|
|
`updateOrCreate()` 후 `trashed()` 확인 → `restore()`로 복원:
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
$attendance = Attendance::withTrashed()->updateOrCreate(...);
|
|
|
|
|
|
|
|
|
|
if ($attendance->trashed()) {
|
|
|
|
|
$attendance->restore();
|
|
|
|
|
$attendance->update(['deleted_by' => null]);
|
|
|
|
|
}
|
|
|
|
|
```
|
2026-03-19 21:47:20 +09:00
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
### 2. 방어 로직 추가 (`AttendanceService`)
|
2026-03-19 21:47:20 +09:00
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
`syncApprovedLeaveAttendances()` 메서드 — 근태 목록/통계/엑셀 조회 전 실행:
|
2026-03-19 21:47:20 +09:00
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
- 조회 기간 내 승인된 휴가 중 attendance 레코드가 없는 건을 자동 생성
|
|
|
|
|
- `getAttendances()`, `getExportData()`, `getMonthlyStats()`에서 호출
|
2026-03-19 21:47:20 +09:00
|
|
|
|
|
|
|
|
## 테스트 체크리스트
|
|
|
|
|
|
2026-03-19 21:58:07 +09:00
|
|
|
- [x] 승인된 휴가 3건 모두 근태현황에 표시 확인 (운영서버)
|
|
|
|
|
- [x] 기존 attendance 레코드가 있는 경우 중복 생성 안 됨
|
|
|
|
|
- [x] soft-deleted 레코드가 정상 복원됨 (id:380 전진선)
|
|
|
|
|
- [x] 운영서버 배포 후 실데이터 확인 완료
|
2026-03-19 21:47:20 +09:00
|
|
|
|
|
|
|
|
## 관련 문서
|
|
|
|
|
|
|
|
|
|
- `rules/attendance-api.md` — 근태 API 규칙
|
|
|
|
|
- `features/hr/` — 인사관리 기능
|