[feat] 급여 지급일 전날 근무 기록 확인 알림 도입#125
Conversation
- NotificationType에 WORK_RECORD_CONFIRMATION 추가 - UserSettingsService switch에 신규 타입을 paymentAlertEnabled로 매핑 - WorkerContractRepository에 내일이 급여 지급일인 활성 계약 조회 쿼리 추가 (일반 날짜용 / 월말 처리용으로 분리) - PaymentDayReminderService: 급여 기간 계산, 근무 기록 요약 집계, 알림 이벤트 발행 - PaymentDayReminderScheduler: 매일 오전 9시 실행 스케줄러
- 일반 날짜에 paymentDay 일치 계약 알림 발송 검증 - 월말(2월 28일)에 findActiveContractsByPaymentDayOnLastDay 쿼리 사용 검증 - paymentDay=31인 계약의 2월 급여 기간 계산 정확성 검증 - 근무 기록 0건에도 알림 발송되는지 검증 - 대상 계약 없으면 이벤트 미발행 검증 - 특정 계약 예외 발생 시 다른 계약 알림 정상 발송 검증 - 알림 actionData에 contractId/year/month 포함 검증 - sendPaymentDayRemindersForDate(LocalDate) 패키지 프라이빗 메서드 추가로 날짜 주입 가능
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 개요급여 지급일 하루 전에 근로자에게 근무 기록 확인 알림을 발송하는 기능을 도입합니다. 스케줄러가 매일 오전 9시에 실행되어 활성 계약의 결제 일정을 확인하고, 해당 월의 근무 기록을 조회한 후 알림 이벤트를 발행합니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant Scheduler as PaymentDayReminderScheduler
participant Service as PaymentDayReminderService
participant ContractRepo as WorkerContractRepository
participant WorkRecordRepo as WorkRecordRepository
participant EventPublisher as ApplicationEventPublisher
participant NotificationSystem as 알림 시스템
Scheduler->>Service: sendPaymentDayReminders()
activate Service
Service->>ContractRepo: findActiveContractsByExactPaymentDay(내일)
ContractRepo-->>Service: 계약 목록
loop 각 계약별
Service->>WorkRecordRepo: 해당 월 근무 기록 조회
WorkRecordRepo-->>Service: 근무 기록 목록
Service->>Service: 총 근무시간 계산
Service->>EventPublisher: NotificationEvent 발행<br/>(WORK_RECORD_CONFIRMATION, VIEW_SALARY)
EventPublisher->>NotificationSystem: 알림 이벤트 처리
end
deactivate Service
Scheduler->>Scheduler: 로깅
예상 코드 리뷰 노력🎯 3 (Moderate) | ⏱️ ~25 분 관련 PR
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/main/java/com/example/paycheck/domain/salary/service/PaymentDayReminderService.java (3)
131-142:buildActionData실패 시null이actionData로 설정됩니다.
ObjectMapper직렬화 실패 시null을 반환하여 알림이actionData없이 발행됩니다. 알림 소비자 측에서actionData가null인 경우를 처리할 수 있는지 확인이 필요합니다.ObjectMapper직렬화가HashMap에 대해 실패하는 경우는 거의 없지만, 방어적으로 빈 JSON 객체("{}")를 fallback으로 반환하는 것을 고려해 보세요.💡 fallback 제안
} catch (JsonProcessingException e) { log.warn("알림 액션 데이터 생성 실패: contractId={}", contractId, e); - return null; + return "{}"; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/paycheck/domain/salary/service/PaymentDayReminderService.java` around lines 131 - 142, The buildActionData method currently returns null on JsonProcessingException which causes actionData to be missing; change the catch block in buildActionData to return a defensive empty JSON string ("{}") instead of null, while keeping the warning log (log.warn("알림 액션 데이터 생성 실패: contractId={}", contractId, e)); ensure the method signature and callers that expect a String continue to work with "{}" as a safe fallback.
43-46: 읽기 전용 작업에 불필요한 writable@Transactional사용.이 서비스는 DB 쓰기 작업 없이 조회와 이벤트 발행만 수행합니다. 클래스 레벨의
@Transactional(readOnly = true)로 충분하므로, 메서드 레벨의@Transactional을@Transactional(readOnly = true)로 변경하거나 제거하는 것을 고려하세요. 읽기 전용 트랜잭션은 DB 최적화(예: MySQL의 경우 snapshot 읽기)를 활용할 수 있습니다.Also applies to: 52-53
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/paycheck/domain/salary/service/PaymentDayReminderService.java` around lines 43 - 46, The methods in PaymentDayReminderService (notably sendPaymentDayReminders and the other method at lines 52-53, e.g., sendPaymentDayRemindersForDate) are marked with a writable `@Transactional` but only perform reads and event publication; change the transactional annotations to read-only by replacing method-level `@Transactional` with `@Transactional`(readOnly = true) or remove the method-level annotation and put `@Transactional`(readOnly = true) at the class level (on PaymentDayReminderService) so reads use a read-only transaction; update both sendPaymentDayReminders and the other method mentioned to use readOnly transactions consistently.
144-151:adjustDayOfMonth메서드가SalaryService에서 중복되고 있습니다.주석에도 명시된 대로 동일한 로직이 두 곳에 존재합니다. 급여 기간 계산 로직이 변경될 경우 양쪽을 동시에 수정해야 하므로, 공통 유틸리티 클래스로 추출하는 것을 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/paycheck/domain/salary/service/PaymentDayReminderService.java` around lines 144 - 151, The adjustDayOfMonth method in PaymentDayReminderService duplicates SalaryService.adjustDayOfMonth; extract this logic into a shared utility (e.g., DateUtils.adjustDayOfMonth or PayDateUtils.adjustDayOfMonth) that takes (LocalDate baseDate, int desiredDay), move the existing implementation there, and update both PaymentDayReminderService.adjustDayOfMonth and SalaryService to call the new shared method; ensure the new utility is placed in a common package and add unit tests or reuse existing tests to verify behavior remains identical.src/main/java/com/example/paycheck/domain/contract/repository/WorkerContractRepository.java (1)
55-60: 월말 쿼리의>=조건은 정확하지만, 인덱스 추가를 고려하세요.
paymentDay >= :tomorrowDay조건은 월말 처리(예: paymentDay=31인 계약이 2월 28일에 알림 대상이 되는 케이스)를 올바르게 처리합니다.이 두 쿼리 모두
isActive와paymentDay컬럼을 조건으로 사용하므로, 계약 수가 증가하면(is_active, payment_day)복합 인덱스 추가를 검토하시기 바랍니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/paycheck/domain/contract/repository/WorkerContractRepository.java` around lines 55 - 60, Add a composite index on the isActive and paymentDay columns to optimize queries like WorkerContractRepository.findActiveContractsByPaymentDayOnLastDay that filter by isActive and paymentDay; update the WorkerContract entity (or DB migration) to create an index covering (is_active, payment_day) so the JPA query using WHERE c.isActive and c.paymentDay >= :tomorrowDay can use the index for faster lookups.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/test/java/com/example/paycheck/domain/salary/service/PaymentDayReminderServiceTest.java`:
- Around line 231-234: The test stubs an exception on
failingContract.getWorker() but sendReminderForContract invokes
getWorkplace().getName() first, so update the mock to throw from the actual call
site: change the mock setup for failingContract to throw (or stub) on
getWorkplace() / getWorkplace().getName() rather than getWorker() so
sendReminderForContract will hit the intended RuntimeException; reference
failingContract, sendReminderForContract, getWorkplace, getName and getWorker
when locating and updating the stub.
---
Nitpick comments:
In
`@src/main/java/com/example/paycheck/domain/contract/repository/WorkerContractRepository.java`:
- Around line 55-60: Add a composite index on the isActive and paymentDay
columns to optimize queries like
WorkerContractRepository.findActiveContractsByPaymentDayOnLastDay that filter by
isActive and paymentDay; update the WorkerContract entity (or DB migration) to
create an index covering (is_active, payment_day) so the JPA query using WHERE
c.isActive and c.paymentDay >= :tomorrowDay can use the index for faster
lookups.
In
`@src/main/java/com/example/paycheck/domain/salary/service/PaymentDayReminderService.java`:
- Around line 131-142: The buildActionData method currently returns null on
JsonProcessingException which causes actionData to be missing; change the catch
block in buildActionData to return a defensive empty JSON string ("{}") instead
of null, while keeping the warning log (log.warn("알림 액션 데이터 생성 실패:
contractId={}", contractId, e)); ensure the method signature and callers that
expect a String continue to work with "{}" as a safe fallback.
- Around line 43-46: The methods in PaymentDayReminderService (notably
sendPaymentDayReminders and the other method at lines 52-53, e.g.,
sendPaymentDayRemindersForDate) are marked with a writable `@Transactional` but
only perform reads and event publication; change the transactional annotations
to read-only by replacing method-level `@Transactional` with
`@Transactional`(readOnly = true) or remove the method-level annotation and put
`@Transactional`(readOnly = true) at the class level (on
PaymentDayReminderService) so reads use a read-only transaction; update both
sendPaymentDayReminders and the other method mentioned to use readOnly
transactions consistently.
- Around line 144-151: The adjustDayOfMonth method in PaymentDayReminderService
duplicates SalaryService.adjustDayOfMonth; extract this logic into a shared
utility (e.g., DateUtils.adjustDayOfMonth or PayDateUtils.adjustDayOfMonth) that
takes (LocalDate baseDate, int desiredDay), move the existing implementation
there, and update both PaymentDayReminderService.adjustDayOfMonth and
SalaryService to call the new shared method; ensure the new utility is placed in
a common package and add unit tests or reuse existing tests to verify behavior
remains identical.
sendReminderForContract 실행 순서상 getWorkplace().getName()이 getWorker()보다 먼저 호출되므로, 예외 발생 지점을 getWorker() → getWorkplace()로 수정하여 실제 로직 흐름과 일치시킴
관련 이슈
Closes #90
작업 내용
급여 지급일 하루 전날 오전 9시에 근로자에게 해당 월의 근무 기록 확인 알림을 발송하는 기능을 구현합니다.
변경 사항
수정된 파일
NotificationType:WORK_RECORD_CONFIRMATION신규 타입 추가UserSettingsService: 신규 타입을paymentAlertEnabled설정 그룹으로 매핑WorkerContractRepository: 내일이 급여 지급일인 활성 계약 조회 쿼리 2개 추가findActiveContractsByExactPaymentDay— 일반 날짜용 (paymentDay == 내일)findActiveContractsByPaymentDayOnLastDay— 월말용 (paymentDay >= 내일, 예:paymentDay=31인 계약이 2월 28일에도 포함)신규 파일
PaymentDayReminderService: 핵심 비즈니스 로직SalaryService와 동일한adjustDayOfMonth로직)COMPLETED상태 근무 기록 집계 (근무 일수, 총 근무 시간)eventPublisher.publishEvent)PaymentDayReminderScheduler: 매일 오전 9시 스케줄러 (@Scheduled(cron = "0 0 9 * * *"))PaymentDayReminderServiceTest: 단위 테스트 7건알림 명세
WORK_RECORD_CONFIRMATIONpaymentAlertEnabledVIEW_SALARY(급여 상세 페이지 이동){"contractId": 123, "year": 2025, "month": 3}내일은 OO 카페 급여일이에요. 2025년 3월 근무 기록을 확인해 보세요! (근무 15일, 총 120.0시간)월말 처리 검증
Test plan
paymentDay가 내일 날짜와 일치하는 활성 계약의 근로자에게 알림이 발송되는지 확인paymentDay=31인 계약이 2월 말일 전날에 올바른 급여 기간(1/31~2/27)으로 알림을 발송하는지 확인2/28)에findActiveContractsByPaymentDayOnLastDay쿼리가 사용되는지 확인actionData에contractId,year,month가 포함되는지 확인./gradlew test --tests "PaymentDayReminderServiceTest"7건 전체 통과Summary by CodeRabbit
릴리스 노트
새로운 기능
테스트