diff --git a/src/main/java/com/example/repick/domain/clothingSales/api/ClothingSalesController.java b/src/main/java/com/example/repick/domain/clothingSales/api/ClothingSalesController.java index 2b4a850d..a9494a0d 100644 --- a/src/main/java/com/example/repick/domain/clothingSales/api/ClothingSalesController.java +++ b/src/main/java/com/example/repick/domain/clothingSales/api/ClothingSalesController.java @@ -4,7 +4,6 @@ import com.example.repick.domain.clothingSales.service.BagService; import com.example.repick.domain.clothingSales.service.BoxService; import com.example.repick.domain.clothingSales.service.ClothingSalesService; -import com.example.repick.domain.product.entity.ProductStateType; import com.example.repick.global.page.DateCondition; import com.example.repick.global.page.PageCondition; import com.example.repick.global.page.PageResponse; @@ -116,21 +115,6 @@ public SuccessResponse updateClothingSalesStatus(@RequestBody PostCloth return SuccessResponse.success(clothingSalesService.updateClothingSalesState(postClothingSalesState)); } - @Operation(summary = "상품 종합 현황") - @GetMapping("/product-count") - public SuccessResponse>> getClothingSalesProductCount(@RequestParam(required = false) Long userId, - @ParameterObject PageCondition pageCondition) { - return SuccessResponse.success(clothingSalesService.getClothingSalesProductCount(userId, pageCondition)); - } - - @Operation(summary = "유저 상품 현황") - @GetMapping("/products/{clothingSalesId}/{productStateType}") - public SuccessResponse>> getClothingSalesProduct(@PathVariable Long clothingSalesId, - @PathVariable ProductStateType productStateType, - @ParameterObject PageCondition pageCondition) { - return SuccessResponse.success(clothingSalesService.getClothingSalesProduct(clothingSalesId, productStateType, pageCondition)); - } - @Operation(summary = "옷장 정리 상품 무게 등록") @PatchMapping("/weight") public SuccessResponse updateClothingSalesWeight(@RequestBody PatchClothingSalesWeight patchClothingSalesWeight) { diff --git a/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSales.java b/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSales.java index 8ccb0652..2a2c6440 100644 --- a/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSales.java +++ b/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSales.java @@ -43,9 +43,9 @@ public static GetClothingSales of(ClothingSales clothingSales) { .status(clothingSalesState.getAdminValue()) .requestDate(clothingSales.getCreatedDate().format(formatter)) .isForCollect(!ClothingSalesStateType.BEFORE_COLLECTION.contains(clothingSalesState)) - .productStartDate(isSelling? clothingSales.getProductList().get(0).getSalesStartDate().format(formatter) : null) - .salesPeriod(isSelling? clothingSales.getProductList().get(0).getSalesStartDate().format(formatter) - + " ~ " + clothingSales.getProductList().get(0).getSalesStartDate().plusDays(90).format(formatter) : null) + .productStartDate(isSelling? clothingSales.getSalesStartDate().format(formatter) : null) + .salesPeriod(isSelling? clothingSales.getSalesStartDate().format(formatter) + + " ~ " + clothingSales.getSalesStartDate().plusDays(90).format(formatter) : null) .settlementRequestDate(clothingSales.getUser().getSettlementRequestDate() != null ? clothingSales.getUser().getSettlementRequestDate().format(formatter) : null) .settlementCompleteDate(clothingSales.getUser().getSettlementCompleteDate() != null ? clothingSales.getUser().getSettlementCompleteDate().format(formatter) : null) .isRejected(isProducted? clothingSales.getProductList().stream().anyMatch(product -> product.getProductState() == ProductStateType.REJECTED) : null) diff --git a/src/main/java/com/example/repick/domain/clothingSales/entity/ClothingSales.java b/src/main/java/com/example/repick/domain/clothingSales/entity/ClothingSales.java index f2e3429d..603f9463 100644 --- a/src/main/java/com/example/repick/domain/clothingSales/entity/ClothingSales.java +++ b/src/main/java/com/example/repick/domain/clothingSales/entity/ClothingSales.java @@ -61,6 +61,9 @@ public abstract class ClothingSales { @OneToMany(mappedBy = "clothingSales") private List productList; + private LocalDateTime salesStartDate; // 판매 시작일 + private LocalDateTime returnRequestDate; // kg 매입요청/반송 요청일 + @CreatedDate @Column(updatable = false) private LocalDateTime createdDate; @@ -94,4 +97,12 @@ public void updateBagCollectInfo(Integer bagQuantity, String postalCode, String this.address = new Address(postalCode, mainAddress, detailAddress); this.collectionDate = LocalDate.parse(collectionDate); } + + public void updateSalesStartDate(LocalDateTime salesStartDate) { + this.salesStartDate = salesStartDate; + } + + public void updateReturnRequestDate(LocalDateTime returnRequestDate) { + this.returnRequestDate = returnRequestDate; + } } diff --git a/src/main/java/com/example/repick/domain/clothingSales/repository/ClothingSalesRepository.java b/src/main/java/com/example/repick/domain/clothingSales/repository/ClothingSalesRepository.java index ec0cc522..40846717 100644 --- a/src/main/java/com/example/repick/domain/clothingSales/repository/ClothingSalesRepository.java +++ b/src/main/java/com/example/repick/domain/clothingSales/repository/ClothingSalesRepository.java @@ -14,6 +14,7 @@ public interface ClothingSalesRepository extends JpaRepository { Page findAllByOrderByCreatedDateDesc(Pageable pageable); + List findByClothingSalesState(ClothingSalesStateType clothingSalesStateType); List findByUserAndClothingSalesState(User user, ClothingSalesStateType clothingSalesStateType); Page findByCreatedDateBetweenOrderByCreatedDateDesc(LocalDateTime startDate, LocalDateTime endDate, Pageable pageable); List findByUserOrderByCreatedDateDesc(User user); diff --git a/src/main/java/com/example/repick/domain/clothingSales/service/ClothingSalesService.java b/src/main/java/com/example/repick/domain/clothingSales/service/ClothingSalesService.java index 438212d6..8067e762 100644 --- a/src/main/java/com/example/repick/domain/clothingSales/service/ClothingSalesService.java +++ b/src/main/java/com/example/repick/domain/clothingSales/service/ClothingSalesService.java @@ -47,11 +47,6 @@ public class ClothingSalesService { private final ClothingSalesStateRepository clothingSalesStateRepository; private final ProductOrderRepository productOrderRepository; - public void updateSellingExpired(Product product) { - ClothingSalesState clothingSalesState = ClothingSalesState.of(product.getClothingSales().getId(), ClothingSalesStateType.SELLING_EXPIRED); - clothingSalesStateRepository.save(clothingSalesState); - } - public List getPendingClothingSales() { User user = userRepository.findByProviderId(SecurityContextHolder.getContext().getAuthentication().getName()) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); @@ -96,7 +91,6 @@ public Boolean updateProductPrice(List postProductPriceList) { productList.forEach(product -> { productService.calculateDiscountPriceAndPredictDiscountRateAndSave(product); productService.changeSellingState(product, ProductStateType.SELLING); - product.updateSalesStartDate(LocalDateTime.now()); }); productRepository.saveAll(productList); @@ -104,6 +98,7 @@ public Boolean updateProductPrice(List postProductPriceList) { ClothingSalesState clothingSalesState = ClothingSalesState.of(clothingSales.getId(), ClothingSalesStateType.SELLING); clothingSalesStateRepository.save(clothingSalesState); clothingSales.updateClothingSalesState(ClothingSalesStateType.SELLING); + clothingSales.updateSalesStartDate(LocalDateTime.now()); clothingSalesRepository.save(clothingSales); return true; @@ -121,8 +116,7 @@ public List getSellingClothingSales() { if(productList.isEmpty()) { continue; } - LocalDateTime salesStartDate = productList.get(0).getSalesStartDate(); - int remainingSalesDays = (int) ChronoUnit.DAYS.between(LocalDate.now(), salesStartDate.toLocalDate().plusDays(90)); + int remainingSalesDays = (int) ChronoUnit.DAYS.between(LocalDate.now(), clothingSales.getSalesStartDate().toLocalDate().plusDays(90)); int sellingQuantity = 0; int pendingQuantity = 0; int soldQuantity = 0; @@ -139,7 +133,7 @@ public List getSellingClothingSales() { } } } - sellingClothingSalesList.add(GetSellingClothingSales.of(clothingSales, salesStartDate.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")), remainingSalesDays, sellingQuantity, pendingQuantity, soldQuantity)); + sellingClothingSalesList.add(GetSellingClothingSales.of(clothingSales, clothingSales.getSalesStartDate().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")), remainingSalesDays, sellingQuantity, pendingQuantity, soldQuantity)); } return sellingClothingSalesList; @@ -208,12 +202,6 @@ public Boolean updateClothingSalesState(PostClothingSalesState postClothingSales return true; } - @Transactional - public PageResponse> getClothingSalesProductCount(Long userId, PageCondition pageCondition) { - Page pages = productRepository.getClothingSalesProductCount(pageCondition.toPageable(), userId); - return PageResponse.of(pages.getContent(), pages.getTotalPages(), pages.getTotalElements()); - } - @Transactional public void updateClothingSalesWeight(PatchClothingSalesWeight patchClothingSalesWeight) { ClothingSales clothingSales = clothingSalesRepository.findById(patchClothingSalesWeight.clothingSalesId()) @@ -222,17 +210,6 @@ public void updateClothingSalesWeight(PatchClothingSalesWeight patchClothingSale clothingSalesRepository.save(clothingSales); } - public PageResponse> getClothingSalesProduct(Long clothingSalesId, ProductStateType productStateType, PageCondition pageCondition) { - Page pages; - if (productStateType == ProductStateType.SELLING || productStateType == ProductStateType.SOLD_OUT) { - pages = productRepository.getClothingSalesPendingProduct(clothingSalesId, productStateType, pageCondition.toPageable()); - } - else { - pages = productRepository.getClothingSalesCancelledProduct(clothingSalesId, productStateType, pageCondition.toPageable()); - } - return PageResponse.of(pages.getContent(), pages.getTotalPages(), pages.getTotalElements()); - } - public GetClothingSalesUser getClothingSalesUser(Long clothingSalesId){ ClothingSales clothingSales = clothingSalesRepository.findById(clothingSalesId) .orElseThrow(() -> new CustomException(INVALID_CLOTHING_SALES_ID)); diff --git a/src/main/java/com/example/repick/domain/product/api/ProductController.java b/src/main/java/com/example/repick/domain/product/api/ProductController.java index cb319fc2..d2194d9e 100644 --- a/src/main/java/com/example/repick/domain/product/api/ProductController.java +++ b/src/main/java/com/example/repick/domain/product/api/ProductController.java @@ -1,5 +1,6 @@ package com.example.repick.domain.product.api; +import com.example.repick.domain.product.dto.productClothingSales.GetProductCountClothingSales; import com.example.repick.domain.product.dto.product.*; import com.example.repick.domain.product.dto.productOrder.GetProductCart; import com.example.repick.domain.product.service.ProductService; @@ -214,4 +215,45 @@ public SuccessResponse getProductDetail( return SuccessResponse.success(productService.getProductDetail(productId)); } + @Operation(summary = "상품 리턴 상태 변경", + description = """ + 상품 리턴 상태 변경은 다음 상황에서 사용합니다: + + (리젝된 경우, 만료된 경우) + + 사용자: kg 매입 신청/돌려받기 신청 + + 관리자: 반송 완료 업데이트 + + **returnState: kg 매입, 반송 요청, 반송 완료** + + """) + @PatchMapping("/return") + public SuccessResponse patchProductReturn(@RequestBody PatchProductReturn patchProductReturn) { + return SuccessResponse.success(productService.updateProductReturnState(patchProductReturn)); + } + + @Operation(summary = "상품 종합 현황") + @GetMapping("/count") + public SuccessResponse>> getProductCountByClothingSales(@RequestParam(required = false) Long userId, + @ParameterObject PageCondition pageCondition) { + return SuccessResponse.success(productService.getProductCountByClothingSales(userId, pageCondition)); + } + + @Operation(summary = "유저 상품 현황", + description = """ + + productState: selling, sold-out, rejected, selling-end, kg-sell + + isExpired: kg 매입 화면에서만 사용 (true: 만료된 상품 조회, false: 리젝 상품 조회) + + """) + @GetMapping("/{clothingSalesId}/{productState}") + public SuccessResponse>> getProductsByUserClothingSales(@PathVariable Long clothingSalesId, + @PathVariable String productState, + @ParameterObject PageCondition pageCondition, + @RequestParam(required = false) Boolean isExpired) { + return SuccessResponse.success(productService.getProductsByUserClothingSales(clothingSalesId, productState, isExpired, pageCondition)); + } + } diff --git a/src/main/java/com/example/repick/domain/product/dto/product/PatchProductReturn.java b/src/main/java/com/example/repick/domain/product/dto/product/PatchProductReturn.java new file mode 100644 index 00000000..4e95d09c --- /dev/null +++ b/src/main/java/com/example/repick/domain/product/dto/product/PatchProductReturn.java @@ -0,0 +1,11 @@ +package com.example.repick.domain.product.dto.product; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +public record PatchProductReturn( + @Schema(description = "반품 상태") String returnState, + @Schema(description = "상품 id 목룍") List productIds + ) { +} diff --git a/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetKgSellProductClothingSales.java b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetKgSellProductClothingSales.java new file mode 100644 index 00000000..7d7f6e9f --- /dev/null +++ b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetKgSellProductClothingSales.java @@ -0,0 +1,14 @@ +package com.example.repick.domain.product.dto.productClothingSales; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record GetKgSellProductClothingSales( + @Schema(description = "상품코드") String productCode, + @Schema(description = "썸네일") String thumbnailImageUrl, + @Schema(description = "상품명") String productName, + @Schema(description = "등급") String grade, + @Schema(description = "신청일") String requestDate, + @Schema(description = "만료 여부 (kg 매입 경로용)") Boolean isExpired, + @Schema(description = "kg 매입 정산 상태") Boolean settlementStatus +) { +} diff --git a/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSalesProductCount.java b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetProductCountClothingSales.java similarity index 87% rename from src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSalesProductCount.java rename to src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetProductCountClothingSales.java index c4b202c6..357cee5e 100644 --- a/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSalesProductCount.java +++ b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetProductCountClothingSales.java @@ -1,10 +1,10 @@ -package com.example.repick.domain.clothingSales.dto; +package com.example.repick.domain.product.dto.productClothingSales; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; -public record GetClothingSalesProductCount ( +public record GetProductCountClothingSales( @Schema(description = "코드") String code, @Schema(description = "이름") String name, @Schema(description = "유저 ID") Long userId, diff --git a/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSalesProduct.java b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetProductsClothingSales.java similarity index 64% rename from src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSalesProduct.java rename to src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetProductsClothingSales.java index f544cb9f..ce93db67 100644 --- a/src/main/java/com/example/repick/domain/clothingSales/dto/GetClothingSalesProduct.java +++ b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetProductsClothingSales.java @@ -1,8 +1,8 @@ -package com.example.repick.domain.clothingSales.dto; +package com.example.repick.domain.product.dto.productClothingSales; import io.swagger.v3.oas.annotations.media.Schema; -public record GetClothingSalesProduct( +public record GetProductsClothingSales( @Schema(description = "상품코드") String productCode, @Schema(description = "썸네일") String thumbnailImageUrl, @Schema(description = "상품명") String productName, @@ -10,8 +10,6 @@ public record GetClothingSalesProduct( @Schema(description = "판매기간") String salesPeriod, @Schema(description = "판매 금액") Long salesPrice, @Schema(description = "정산금") Long settlementPrice, - @Schema(description = "수수료") Long fee, - @Schema(description = "신청일") String requestDate, - @Schema(description = "반송 여부") Boolean isReturned + @Schema(description = "수수료") Long fee ) { } diff --git a/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetReturnedProductClothingSales.java b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetReturnedProductClothingSales.java new file mode 100644 index 00000000..fb3695e2 --- /dev/null +++ b/src/main/java/com/example/repick/domain/product/dto/productClothingSales/GetReturnedProductClothingSales.java @@ -0,0 +1,12 @@ +package com.example.repick.domain.product.dto.productClothingSales; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record GetReturnedProductClothingSales( + @Schema(description = "상품코드") String productCode, + @Schema(description = "썸네일") String thumbnailImageUrl, + @Schema(description = "상품명") String productName, + @Schema(description = "등급") String grade, + @Schema(description = "신청일") String requestDate +) { +} diff --git a/src/main/java/com/example/repick/domain/product/entity/Product.java b/src/main/java/com/example/repick/domain/product/entity/Product.java index d4bb2b2c..5c6aacdd 100644 --- a/src/main/java/com/example/repick/domain/product/entity/Product.java +++ b/src/main/java/com/example/repick/domain/product/entity/Product.java @@ -9,7 +9,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; import java.util.List; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -46,9 +45,10 @@ public class Product extends BaseEntity { private Gender gender; @Column(length = 1000) private String thumbnailImageUrl; - private LocalDateTime salesStartDate; @Enumerated(EnumType.STRING) private ProductStateType productState; + @Enumerated(EnumType.STRING) + private ProductReturnStateType returnState; // 리젝되거나 만료된 상품의 상태 private long settlement; // 정산금 @OneToMany(mappedBy = "product", cascade = CascadeType.ALL) @@ -162,14 +162,14 @@ public void updateThumbnailImageUrl(String thumbnailImageUrl) { this.thumbnailImageUrl = thumbnailImageUrl; } - public void updateSalesStartDate(LocalDateTime salesStartDate) { - this.salesStartDate = salesStartDate; - } - public void updateProductState(ProductStateType productState) { this.productState = productState; } + public void updateReturnState(ProductReturnStateType returnState) { + this.returnState = returnState; + } + public void updateSettlement(long settlement) { this.settlement = settlement; } diff --git a/src/main/java/com/example/repick/domain/product/entity/ProductReturnStateType.java b/src/main/java/com/example/repick/domain/product/entity/ProductReturnStateType.java new file mode 100644 index 00000000..c2b3768d --- /dev/null +++ b/src/main/java/com/example/repick/domain/product/entity/ProductReturnStateType.java @@ -0,0 +1,29 @@ +package com.example.repick.domain.product.entity; + +import com.example.repick.global.error.exception.CustomException; +import com.example.repick.global.error.exception.ErrorCode; +import lombok.Getter; + +@Getter +public enum ProductReturnStateType { + KG_SELL(1, "KG 매입"), + RETURN_REQUESTED(2, "반송 요청"), + RETURN_COMPLETED(3, "반송 완료"); + + private final int id; + private final String value; + + ProductReturnStateType(int id, String value) { + this.id = id; + this.value = value; + } + + public static ProductReturnStateType fromValue(String keyword) { + for (ProductReturnStateType productReturnStateType : values()) { + if (productReturnStateType.getValue().equals(keyword)) { + return productReturnStateType; + } + } + throw new CustomException(ErrorCode.INVALID_PRODUCT_RETURN_STATE); + } +} diff --git a/src/main/java/com/example/repick/domain/product/entity/ProductStateType.java b/src/main/java/com/example/repick/domain/product/entity/ProductStateType.java index c7f7f162..04f03aa8 100644 --- a/src/main/java/com/example/repick/domain/product/entity/ProductStateType.java +++ b/src/main/java/com/example/repick/domain/product/entity/ProductStateType.java @@ -7,21 +7,23 @@ @Getter public enum ProductStateType { - PREPARING(1, "판매준비중"), - SELLING(2, "판매중"), - SOLD_OUT(3, "판매완료"), - SETTLING(4, "정산중"), - SETTLED(5, "정산완료"), - REJECTED(6, "리젝됨"), - SELLING_END(7, "판매종료"), - RETURN_REQUESTED(8, "반품요청됨"); + PREPARING(1, "판매준비중", "preparing"), + SELLING(2, "판매중", "selling"), + SOLD_OUT(3, "판매완료", "sold-out"), + SETTLING(4, "정산중", "settling"), + SETTLED(5, "정산완료", "settled"), + REJECTED(6, "리젝됨", "rejected"), + SELLING_END(7, "판매종료", "selling-end"), + RETURN_REQUESTED(8, "반품요청됨", "return-requested"); private final int id; private final String value; + private final String engValue; - ProductStateType(int id, String value) { + ProductStateType(int id, String value, String engValue) { this.id = id; this.value = value; + this.engValue = engValue; } public static ProductStateType fromId(int id) { @@ -41,4 +43,13 @@ public static ProductStateType fromValue(String keyword) { } throw new CustomException(ErrorCode.INVALID_SELLING_STATE_NAME); } + + public static ProductStateType fromEngValue(String keyword) { + for (ProductStateType productStateType : values()) { + if (productStateType.getEngValue().equals(keyword)) { + return productStateType; + } + } + throw new CustomException(ErrorCode.INVALID_SELLING_STATE_NAME); + } } diff --git a/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryCustom.java b/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryCustom.java index 5672c438..8ce4747d 100644 --- a/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryCustom.java +++ b/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryCustom.java @@ -1,11 +1,11 @@ package com.example.repick.domain.product.repository; -import com.example.repick.domain.clothingSales.dto.GetClothingSalesProduct; -import com.example.repick.domain.clothingSales.dto.GetClothingSalesProductCount; +import com.example.repick.domain.product.dto.product.*; import com.example.repick.domain.clothingSales.dto.GetProductByClothingSalesDto; -import com.example.repick.domain.product.dto.product.GetBrandList; -import com.example.repick.domain.product.dto.product.GetProductThumbnail; -import com.example.repick.domain.product.dto.product.ProductFilter; +import com.example.repick.domain.product.dto.productClothingSales.GetKgSellProductClothingSales; +import com.example.repick.domain.product.dto.productClothingSales.GetProductCountClothingSales; +import com.example.repick.domain.product.dto.productClothingSales.GetProductsClothingSales; +import com.example.repick.domain.product.dto.productClothingSales.GetReturnedProductClothingSales; import com.example.repick.domain.product.dto.productOrder.GetProductCart; import com.example.repick.domain.product.entity.Product; import com.example.repick.domain.product.entity.ProductStateType; @@ -50,9 +50,11 @@ Page findHighestDiscountProducts( List findRecommendation(Long userId); - Page getClothingSalesProductCount(Pageable pageable, Long userId); + Page getClothingSalesProductCount(Pageable pageable, Long userId); - Page getClothingSalesPendingProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable); + Page getClothingSalesProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable); - Page getClothingSalesCancelledProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable); + Page getClothingSalesReturnedProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable); + + Page getClothingSalesKgSellProduct(Long clothingSalesId, Boolean isExpired, Pageable pageable); } diff --git a/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryImpl.java b/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryImpl.java index 234696b2..360385e4 100644 --- a/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryImpl.java +++ b/src/main/java/com/example/repick/domain/product/repository/ProductRepositoryImpl.java @@ -1,11 +1,11 @@ package com.example.repick.domain.product.repository; -import com.example.repick.domain.clothingSales.dto.GetClothingSalesProduct; -import com.example.repick.domain.clothingSales.dto.GetClothingSalesProductCount; +import com.example.repick.domain.product.dto.product.*; import com.example.repick.domain.clothingSales.dto.GetProductByClothingSalesDto; -import com.example.repick.domain.product.dto.product.GetBrandList; -import com.example.repick.domain.product.dto.product.GetProductThumbnail; -import com.example.repick.domain.product.dto.product.ProductFilter; +import com.example.repick.domain.product.dto.productClothingSales.GetKgSellProductClothingSales; +import com.example.repick.domain.product.dto.productClothingSales.GetProductCountClothingSales; +import com.example.repick.domain.product.dto.productClothingSales.GetProductsClothingSales; +import com.example.repick.domain.product.dto.productClothingSales.GetReturnedProductClothingSales; import com.example.repick.domain.product.dto.productOrder.GetProductCart; import com.example.repick.domain.product.entity.*; import com.querydsl.core.types.OrderSpecifier; @@ -378,9 +378,9 @@ public List findRecommendation(Long userId) { } @Override - public Page getClothingSalesProductCount(Pageable pageable, Long userId) { - JPAQuery query = jpaQueryFactory - .select(Projections.constructor(GetClothingSalesProductCount.class, + public Page getClothingSalesProductCount(Pageable pageable, Long userId) { + JPAQuery query = jpaQueryFactory + .select(Projections.constructor(GetProductCountClothingSales.class, product.user.id.stringValue().concat("-").concat(product.clothingSalesCount.stringValue()), product.user.nickname, product.user.id, @@ -406,7 +406,7 @@ public Page getClothingSalesProductCount(Pageable } long total = query.stream().count(); - List content = query + List content = query .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); @@ -415,23 +415,23 @@ public Page getClothingSalesProductCount(Pageable } @Override - public Page getClothingSalesPendingProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable) { + public Page getClothingSalesProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable) { JPAQuery products = jpaQueryFactory .selectFrom(product) .where(product.clothingSales.id.eq(clothingSalesId) .and(product.productState.eq(productStateType))); long total = products.stream().count(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yy"); - List contents = products + List contents = products .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .stream() .map(p -> { - LocalDate salesStartDate = p.getSalesStartDate().toLocalDate(); + LocalDate salesStartDate = p.getClothingSales().getSalesStartDate().toLocalDate(); LocalDate endDate = salesStartDate.plusDays(90); String dateRange = String.format("%s ~ %s", salesStartDate.format(formatter), endDate.format(formatter)); - return new GetClothingSalesProduct( + return new GetProductsClothingSales( p.getProductCode(), p.getThumbnailImageUrl(), p.getProductName(), @@ -439,9 +439,7 @@ public Page getClothingSalesPendingProduct(Long clothin dateRange, p.getDiscountPrice(), p.getSettlement(), - p.getDiscountPrice() - p.getSettlement(), - null, - null + p.getDiscountPrice() - p.getSettlement() ); }) .collect(Collectors.toList()); @@ -449,33 +447,56 @@ public Page getClothingSalesPendingProduct(Long clothin } @Override - public Page getClothingSalesCancelledProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable) { + public Page getClothingSalesReturnedProduct(Long clothingSalesId, ProductStateType productStateType, Pageable pageable) { JPAQuery products = jpaQueryFactory .selectFrom(product) .where(product.clothingSales.id.eq(clothingSalesId) .and(product.productState.eq(productStateType))); long total = products.stream().count(); - // TODO: Mock data 제거 및 반송 기능 구현 - List contents = products + List contents = products .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .stream() - .map(p -> new GetClothingSalesProduct( + .map(p -> new GetReturnedProductClothingSales( p.getProductCode(), p.getThumbnailImageUrl(), p.getProductName(), p.getQualityRate().toString(), - null, - null, - null, - null, - "07/09/24", - false + p.getClothingSales().getReturnRequestDate().format(DateTimeFormatter.ofPattern("MM/dd/yy")) )) .collect(Collectors.toList()); return new PageImpl<>(contents, pageable, total); } + @Override + public Page getClothingSalesKgSellProduct(Long clothingSalesId, Boolean isExpired, Pageable pageable) { + JPAQuery products = jpaQueryFactory + .selectFrom(product) + .where(product.clothingSales.id.eq(clothingSalesId) + .and(product.returnState.eq(ProductReturnStateType.KG_SELL))); + if (isExpired != null){ + if (isExpired) products.where(product.productState.eq(ProductStateType.SELLING_END)); + else products.where(product.productState.ne(ProductStateType.REJECTED)); + } + long total = products.stream().count(); + List contents = products + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .stream() + .map(p -> new GetKgSellProductClothingSales( + p.getProductCode(), + p.getThumbnailImageUrl(), + p.getProductName(), + p.getQualityRate().toString(), + p.getClothingSales().getReturnRequestDate().format(DateTimeFormatter.ofPattern("MM/dd/yy")), + isExpired != null ? isExpired : p.getProductState().equals(ProductStateType.SELLING_END), + null // TODO: kg 매입 정산 플로우 + )) + .collect(Collectors.toList()); + return new PageImpl<>(contents, pageable, total); + } + + private BooleanExpression notExistsUserPreferenceProduct(Long userId) { return JPAExpressions diff --git a/src/main/java/com/example/repick/domain/product/scheduler/ProductScheduler.java b/src/main/java/com/example/repick/domain/product/scheduler/ProductScheduler.java index 95ae228f..d953186a 100644 --- a/src/main/java/com/example/repick/domain/product/scheduler/ProductScheduler.java +++ b/src/main/java/com/example/repick/domain/product/scheduler/ProductScheduler.java @@ -1,5 +1,10 @@ package com.example.repick.domain.product.scheduler; +import com.example.repick.domain.clothingSales.entity.ClothingSales; +import com.example.repick.domain.clothingSales.entity.ClothingSalesState; +import com.example.repick.domain.clothingSales.entity.ClothingSalesStateType; +import com.example.repick.domain.clothingSales.repository.ClothingSalesRepository; +import com.example.repick.domain.clothingSales.repository.ClothingSalesStateRepository; import com.example.repick.domain.clothingSales.service.ClothingSalesService; import com.example.repick.domain.product.entity.Product; import com.example.repick.domain.product.entity.ProductOrder; @@ -25,35 +30,48 @@ public class ProductScheduler { private final ProductOrderRepository productOrderRepository; private final ProductOrderService productOrderService; private final ProductService productService; - private final ClothingSalesService clothingSalesService; + private final ClothingSalesRepository clothingSalesRepository; + private final ClothingSalesStateRepository clothingSalesStateRepository; @Scheduled(cron = "0 0 0 * * *") public void updateProductDiscountRate() { - List sellingProducts = productRepository.findByProductSellingStateType(ProductStateType.SELLING); - - updateDiscountRate(sellingProducts, p -> p.getPrice() >= 300000, 60); - updateDiscountRate(sellingProducts, p -> p.getPrice() >= 200000 && p.getPrice() < 300000, 70); - updateDiscountRate(sellingProducts, p -> p.getPrice() >= 100000 && p.getPrice() < 200000, 80); - updateDiscountRate(sellingProducts, p -> p.getPrice() < 100000, 90); + List clothingSales = clothingSalesRepository.findByClothingSalesState(ClothingSalesStateType.SELLING); + clothingSales.forEach(cs -> { + long days = Duration.between(cs.getSalesStartDate(), LocalDateTime.now()).toDays(); + if (days >= 90) handleExpiredSales(cs); + else updateDiscountRate(cs.getProductList(), days); + cs.getProductList().forEach(productService::calculateDiscountPriceAndPredictDiscountRateAndSave); + }); + } - sellingProducts.forEach(productService::calculateDiscountPriceAndPredictDiscountRateAndSave); + private void handleExpiredSales(ClothingSales clothingSales) { + clothingSales.getProductList().forEach(p -> { + productService.addProductSellingState(p.getId(), ProductStateType.SELLING_END); + p.updateProductState(ProductStateType.SELLING_END); + productRepository.save(p); + }); + ClothingSalesState clothingSalesState = ClothingSalesState.of(clothingSales.getId(), ClothingSalesStateType.SELLING_EXPIRED); + clothingSales.updateClothingSalesState(ClothingSalesStateType.SELLING_EXPIRED); + clothingSalesStateRepository.save(clothingSalesState); + clothingSalesRepository.save(clothingSales); } - private void updateDiscountRate(List products, Predicate priceRange, long maxDiscountRate) { - products.stream() - .filter(priceRange) - .forEach(p -> { - long days = Duration.between(p.getSalesStartDate(), LocalDateTime.now()).toDays(); - if (days >= 30 && days < 60) p.updateDiscountRate(maxDiscountRate / 2); - else if (days >= 60 && days < 90) p.updateDiscountRate(maxDiscountRate); - else if (days >= 90) { - productService.addProductSellingState(p.getId(), ProductStateType.SELLING_END); - // Product 엔티티의 productState 업데이트 - p.updateProductState(ProductStateType.SELLING_END); - productRepository.save(p); - clothingSalesService.updateSellingExpired(p); - } - }); + private void updateDiscountRate(List products, long days) { + products.forEach(p -> { + long price = p.getPrice(); + if (days >= 30 && days < 60){ + if (price >= 300000) p.updateDiscountRate(30L); + else if (price >= 200000) p.updateDiscountRate(35L); + else if (price >= 100000) p.updateDiscountRate(40L); + else p.updateDiscountRate(45L); + } + else if (days >= 60 && days < 90){ + if (price >= 300000) p.updateDiscountRate(60L); + else if (price >= 200000) p.updateDiscountRate(70L); + else if (price >= 100000) p.updateDiscountRate(80L); + else p.updateDiscountRate(90L); + } + }); } @Scheduled(cron = "0 0 0 * * *") diff --git a/src/main/java/com/example/repick/domain/product/service/ProductService.java b/src/main/java/com/example/repick/domain/product/service/ProductService.java index 6a98bc8e..c44a7555 100644 --- a/src/main/java/com/example/repick/domain/product/service/ProductService.java +++ b/src/main/java/com/example/repick/domain/product/service/ProductService.java @@ -1,8 +1,10 @@ package com.example.repick.domain.product.service; +import com.example.repick.domain.product.dto.productClothingSales.GetProductCountClothingSales; import com.example.repick.domain.clothingSales.entity.ClothingSales; import com.example.repick.domain.clothingSales.repository.ClothingSalesRepository; import com.example.repick.domain.product.dto.product.*; +import com.example.repick.domain.product.dto.productClothingSales.GetKgSellProductClothingSales; import com.example.repick.domain.product.dto.productOrder.GetProductCart; import com.example.repick.domain.product.entity.*; import com.example.repick.domain.product.repository.*; @@ -340,6 +342,7 @@ private List getUserSizesForCategory(User user, Category category) { return sizes; } + @Transactional public Boolean toggleLike(Long productId) { User user = userRepository.findByProviderId(SecurityContextHolder.getContext().getAuthentication().getName()) .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); @@ -466,4 +469,42 @@ private void handleUserPreference(Long userId, Long productId) { } + @Transactional + public Boolean updateProductReturnState(PatchProductReturn patchProductReturn){ + patchProductReturn.productIds().forEach(productId -> { + Product product = productRepository.findById(productId) + .orElseThrow(() -> new CustomException(INVALID_PRODUCT_ID)); + product.updateReturnState(ProductReturnStateType.fromValue(patchProductReturn.returnState())); + productRepository.save(product); + }); + return true; + } + + @Transactional + public PageResponse> getProductCountByClothingSales(Long userId, PageCondition pageCondition) { + Page pages = productRepository.getClothingSalesProductCount(pageCondition.toPageable(), userId); + return PageResponse.of(pages.getContent(), pages.getTotalPages(), pages.getTotalElements()); + } + + public PageResponse> getProductsByUserClothingSales(Long clothingSalesId, String productState, Boolean isExpired, PageCondition pageCondition) { + Page pages; + if (productState.equals("kg-sell")) { // kg 매입 상품(리젝, 만료되었을 경우 kg 매입 가능) + pages = productRepository.getClothingSalesKgSellProduct(clothingSalesId, isExpired, pageCondition.toPageable()); + return PageResponse.of(pages.getContent(), pages.getTotalPages(), pages.getTotalElements()); + } + else{ + ProductStateType productStateType = ProductStateType.fromEngValue(productState); + // 판매 중, 판매 완료 상품 + if (productStateType == ProductStateType.SELLING || productStateType == ProductStateType.SOLD_OUT) { + pages = productRepository.getClothingSalesProduct(clothingSalesId, productStateType, pageCondition.toPageable()); + } + // 리젝, 만료 상품 + else { + pages = productRepository.getClothingSalesReturnedProduct(clothingSalesId, productStateType, pageCondition.toPageable()); + } + } + return PageResponse.of(pages.getContent(), pages.getTotalPages(), pages.getTotalElements()); + } + + } diff --git a/src/main/java/com/example/repick/global/error/exception/ErrorCode.java b/src/main/java/com/example/repick/global/error/exception/ErrorCode.java index 3015ec7f..19c83076 100644 --- a/src/main/java/com/example/repick/global/error/exception/ErrorCode.java +++ b/src/main/java/com/example/repick/global/error/exception/ErrorCode.java @@ -39,6 +39,7 @@ public enum ErrorCode { PRODUCT_NOT_DESIRED_STATE(400, "P029", "정상적인 접근이 아닙니다."), PRODUCT_STATE_NOT_FOUND(400, "P030", "상품 상태가 존재하지 않습니다."), DUPLICATE_PRODUCT_CART(400, "P031", "이미 장바구니에 담긴 상품입니다."), + INVALID_PRODUCT_RETURN_STATE(400, "P032", "존재하지 않는 상품 리턴 상태입니다."), // ClothingSales INVALID_CLOTHING_SALES_STATE_NAME(400, "C101", "존재하지 않는 옷장 정리 상태 이름입니다."), INVALID_CLOTHING_SALES_ID(404, "C102", "존재하지 않거나 권한이 없는 옷장 정리 ID입니다."),