- PassDelivery(pass_deliveries) outbox 엔티티: 발송 결과(SENT/FAILED)·채널·수신처·시도횟수·오류 기록.
- PassNotifier 계약 변경: 실패 시 예외 throw(+ channel()/recipient()). Hanbank/Email이 실패를
더 이상 삼키지 않고 throw → PassDeliveryService가 결과를 outbox에 기록(승인은 롤백 안 됨).
- PassDeliveryRetryScheduler: 매 10분(acs.delivery.retry-cron) FAILED & attempts<max(기본5) 재발송.
- 관리 API: GET /api/admin/deliveries?status=FAILED, POST /api/admin/deliveries/{id}/retry (ADMIN).
- ApprovalService는 passDeliveryService.deliver(vr)로 위임(QR생성·발송·기록 일원화).
- Flyway V3__pass_delivery.sql. FlywayValidationTest가 V1+V2+V3를 엔티티와 대조.
- 테스트: PassDeliveryTest(발송 실패→FAILED 기록→재시도→SENT). 전체 8건 통과.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- spring-boot-starter-mail 기반 EmailPassNotifier: acs.sms.provider=email일 때 활성.
발신=acs.mail.from(기본 dept_itcm000@bok.or.kr), 수신=방문자 email,
QR PNG를 첨부하고 공개 출입증 링크를 본문에 포함. 발송 실패는 log(승인 롤백 없음).
- provider 스위치에 email 추가(dev/hanbank/email). prod는 spring.mail.* 를 ACS_MAIL_*
env로 주입(인증 선택), .env.example·docker-compose 반영.
- 테스트: EmailPassNotifierTest(발신/수신/제목/QR 첨부 구성, 이메일 없으면 미발송).
검증: 전체 5개 테스트 통과. 로컬 SMTP(aiosmtpd:1025)로 실발송 e2e 확인 —
MAIL FROM=dept_itcm000@bok.or.kr, RCPT=visitor, 제목/본문/pass-qr.png 첨부 수신 확인.
주의: 발신자가 bok.or.kr이면 SPF/DMARC상 실제 사내 SMTP 릴레이를 거쳐야 하며,
ACS_MAIL_HOST/PORT(및 필요 시 인증) 확보 후 실환경 발송 배선 필요.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 체크인/아웃이 VisitRequest 행을 PESSIMISTIC_WRITE 락으로 로드(resolve)하여
동일 방문의 입·출입을 직렬화 → 동시 스캔 시 중복 입장 이벤트 방지(TOCTOU 제거).
스키마 변경 없음(H2/PostgreSQL 이식성 위해 부분 유니크 대신 락 채택).
- VisitExpiryScheduler: 매일 00:10(acs.expiry.cron) visit_to가 지난 APPROVED를
EXPIRED로 벌크 전이 → 재실/통계/리포트가 만료건을 유효로 취급하지 않음.
- @EnableScheduling 활성화.
- 테스트: CheckInConcurrencyTest(2스레드 → 정확히 1건 입장),
VisitExpirySchedulerTest(과거 승인만 만료). 전체 3건 통과.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>