[BE-51] recruitment 도메인 코어 — Recruitment·Application·CancellationPolicy

작업 내용 (설계 의도)

변경 사항

근거 TDD: “Detail Design 도메인 모델·인터페이스 시그니처·상태 전이 표”. 신규 domain/recruitment 바운디드 컨텍스트의 순수 도메인 코어를 구축한다. 후행 티켓(BE-52 서비스·BE-53 프레젠테이션·BE-55 결제확장)이 참조하는 공통 계약이라 선행 wave 단독 병목으로 확립한다.

포함:

  • Recruitment(@Entity, private 생성자 + create/reconstitute) — capacity·feeAmount(0 허용)·activityAt·applicationDeadline·communityId(Long? ID참조)·recruiterUserId·status. canApply()·closeWhenFull()·cancelByHost(userId) 캡슐화. 다른 도메인 Entity 직접 참조 금지(communityId는 Long).
  • RecruitmentStatus(OPEN/CLOSED/CANCELLED, canTransitTo)
  • Application(@Entity) — recruitmentId·applicantUserId·status·paymentId(Long?). confirm(paymentId)(멱등)·cancelByApplicant()·cancelPending()·markRefunded().
  • ApplicationStatus(PENDING/CONFIRMED/CANCELLED/REFUNDED, canTransitTo)
  • CancellationPolicy interface + TieredCancellationPolicy(now 내부 해결, no-time-parameter) — 7일초과 0 / 3~7일 0.05 / 3일이내 0.10
  • 이벤트: ApplicationRefundRequestedEvent(Layer1, topic=null, refundAmount 포함), 필요 시 RecruitmentCreatedEvent
  • 예외: RecruitmentFullException(409)·ApplicationCancellationClosedException(422)·NotRecruiterException(403) 등 (BusinessException 상속)
  • Repository interface: RecruitmentRepository(findForUpdateById 포함)·ApplicationRepository(countActiveByRecruitmentId 등)
  • Gateway interface: RecruitmentRefundGateway(booking PaymentRefundGateway와 별개 — R1)
  • 시간은 ZonedDateTime, !!·@Query·LocalDateTime 금지.

롤백

신규 패키지 추가라 롤백은 배포 되돌림(플래그와 무관, 미참조 코드는 무해).

의존

  • 없음

다이어그램

클래스 의존

flowchart LR
    Rec[Recruitment] --> RS[RecruitmentStatus]
    App[Application] --> AS[ApplicationStatus]
    Pol[TieredCancellationPolicy] -.->|implements| PolI[CancellationPolicy]
    RepoR[RecruitmentRepository]
    RepoA[ApplicationRepository]
    Gw[RecruitmentRefundGateway]

테스트 케이스

  • 정원 여유·미마감 OPEN 모집은 canApply()가 true다
  • 정원이 가득 차면 closeWhenFull()로 CLOSED로 전이한다
  • CancellationPolicy: 마감 8일 전 취소는 0%, 정확히 7일 전은 0%, 5일 전은 5%, 정확히 3일 전은 10%, 1일 전은 10%다 (경계값)
  • 이미 CONFIRMED된 Application에 confirm 재호출 시 no-op으로 멱등하다
  • CANCELLED된 Application에 cancelByApplicant 재호출은 상태 가드로 no-op이다
  • 마감 이후 cancelByApplicant는 ApplicationCancellationClosedException을 던진다
  • 개설자가 아닌 사용자의 cancelByHost는 NotRecruiterException을 던진다
  • feeAmount 음수·capacity 0 생성은 검증 예외를 던진다