[BE-66] 교차 회사 목록에 추천도 노출 · 추천도 정렬 (FR-78 + FR-96 연결)

작업 내용 (설계 의도)

근거 TDD: 20260808-지원관리-확장-tdd.md — “API 계약 1단계 교차 회사 공고 목록(recommendation 필드)”, “단계별 점진 노출”

변경 사항

  1. BE-52가 만든 CrossCompanyJobPostingItem.recommendation 필드를 실제 값으로 채웁니다. 1단계 배포 시점에는 항상 null이었고(하위 호환), 2단계에서 recommendation 컨텍스트가 생기면서 채울 수 있게 됩니다.
  2. id 집합 배치 조회 1회로 가져옵니다 — 페이지 항목의 jobPostingId 집합으로 RecommendationDomainService.findAllBy(ids) 한 번. 항목마다 조회하지 않습니다.
  3. 정렬 옵션 RECOMMENDATION_DESC 추가 — 총점 내림차순, 미평가·평가 불가는 마지막, 동순위는 마감일 오름차순 → id 오름차순. 총점은 job_posting_recommendations 조인으로 SQL 정렬합니다(메모리 정렬 금지 — 페이지네이션과 결합 불가).
  4. recommendationGrade 필터 추가grade=RECOMMENDED&grade=STRONGLY_RECOMMENDED 형태 OR 필터. PRD Success Metrics “등급별 저장률” 측정의 조회 수단이 됩니다.
  5. 하위 호환 — 기존 파라미터·필드는 변경하지 않고 추가만 합니다. 추천도가 없는 공고는 recommendation: null로 계속 내려갑니다.

파일 소유: ListAllJobPostingsUseCase.kt·CrossCompanyPostingResponseMapper.kt·JobPostingSearchRepositoryImpl.kt를 수정합니다. 같은 wave(BE-65)와 파일이 겹치지 않습니다 — BE-65는 application/recommendation·presentation/recommendation만 건드립니다.

의존

  • BE-52 (교차 목록 API — 1단계)
  • BE-63 (추천도 결과 저장)

다이어그램

처리 흐름

sequenceDiagram
    participant FE as web(SPA)
    participant C as JobPostingApiController
    participant U as ListAllJobPostingsUseCase
    participant S as JobPostingSearchDomainService
    participant R as RecommendationDomainService
    FE->>C: GET /api/job-postings?sort=RECOMMENDATION_DESC&grade=RECOMMENDED
    C->>U: execute(command)
    U->>S: search(criteria + 추천도 정렬·등급 필터)
    S-->>U: 페이지 (SQL 정렬)
    U->>R: findAllBy(jobPostingIds) 배치 1회
    U->>U: recommendation 필드 채움
    U-->>C: PageResponse
    C-->>FE: 200

클래스 의존

flowchart LR
    subgraph Application["application/posting"]
        UC[ListAllJobPostingsUseCase]
        Mapper[CrossCompanyPostingResponseMapper]
    end
    subgraph Domain["domain"]
        SDS[JobPostingSearchDomainService]
        RDS[RecommendationDomainService]
    end
    subgraph Infra["infrastructure/posting"]
        Impl[JobPostingSearchRepositoryImpl]
    end
    UC --> Mapper
    UC --> SDS
    UC --> RDS
    SDS --> Impl

테스트 케이스

  • 평가된 공고의 recommendation에 점수·등급·상한 여부가 채워진다
  • 미평가 공고의 recommendation이 null이다 (하위 호환)
  • NOT_EVALUABLE 공고는 totalScore=null + notEvaluableReason이 채워진다
  • 20건 페이지에 대해 추천도 조회가 1회만 발생한다 (N+1 방지)
  • sort=RECOMMENDATION_DESC에서 총점 내림차순으로 정렬된다
  • sort=RECOMMENDATION_DESC에서 미평가 공고가 마지막에 온다
  • sort=RECOMMENDATION_DESC 동점 시 마감일 오름차순, 그다음 id 오름차순이다
  • grade=RECOMMENDED 필터로 해당 등급만 조회된다
  • grade를 2개 지정하면 OR로 조회된다
  • 추천도 정렬 시 페이지 1·2 사이 항목 중복·누락이 0건이다
  • 기존 파라미터(watchStatus·platform 등) 동작이 변경되지 않는다 (회귀)
  • 상한이 적용된 공고의 capApplied=true, 해제된 공고의 capReleased=true가 노출된다