From f86ef34bc876bf183a29aec64e02c8a249cf13db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Thu, 19 Mar 2026 21:58:07 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20[changes]=20=EA=B7=BC=ED=83=9C=ED=98=84?= =?UTF-8?q?=ED=99=A9=20attendance=20=EB=88=84=EB=9D=BD=20=EA=B7=BC?= =?UTF-8?q?=EB=B3=B8=20=EC=9B=90=EC=9D=B8=20=EB=B6=84=EC=84=9D=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes/20260319_attendance_leave_sync_fix.md | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/changes/20260319_attendance_leave_sync_fix.md b/changes/20260319_attendance_leave_sync_fix.md index f5b62b4..ec2e809 100644 --- a/changes/20260319_attendance_leave_sync_fix.md +++ b/changes/20260319_attendance_leave_sync_fix.md @@ -1,4 +1,4 @@ -# 근태현황 승인된 휴가 누락 attendance 레코드 자동 보정 +# 근태현황 승인된 휴가 누락 버그 수정 **날짜:** 2026-03-19 **작업자:** Claude Code @@ -6,43 +6,65 @@ ## 변경 개요 휴가관리에서 3명의 휴가가 승인 상태인데, 근태현황에는 2명만 표시되는 버그 수정. -승인된 휴가에 대응하는 `attendances` 레코드가 누락된 경우, 근태 목록/통계/엑셀 조회 시 자동으로 누락 레코드를 생성하는 방어 로직 추가. -## 원인 분석 +## 근본 원인 -휴가 승인 흐름: 결재 최종 승인 → `handleApprovalCompleted` → `approveByApproval` → `createAttendanceRecords` +`Attendance` 모델의 `$fillable`에 `deleted_at`이 포함되지 않아서, `updateOrCreate()`에서 `'deleted_at' => null` 설정이 **mass assignment 보호에 의해 무시**됨. -이 흐름은 단일 DB 트랜잭션 내에서 실행되어 코드 상 정상이나, 특정 조건에서 attendance 레코드가 누락되는 케이스가 발생. 근본 원인 특정은 어렵지만, 데이터 정합성을 보장하기 위해 조회 시점에 보정하는 방어 로직을 추가. +### 재현 시나리오 + +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에 없어서 무시됨 +); +``` ## 수정된 파일 | 파일 | 변경 내용 | |------|----------| -| `app/Services/HR/AttendanceService.php` | `syncApprovedLeaveAttendances()` 메서드 추가, `getAttendances()`/`getExportData()`/`getMonthlyStats()`에서 호출 | +| `app/Services/HR/LeaveService.php` | `createAttendanceRecords()` — `restore()` 메서드로 soft-delete 복원 (근본 원인 수정) | +| `app/Services/HR/AttendanceService.php` | `syncApprovedLeaveAttendances()` 추가 — 조회 시 누락 레코드 자동 보정 (방어 로직) | ## 상세 변경 사항 -### `syncApprovedLeaveAttendances()` 메서드 추가 +### 1. 근본 원인 수정 (`LeaveService`) -- 조회 기간 내 승인된 휴가(`leaves.status = 'approved'`) 중 `ATTENDANCE_STATUS_MAP`에 매핑이 있는 유형만 대상 -- 기간 내 기존 `attendances` 레코드를 `user_id + base_date` 조합으로 빠르게 조회 -- 누락된 날짜에 대해 `Attendance::withTrashed()->updateOrCreate()`로 생성 (soft-deleted 레코드도 복원) -- 주말은 건너뜀 +`updateOrCreate()` 후 `trashed()` 확인 → `restore()`로 복원: -### 호출 위치 +```php +$attendance = Attendance::withTrashed()->updateOrCreate(...); -| 메서드 | 용도 | -|--------|------| -| `getAttendances()` | 근태 목록 조회 (HTMX 테이블) | -| `getExportData()` | 엑셀 내보내기 | -| `getMonthlyStats()` | 월간 통계 카드 (정시출근/지각/결근/휴가/기타) | +if ($attendance->trashed()) { + $attendance->restore(); + $attendance->update(['deleted_by' => null]); +} +``` + +### 2. 방어 로직 추가 (`AttendanceService`) + +`syncApprovedLeaveAttendances()` 메서드 — 근태 목록/통계/엑셀 조회 전 실행: + +- 조회 기간 내 승인된 휴가 중 attendance 레코드가 없는 건을 자동 생성 +- `getAttendances()`, `getExportData()`, `getMonthlyStats()`에서 호출 ## 테스트 체크리스트 -- [x] 승인된 휴가 3건 중 누락된 1건이 근태현황에 표시되는지 확인 -- [x] 기존 attendance 레코드가 있는 경우 중복 생성되지 않는지 확인 -- [x] 월간 통계 카드의 휴가 건수가 정확한지 확인 -- [ ] 운영서버 배포 후 실데이터 확인 +- [x] 승인된 휴가 3건 모두 근태현황에 표시 확인 (운영서버) +- [x] 기존 attendance 레코드가 있는 경우 중복 생성 안 됨 +- [x] soft-deleted 레코드가 정상 복원됨 (id:380 전진선) +- [x] 운영서버 배포 후 실데이터 확인 완료 ## 관련 문서