Conversation
Walkthrough관리자용 상품 옵션 재고를 수정하는 REST API와 서비스·도메인 로직, 예외 코드 및 단위·통합·동시성 테스트가 추가됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Controller as AdminProductController
participant Service as AdminProductService
participant Repo as ProductRepository
participant Domain as Product
participant Response as ResponseService
participant Exception as ExceptionHandler
Client->>Controller: PATCH /options/{optionId}/stock (AdminEditStockRequest)
Controller->>Service: editProductStock(optionId, amount, flag)
Service->>Repo: findWithLockById(optionId)
alt not found
Repo-->>Service: Optional.empty()
Service->>ExceptionHandler: throw BbangleException(NOT_FOUND_OPTION)
ExceptionHandler-->>Client: ErrorResponse
else found
Repo-->>Service: Product
Service->>Domain: editStock(amount, flag)
alt invalid decrease
Domain->>ExceptionHandler: throw BbangleException(INVALID_DECREASE_STOCK_AMOUNT)
ExceptionHandler-->>Client: ErrorResponse
else success
Domain-->>Service: void
Service-->>Controller: void
Controller->>Response: getSuccessResult()
Response-->>Client: CommonResult(success)
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
459b999 to
784837c
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In
`@src/main/java/com/bbangle/bbangle/board/admin/controller/AdminBoardController.java`:
- Around line 54-61: The controller currently extracts a productId in
AdminBoardController.deleteProducts and constructs
RemoveProductsCommand(productId), but the service layer uses
softDeleteByBoardId—causing mismatched semantics and risk of deleting the wrong
board's products; fix by making the identifier semantics consistent: either
rename the path and parameter to boardId (change
`@DeleteMapping`("{boardId}/options"), `@PathVariable` Long boardId, and new
RemoveProductsCommand(boardId, ...)) so the command and
adminBoardService.deleteProducts/softDeleteByBoardId align, or keep productId
semantics and update the controller to call a product-specific service method
(e.g., adminBoardService.deleteProductOptionsByProductId) and change
RemoveProductsCommand to accept productId; update DTO/variable names
(request.optionIds(), request.removeAll()) accordingly wherever
RemoveProductsCommand and adminBoardService methods are defined so the
identifier meaning is unambiguous.
In
`@src/main/java/com/bbangle/bbangle/board/admin/controller/dto/AdminEditStockRequest.java`:
- Around line 10-18: Add validation in the AdminEditStockRequest compact
constructor to guard against null editStockFlag and to enforce flag-specific
amount rules: check editStockFlag != null and throw a BbangleException with an
appropriate error code if null; then validate amount against EditStockFlag
(e.g., for INCREASE and DECREASE require amount > 0, for SOLDOUT allow amount ==
0 but reject amount > 0, etc.) and throw BbangleException (use or add relevant
error constants, not just INVALID_STOCK_AMOUNT) when rules are violated so
downstream domain logic cannot hit NPEs or accept meaningless requests.
In
`@src/main/java/com/bbangle/bbangle/board/admin/controller/dto/AdminRemoveProductRequest.java`:
- Around line 17-19: The null-check for optionIds is missing in the
AdminRemoveProductRequest validation, so calling optionIds.isEmpty() can NPE
when optionIds is absent; update the validation in AdminRemoveProductRequest
(the constructor/initializer that currently checks if (!removeAll &&
optionIds.isEmpty())) to explicitly handle null (e.g., treat null as empty by
checking optionIds == null || optionIds.isEmpty() or use a utility like
CollectionUtils.isEmpty(optionIds)) and still throw BbangleException with
INVALID_REMOVE_PRODUCTS_REQUEST when removeAll is false and optionIds is
null/empty.
In
`@src/main/java/com/bbangle/bbangle/board/admin/controller/swagger/AdminBoardApi.java`:
- Around line 36-39: The Operation annotation in AdminBoardApi has a typo in the
description string ("선책한" should be "선택한"); update the description attribute of
the `@Operation` on the admin product option delete API (in the AdminBoardApi
class) to use "선택한" instead of "선책한" so the description reads "관리자가 선택한 상품의 옵션을
삭제합니다."
In
`@src/main/java/com/bbangle/bbangle/board/admin/service/dto/RemoveProductsCommand.java`:
- Around line 6-15: The Swagger descriptions in RemoveProductsCommand are
inconsistent with the field names (boardId, productIds) and mention both "상품"
and "옵션"; update them so each field's `@Schema` accurately reflects its real
domain meaning: confirm whether boardId is a "게시글 ID" or "상품 ID" and change the
annotation text (or rename the field) accordingly; similarly, if productIds
refers to option IDs rename it to optionIds or change the description to "삭제 대상
상품 ID List" / "삭제 대상 옵션 ID List" to match removeAll semantics; ensure
removeAll's description clearly states when productIds/optionIds is required and
keep the class name RemoveProductsCommand unchanged.
In `@src/main/java/com/bbangle/bbangle/board/domain/Product.java`:
- Around line 184-198: The editStock method in Product must validate negative
amounts and clear soldout on increases: add an initial guard in editStock(int
amount, EditStockFlag editStockFlag) to throw a BbangleException for amount <= 0
(or specifically amount < 0 if zero is allowed) using an appropriate
BbangleErrorCode (e.g., INVALID_STOCK_AMOUNT or reuse
INVALID_DECREASE_STOCK_AMOUNT if that's the intended code), then in the INCREASE
branch set this.soldout = false after increasing stock (this.stock += amount) to
reopen soldout products; keep the DECREASE branch checks (ensure this.stock >=
amount) but ensure amount is positive by the initial guard; leave SOLDOUT
behavior setting stock = 0 and soldout = true. Ensure you reference the
editStock method, the EditStockFlag enum values (INCREASE, DECREASE, SOLDOUT),
and throw BbangleException with the chosen BbangleErrorCode for invalid amounts.
- Around line 106-108: Product currently redeclares the isDeleted field that is
already mapped in SoftDeleteBaseEntity, causing duplicate JPA column mapping;
remove the isDeleted declaration from the Product class so it inherits the field
and mapping from SoftDeleteBaseEntity (if you need access in Product, use the
inherited getter/setter or override accessor methods without re-declaring the
field or `@Column`).
In `@src/main/java/com/bbangle/bbangle/board/repository/ProductRepository.java`:
- Around line 23-29: The JPQL in method softDeleteByBoardId has a mismatched
parameter name (:boardIds) that doesn't match the `@Param`("boardId") binding;
update the `@Query` in softDeleteByBoardId to use :boardId so the JPQL parameter
name matches the `@Param` and the update executes correctly.
In `@src/main/java/com/bbangle/bbangle/exception/BbangleErrorCode.java`:
- Around line 76-79: The enum BbangleErrorCode contains user-facing Korean
messages with spacing/typos that need polishing; update the message strings for
INVALID_REMOVE_PRODUCTS_REQUEST, INVALID_STOCK_AMOUNT, NOT_FOUND_OPTION, and
INVALID_DECREASE_STOCK_AMOUNT in BbangleErrorCode to correct grammar and spacing
(e.g., make them natural Korean: "옵션 전체 삭제가 아닐 경우 옵션 ID가 하나 이상 있어야 합니다.", "재고
수량은 0 이상이어야 합니다.", "존재하지 않는 상품 옵션입니다.", "감소하려는 수량이 현재 재고보다 큽니다.") so the
user-facing text is clear and consistent while keeping the same enum names and
error codes.
🧹 Nitpick comments (2)
src/test/java/com/bbangle/bbangle/fixture/board/domain/ProductFixture.java (1)
18-24: stock 값과 soldout 상태의 일관성을 맞추는 게 좋습니다.stock=0인 케이스에서도 soldout이 false로 고정됩니다. 테스트 신뢰성을 위해 stock 기반으로 설정을 권장합니다.
🧩 제안 수정
return Product.builder() .title(title) .board(board) .stock(stock) - .soldout(false) + .soldout(stock == 0) .build();src/test/java/com/bbangle/bbangle/board/admin/service/AdminProductServiceIntegrationTest.java (1)
55-61: DB 반영 검증을 위해 영속성 컨텍스트를 비워주세요.
flush()후 동일 컨텍스트에서findById()를 호출하면 캐시된 엔티티가 그대로 반환될 수 있어 “DB 반영” 검증이 약해집니다.EntityManager.clear()(또는flushAndClear()헬퍼)로 컨텍스트를 비운 뒤 재조회하는 방식이 더 안전합니다.♻️ 예시 변경
+import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; ... `@Autowired` private StoreRepository storeRepository; + + `@PersistenceContext` + private EntityManager entityManager; ... adminProductService.editProductStock(product.getId(), 5, EditStockFlag.INCREASE); productRepository.flush(); + entityManager.clear(); // then Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
src/main/java/com/bbangle/bbangle/board/admin/controller/AdminBoardController.java
Show resolved
Hide resolved
src/main/java/com/bbangle/bbangle/board/admin/controller/dto/AdminEditStockRequest.java
Show resolved
Hide resolved
src/main/java/com/bbangle/bbangle/board/admin/controller/dto/AdminRemoveProductRequest.java
Show resolved
Hide resolved
src/main/java/com/bbangle/bbangle/board/admin/controller/swagger/AdminBoardApi.java
Show resolved
Hide resolved
src/main/java/com/bbangle/bbangle/board/admin/service/dto/RemoveProductsCommand.java
Show resolved
Hide resolved
src/main/java/com/bbangle/bbangle/board/repository/ProductRepository.java
Show resolved
Hide resolved
29077e7 to
d2dc0f1
Compare
1. 기본값 False 로 추가 2. builder 추가
1. EditStockFlag Enum 추가 2. 재고 증가, 감소, 품절 처리 기능 추가 3. 감소하려는 수보다 재고가 적을 경우 예외처리 4. 재고가 0이 될경우 자동 품절 처리 5. 품절 요청 시 재고 0으로 변경
1. command 없이 재고 수정 기능 추가
1. amount 0 이하 Validation 추가
1. Swagger 문서 추가 2. Controller Service 연동
This reverts commit 45b9d11.
1. 재고 증가할 때 soldout 필드 확인 로직 추가
1adbd1e to
68caf6f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@src/main/java/com/bbangle/bbangle/board/admin/service/AdminProductService.java`:
- Around line 18-24: Rename the mismatched parameter in
AdminProductService.editProductStock to match the API/domain term "option"
(e.g., change the parameter name productId to optionId) and update any local
variable names and usages inside the method (including the call to
productRepository.findById(...) if that lookup should target an option
repository or keep as-is only renaming the identifier). Ensure the method
signature, any callers, and related exception message
(BbangleException(BbangleErrorCode.NOT_FOUND_OPTION)) remain consistent with the
"option" terminology (or alternatively harmonize all names to "product" across
controller, service, and error code if you choose that approach).
🧹 Nitpick comments (3)
src/main/java/com/bbangle/bbangle/board/domain/Product.java (1)
186-204: 재고 증가 시 품절 해제 로직이 적절하게 구현되었습니다.이전 리뷰에서 지적된
soldout해제 로직이 잘 반영되었습니다.선택적 개선 제안:
if-else체인 대신switch표현식을 사용하면 가독성이 향상됩니다.,
♻️ switch 표현식으로 리팩토링 제안
public void editStock(int amount, EditStockFlag editStockFlag) { - if (editStockFlag == EditStockFlag.INCREASE) { - this.stock = this.stock + amount; - if (this.soldout) { - this.soldout = false; - } - } else if (editStockFlag == EditStockFlag.DECREASE) { - if (this.stock < amount) { - throw new BbangleException(BbangleErrorCode.INVALID_DECREASE_STOCK_AMOUNT); - } - this.stock = this.stock - amount; - if (this.stock == 0) { - this.soldout = true; - } - } else if (editStockFlag == EditStockFlag.SOLDOUT) { - this.stock = 0; - this.soldout = true; + switch (editStockFlag) { + case INCREASE -> { + this.stock += amount; + this.soldout = false; + } + case DECREASE -> { + if (this.stock < amount) { + throw new BbangleException(BbangleErrorCode.INVALID_DECREASE_STOCK_AMOUNT); + } + this.stock -= amount; + this.soldout = (this.stock == 0); + } + case SOLDOUT -> { + this.stock = 0; + this.soldout = true; + } } }src/test/java/com/bbangle/bbangle/board/admin/service/AdminProductServiceIntegrationTest.java (2)
47-63: INCREASE 테스트에서 품절 해제 검증 누락현재 INCREASE 테스트는 품절 상태가 아닌 상품만 테스트합니다.
Product.editStock에서 품절 상태 해제 로직이 추가되었으므로, 품절 상태인 상품에 재고를 증가시키면 품절이 해제되는 시나리오도 테스트해야 합니다.🧪 품절 해제 테스트 추가 제안
`@Test` `@DisplayName`("INCREASE: 품절 상태인 상품에 재고를 증가시키면 품절이 해제된다") void increase_soldoutProduct_clearsSoldout() { // given Store store = storeRepository.save(StoreFixture.defaultStore()); Board board = boardRepository.save(BoardFixture.crawlingActiveBoardWithStore(store, "테스트게시글")); Product product = Product.builder() .title("품절상품") .board(board) .stock(0) .soldout(true) .build(); productRepository.save(product); // when adminProductService.editProductStock(product.getId(), 5, EditStockFlag.INCREASE); productRepository.flush(); // then Product updatedProduct = productRepository.findById(product.getId()).orElseThrow(); assertThat(updatedProduct.getStock()).isEqualTo(5); assertThat(updatedProduct.isSoldout()).isFalse(); }
136-146: 예외 테스트에서 에러 코드 검증 추가 권장
BbangleException발생 여부만 확인하고 있습니다. 예상되는 에러 코드(NOT_FOUND_OPTION)도 함께 검증하면 테스트가 더 정확해집니다.🧪 에러 코드 검증 추가
assertThatThrownBy(() -> adminProductService.editProductStock(nonExistentProductId, 10, EditStockFlag.INCREASE) -) - .isInstanceOf(BbangleException.class); +) + .isInstanceOf(BbangleException.class) + .extracting("errorCode") + .isEqualTo(BbangleErrorCode.NOT_FOUND_OPTION);
History
🚀 Major Changes & Explanations
📷 Test Image
💡 ETC
Summary by CodeRabbit