Skip to content

[FEATURE] 외주 상세 정보 조회 기능 구현#65

Merged
fervovita merged 8 commits into
devfrom
feat/#53-commission-details
Jun 30, 2026
Merged

[FEATURE] 외주 상세 정보 조회 기능 구현#65
fervovita merged 8 commits into
devfrom
feat/#53-commission-details

Conversation

@fervovita

@fervovita fervovita commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

🚀 Related issue

Closes #53

#️⃣ Summary

  • 외주의 상세 정보를 조회하는 GET /api/v1/commissions/{commissionId} API 구현

🔧 Changes

  • 외주 상세 조회 API 엔드포인트 구현 (GET /api/v1/commissions/{commissionId})
  • 카테고리 다형성 구조 도입 (CategoryDetail 인터페이스 + 카테고리별 구현체)
  • 교재(Textbook) 카테고리 상세 조회 로직 추가
  • 디자이너 역할일 경우에만 가격 정보(PriceInfo) 노출

📸 Test Evidence

  • 강사
image
  • 디자이너
image

💬 Reviewer Notes

  • Spring Boot에서 sealed interface는 같은 패키지 내의 구현체만 permits할 수 있습니다.
    CategoryDetailcommission/core/handler에, TextbookCategoryDetailcommission/category/textbook/dto에 위치하여 패키지가 다르므로 sealed를 사용할 수 없었습니다.
    대신 일반 interface + instanceof 패턴 매칭으로 구현했으며, 이로 인해 textbook 패키지에 대한 의존성이 존재합니다.

  • CommissionCreateRequest에 현재 textbook만 받도록 하드코딩되어 있는데, 이 부분의 적용은 이 PR의 범위를 벗어나 수정하지 않았습니다.

  • 각 카테고리마다 상세 정보가 다르므로, CategoryDetail.toResponse()를 통해 각 카테고리 핸들러가 각자 다른 응답 구조를 반환하도록 구현했습니다. 새 카테고리 추가 시 CategoryDetail 구현체와 CategoryDetailResponse 구현체만 추가하면 됩니다.

  • 현재 @AuthenticationPrincipaluserId만 포함되어 있어, UserRole까지 추가하려면 인증 구조 변경과 함께 모든 API의 시그니처를 수정해야 합니다. 이 영향 범위가 너무 커서, UserService.findById()로 역할을 조회하는 방식으로 구현했습니다.

Summary by CodeRabbit

  • New Features

    • 외주 상세 조회 API가 추가되어, 외주 정보와 디자인, 파일, 일정, 가격, 카테고리별 상세 정보를 한 번에 확인할 수 있습니다.
    • 교재 카테고리의 상세 정보가 새로 제공되어 교재명, 강사명, 과목, 필요 페이지 정보를 볼 수 있습니다.
  • Bug Fixes

    • 상세 조회 시 관련 항목을 더 안정적으로 불러오도록 개선했습니다.
    • 디자이너 조회 실패 시 더 명확한 오류가 반환되도록 정리했습니다.

@fervovita fervovita self-assigned this Jun 29, 2026
@fervovita fervovita requested a review from Jong0128 as a code owner June 29, 2026 07:52
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

외주 상세 조회 API(GET /api/v1/commissions/{commissionId})를 신규 추가한다. 카테고리별 상세 변환 계약(CategoryDetail, CategoryDetailResponse), Textbook 상세 구현, 저장소 조회 메서드, 커미션 서비스·매핑·파사드·컨트롤러가 추가되고, 기존 PriceInfoPriceDetail로 리네임된다.

Changes

외주 상세 조회 기능

Layer / File(s) Summary
응답 계약 및 DTO 정의
...core/handler/CategoryDetail.java, ...core/dto/response/CategoryDetailResponse.java, ...core/dto/CommissionDetail.java, ...core/dto/response/CommissionDetailResponse.java, ...textbook/dto/response/TextbookDetailResponse.java
CategoryDetail 인터페이스와 CategoryDetailResponse 인터페이스를 추가하고, CommissionDetail record, CommissionDetailResponse 및 중첩 record(DesignInfo, FileInfo, DateInfo, PriceInfo), TextbookDetailResponseRequiredPage를 신규 정의한다.
PriceInfo→PriceDetail 리네임 및 DesignerService 변경
...core/dto/PriceDetail.java, ...core/policy/CommissionPricePolicy.java, ...core/dto/response/CommissionListResponse.java, ...core/facade/DesignerCommissionFacade.java, ...designer/service/DesignerService.java, ...designer/exception/DesignerErrorCode.java
PriceInfo record를 PriceDetail로 리네임하고, CommissionPricePolicy 메서드명을 getPriceDetail로 변경한다. CommissionListResponseDesignerCommissionFacade를 갱신하고, DesignerService.getByIdfindById로, DesignerErrorCodeDESIGNER_NOT_FOUND를 추가한다.
저장소 조회 메서드 추가
...core/repository/CommissionColor..., ...CommissionConcept..., ...CommissionFile..., ...textbook/repository/TextbookPageRepository.java
커미션 ID 기준으로 색상·개념·파일·Textbook 페이지를 조회하는 findByCommissionId 메서드를 각 저장소에 추가한다.
Textbook 카테고리 상세 구현
...textbook/dto/TextbookCategoryDetail.java, ...textbook/handler/TextbookCategoryHandler.java, ...core/handler/CommissionCategoryHandler.java
CommissionCategoryHandlerloadDetail 계약을 추가하고, TextbookCategoryDetailtoResponse()TextbookDetailResponse를 조립한다. TextbookCategoryHandlerloadDetail을 구현한다.
커미션 상세 조립 및 매핑
...core/service/CommissionService.java, ...core/mapper/CommissionDetailMapper.java
CommissionService.getDetail이 커미션·개념·색상·파일·카테고리 상세를 묶어 CommissionDetail을 반환한다. CommissionDetailMapper가 디자인 정보, 파일(S3 presigned URL), 날짜, 가격을 조합해 CommissionDetailResponse를 생성한다.
파사드 및 컨트롤러
...core/facade/CommissionFacade.java, ...core/controller/CommissionController.java
CommissionFacade가 사용자 역할에 따라 PriceDetail을 분기 계산하고 상세 응답을 조합한다. CommissionControllerGET /api/v1/commissions/{commissionId} 엔드포인트를 노출한다.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant CommissionController
  participant CommissionFacade
  participant CommissionService
  participant DesignerService
  participant CommissionDetailMapper
  participant S3PresignedUrlGenerator

  Client->>CommissionController: GET /api/v1/commissions/{commissionId}
  CommissionController->>CommissionFacade: getCommissionDetail(userId, commissionId)
  CommissionFacade->>CommissionService: getDetail(commissionId)
  CommissionService-->>CommissionFacade: CommissionDetail
  CommissionFacade->>DesignerService: findById(userId) [DESIGNER인 경우]
  CommissionFacade->>CommissionFacade: getPriceDetail(categoryType, level)
  CommissionFacade->>CommissionDetailMapper: toResponse(detail, priceDetail)
  CommissionDetailMapper->>S3PresignedUrlGenerator: generatePrivateGetUrl(file)
  S3PresignedUrlGenerator-->>CommissionDetailMapper: presigned URL
  CommissionDetailMapper-->>CommissionFacade: CommissionDetailResponse
  CommissionFacade-->>CommissionController: CommissionDetailResponse
  CommissionController-->>Client: ApiResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Ditda-Official/Ditda-Backend#19: CommissionDetailMapperS3PresignedUrlGenerator.generatePrivateGetUrl을 사용해 첨부 파일 URL을 생성하는데, 해당 PR에서 S3PresignedUrlGenerator와 private S3 버킷 설정이 도입됨.
  • Ditda-Official/Ditda-Backend#22: TextbookCategoryDetail/TextbookDetailResponseTextbook, TextbookPage, PageType 도메인 타입을 전제로 하며, 해당 PR에서 이 엔티티들이 추가됨.
  • Ditda-Official/Ditda-Backend#34: TextbookCategoryHandlerloadDetail 구현을 추가한 이번 PR은 해당 PR에서 도입된 TextbookCategoryHandler 전략 구조를 직접 확장함.

Suggested labels

♻️ Refactor

Suggested reviewers

  • Jong0128
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 외주 상세 정보 조회 기능 구현이라는 PR의 핵심 변경사항과 직접 일치합니다.
Linked Issues check ✅ Passed 직접 링크된 #53의 외주 상세 정보 조회 기능 구현 요구를 GET 상세 API와 응답/서비스 구조로 충족합니다.
Out of Scope Changes check ✅ Passed 상세 조회 API 구현을 위한 DTO, 리포지토리, 서비스, 가격 매핑 변경으로 보이며 별도 무관한 변경은 보이지 않습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#53-commission-details

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fervovita

fervovita commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@fervovita fervovita force-pushed the feat/#53-commission-details branch from 4584a79 to 3b1ee45 Compare June 29, 2026 14:39
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionDetailResponse.java (1)

33-43: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

실제 응답 계약이 OpenAPI 스키마에 충분히 드러나지 않을 수 있습니다.

이 필드는 런타임에 categoryDetail은 카테고리별 구체 DTO가 내려가고, priceInfo는 역할에 따라 null이 됩니다. 지금 선언만으로는 generated OpenAPI가 oneOf/nullable을 놓쳐서 SDK 생성이나 스키마 검증이 실제 응답과 어긋날 수 있으니, 필드/인터페이스 쪽에 이를 명시하는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionDetailResponse.java`
around lines 33 - 43, The OpenAPI annotations on CommissionDetailResponse do not
fully describe the runtime response shape, so update the schema metadata for
categoryDetail and priceInfo to reflect the actual contract. Mark categoryDetail
as a polymorphic field using the concrete category DTO variants via oneOf (or
equivalent interface-level schema metadata), and mark priceInfo as nullable
since it can be null by role. Use the existing CommissionDetailResponse,
CategoryDetailResponse, and PriceInfo symbols to place the schema hints where
the generated OpenAPI will pick them up.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/ditda/backend/domain/commission/category/textbook/handler/TextbookCategoryHandler.java`:
- Around line 74-77: `TextbookCategoryHandler.loadDetail` currently throws
`IllegalStateException` when `textbookRepository.findById(commissionId)` returns
empty, which bypasses the domain error handling used elsewhere. Update this path
to throw `GeneralException` with the appropriate not-found error code/message so
`CommissionService`-style exception handling is consistent and the missing
category detail is treated as a domain exception.

In
`@src/main/java/ditda/backend/domain/commission/core/mapper/CommissionDetailMapper.java`:
- Around line 74-81: `CommissionDetailMapper`의 그룹핑 로직에서 같은 `FileKind`를
`groupedFiles`로 묶은 뒤 `getFirst()`의 `description`만 사용해 나머지 파일 설명이 사라지고 있습니다.
`CommissionCreateRequest.FileInfo`와 `FileInfo` 생성 흐름을 기준으로, 응답이 파일 단위 설명을 유지하도록
`map(...)`에서 각 파일의 description을 보존해 반환하거나, `groupedFiles`를 만들기 전에 `FileKind`당 단일
description만 허용하도록 저장/검증 로직을 정리하세요.

In
`@src/main/java/ditda/backend/domain/commission/core/service/CommissionService.java`:
- Around line 47-48: `CommissionService`의 `handler.loadDetail(commissionId)`
경로에서 카테고리별 구현이 던지는 예외가 그대로 밖으로 노출되고 있으니, `CommissionCategoryHandler` 계약에 맞게 도메인
예외로 통일하세요. 특히 `resolve(...)`로 얻은 `handler`가 `TextbookCategoryHandler`처럼
`IllegalStateException`을 던질 수 있는 경우를 `GeneralException`/`CommissionErrorCode` 기반
예외로 변환하거나, 각 handler 구현에서 동일한 도메인 예외를 던지도록 맞춰서 `loadDetail` 호출부가 일관된 응답을 반환하게
하세요.

---

Nitpick comments:
In
`@src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionDetailResponse.java`:
- Around line 33-43: The OpenAPI annotations on CommissionDetailResponse do not
fully describe the runtime response shape, so update the schema metadata for
categoryDetail and priceInfo to reflect the actual contract. Mark categoryDetail
as a polymorphic field using the concrete category DTO variants via oneOf (or
equivalent interface-level schema metadata), and mark priceInfo as nullable
since it can be null by role. Use the existing CommissionDetailResponse,
CategoryDetailResponse, and PriceInfo symbols to place the schema hints where
the generated OpenAPI will pick them up.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 922e98d8-ebe6-4cf6-880f-7d2bf467d31e

📥 Commits

Reviewing files that changed from the base of the PR and between 0da3048 and 3b1ee45.

📒 Files selected for processing (18)
  • src/main/java/ditda/backend/domain/commission/category/textbook/dto/TextbookCategoryDetail.java
  • src/main/java/ditda/backend/domain/commission/category/textbook/dto/response/TextbookDetailResponse.java
  • src/main/java/ditda/backend/domain/commission/category/textbook/handler/TextbookCategoryHandler.java
  • src/main/java/ditda/backend/domain/commission/category/textbook/repository/TextbookPageRepository.java
  • src/main/java/ditda/backend/domain/commission/core/controller/CommissionController.java
  • src/main/java/ditda/backend/domain/commission/core/dto/CommissionDetail.java
  • src/main/java/ditda/backend/domain/commission/core/dto/response/CategoryDetailResponse.java
  • src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionDetailResponse.java
  • src/main/java/ditda/backend/domain/commission/core/facade/CommissionFacade.java
  • src/main/java/ditda/backend/domain/commission/core/handler/CategoryDetail.java
  • src/main/java/ditda/backend/domain/commission/core/handler/CommissionCategoryHandler.java
  • src/main/java/ditda/backend/domain/commission/core/mapper/CommissionDetailMapper.java
  • src/main/java/ditda/backend/domain/commission/core/repository/CommissionColorRepository.java
  • src/main/java/ditda/backend/domain/commission/core/repository/CommissionConceptRepository.java
  • src/main/java/ditda/backend/domain/commission/core/repository/CommissionFileRepository.java
  • src/main/java/ditda/backend/domain/commission/core/service/CommissionService.java
  • src/main/java/ditda/backend/domain/designer/exception/DesignerErrorCode.java
  • src/main/java/ditda/backend/domain/designer/service/DesignerService.java

Base automatically changed from feat/#54-commission-scheduler to dev June 30, 2026 04:15
…tails

# Conflicts:
#	src/main/java/ditda/backend/domain/designer/exception/DesignerErrorCode.java
#	src/main/java/ditda/backend/domain/designer/service/DesignerService.java
@fervovita fervovita linked an issue Jun 30, 2026 that may be closed by this pull request
6 tasks

@Jong0128 Jong0128 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!
따로 커멘트는 없습니다!!
다만, 마지막으로 커밋한거 확인해주시고 merge 해주시면 감사하겠습니다 👍

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionListResponse.java (1)

33-36: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

priceDetails에 매핑이 없을 경우 NPE 가능성에 유의하세요.

priceDetails.get(c.getId())null을 반환하면 CommissionResponse.from 내부의 priceDetail.baseAmount() 호출에서 NPE가 발생합니다. 현재 호출부(DesignerCommissionFacade)에서는 동일한 커미션 목록으로 맵을 구성하므로 안전하지만, 호출부가 바뀌면 깨지기 쉬운 암묵적 결합입니다. 향후 재사용을 대비해 누락 시 방어 처리를 고려해 보세요.

Also applies to: 69-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionListResponse.java`
around lines 33 - 36, `CommissionListResponse.from` currently assumes
`priceDetails.get(c.getId())` is always present, which can propagate null into
`CommissionResponse.from` and cause an NPE. Update the mapping in
`CommissionListResponse.from` (and any similar `from`/factory path used by
`CommissionResponse`) to handle missing `PriceDetail` defensively, either by
skipping, defaulting, or explicitly validating with a clear error before calling
`CommissionResponse.from`. Keep the behavior consistent with the current
`DesignerCommissionFacade` usage but make the DTO conversion safe for future
callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionListResponse.java`:
- Around line 33-36: `CommissionListResponse.from` currently assumes
`priceDetails.get(c.getId())` is always present, which can propagate null into
`CommissionResponse.from` and cause an NPE. Update the mapping in
`CommissionListResponse.from` (and any similar `from`/factory path used by
`CommissionResponse`) to handle missing `PriceDetail` defensively, either by
skipping, defaulting, or explicitly validating with a clear error before calling
`CommissionResponse.from`. Keep the behavior consistent with the current
`DesignerCommissionFacade` usage but make the DTO conversion safe for future
callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ff77a20-9975-4cff-b3b2-c6e32870ec51

📥 Commits

Reviewing files that changed from the base of the PR and between cef7e16 and 19bf6dc.

📒 Files selected for processing (7)
  • src/main/java/ditda/backend/domain/commission/core/dto/PriceDetail.java
  • src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionDetailResponse.java
  • src/main/java/ditda/backend/domain/commission/core/dto/response/CommissionListResponse.java
  • src/main/java/ditda/backend/domain/commission/core/facade/CommissionFacade.java
  • src/main/java/ditda/backend/domain/commission/core/facade/DesignerCommissionFacade.java
  • src/main/java/ditda/backend/domain/commission/core/mapper/CommissionDetailMapper.java
  • src/main/java/ditda/backend/domain/commission/core/policy/CommissionPricePolicy.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/ditda/backend/domain/commission/core/facade/CommissionFacade.java
  • src/main/java/ditda/backend/domain/commission/core/mapper/CommissionDetailMapper.java

@fervovita

Copy link
Copy Markdown
Collaborator Author

PriceInfo가 내부 객체와 응답 DTO 역할을 모두 하고 있어서, 추후 내부 로직 변경 시 API 스펙에도 영향이 갈 수 있을 것 같았습니다.
내부 객체를 PriceDetail로 분리해서 내부 역할만 수행하도록 하고, 응답용 PriceInfoCommissionDetailResponse 내부에nested로 옮겼습니다.

@Jong0128 한번만 확인 부탁드립니다!

@Jong0128

Copy link
Copy Markdown
Collaborator

PriceInfo가 내부 객체와 응답 DTO 역할을 모두 하고 있어서, 추후 내부 로직 변경 시 API 스펙에도 영향이 갈 수 있을 것 같았습니다. 내부 객체를 PriceDetail로 분리해서 내부 역할만 수행하도록 하고, 응답용 PriceInfoCommissionDetailResponse 내부에nested로 옮겼습니다.

@Jong0128 한번만 확인 부탁드립니다!

@fervovita
확인했습니다!! 감사합니다 👍
혹시 이거 머지 후 배포해주실 수 있으실까요?

@fervovita fervovita merged commit 2e9c7c5 into dev Jun 30, 2026
2 checks passed
@fervovita fervovita deleted the feat/#53-commission-details branch June 30, 2026 16:48
@fervovita fervovita mentioned this pull request Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 외주 상세 정보 조회 기능 구현

2 participants