[BE-59] 시설상품(program) 등록·조회·회차 예약 연동

작업 내용 (설계 의도)

변경 사항

근거 TDD: “도메인 모델 Program·Possible Solutions program 회차 예약 A”. facility 산하에 PT·클래스 시설상품(program)을 추가한다(FR-11/12). goods와 별개 개념. 회차 예약·결제는 기존 booking Slot 경로(OrderType.BOOKING) 재사용 — 신규 OrderType·결제 접점 없음.

포함 (전부 신규 파일):

  • domain: Program(@Entity — facilityId(String ID참조)·ownerUserId·name·description·price(≥0)·capacity(≥1)·durationMinutes(>0), private 생성자+create 검증·requireOwnedBy·회차생성용 정원/가격 노출), ProgramRepository interface.
  • domain service: ProgramDomainServiceregister(command)(FacilityOwnershipGateway 재사용해 소유 검증)·findByFacility(facilityId)·회차 슬롯 생성 시 SlotDomainService.createSlot(programId=program.id, capacity=program.capacity) 호출(BE-57 확장분).
  • application: RegisterProgramUseCase·ListProgramsUseCase·(선택)CreateProgramSessionUseCase + Command/Response.
  • infrastructure: ProgramRepositoryImpl+JpaRepository.
  • presentation: ProgramApiController(POST/GET /facilities/{facilityId}/programs) + @ConditionalOnProperty(facility.program.enabled).

주의: 예약·동시성은 기존 BookingDomainService.requestBooking 그대로 재사용(변경 없음) — 프로그램 회차는 programId를 가진 Slot일 뿐. BookingDomainService/CreateBookingUseCase 파일 수정 금지(Single Writer — BE-57과 분리 유지).

롤백

facility.program.enabled=false(경로 404) + programs 테이블 역방향 DDL.

의존

  • BE-57 (Slot programId·createSlot 확장)

다이어그램

처리 흐름

sequenceDiagram
    participant C as ProgramApiController
    participant U as RegisterProgramUseCase
    participant S as ProgramDomainService
    participant O as FacilityOwnershipGateway
    C->>U: register(command)
    U->>S: register(facilityId, ownerId, ...)
    S->>O: requireOwner(facilityId, ownerId)
    S-->>C: ProgramResponse

클래스 의존

flowchart LR
    PDS[ProgramDomainService] --> Repo[ProgramRepository]
    PDS --> Own[FacilityOwnershipGateway]
    PDS --> SDS[SlotDomainService]

테스트 케이스

  • 소유자가 PT 상품(정원1·60분)을 등록하면 Program이 저장된다
  • 시설상품 목록 조회가 등록된 program을 반환한다
  • 소유자가 아닌 사용자의 program 등록은 소유권 예외로 거부된다
  • price 음수·capacity 0·duration 0 등록은 검증 예외를 던진다
  • program 회차(programId 슬롯) 예약은 기존 booking 경로로 정상 결제된다
  • 정원이 찬 program 회차 예약은 SlotFullException으로 거부된다
  • facility.program.enabled=false면 program 경로가 404다