Skip to content

포인트 상점 탭 분리 기능 구현#395

Merged
sanchaehwa merged 5 commits intomainfrom
fix/pointItem
Dec 17, 2025
Merged

포인트 상점 탭 분리 기능 구현#395
sanchaehwa merged 5 commits intomainfrom
fix/pointItem

Conversation

@sanchaehwa
Copy link
Copy Markdown
Contributor

@sanchaehwa sanchaehwa commented Dec 14, 2025

#️⃣ 연관된 이슈

ex) close #Issue number

📝 작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)

📷 스크린샷

이미지

💬 리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 명칭

📌 PR 진행 시 참고사항

  • 리뷰어는 좋은 코드 방향을 제시하되, 수정을 강요하지 않습니다.
  • 좋은 코드를 발견하면 칭찬과 격려를 아끼지 않습니다.
  • 리뷰는 Reviewer로 지정된 시점 기준으로 3일 이내에 진행해 주세요.
  • Comment 작성 시 아래 Prefix를 사용해 주세요:
    • P1: 꼭 반영해 주세요 (Request Changes) – 이슈나 취약점 관련
    • P2: 반영을 고려해 주세요 (Comment) – 개선 의견
    • P3: 단순 제안 (Chore)

Summary by CodeRabbit

  • New Features
    • Added unified admin API for managing point shop items and products with create, update, and delete operations
    • Point shop items now support category classification to distinguish between item and product types

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 14, 2025

Walkthrough

Centralizes admin point-shop write operations (create, update, delete) into a new PointShopAdminController that dispatches to either PointItemService or PointProductService based on PointShopType. Adds Category field to PointItem and PointProduct entities. Removes write endpoints from existing item and product admin controllers.

Changes

Cohort / File(s) Summary
New Admin Controller & Core Support
src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java, src/main/java/com/example/green/domain/pointshop/admin/controller/docs/PointShopAdminControllerDocs.java, src/main/java/com/example/green/domain/pointshop/admin/controller/message/PointShopAdminResponseMessage.java
Introduces new REST controller with OpenAPI docs interface and response messages for centralized admin point-shop operations (create, update, delete with 200/400/404 responses).
Admin DTOs & Type
src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java, src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java, src/main/java/com/example/green/domain/pointshop/admin/service/PointShopType.java
Adds request records for create/update operations with validation constraints and enum to distinguish between ITEM and PRODUCT types.
Admin Exception Handling
src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java, src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminExceptionMessage.java
Introduces domain exception and message enum for category/product not found errors.
Admin Service
src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java
Implements multi-branch create/update/delete logic: creates via PointShopType, updates/deletes with fallback lookup across item and product repositories.
Category Entity VO
src/main/java/com/example/green/domain/pointshop/item/entity/vo/Category.java
Introduces new enum with ITEM and PRODUCT constants, JSON-serialized via value field.
Item Entity & Commands
src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java, src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemCreateCommand.java, src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemUpdateCommand.java
Adds non-null Category field to PointItem entity and updates create/update command records to include category.
Item DTOs & Request Mapping
src/main/java/com/example/green/domain/pointshop/item/dto/request/CreatePointItemRequest.java, src/main/java/com/example/green/domain/pointshop/item/dto/response/PointItemResponse.java
Adds category field to CreatePointItemRequest and removes sellingStatus from PointItemResponse.
Item Controller Endpoint Removal
src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java, src/main/java/com/example/green/domain/pointshop/item/controller/docs/PointItemAdminControllerDocs.java
Removes create, update, delete endpoints; retains list, show, display toggles, and Excel export.
Product Entity & Commands
src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java, src/main/java/com/example/green/domain/pointshop/product/service/command/PointProductUpdateCommand.java
Adds non-null Category field to PointProduct entity (default PRODUCT) and updates update command record.
Product Controller Endpoint Removal
src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java, src/main/java/com/example/green/domain/pointshop/product/controller/docs/PointProductAdminControllerDocs.java
Removes create, update, delete endpoints; retains read, Excel export, and display toggle operations.
Database Migration
src/main/resources/db/migration/V14__add_category_to_point_tables.sql
Adds category columns to point_item and point_products tables with NOT NULL constraint, defaulting existing rows.
Admin Tests
src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java, src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java
Adds integration and unit tests for new admin controller and service with multi-branch create/update/delete flows.
Item Test Updates
src/test/java/com/example/green/domain/pointshop/item/controller/PointItemAdminControllerTest.java, src/test/java/com/example/green/domain/pointshop/item/controller/PointItemOrderServiceControllerTest.java, src/test/java/com/example/green/domain/pointshop/item/service/\\*Test.java, src/test/java/com/example/green/domain/pointshop/item/controller/DummyData.java
Removes tests for create/update/delete operations; updates PointItem constructor calls to include Category parameter; deletes helper DTO class.
Product Test Updates
src/test/java/com/example/green/domain/pointshop/product/controller/PointProductAdminControllerTest.java, src/test/java/com/example/green/domain/pointshop/product/service/\\*Test.java, src/test/java/com/example/green/domain/pointshop/product/controller/DtoGenerator.java
Removes create/update/delete tests and DTO generator; retains show/hide and stock management tests.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin API
    participant ShopCtrl as PointShopAdminController
    participant ShopSvc as PointShopAdminService
    participant ItemSvc as PointItemService
    participant ItemRepo as PointItemRepository
    participant ProdSvc as PointProductService
    participant ProdRepo as PointProductRepository
    
    rect rgb(200, 240, 200)
    Note over Admin,ProdRepo: Create Operation (Type-based routing)
    Admin->>ShopCtrl: POST /api/admin/point-shop/create<br/>AdminCreatePointShopRequest
    ShopCtrl->>ShopSvc: create(request)
    alt Type == ITEM
        ShopSvc->>ItemSvc: create(PointItemCreateCommand)
        ItemSvc-->>ShopSvc: Long itemId
    else Type == PRODUCT
        ShopSvc->>ProdSvc: create(PointProductCreateCommand)
        ProdSvc-->>ShopSvc: Long productId
    else Unknown Type
        ShopSvc-->>ShopCtrl: PointShopAdminException(NOT_FOUND_CATEGORY)
    end
    ShopCtrl-->>Admin: 200 OK with ApiTemplate<Long>
    end
    
    rect rgb(200, 220, 240)
    Note over Admin,ProdRepo: Update Operation (Fallback lookup)
    Admin->>ShopCtrl: PUT /api/admin/point-shop/update/{id}<br/>AdminUpdatePointShopRequest
    ShopCtrl->>ShopSvc: update(request, id)
    ShopSvc->>ItemRepo: findById(id)
    alt Item Found
        ItemRepo-->>ShopSvc: PointItem
        ShopSvc->>ItemSvc: updatePointItem(PointItemUpdateCommand)
    else Item Not Found
        ShopSvc->>ProdRepo: findById(id)
        alt Product Found
            ProdRepo-->>ShopSvc: PointProduct
            ShopSvc->>ProdSvc: update(PointProductUpdateCommand)
        else Product Not Found
            ShopSvc-->>ShopCtrl: PointShopAdminException(NOT_FOUND_PRODUCT)
        end
    end
    ShopCtrl-->>Admin: 200 OK with NoContent
    end
    
    rect rgb(240, 200, 200)
    Note over Admin,ProdRepo: Delete Operation (Fallback lookup)
    Admin->>ShopCtrl: DELETE /api/admin/point-shop/delete/{id}
    ShopCtrl->>ShopSvc: delete(id)
    ShopSvc->>ItemRepo: findById(id)
    alt Item Found
        ItemRepo-->>ShopSvc: PointItem
        ShopSvc->>ItemSvc: delete(itemId)
    else Item Not Found
        ShopSvc->>ProdRepo: findById(id)
        alt Product Found
            ProdRepo-->>ShopSvc: PointProduct
            ShopSvc->>ProdSvc: delete(productId)
        else Product Not Found
            ShopSvc-->>ShopCtrl: PointShopAdminException(NOT_FOUND_PRODUCT)
        end
    end
    ShopCtrl-->>Admin: 200 OK with NoContent
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • PointShopAdminService: Multi-branch create logic and fallback update/delete flows with repository lookups across both item and product — verify correctness of branching logic and exception handling.
  • Category field addition: Ensure non-null constraints in database migration are backward-compatible; verify that all entity constructors, factory methods, and test instantiations correctly pass the new Category parameter (spot-check PointItem and PointProduct builders).
  • Endpoint removal: Verify that removing create/update/delete from PointItemAdminController and PointProductAdminController does not break dependent code or introduce orphaned imports; confirm the new PointShopAdminController handles all use cases.
  • Command record updates: Confirm PointItemCreateCommand, PointItemUpdateCommand, and PointProductUpdateCommand updates are consistently propagated across their usage sites (DTOs, service calls, tests).
  • Test coverage: Verify new PointShopAdminServiceTest covers all three branches (ITEM create, PRODUCT create, not found scenarios) and edge cases in update/delete fallback logic.

Possibly related PRs

Suggested labels

dev:feature

Poem

🐰 Hops with glee, the rabbit did see,
One controller for items and goods with such spree,
Category tags now adorn the wares,
Admin's domain unified—no more care,
Centralized creation, update, and deletion,
Our shops now organized to perfection!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title "포인트 상점 탭 분리 기능 구현" (Implement Point Shop Tab Separation Feature) accurately describes the main objective: separating point shop functionality into distinct tabs/categories (ITEM vs PRODUCT) with dedicated admin controllers and services.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/pointItem

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java (1)

86-92: Add validation for the required category field.

The category field is marked nullable = false but is not validated in validatePointItem. All other required fields (itemCode, itemBasicInfo, itemMedia, itemPrice) are validated with validateNullData.

Apply this diff to add category validation:

 private static void validatePointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia,
-	ItemPrice itemPrice) {
+	ItemPrice itemPrice, Category category) {
 	validateNullData(itemCode, REQUIRED_ITEM_CODE);
 	validateNullData(itemBasicInfo, REQUIRED_ITEM_BASIC_INFO);
 	validateNullData(itemMedia, REQUIRED_ITEM_MEDIA);
 	validateNullData(itemPrice, REQUIRED_ITEM_PRICE);
+	validateNullData(category, "Category is required");
 }

And update the constructor to call the updated validation:

 public PointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, ItemPrice itemPrice,
 	Category category) {
-	validatePointItem(itemCode, itemBasicInfo, itemMedia, itemPrice);
+	validatePointItem(itemCode, itemBasicInfo, itemMedia, itemPrice, category);
 	this.itemCode = itemCode;
🧹 Nitpick comments (10)
src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemUpdateCommand.java (1)

3-15: Consider null-safety for category and reduce “positional constructor” footguns

Adding Category category changes the record’s canonical constructor signature/order (Lines 9-15). If PointItemUpdateCommand is constructed positionally in multiple places, it’s easy to swap price/category accidentally—consider adding a static factory (named params) or at least ensuring validation rejects null category early (e.g., Objects.requireNonNull(category) in a compact constructor).

src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java (1)

44-67: Rename the Excel export handler to avoid confusing overloads

There are two findPointProducts(...) methods (Lines 45-50, 59-67) with different behaviors (paged query vs excel streaming). Recommend renaming the excel one (e.g., downloadPointProductsExcel) to keep controller intent obvious and make tracing/metrics clearer.

src/main/java/com/example/green/domain/pointshop/product/service/command/PointProductUpdateCommand.java (1)

3-3: Consider relocating Category to a shared package.

The Category enum is imported from the item domain (com.example.green.domain.pointshop.item.entity.vo.Category) but is used by both product and item domains. This creates a cross-domain dependency where the product domain depends on item domain for a shared concept.

Consider moving Category to a shared location such as:

  • com.example.green.domain.pointshop.common.vo.Category, or
  • com.example.green.domain.pointshop.vo.Category

This would eliminate the cross-domain dependency and better represent that Category is a shared concept across both items and products.

src/main/java/com/example/green/domain/pointshop/admin/service/PointShopType.java (1)

3-6: Clarify the distinction between PointShopType and Category enums.

Both PointShopType (this enum) and Category (in com.example.green.domain.pointshop.item.entity.vo.Category) define ITEM and PRODUCT values. While they may serve different purposes (admin API type discrimination vs. domain entity categorization), having two enums with identical values can lead to confusion and maintenance issues.

Consider one of the following approaches:

  1. If they serve the same conceptual purpose, unify them into a single enum
  2. If they serve distinct purposes, add documentation to clarify when to use each
  3. Add a mapping method or validation to ensure consistency between them

Alternatively, if PointShopType is purely for API layer routing, you might consider using the domain Category enum directly in the admin API to reduce duplication.

src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java (1)

74-89: Consider adding null validation for Category.

While the database constraint ensures Category is non-null, adding validation in validatePointProduct would provide earlier feedback and maintain consistency with other required fields (code, basicInfo, media, price, stock).

Apply this diff to add validation:

 private static void validatePointProduct(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock) {
   validateNullData(code, REQUIRED_CODE);
   validateNullData(basicInfo, REQUIRED_BASIC_INFO);
   validateNullData(media, REQUIRED_MEDIA);
   validateNullData(price, REQUIRED_PRICE);
   validateNullData(stock, REQUIRED_STOCK);
+  validateNullData(category, REQUIRED_CATEGORY);
   if (stock.isSoldOut()) {
     throw new PointProductException(PointProductExceptionMessage.INVALID_PRODUCT_STOCK_CREATION);
   }
 }

Note: You'll need to add the Category parameter to validatePointProduct signature and define REQUIRED_CATEGORY in PointProductExceptionMessage.

src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java (1)

228-237: Add test case for null category validation.

The test cases validate that code, basicInfo, media, price, and stock cannot be null, but there's no test case for a null category. For consistency and completeness, add a test case to verify that PointProduct.create throws an exception when category is null.

Apply this diff to add the missing test case:

 static Stream<Arguments> invalidPointProductTestCases() {
 	return Stream.of(
-		Arguments.of(null, basicInfo, media, price, stock),
-		Arguments.of(code, null, media, price, stock),
-		Arguments.of(code, basicInfo, null, price, stock),
-		Arguments.of(code, basicInfo, media, null, stock),
-		Arguments.of(code, basicInfo, media, price, null),
-		Arguments.of(code, basicInfo, media, price, new Stock(0))
+		Arguments.of(null, basicInfo, media, price, stock, category),
+		Arguments.of(code, null, media, price, stock, category),
+		Arguments.of(code, basicInfo, null, price, stock, category),
+		Arguments.of(code, basicInfo, media, null, stock, category),
+		Arguments.of(code, basicInfo, media, price, null, category),
+		Arguments.of(code, basicInfo, media, price, new Stock(0), category),
+		Arguments.of(code, basicInfo, media, price, stock, null)
 	);
 }
src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java (1)

40-41: Consider conditional validation for stock based on type.

The stock field is nullable and has no validation. However, for PRODUCT type entities, stock is typically required, while for ITEM type entities, stock may not apply. Consider adding conditional validation or documenting the expected behavior when stock is null for each type.

You might want to either:

  1. Add a custom validator that checks stock is provided when type is PRODUCT
  2. Document that null stock means "no change" for updates
  3. Use separate DTOs for item vs. product updates

Which approach aligns with your business requirements?

src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java (1)

40-41: Add conditional validation for stock based on type.

The stock field should be required for PRODUCT type but may not apply to ITEM type. Consider adding validation to enforce this business rule, such as:

  • A custom validator that requires stock when type is PRODUCT
  • Bean validation groups to apply different constraints based on type
  • Separate request DTOs for items vs. products

Which approach best fits your domain model?

src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java (1)

33-112: Add test coverage for validation failures and different types.

The current tests only cover happy-path scenarios. Consider adding tests for:

  • Validation failures (invalid/missing required fields) → expecting 400 Bad Request
  • Entity not found scenarios for update/delete → expecting 404 Not Found
  • Different behavior for ITEM vs. PRODUCT types

Example test structure:

@Test
void 필수_필드가_없으면_400_에러가_발생한다() {
    AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder()
        // missing required fields
        .build();
    
    RestAssuredMockMvc
        .given().contentType(MediaType.APPLICATION_JSON).body(request)
        .when().post("/api/admin/point-shop/create")
        .then().status(HttpStatus.BAD_REQUEST);
}
src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java (1)

46-55: Redundant Category parameter.

Category.ITEM is included in the PointItemCreateCommand (line 52) and also passed separately to pointItemService.create() (line 54). This duplication risks inconsistency if they diverge. Consider removing the redundant parameter from either the command or the service method signature.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1717b7 and 275f228.

📒 Files selected for processing (37)
  • src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/controller/docs/PointShopAdminControllerDocs.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/controller/message/PointShopAdminResponseMessage.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminExceptionMessage.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/admin/service/PointShopType.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java (0 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/controller/docs/PointItemAdminControllerDocs.java (0 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/dto/request/CreatePointItemRequest.java (2 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/dto/response/PointItemResponse.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java (2 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/entity/vo/Category.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java (2 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemCreateCommand.java (2 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemUpdateCommand.java (2 hunks)
  • src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/product/controller/docs/PointProductAdminControllerDocs.java (0 hunks)
  • src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java (3 hunks)
  • src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java (2 hunks)
  • src/main/java/com/example/green/domain/pointshop/product/service/command/PointProductUpdateCommand.java (2 hunks)
  • src/main/resources/db/migration/V14__add_category_to_point_tables.sql (1 hunks)
  • src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java (1 hunks)
  • src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java (1 hunks)
  • src/test/java/com/example/green/domain/pointshop/item/controller/DummyData.java (0 hunks)
  • src/test/java/com/example/green/domain/pointshop/item/controller/PointItemAdminControllerTest.java (0 hunks)
  • src/test/java/com/example/green/domain/pointshop/item/controller/PointItemOrderServiceControllerTest.java (2 hunks)
  • src/test/java/com/example/green/domain/pointshop/item/service/PointItemQueryServiceTest.java (2 hunks)
  • src/test/java/com/example/green/domain/pointshop/item/service/PointItemServiceTest.java (0 hunks)
  • src/test/java/com/example/green/domain/pointshop/product/controller/DtoGenerator.java (0 hunks)
  • src/test/java/com/example/green/domain/pointshop/product/controller/PointProductAdminControllerTest.java (1 hunks)
  • src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java (2 hunks)
  • src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java (11 hunks)
  • src/test/java/com/example/green/domain/pointshop/product/service/PointProductServiceTest.java (1 hunks)
  • src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java (2 hunks)
💤 Files with no reviewable changes (7)
  • src/main/java/com/example/green/domain/pointshop/product/controller/docs/PointProductAdminControllerDocs.java
  • src/test/java/com/example/green/domain/pointshop/product/controller/DtoGenerator.java
  • src/main/java/com/example/green/domain/pointshop/item/controller/docs/PointItemAdminControllerDocs.java
  • src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java
  • src/test/java/com/example/green/domain/pointshop/item/service/PointItemServiceTest.java
  • src/test/java/com/example/green/domain/pointshop/item/controller/PointItemAdminControllerTest.java
  • src/test/java/com/example/green/domain/pointshop/item/controller/DummyData.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java (3)
src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java (1)
  • Service (22-109)
src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java (1)
  • Service (19-95)
src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java (1)
  • PointShopAdminException (6-11)
src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java (2)
src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java (1)
  • RestController (33-80)
src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java (1)
  • RestController (36-96)
🔇 Additional comments (20)
src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java (1)

33-80: Verify API contract after removing create/update/delete from this controller

Given the removed endpoints, please double-check clients/docs/tests now point to the new admin controller for create/update/delete, and that no internal tooling still calls /api/admin/point-products POST/PUT/DELETE.

src/main/java/com/example/green/domain/pointshop/item/dto/response/PointItemResponse.java (1)

5-10: Breaking response-shape change: ensure consumers are updated (or versioned)

Removing SellingStatus from PointItemResponse changes the JSON contract (Lines 5-10). Please ensure all API consumers (front-end/batch/partners) and any serialization-based tests/docs are updated accordingly, or consider response versioning if backward compatibility is required.

src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemCreateCommand.java (1)

9-15: LGTM!

The addition of the Category field to the create command aligns well with the broader refactor to support category-based point shop tab separation.

src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java (1)

6-11: LGTM!

The exception class follows standard patterns, correctly extends BusinessException, and provides appropriate constructor for exception message handling.

src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java (1)

39-46: LGTM!

The test correctly passes Category.PRODUCT to the updated PointProduct.create method, maintaining test validity while aligning with the new category-aware entity creation flow.

src/test/java/com/example/green/domain/pointshop/item/service/PointItemQueryServiceTest.java (1)

73-80: LGTM!

The test correctly constructs the PointItem with Category.ITEM, properly aligning with the extended entity constructor signature.

src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java (1)

31-43: No action required—all callers have been updated.

The sole production caller of PointItemService.create() in PointShopAdminService.java (line 54) correctly provides both the PointItemCreateCommand and Category parameters. The method signature change is fully implemented.

src/test/java/com/example/green/domain/pointshop/product/controller/PointProductAdminControllerTest.java (1)

43-104: The point product create, update, and delete operations are adequately covered in PointShopAdminControllerTest, which tests unified admin operations for both point items and point products through PointShopAdminService. The test class includes coverage for create, update, and delete operations via the three test methods present.

src/test/java/com/example/green/domain/pointshop/item/controller/PointItemOrderServiceControllerTest.java (1)

22-22: LGTM!

The Category import and its usage in the PointItem constructor are correct. The test properly reflects the updated entity signature.

Also applies to: 55-55

src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java (1)

15-15: LGTM!

The test correctly uses Category.PRODUCT for product entity creation, properly reflecting the domain model changes.

Also applies to: 74-75

src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java (1)

69-71: LGTM - Category field properly configured.

The Category field is correctly defined with non-null constraint and STRING enumeration. Using STRING enumeration is a good practice for maintainability.

src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java (3)

48-95: LGTM - Comprehensive create routing tests.

The tests properly verify that create operations are routed to the correct service (PointItemService or PointProductService) based on the request type, with appropriate verification of service interactions.


97-157: LGTM - Update routing logic well tested.

The tests comprehensively cover update scenarios including fallback behavior when an item doesn't exist but a product does, and proper exception handling when neither exists.


159-197: LGTM - Delete routing logic properly tested.

The delete tests mirror the update test structure and provide comprehensive coverage of the routing logic with proper exception handling.

src/test/java/com/example/green/domain/pointshop/product/service/PointProductServiceTest.java (1)

1-99: Test scope appropriately adjusted for architectural changes.

The removal of create, update, and delete tests from this file aligns with the PR's centralization of admin operations into PointShopAdminService. These operations are now tested in PointShopAdminServiceTest.java. The remaining tests focus on non-admin product operations (display toggles, snapshots, stock management).

src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java (1)

28-41: All callers have been properly updated.

The method signature change to include the Category parameter has been fully implemented. PointShopAdminService.create() passes Category.PRODUCT at line 65, and the corresponding test mock at line 85 properly accepts the additional parameter.

src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java (1)

65-67: Consider whether category should be mutable.

Unlike other fields (itemCode, itemBasicInfo, itemMedia, itemPrice), there is no updateCategory method. If the category is intended to be immutable after creation, this is fine. Otherwise, consider adding an update method for consistency.

Is the category field intended to be immutable, or should it support updates like other entity fields?

src/main/java/com/example/green/domain/pointshop/item/entity/vo/Category.java (1)

1-16: LGTM! Clean enum implementation.

The Category enum is well-designed with clear constant names and Korean display values. The use of @JsonValue ensures proper JSON serialization, and Lombok annotations eliminate boilerplate. This provides a type-safe way to distinguish between products and items.

src/main/java/com/example/green/domain/pointshop/admin/controller/docs/PointShopAdminControllerDocs.java (1)

1-45: LGTM! Comprehensive OpenAPI documentation.

The interface provides clear API documentation with appropriate response codes and error schemas. The operation summaries and descriptions are informative, and the consistent use of ExceptionResponse for error cases follows best practices.

src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java (1)

24-51: Update the review with accurate information about the existing controllers.

The existing PointItemAdminController and PointProductAdminController have no create/update/delete endpoints—only read operations (GET) and partial updates (PATCH for display visibility). The new PointShopAdminController provides a unified CUD API for both items and products, determined by the type field in the request. This is intentional by design (documented in the controller docs), not redundant with existing endpoints.

However, clarify the architectural rationale: Why consolidate items and products under a single endpoint when separate domain controllers exist? Consider documenting whether this unified approach is part of a broader admin API refactoring strategy or if there's a specific reason to merge CUD operations at the /api/admin/point-shop level.


POINT_ITEM_CREATION_SUCCESS("포인트 상점 아이템 / 상품 생성에 성공했습니다."),
POINT_ITEM_UPDATE_SUCCESS("포인트 상점 아이템 / 상품 수정에 성공했습니다."),
POINT_ITEM_DELETE_SUCCESS("아이템 / 상품수정 삭제에 성공했습니다.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in delete success message.

The message contains "상품수정 삭제" where "수정" (modification) appears incorrectly. It should be "상품 삭제" (product deletion).

Apply this diff to correct the message:

 	POINT_ITEM_CREATION_SUCCESS("포인트 상점 아이템 / 상품 생성에 성공했습니다."),
 	POINT_ITEM_UPDATE_SUCCESS("포인트 상점 아이템 / 상품 수정에 성공했습니다."),
-	POINT_ITEM_DELETE_SUCCESS("아이템 / 상품수정 삭제에 성공했습니다.");
+	POINT_ITEM_DELETE_SUCCESS("아이템 / 상품 삭제에 성공했습니다.");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
POINT_ITEM_DELETE_SUCCESS("아이템 / 상품수정 삭제에 성공했습니다.");
POINT_ITEM_CREATION_SUCCESS("포인트 상점 아이템 / 상품 생성에 성공했습니다."),
POINT_ITEM_UPDATE_SUCCESS("포인트 상점 아이템 / 상품 수정에 성공했습니다."),
POINT_ITEM_DELETE_SUCCESS("아이템 / 상품 삭제에 성공했습니다.");
🤖 Prompt for AI Agents
In
src/main/java/com/example/green/domain/pointshop/admin/controller/message/PointShopAdminResponseMessage.java
around line 14, the enum constant message contains a typo "아이템 / 상품수정 삭제에
성공했습니다." — replace "상품수정" with "상품" so the message reads "아이템 / 상품 삭제에 성공했습니다."
and save the file.

@PostMapping("/create")
public ApiTemplate<Long> create(@RequestBody @Valid AdminCreatePointShopRequest createPointShopRequest) {
Long result = pointShopAdminService.create(createPointShopRequest);
return ApiTemplate.ok(POINT_ITEM_CREATION_SUCCESS, result);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use type-agnostic message constants or conditional messages.

The endpoint uses POINT_ITEM_CREATION_SUCCESS for all creations, but this controller handles both items and products. Consider either:

  1. Renaming the constants to be generic (e.g., POINT_SHOP_CREATION_SUCCESS)
  2. Using conditional messages based on the request type

The same issue applies to lines 44 and 50 with POINT_ITEM_UPDATE_SUCCESS and POINT_ITEM_DELETE_SUCCESS.

🤖 Prompt for AI Agents
In
src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java
around lines 35, 44 and 50, the controller currently returns
POINT_ITEM_CREATION_SUCCESS, POINT_ITEM_UPDATE_SUCCESS and
POINT_ITEM_DELETE_SUCCESS for both item and product operations; change to use
type-agnostic constants (e.g., POINT_SHOP_CREATION_SUCCESS,
POINT_SHOP_UPDATE_SUCCESS, POINT_SHOP_DELETE_SUCCESS) or select the message
conditionally based on the incoming DTO/type before calling ApiTemplate.ok, and
update usages/imports accordingly so responses reflect the correct generic or
conditional message.

@Schema(description = "상품 생성 요청")
@Builder
public record AdminCreatePointShopRequest(
PointShopType type,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add @NotNull validation to the type field.

The type field is essential for determining whether to create an item or a product, but it lacks validation. Without @NotNull, the field could be null, causing routing failures in the service layer.

Apply this diff to add validation:

 public record AdminCreatePointShopRequest(
+	@NotNull(message = "상품 타입은 필수입니다.")
 	PointShopType type,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PointShopType type,
@NotNull(message = "상품 타입은 필수입니다.")
PointShopType type,
🤖 Prompt for AI Agents
In
src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java
around line 16, the PointShopType type field lacks validation; add the
javax.validation constraint @NotNull to the type field declaration (and import
javax.validation.constraints.NotNull if missing) so the DTO enforces non-null
input and prevents null routing to the service layer.

@Schema(description = "상품 수정 요청")
@Builder
public record AdminUpdatePointShopRequest(
PointShopType type,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add @NotNull validation to the type field.

The type field is essential for determining whether the request updates an item or a product, but it lacks validation. Without @NotNull, the field could be null, leading to potential NullPointerException in the service layer.

Apply this diff to add validation:

 public record AdminUpdatePointShopRequest(
+	@NotNull(message = "상품 타입은 필수입니다.")
 	PointShopType type,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PointShopType type,
@NotNull(message = "상품 타입은 필수입니다.")
PointShopType type,
🤖 Prompt for AI Agents
In
src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java
around line 16, the PointShopType type field lacks validation; add the
javax.validation constraint @NotNull to the type field declaration and import
the annotation so the field is validated on request binding, ensuring null
values are rejected before reaching the service layer.

Comment on lines +12 to +22
@RequiredArgsConstructor
@Getter
public enum PointShopAdminExceptionMessage implements ExceptionMessage {

NOT_FOUND_CATEGORY(NOT_FOUND, "카테고리를 찾을 수가 없습니다"),
NOT_FOUND_PRODUCT(NOT_FOUND, "등록된 상품 / 아이템을 찾을 수 없습니다");

private final HttpStatus httpStatus;
private final String message;

}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Polish error message Korean phrasing for consistency

"카테고리를 찾을 수가 없습니다" (Line 16) reads unnatural; usually "카테고리를 찾을 수 없습니다". Same suggestion if other enums standardize on that phrasing.

-	NOT_FOUND_CATEGORY(NOT_FOUND, "카테고리를 찾을 수가 없습니다"),
+	NOT_FOUND_CATEGORY(NOT_FOUND, "카테고리를 찾을 수 없습니다"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@RequiredArgsConstructor
@Getter
public enum PointShopAdminExceptionMessage implements ExceptionMessage {
NOT_FOUND_CATEGORY(NOT_FOUND, "카테고리를 찾을 수가 없습니다"),
NOT_FOUND_PRODUCT(NOT_FOUND, "등록된 상품 / 아이템을 찾을 수 없습니다");
private final HttpStatus httpStatus;
private final String message;
}
@RequiredArgsConstructor
@Getter
public enum PointShopAdminExceptionMessage implements ExceptionMessage {
NOT_FOUND_CATEGORY(NOT_FOUND, "카테고리를 찾을 없습니다"),
NOT_FOUND_PRODUCT(NOT_FOUND, "등록된 상품 / 아이템을 찾을 수 없습니다");
private final HttpStatus httpStatus;
private final String message;
}
🤖 Prompt for AI Agents
In
src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminExceptionMessage.java
around lines 12 to 22, the Korean message for NOT_FOUND_CATEGORY uses the less
natural phrasing "카테고리를 찾을 수가 없습니다"; update it to the more natural "카테고리를 찾을 수
없습니다" and ensure other enum entries follow the same phrasing convention for
consistency (adjust strings accordingly).

Comment on lines +104 to +116
public void delete(Long productId) {
Optional<PointItem> pointItem = pointItemRepository.findById(productId);
if (pointItem.isPresent()) {
pointItemService.delete(productId);
return;
}
Optional<PointProduct> pointProduct = pointProductRepository.findById(productId);
if (pointProduct.isPresent()) {
pointProductService.delete(productId);
return;
}
throw new PointShopAdminException(PointShopAdminExceptionMessage.NOT_FOUND_PRODUCT);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same ID collision issue as update(); also inefficient existence check.

This method has the same problem: if both tables have an entity with the same ID, the PointItem is always deleted. Consider adding a PointShopType parameter to explicitly route to the correct service.

Additionally, findById fetches the entire entity just to check existence. Use existsById for efficiency if you retain the probing approach:

 public void delete(Long productId) {
-    Optional<PointItem> pointItem = pointItemRepository.findById(productId);
-    if (pointItem.isPresent()) {
+    if (pointItemRepository.existsById(productId)) {
         pointItemService.delete(productId);
         return;
     }
-    Optional<PointProduct> pointProduct = pointProductRepository.findById(productId);
-    if (pointProduct.isPresent()) {
+    if (pointProductRepository.existsById(productId)) {
         pointProductService.delete(productId);
         return;
     }
     throw new PointShopAdminException(PointShopAdminExceptionMessage.NOT_FOUND_PRODUCT);
 }

However, the preferred fix is to require a type discriminator like create() does.

Comment on lines +38 to 51
BigDecimal price,

@Schema(description = "분류 카테고리", example = "ITEM")
Category category

) {
public PointItemCreateCommand toCommand() {
return new PointItemCreateCommand(
new ItemCode(code),
new ItemBasicInfo(name, description),
new ItemMedia(thumbnailUrl),
new ItemPrice(price)
new ItemPrice(price),
category
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add validation constraint for required category field.

The category field lacks a @NotNull validation annotation, unlike other required fields in this DTO. This could allow null values to pass through to the service layer, causing issues when creating entities that expect non-null categories.

Apply this diff to add validation:

 	@NotNull(message = "아이템 상품 가격은 필수입니다.")
 	@Schema(description = "아이템 상품 가격", example = "10000")
 	BigDecimal price,

+	@NotNull(message = "아이템 카테고리는 필수입니다.")
 	@Schema(description = "분류 카테고리", example = "ITEM")
 	Category category
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BigDecimal price,
@Schema(description = "분류 카테고리", example = "ITEM")
Category category
) {
public PointItemCreateCommand toCommand() {
return new PointItemCreateCommand(
new ItemCode(code),
new ItemBasicInfo(name, description),
new ItemMedia(thumbnailUrl),
new ItemPrice(price)
new ItemPrice(price),
category
);
BigDecimal price,
@NotNull(message = "아이템 카테고리는 필수입니다.")
@Schema(description = "분류 카테고리", example = "ITEM")
Category category
) {
public PointItemCreateCommand toCommand() {
return new PointItemCreateCommand(
new ItemCode(code),
new ItemBasicInfo(name, description),
new ItemMedia(thumbnailUrl),
new ItemPrice(price),
category
);
🤖 Prompt for AI Agents
In
src/main/java/com/example/green/domain/pointshop/item/dto/request/CreatePointItemRequest.java
around lines 38 to 51, the Category field is missing a @NotNull validation; add
the javax.validation.constraints.NotNull annotation to the category field
declaration (above the @Schema) so it cannot be null, and add the corresponding
import statement for NotNull at the top of the file.

private final FileClient fileClient;

public Long create(PointItemCreateCommand createCommand) {
public Long create(PointItemCreateCommand createCommand, Category category) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add validation or documentation for the Category parameter.

The create method now accepts a Category parameter without validation or null checks. While the parameter may come from a command object that enforces non-null, the service method should be defensive.

Consider adding:

  1. Parameter validation (e.g., Objects.requireNonNull(category, "category must not be null"))
  2. JavaDoc documentation explaining valid category values and their significance
+	/**
+	 * Creates a new point item with the specified category.
+	 * @param createCommand the command containing item details
+	 * @param category the category for the point item (must not be null)
+	 * @return the ID of the created point item
+	 */
 	public Long create(PointItemCreateCommand createCommand, Category category) {
+		Objects.requireNonNull(category, "category must not be null");
 		validatePointItemCode(createCommand.itemCode());
🤖 Prompt for AI Agents
In
src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java
around line 31, the create(PointItemCreateCommand createCommand, Category
category) method accepts a Category without validation or documentation; add a
defensive null-check (e.g., Objects.requireNonNull(category, "category must not
be null") or equivalent validation for allowed values) at the start of the
method and add JavaDoc to the method signature describing that category must be
non-null and listing/pointing to valid Category values and their meaning.

Comment on lines +1 to +13
-- point_item 테이블에 category 컬럼 추가
ALTER TABLE point_item
ADD COLUMN category VARCHAR(20);

-- 기존 point_item 데이터 → ITEM
UPDATE point_item
SET category = 'ITEM'
WHERE category IS NULL;

-- NOT NULL 제약 추가
ALTER TABLE point_item
ALTER COLUMN category SET NOT NULL;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a DB-level DEFAULT (and optionally a CHECK) for category

Right now there’s no default, so inserts that omit category will fail once NOT NULL is enforced (Lines 10-13, 24-26). Consider setting a per-table default, and (optionally) a CHECK constraint to prevent invalid strings.

 -- point_item 테이블에 category 컬럼 추가
 ALTER TABLE point_item
-ADD COLUMN category VARCHAR(20);
+ADD COLUMN category VARCHAR(20) NOT NULL DEFAULT 'ITEM';
 
--- 기존 point_item 데이터 → ITEM
-UPDATE point_item
-SET category = 'ITEM'
-WHERE category IS NULL;
-
--- NOT NULL 제약 추가
-ALTER TABLE point_item
-ALTER COLUMN category SET NOT NULL;
+-- (optional) 값 제한
+-- ALTER TABLE point_item ADD CONSTRAINT chk_point_item_category CHECK (category IN ('ITEM'));
 
@@
 ALTER TABLE point_products
-ADD COLUMN category VARCHAR(20);
+ADD COLUMN category VARCHAR(20) NOT NULL DEFAULT 'PRODUCT';
 
--- 기존 point_products 데이터 → PRODUCT
-UPDATE point_products
-SET category = 'PRODUCT'
-WHERE category IS NULL;
-
--- NOT NULL 제약 추가
-ALTER TABLE point_products
-ALTER COLUMN category SET NOT NULL;
+-- (optional) 값 제한
+-- ALTER TABLE point_products ADD CONSTRAINT chk_point_products_category CHECK (category IN ('PRODUCT'));

Also applies to: 15-26

🤖 Prompt for AI Agents
In src/main/resources/db/migration/V14__add_category_to_point_tables.sql around
lines 1-13 (and similarly for the later block around 15-26), the migration adds
a NOT NULL category column but does not define a DB-level DEFAULT or a CHECK
constraint; add a DEFAULT 'ITEM' to the column definition (or via ALTER COLUMN
SET DEFAULT) so inserts that omit category succeed, run the backfill to set
existing NULLs to 'ITEM', then set NOT NULL, and optionally add a CHECK
constraint limiting allowed values (e.g. category IN ('ITEM', ...)) to prevent
invalid strings; ensure the migration orders are: add column with DEFAULT,
update NULLs, add CHECK (if desired), then ALTER COLUMN ... SET NOT NULL.

Comment on lines +35 to +41
AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder()
.code("ITEM001")
.name("테스트 상품")
.description("테스트 설명")
.thumbnailUrl("https://test.com/image.png")
.price(BigDecimal.valueOf(1000))
.build();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add the required type field to the test request.

The test builds an AdminCreatePointShopRequest without setting the type field, which means type will be null. Since the service logic routes based on this field, the test should specify whether it's creating a PRODUCT or an ITEM.

Apply this diff to fix the test:

 AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder()
+	.type(PointShopType.ITEM)  // or PRODUCT
 	.code("ITEM001")
 	.name("테스트 상품")
 	.description("테스트 설명")

The same issue exists in the update test at lines 70-76.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder()
.code("ITEM001")
.name("테스트 상품")
.description("테스트 설명")
.thumbnailUrl("https://test.com/image.png")
.price(BigDecimal.valueOf(1000))
.build();
AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder()
.type(PointShopType.ITEM)
.code("ITEM001")
.name("테스트 상품")
.description("테스트 설명")
.thumbnailUrl("https://test.com/image.png")
.price(BigDecimal.valueOf(1000))
.build();
🤖 Prompt for AI Agents
In
src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java
around lines 35-41 and 70-76, the test request builders omit the required type
field causing null routing; update both the create and update
AdminCreatePointShopRequest/AdminUpdatePointShopRequest builders to set the type
explicitly (e.g., PointShopType.PRODUCT or PointShopType.ITEM) so the service
routing logic has a non-null value and tests exercise the intended branch.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Dec 17, 2025

Unit Test Coverage Report

Total Project Coverage 36.9%
File Coverage [86.77%]
PointShopAdminController.java 100% 🍏
PointShopAdminResponseMessage.java 100% 🍏
PointProductAdminController.java 100% 🍏
PointShopType.java 100% 🍏
PointShopAdminExceptionMessage.java 100% 🍏
PointShopAdminException.java 100% 🍏
Category.java 100% 🍏
PointShopAdminService.java 97.55% 🍏
PointProduct.java 96.2% 🍏
PointItemAdminController.java 64.15% 🍏
PointItem.java 42.57%

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java (1)

218-228: Remove unused test data method.

The invalidPointProductTestCases() method is not referenced by any @ParameterizedTest in this file. According to the AI summary, the invalid-argument parameterized test was removed, making this method dead code.

Remove the unused method:

-	static Stream<Arguments> invalidPointProductTestCases() {
-		return Stream.of(
-			Arguments.of(null, basicInfo, media, price, stock, category),
-			Arguments.of(code, null, media, price, stock, category),
-			Arguments.of(code, basicInfo, null, price, stock, category),
-			Arguments.of(code, basicInfo, media, null, stock, category),
-			Arguments.of(code, basicInfo, media, price, null, category),
-			Arguments.of(code, basicInfo, media, price, new Stock(0), category),
-			Arguments.of(code, basicInfo, media, price, stock, Category.PRODUCT)
-		);
-	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 275f228 and b3c1227.

📒 Files selected for processing (1)
  • src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java (10 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: check

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java (3)

34-36: Missing @Transactional annotation (already flagged).

This issue was raised in a previous review. Both delegate services use @Transactional at the class level, and this service performs multi-step operations (lookup + delegate) that should be atomic.


72-101: ID collision risk when probing both repositories (already flagged).

This issue was raised in a previous review. When PointItem and PointProduct have separate ID sequences, overlapping IDs cause misrouting. The past comment recommended adding a PointShopType discriminator to the request (similar to create()) to route directly.


104-116: Same ID collision issue as update() (already flagged).

This method has the same probing-by-ID problem that was raised in the previous review. Consider requiring a type discriminator to route correctly when IDs overlap across tables.

🧹 Nitpick comments (2)
src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java (1)

46-68: Consider whether Category and PointShopType represent the same concept.

The code hardcodes Category.ITEM when request.type() == PointShopType.ITEM (line 52) and presumably Category.PRODUCT for PointShopType.PRODUCT. If these enums are always mapped 1:1, maintaining both creates redundancy and potential for drift. Consider deriving Category from PointShopType or consolidating into a single enum.

src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java (1)

94-112: Consider whether category should be mutable.

The class provides update methods for other fields (updateItemCode, updateItemBasicInfo, etc.), but not for category. If category is intentionally immutable after entity creation, this is fine. However, if it should be mutable, consider adding an updateCategory method for consistency with the existing design pattern.

If category should be mutable, apply this diff:

 	public void updateItemPrice(ItemPrice itemPrice) {
 		validateNullData(itemPrice, REQUIRED_ITEM_PRICE);
 		this.itemPrice = itemPrice;
 	}
+
+	public void updateCategory(Category category) {
+		validateNullData(category, "카테고리는 필수입니다.");
+		this.category = category;
+	}
 
 	public boolean isNewImage(ItemMedia media) {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3c1227 and 7ee2567.

📒 Files selected for processing (6)
  • src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java (1 hunks)
  • src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java (2 hunks)
  • src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java (3 hunks)
  • src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java (1 hunks)
  • src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java (1 hunks)
  • src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java
  • src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java
  • src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java (3)
src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java (1)
  • Service (18-93)
src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java (1)
  • Service (21-107)
src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java (1)
  • PointShopAdminException (6-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: check
🔇 Additional comments (3)
src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java (3)

7-7: LGTM!

The import for the new Category enum is properly added.


65-67: LGTM!

The category field is properly declared with appropriate JPA annotations. Using EnumType.STRING ensures database readability and the nullable = false constraint maintains data integrity.


81-84: LGTM!

The static factory method provides backward compatibility by defaulting to Category.ITEM, which is appropriate for the PointItem entity.

Comment on lines +70 to 72
public PointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, ItemPrice itemPrice,
Category category) {
validatePointItem(itemCode, itemBasicInfo, itemMedia, itemPrice);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation for the category parameter.

The constructor accepts a category parameter and assigns it (line 78), but the validation method validatePointItem (line 72) does not validate it. Since the database column is marked as nullable = false (line 65), passing a null category will cause a constraint violation at persist time.

Apply this diff to add validation for the category parameter:

 	private static void validatePointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia,
-		ItemPrice itemPrice) {
+		ItemPrice itemPrice, Category category) {
 		validateNullData(itemCode, REQUIRED_ITEM_CODE);
 		validateNullData(itemBasicInfo, REQUIRED_ITEM_BASIC_INFO);
 		validateNullData(itemMedia, REQUIRED_ITEM_MEDIA);
 		validateNullData(itemPrice, REQUIRED_ITEM_PRICE);
+		validateNullData(category, "카테고리는 필수입니다.");
 	}

Then update the constructor call:

 	public PointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, ItemPrice itemPrice,
 		Category category) {
-		validatePointItem(itemCode, itemBasicInfo, itemMedia, itemPrice);
+		validatePointItem(itemCode, itemBasicInfo, itemMedia, itemPrice, category);
 		this.itemCode = itemCode;

Note: You'll need to add a constant for the category validation message (e.g., REQUIRED_CATEGORY) in the exception message class, similar to the other REQUIRED_* constants.

Also applies to: 78-78

🤖 Prompt for AI Agents
In src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java
around lines 70-78, the constructor accepts a Category but
validatePointItem(...) is not checking it, causing potential DB constraint
violations; update validation to check category != null and throw the domain
validation exception with a new message constant REQUIRED_CATEGORY, add that
REQUIRED_CATEGORY constant to the exception message class to match existing
REQUIRED_* constants, and adjust the constructor to call the updated
validatePointItem (or include a separate null check) before assigning the
category field.

@sanchaehwa sanchaehwa merged commit e0cbe39 into main Dec 17, 2025
3 checks passed
@sanchaehwa sanchaehwa deleted the fix/pointItem branch December 17, 2025 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant