Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ dependencies {

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// Excel (Apache POI)
implementation 'org.apache.poi:poi-ooxml:5.2.3'
Comment on lines +54 to +55
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

curl -s "https://search.maven.org/solrsearch/select?q=g:org.apache.poi+AND+a:poi-ooxml&rows=10&wt=json" | jq -r '.response.docs[] | "\(.v)"' | head -20

Repository: almang2/inventory-server

Length of output: 72


🌐 Web query:

Apache POI poi-ooxml 5.2.3 security vulnerabilities CVE

💡 Result:

Yes — poi-ooxml 5.2.3 is vulnerable to CVE-2025-31672 (Improper Input Validation: OOXML ZIP entries with duplicate names). The issue affects all poi-ooxml versions < 5.4.0; upgrade to >= 5.4.0 to fix it. [1][2][3]

Sources:

  • NVD entry for CVE-2025-31672. [1]
  • GitHub Advisory (Apache POI poi-ooxml < 5.4.0). [2]
  • Snyk vulnerability page / package details. [3]

🌐 Web query:

Apache POI latest version 2024 2025

💡 Result:

Latest Apache POI releases (relevant to 2024–2025):

  • 5.3.0 — released 2024-07-02. [1]
  • 5.4.0 — released 2025-01-08. [1]
  • 5.4.1 — released 2025-04-06. [1]
  • 5.5.0 — released 2025-11-15 (latest stable). [2][1]

Sources: Apache POI changelog and download page. [1] https://poi.apache.org/changes.html [2] https://poi.apache.org/download.cgi


Upgrade Apache POI to patch CVE-2025-31672.

poi-ooxml:5.2.3 is vulnerable to CVE-2025-31672 (improper input validation in OOXML ZIP entries with duplicate names). This affects all versions below 5.4.0. Upgrade to at least 5.4.0 (or 5.5.0, the current latest stable release).

🤖 Prompt for AI Agents
In build.gradle around lines 54-55 the Apache POI dependency is pinned to
vulnerable version 'org.apache.poi:poi-ooxml:5.2.3'; update the dependency
version to at least 5.4.0 (preferably 5.5.0) to address CVE-2025-31672 by
replacing the version string with 'org.apache.poi:poi-ooxml:5.5.0' (or '5.4.0'
if you prefer), then run a Gradle refresh/resolve to ensure the updated artifact
is fetched and the build still passes.

}

tasks.named('test') {
Expand Down
16 changes: 16 additions & 0 deletions generate_sample_excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pandas as pd

# Create sample data
data = {
'Product Code': ['P00000LM000D', 'P00000WA000B', 'P00000IP000A'],
'Quantity': [10, 5, 20],
'Applied At': ['2023-12-01 10:00:00', '2023-12-01 11:00:00', '']
}
Comment on lines +4 to +8
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

샘플 데이터 컬럼 위치가 RetailService와 일치하지 않습니다.

RetailService에서는 Cell 1(인덱스 1)에서 POS 코드를, Cell 3(인덱스 3)에서 수량을 읽습니다. 현재 샘플 데이터는 인덱스 0, 1, 2에 데이터를 배치하므로 실제 업로드 시 파싱 오류가 발생할 수 있습니다.

다음과 같이 수정하여 컬럼 위치를 맞추세요:

 data = {
-    'Product Code': ['P00000LM000D', 'P00000WA000B', 'P00000IP000A'],
-    'Quantity': [10, 5, 20],
-    'Applied At': ['2023-12-01 10:00:00', '2023-12-01 11:00:00', '']
+    'Empty': ['', '', ''],           # Cell 0
+    'POS Code': ['P00000LM000D', 'P00000WA000B', 'P00000IP000A'],  # Cell 1
+    'Empty2': ['', '', ''],          # Cell 2
+    'Quantity': [10, 5, 20],         # Cell 3
+    'Applied At': ['2023-12-01 10:00:00', '2023-12-01 11:00:00', '']  # Cell 4 (미사용)
 }
🤖 Prompt for AI Agents
In generate_sample_excel.py around lines 4 to 8, the sample data columns are
currently at indices 0,1,2 but RetailService expects the POS code at index 1 and
quantity at index 3; modify the data dict so there are placeholder columns to
align positions (e.g., add a first-column placeholder and a third-column
placeholder) and place 'Product Code' at index 1 and 'Quantity' at index 3 (keep
'Applied At' after those), ensuring each row has the same number of entries so
exported Excel columns match RetailService's expected cell indices.


# Create DataFrame
df = pd.DataFrame(data)

# Save to Excel
df.to_excel('sample_retail.xlsx', index=False)

print("Sample Excel file 'sample_retail.xlsx' created successfully.")
Binary file added sample_retail.xlsx
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import com.almang.inventory.customerorder.repository.CustomerOrderRepository;
import com.almang.inventory.global.exception.BaseException;
import com.almang.inventory.global.exception.ErrorCode;
import com.almang.inventory.inventory.domain.Inventory;
import com.almang.inventory.inventory.repository.InventoryRepository;
import com.almang.inventory.product.domain.Product;
import com.almang.inventory.product.repository.ProductRepository;
Expand All @@ -16,12 +15,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
Expand All @@ -37,7 +30,8 @@ public Long createCustomerOrderAndProcessStock(CustomerOrderRequest request) {
// 1. 이미 존재하는 카페24 주문인지 확인 (중복 처리 방지)
customerOrderRepository.findByCafe24OrderId(request.getCafe24OrderId())
.ifPresent(order -> {
throw new BaseException(ErrorCode.DUPLICATE_CUSTOMER_ORDER, "Cafe24 Order ID already exists: " + request.getCafe24OrderId());
throw new BaseException(ErrorCode.DUPLICATE_CUSTOMER_ORDER,
"Cafe24 Order ID already exists: " + request.getCafe24OrderId());
});

// 2. CustomerOrder 엔티티 생성
Expand All @@ -46,7 +40,9 @@ public Long createCustomerOrderAndProcessStock(CustomerOrderRequest request) {
.orderAt(request.getOrderAt())
.isPaid(request.getIsPaid().equalsIgnoreCase("T")) // 'T'/'F' 문자열을 boolean으로 변환
.isCanceled(request.getIsCanceled().equalsIgnoreCase("T")) // 'T'/'F' 문자열을 boolean으로 변환
.paymentMethod(request.getPaymentMethodName() != null && !request.getPaymentMethodName().isEmpty() ? request.getPaymentMethodName().get(0) : null)
.paymentMethod(request.getPaymentMethodName() != null && !request.getPaymentMethodName().isEmpty()
? request.getPaymentMethodName().get(0)
: null)
.paymentAmount(request.getPaymentAmount())
.billingName(request.getBillingName())
.memberId(request.getMemberId())
Expand All @@ -58,20 +54,23 @@ public Long createCustomerOrderAndProcessStock(CustomerOrderRequest request) {
// 3. CustomerOrderItem 처리 및 재고 처리 (감소 로직은 Placeholder)
if (request.getItems() != null && !request.getItems().isEmpty()) {
for (CustomerOrderItemRequest itemRequest : request.getItems()) {
// 3.1. 상품 조회 (productCode 사용)
Product product = productRepository.findByCode(itemRequest.getProductCode())
.orElseThrow(() -> new BaseException(ErrorCode.PRODUCT_NOT_FOUND, "Product not found with code: " + itemRequest.getProductCode()));
// 3.1. 상품 조회 (cafe24Code 사용)
Product product = productRepository.findByCafe24Code(itemRequest.getProductCode())
.orElseThrow(() -> new BaseException(ErrorCode.PRODUCT_NOT_FOUND,
"Product not found with code: " + itemRequest.getProductCode()));

// 3.2. Inventory 조회 (상품과 연결된 재고 정보)
Inventory inventory = inventoryRepository.findByProduct(product)
.orElseThrow(() -> new BaseException(ErrorCode.INVENTORY_NOT_FOUND, "Inventory not found for product: " + product.getName()));
// Inventory inventory = inventoryRepository.findByProduct(product)
// .orElseThrow(() -> new BaseException(ErrorCode.INVENTORY_NOT_FOUND,
// "Inventory not found for product: " + product.getName()));
// TODO: 재고 조회 및 감소 로직 구현 필요

// 3.3. 재고 감소 로직 (Placeholder)
// ========================================================================
// TODO: 카페24 연동 정책에 따라 정확한 재고 감소 로직을 여기에 구현해야 합니다.
// 예: inventory.decreaseWarehouse(new BigDecimal(itemRequest.getQuantity()));
// 현재는 재고 감소 로직이 적용되지 않습니다.
// ========================================================================
// ========================================================================
// TODO: 카페24 연동 정책에 따라 정확한 재고 감소 로직을 여기에 구현해야 합니다.
// 예: inventory.decreaseWarehouse(new BigDecimal(itemRequest.getQuantity()));
// 현재는 재고 감소 로직이 적용되지 않습니다.
// ========================================================================
log.warn("카페24 주문 ID {}의 상품 {} (수량 {})에 대한 재고 감소 로직이 정의되지 않았습니다.",
request.getCafe24OrderId(), itemRequest.getProductName(), itemRequest.getQuantity());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ public record InventoryResponse(
Long inventoryId,
Long productId,
String productName,
String productCode,
String cafe24Code,
String posCode,
BigDecimal displayStock,
BigDecimal warehouseStock,
BigDecimal outgoingReserved,
BigDecimal incomingReserved,
BigDecimal reorderTriggerPoint
) {
BigDecimal reorderTriggerPoint) {
public static InventoryResponse from(Inventory inventory) {
Product product = inventory.getProduct();

return new InventoryResponse(
inventory.getId(),
product.getId(),
product.getName(),
product.getCode(),
product.getCafe24Code(),
product.getPosCode(),
inventory.getDisplayStock(),
inventory.getWarehouseStock(),
inventory.getOutgoingReserved(),
inventory.getIncomingReserved(),
inventory.getReorderTriggerPoint()
);
inventory.getReorderTriggerPoint());
}
}
18 changes: 12 additions & 6 deletions src/main/java/com/almang/inventory/product/domain/Product.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ public class Product extends BaseTimeEntity {
@JoinColumn(name = "vendor_id", nullable = false)
private Vendor vendor;

@Column(name = "name", length = 30, nullable = false)
@Column(name = "name", length = 255, nullable = false)
private String name;

@Column(name = "code", length = 30, nullable = false)
private String code;
@Column(name = "cafe24_code", length = 50, nullable = false)
private String cafe24Code;

@Column(name = "pos_code", length = 50, nullable = false)
private String posCode;
Comment on lines +36 to +43
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n src/main/java/com/almang/inventory/product/domain/Product.java | head -100

Repository: almang2/inventory-server

Length of output: 3638


🏁 Script executed:

# Search for migration files and flyway/liquibase configuration
find . -type f \( -name "*.sql" -o -name "*.yaml" -o -name "*.yml" -o -name "application*.properties" \) | grep -E "(migration|db|flyway|liquibase)" | head -20

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Check for migration directories
find . -type d \( -name "migration*" -o -name "db" -o -name "flyway" -o -name "liquibase" \) 2>/dev/null

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Search for cafe24_code or pos_code references in SQL or migration files
rg -i "cafe24|pos_code" --type sql --type yaml -l 2>/dev/null || echo "No matches found"

Repository: almang2/inventory-server

Length of output: 94


🏁 Script executed:

cat docs/rds_create_tables.sql | grep -A 5 -B 5 "cafe24\|pos_code"

Repository: almang2/inventory-server

Length of output: 980


🏁 Script executed:

# Search for Flyway or Liquibase migration directories
find . -type d -name "db" -o -name "migration" 2>/dev/null | head -10

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Check for migration files in common locations
find . -path "*/db/migration*" -o -path "*/flyway*" -o -path "*/liquibase*" 2>/dev/null | head -20

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Look at the structure of migration/db directories if they exist
ls -la src/main/resources/ 2>/dev/null | grep -E "db|migration|flyway"

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

cat docs/rds_create_tables.sql | grep -n "CREATE TABLE.*products"

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

cat docs/rds_create_tables.sql

Repository: almang2/inventory-server

Length of output: 2017


🏁 Script executed:

# Search for any mention of cafe24_code or pos_code in the entire codebase
rg "cafe24_code|pos_code" --type sql

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Search for products table definition anywhere in codebase
rg "CREATE TABLE.*products" --type sql

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Find all SQL files
fd -e sql

Repository: almang2/inventory-server

Length of output: 94


🏁 Script executed:

# Search for any SQL files or migration-related files
find . -type f \( -name "*.sql" -o -name "*.ddl" \) 2>/dev/null

Repository: almang2/inventory-server

Length of output: 96


🏁 Script executed:

# Check if there's an existing products table schema somewhere else
rg "products" -t sql

Repository: almang2/inventory-server

Length of output: 175


🏁 Script executed:

# Look for any schema or DDL comments in Java files
rg "cafe24_code|pos_code" -A 3 -B 3

Repository: almang2/inventory-server

Length of output: 1003


Database migration script required for cafe24_code and pos_code columns.

The new cafe24Code and posCode fields are marked nullable = false in the entity, but no corresponding database migration script exists in the codebase. The docs/rds_create_tables.sql file does not contain definitions for these columns on the products table.

Before merging, create a database migration script that:

  • Adds cafe24_code (VARCHAR(50), NOT NULL) column
  • Adds pos_code (VARCHAR(50), NOT NULL) column
  • Provides default values or migration strategy for existing product records

Without this migration, the application will fail when Hibernate attempts to persist or update products.


@Enumerated(EnumType.STRING)
@Column(name = "unit", nullable = false)
Expand Down Expand Up @@ -73,12 +76,15 @@ public void updateVendor(Vendor vendor) {
}
}

public void updateBasicInfo(String name, String code, ProductUnit unit) {
public void updateBasicInfo(String name, String cafe24Code, String posCode, ProductUnit unit) {
if (name != null) {
this.name = name;
}
if (code != null) {
this.code = code;
if (cafe24Code != null) {
this.cafe24Code = cafe24Code;
}
if (posCode != null) {
this.posCode = posCode;
}
if (unit != null) {
this.unit = unit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
import java.math.BigDecimal;

public record CreateProductRequest(
@NotNull Long vendorId,
@NotBlank String name,
@NotBlank String code,
@NotNull ProductUnit unit,
BigDecimal boxWeightG,
@Positive Integer unitPerBox,
BigDecimal unitWeightG,
@Min(0) Integer costPrice,
@Min(0) Integer retailPrice,
@Min(0) Integer wholesalePrice
) {}
@NotNull Long vendorId,
@NotBlank String name,
@NotBlank String cafe24Code,
@NotBlank String posCode,
@NotNull ProductUnit unit,
BigDecimal boxWeightG,
@Positive Integer unitPerBox,
BigDecimal unitWeightG,
@Min(0) Integer costPrice,
@Min(0) Integer retailPrice,
@Min(0) Integer wholesalePrice) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
import java.math.BigDecimal;

public record UpdateProductRequest(
@NotNull Long vendorId,
String name,
String code,
ProductUnit unit,
BigDecimal boxWeightG,
@Positive Integer unitPerBox,
BigDecimal unitWeightG,
Boolean isActivated,
@Min(0) Integer costPrice,
@Min(0) Integer retailPrice,
@Min(0) Integer wholesalePrice
) {}
@NotNull Long vendorId,
String name,
String cafe24Code,
String posCode,
ProductUnit unit,
BigDecimal boxWeightG,
@Positive Integer unitPerBox,
BigDecimal unitWeightG,
Boolean isActivated,
@Min(0) Integer costPrice,
@Min(0) Integer retailPrice,
@Min(0) Integer wholesalePrice) {
}
Comment on lines 9 to +22
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine CreateProductRequest
echo "=== CreateProductRequest.java ==="
fd -t f "CreateProductRequest.java" --exec cat {}

# Find the test file and relevant test method
echo "=== ProductControllerTest - validation test ==="
fd -t f "ProductControllerTest.java" --exec wc -l {} \;

Repository: almang2/inventory-server

Length of output: 1069


🏁 Script executed:

#!/bin/bash
# Search for the specific test mentioned in the review (line 369-381 or 178-204)
echo "=== Test method searching for empty string validation ==="
rg -n -B 2 -A 15 "empty|Empty|빈.*문자" src/test/java/com/almang/inventory/product/controller/ --type java | head -100

Repository: almang2/inventory-server

Length of output: 125


🏁 Script executed:

#!/bin/bash
# Check UpdateProductRequest current validation state more thoroughly
echo "=== Full UpdateProductRequest.java ==="
fd -t f "UpdateProductRequest.java" --exec cat {}

Repository: almang2/inventory-server

Length of output: 889


🏁 Script executed:

#!/bin/bash
# Find test methods in ProductControllerTest that test update product validation
echo "=== Test methods for update product ==="
rg -n "void.*update|updateProduct|PUT|patch" src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java -A 3

# Also search for test assertions with invalid input or validation
echo "=== Test validation assertions ==="
rg -n "INVALID_INPUT_VALUE|BindException|valid" src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java | head -30

Repository: almang2/inventory-server

Length of output: 4505


🏁 Script executed:

#!/bin/bash
# Read the test file to check for update-related tests
echo "=== ProductControllerTest content (lines 350-400) ==="
sed -n '350,400p' src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java

echo "=== ProductControllerTest content (lines 1-50) to find test class structure ==="
head -50 src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java

Repository: almang2/inventory-server

Length of output: 5370


UpdateProductRequest의 검증 어노테이션 누락 확인됨. name, cafe24Code, posCode 필드에 @NotBlank 추가 필요.

CreateProductRequest에는 이 세 필드에 @NotBlank 어노테이션이 있지만, UpdateProductRequest에는 누락되어 있습니다. 현재 테스트 "품목_수정_요청값_검증에_실패하면_예외가_발생한다()"(line 367-394)는 vendorId@NotNull 위반과 가격 필드의 @Min(0) 위반으로 인해 통과하고 있지만, 빈 문자열 값들은 검증되지 않고 있습니다.

일관성 유지와 입력값 검증을 위해 name, cafe24Code, posCode@NotBlank 어노테이션을 추가해주세요.

🤖 Prompt for AI Agents
In
src/main/java/com/almang/inventory/product/dto/request/UpdateProductRequest.java
around lines 9 to 22, add @NotBlank to the parameters name, cafe24Code, and
posCode so they are validated like in CreateProductRequest; update imports to
include javax.validation.constraints.NotBlank if not already present, rebuild to
ensure the existing validation test now also fails on blank strings and adjust
any tests if necessary.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
public record ProductResponse(
Long productId,
String name,
String code,
String cafe24Code,
String posCode,
ProductUnit unit,
BigDecimal boxWeightG,
boolean isActivated,
Expand All @@ -17,13 +18,13 @@ public record ProductResponse(
int retailPrice,
int wholesalePrice,
Long storeId,
Long vendorId
) {
Long vendorId) {
public static ProductResponse from(Product product) {
return new ProductResponse(
product.getId(),
product.getName(),
product.getCode(),
product.getCafe24Code(),
product.getPosCode(),
product.getUnit(),
product.getBoxWeightG(),
product.isActivated(),
Expand All @@ -33,7 +34,6 @@ public static ProductResponse from(Product product) {
product.getRetailPrice(),
product.getWholesalePrice(),
product.getStore().getId(),
product.getVendor().getId()
);
product.getVendor().getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,25 @@

public interface ProductRepository extends JpaRepository<Product, Long> {

Page<Product> findAllByStoreId(Long storeId, Pageable pageable);
Page<Product> findAllByStoreId(Long storeId, Pageable pageable);

Page<Product> findAllByStoreIdAndActivatedTrue(Long storeId, Pageable pageable);
Page<Product> findAllByStoreIdAndActivatedTrue(Long storeId, Pageable pageable);

Page<Product> findAllByStoreIdAndActivatedFalse(Long storeId, Pageable pageable);
Page<Product> findAllByStoreIdAndActivatedFalse(Long storeId, Pageable pageable);

Page<Product> findAllByStoreIdAndNameContainingIgnoreCase(Long storeId, String name, Pageable pageable);
Page<Product> findAllByStoreIdAndNameContainingIgnoreCase(Long storeId, String name, Pageable pageable);

Page<Product> findAllByStoreIdAndActivatedTrueAndNameContainingIgnoreCase(
Long storeId, String name, Pageable pageable
);
Page<Product> findAllByStoreIdAndActivatedTrueAndNameContainingIgnoreCase(
Long storeId, String name, Pageable pageable);

Page<Product> findAllByStoreIdAndActivatedFalseAndNameContainingIgnoreCase(
Long storeId, String name, Pageable pageable
);
Page<Product> findAllByStoreIdAndActivatedFalseAndNameContainingIgnoreCase(
Long storeId, String name, Pageable pageable);

boolean existsByVendorId(Long vendorId);
boolean existsByVendorId(Long vendorId);

// 상품 코드로 상품 찾기 (카페24 주문 처리용)
Optional<Product> findByCode(String code);
// 상품 코드로 상품 찾기 (카페24 주문 처리용)
Optional<Product> findByCafe24Code(String cafe24Code);

// POS 코드로 상품 찾기 (소매 판매 엑셀 업로드용)
Optional<Product> findByPosCode(String posCode);
Comment on lines +28 to +32
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine ProductRepository.java
find . -type f -name "ProductRepository.java" | head -5

Repository: almang2/inventory-server

Length of output: 146


🏁 Script executed:

# Find and examine Product entity class
find . -type f -name "Product.java" | grep -E "entity|model" | head -5

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Search for cafe24Code and posCode field definitions
rg -n "cafe24Code|posCode" --type java -B2 -A2

Repository: almang2/inventory-server

Length of output: 24586


🏁 Script executed:

# Read the full Product entity class to check for unique constraints
cat -n src/main/java/com/almang/inventory/product/domain/Product.java | head -70

Repository: almang2/inventory-server

Length of output: 2557


Add unique constraints on cafe24Code and posCode fields in Product entity

The cafe24Code and posCode fields are currently missing unique constraints but are used as lookup keys in findByCafe24Code() and findByPosCode() queries. Without these constraints, duplicate codes could exist in the database, causing these repository methods to silently return only the first match. This creates a data integrity risk in order processing workflows (Cafe24 orders and retail Excel uploads).

Add unique = true to both @column annotations in the Product entity, or use @table(uniqueConstraints = {...}) if store-scoped uniqueness is required.

🤖 Prompt for AI Agents
In src/main/java/com/almang/inventory/product/repository/ProductRepository.java
around lines 28 to 32, the repository methods findByCafe24Code and findByPosCode
rely on fields that lack database uniqueness, risking duplicate entries and
ambiguous lookups; update the Product entity to enforce uniqueness by adding
unique = true to the @Column for cafe24Code and posCode, or if uniqueness must
be scoped (e.g., per store), add a @Table(uniqueConstraints =
{@UniqueConstraint(columnNames = {"store_id","cafe24_code"}),
@UniqueConstraint(columnNames = {"store_id","pos_code"})}) and adjust column
names accordingly so the database enforces the constraint.

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public ProductResponse updateProduct(Long productId, UpdateProductRequest reques
log.info("[ProductService] 품목 수정 요청 - userId: {}, productId: {}", user.getId(), product.getId());

product.updateVendor(vendor);
product.updateBasicInfo(request.name(), request.code(), request.unit());
product.updateBasicInfo(request.name(), request.cafe24Code(), request.posCode(), request.unit());
product.updateWeights(request.boxWeightG(), request.unitPerBox(), request.unitWeightG());
product.updatePrices(request.costPrice(), request.retailPrice(), request.wholesalePrice());
product.updateActivation(request.isActivated());
Expand Down Expand Up @@ -96,8 +96,7 @@ public ProductResponse getProductDetail(Long productId, Long userId) {

@Transactional(readOnly = true)
public PageResponse<ProductResponse> getProductList(
Long userId, Integer page, Integer size, Boolean isActivate, String nameKeyword
) {
Long userId, Integer page, Integer size, Boolean isActivate, String nameKeyword) {
UserStoreContext context = userContextProvider.findUserAndStore(userId);
Store store = context.store();

Expand All @@ -117,7 +116,8 @@ private Product toEntity(CreateProductRequest request, User user) {
.store(user.getStore())
.vendor(vendor)
.name(request.name())
.code(request.code())
.cafe24Code(request.cafe24Code())
.posCode(request.posCode())
.unit(request.unit())
.boxWeightG(request.boxWeightG())
.unitPerBox(request.unitPerBox())
Expand All @@ -141,7 +141,7 @@ private Vendor findVendorByIdAndValidateAccess(Long vendorId, User user) {
}

private Product findProductById(Long id) {
Product product = productRepository.findById(id)
Product product = productRepository.findById(id)
.orElseThrow(() -> new BaseException(ErrorCode.PRODUCT_NOT_FOUND));

if (product.getDeletedAt() != null) {
Expand All @@ -157,8 +157,7 @@ private void validateStoreAccess(Product product, User user) {
}

private Page<Product> findProductsByFilter(
Long storeId, Boolean isActivate, String nameKeyword, PageRequest pageable
) {
Long storeId, Boolean isActivate, String nameKeyword, PageRequest pageable) {
boolean hasName = nameKeyword != null && !nameKeyword.isBlank();
boolean filterActivate = isActivate != null;

Expand All @@ -178,18 +177,15 @@ private Page<Product> findProductsByFilter(
// 3) 이름 검색
if (!filterActivate) {
return productRepository.findAllByStoreIdAndNameContainingIgnoreCase(
storeId, nameKeyword, pageable
);
storeId, nameKeyword, pageable);
}

// 4) 활성 + 이름 검색
if (isActivate) {
return productRepository.findAllByStoreIdAndActivatedTrueAndNameContainingIgnoreCase(
storeId, nameKeyword, pageable
);
storeId, nameKeyword, pageable);
}
return productRepository.findAllByStoreIdAndActivatedFalseAndNameContainingIgnoreCase(
storeId, nameKeyword, pageable
);
storeId, nameKeyword, pageable);
}
}
Loading