diff --git a/.gitignore b/.gitignore index b3f1be53..22a87618 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +docker-compose.yml ### STS ### .apt_generated diff --git a/Dockerfile b/Dockerfile index 281d9768..a837f55b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,8 @@ -# Stage 1: Pinpoint setup -# FROM alpine:3.14 as pinpoint-setup -# WORKDIR /usr/local -# ADD https://github.com/pinpoint-apm/pinpoint/releases/download/v2.5.2/pinpoint-agent-2.5.2.tar.gz /usr/local -# RUN tar -zxvf pinpoint-agent-2.5.2.tar.gz && \ -# sed -i 's/profiler.transport.grpc.collector.ip=127.0.0.1/profiler.transport.grpc.collector.ip=10.41.183.156/g' pinpoint-agent-2.5.2/pinpoint-root.config && \ -# sed -i 's/profiler.collector.ip=127.0.0.1/profiler.collector.ip=10.41.183.156/g' pinpoint-agent-2.5.2/pinpoint-root.config - -# Stage 2: Build final image FROM openjdk:17-jdk-alpine WORKDIR /app -# COPY --from=pinpoint-setup /usr/local/pinpoint-agent-2.5.2/ ./pinpoint-agent ARG JAR_FILE=build/libs/*.jar COPY ${JAR_FILE} app.jar EXPOSE 8080 ENTRYPOINT ["java","-jar", \ -# "-javaagent:/app/pinpoint-agent/pinpoint-bootstrap-2.5.2.jar", \ -# "-Dpinpoint.applicationName=TrendPick", \ -# "-Dpinpoint.config=/app/pinpoint-agent/pinpoint-root.config", \ "-Dspring.profiles.active=prod", \ "/app/app.jar"] diff --git a/src/main/java/project/trendpick_pro/domain/brand/service/BrandService.java b/src/main/java/project/trendpick_pro/domain/brand/service/BrandService.java index a84a973b..4ce3aa70 100644 --- a/src/main/java/project/trendpick_pro/domain/brand/service/BrandService.java +++ b/src/main/java/project/trendpick_pro/domain/brand/service/BrandService.java @@ -13,4 +13,5 @@ public interface BrandService { Brand findByName(String name); Brand findById(Long id); Long count(); + void saveAll(List brands); } diff --git a/src/main/java/project/trendpick_pro/domain/brand/service/impl/BrandServiceImpl.java b/src/main/java/project/trendpick_pro/domain/brand/service/impl/BrandServiceImpl.java index da3a7a5b..1ce6bf54 100644 --- a/src/main/java/project/trendpick_pro/domain/brand/service/impl/BrandServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/brand/service/impl/BrandServiceImpl.java @@ -7,48 +7,51 @@ import project.trendpick_pro.domain.brand.entity.dto.BrandResponse; import project.trendpick_pro.domain.brand.repository.BrandRepository; import project.trendpick_pro.domain.brand.service.BrandService; +import project.trendpick_pro.domain.store.entity.Store; +import project.trendpick_pro.domain.store.repository.StoreRepository; +import project.trendpick_pro.global.basedata.tagname.entity.TagName; +import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor public class BrandServiceImpl implements BrandService { - + private final BrandRepository brandRepository; + private final StoreRepository storeRepository; @Transactional(readOnly = true) @Override - public boolean isPresent(String name){ + public boolean isPresent(String name) { return brandRepository.findByName(name).isPresent(); } @Transactional @Override - public void save(String name){ + public void save(String name) { brandRepository.save(new Brand(name)); } @Transactional @Override - public void delete(Long id){ + public void delete(Long id) { Brand brand = brandRepository.findById(id).orElseThrow(); brandRepository.delete(brand); } @Transactional(readOnly = true) @Override - public List findAll(){ + public List findAll() { return brandRepository.findAllBy(); } @Transactional(readOnly = true) @Override public Brand findByName(String name) { - try { - return brandRepository.findByName(name).get(); - } catch (Exception e) { - return brandRepository.save(new Brand(name)); - } + return brandRepository.findByName(name).orElseThrow( + () -> new IllegalArgumentException("해당 브랜드는 존재하지 않습니다.") + ); } @Transactional(readOnly = true) @@ -62,4 +65,15 @@ public Brand findById(Long id) { public Long count() { return brandRepository.count(); } + + @Override + @Transactional + public void saveAll(List brands) { + List list = new ArrayList<>(); + for (String brand : brands) { + list.add(new Brand(brand)); + } + + brandRepository.saveAll(list); + } } diff --git a/src/main/java/project/trendpick_pro/domain/cart/controller/CartController.java b/src/main/java/project/trendpick_pro/domain/cart/controller/CartController.java index 85ba51d5..bf95e5b6 100644 --- a/src/main/java/project/trendpick_pro/domain/cart/controller/CartController.java +++ b/src/main/java/project/trendpick_pro/domain/cart/controller/CartController.java @@ -38,7 +38,7 @@ public String showCart(Model model) { @PreAuthorize("hasAuthority({'MEMBER'})") @PostMapping("/add") - public String addItem(@ModelAttribute @Valid CartItemRequest cartItemRequests, Model model) { + public String addItem(@ModelAttribute @Valid CartItemRequest cartItemRequests) { RsData result = cartService.addCartItem(rq.getMember(), cartItemRequests); if(result.isFail()) diff --git a/src/main/java/project/trendpick_pro/domain/cart/entity/CartItem.java b/src/main/java/project/trendpick_pro/domain/cart/entity/CartItem.java index a014b6fb..55f7edce 100644 --- a/src/main/java/project/trendpick_pro/domain/cart/entity/CartItem.java +++ b/src/main/java/project/trendpick_pro/domain/cart/entity/CartItem.java @@ -47,14 +47,14 @@ public static CartItem of(Cart cart, Product product, CartItemRequest cartItemRe return CartItem.builder() .cart(cart) .product(product) - .quantity(cartItemRequest.getQuantity()) + .quantity(0) .size(cartItemRequest.getSize()) .color(cartItemRequest.getColor()) .build(); } public void updateCount(int quantity){ - this.quantity += quantity; this.cart.updateTotalCount(quantity); + this.quantity += quantity; } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/cart/service/impl/CartServiceImpl.java b/src/main/java/project/trendpick_pro/domain/cart/service/impl/CartServiceImpl.java index aed9bbee..71d0711b 100644 --- a/src/main/java/project/trendpick_pro/domain/cart/service/impl/CartServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/cart/service/impl/CartServiceImpl.java @@ -52,8 +52,9 @@ public RsData addCartItem(Member member, CartItemRequest cartItemReque } CartItem cartItem = cartItemRepository.findByCartIdAndProductId(cart.getId(), product.getId()); - if (cartItem == null) + if (cartItem == null) { cartItem = CartItem.of(cart, product, cartItemRequest); + } cartItem.updateCount(cartItemRequest.getQuantity()); cartItemRepository.save(cartItem); diff --git a/src/main/java/project/trendpick_pro/domain/cash/entity/CashLog.java b/src/main/java/project/trendpick_pro/domain/cash/entity/CashLog.java index 07ba24a4..33ac1271 100644 --- a/src/main/java/project/trendpick_pro/domain/cash/entity/CashLog.java +++ b/src/main/java/project/trendpick_pro/domain/cash/entity/CashLog.java @@ -1,42 +1,55 @@ package project.trendpick_pro.domain.cash.entity; import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; -import lombok.experimental.SuperBuilder; +import lombok.*; +import project.trendpick_pro.domain.common.base.BaseTimeEntity; import project.trendpick_pro.domain.member.entity.Member; +import project.trendpick_pro.domain.rebate.entity.RebateOrderItem; +import project.trendpick_pro.domain.withdraw.entity.WithdrawApply; import static jakarta.persistence.FetchType.LAZY; @Entity @Getter @NoArgsConstructor -@SuperBuilder +@AllArgsConstructor +@Builder @ToString(callSuper = true) -public class CashLog { +public class CashLog extends BaseTimeEntity { //돈의 흐름을 기록하기 위한 엔티티 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "cash_log_id") private Long id; - private String relTypeCode; - - private Long relId; - @ManyToOne(fetch = LAZY) private Member member; - + private String store; private long price; // 변동 + private Long rebateOrderItemId; + private Long withDrawApplyId; @Enumerated(EnumType.STRING) private EvenType eventType; - - public CashLog(Long id){ - this.id=id; - } public enum EvenType { 출금__통장입금, 브랜드정산__예치금; } + + static public CashLog of(WithdrawApply withdrawApply){ + return CashLog.builder() + .withDrawApplyId(withdrawApply.getId()) + .price(withdrawApply.getPrice() * -1) + .store(withdrawApply.getStore()) + .eventType(EvenType.출금__통장입금) + .build(); + } + + static public CashLog of(RebateOrderItem rebateOrderItem){ + return CashLog.builder() + .rebateOrderItemId(rebateOrderItem.getId()) + .price(rebateOrderItem.calculateRebatePrice()) + .store(rebateOrderItem.getSellerName()) + .eventType(EvenType.브랜드정산__예치금) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/cash/entity/dto/CashResponse.java b/src/main/java/project/trendpick_pro/domain/cash/entity/dto/CashResponse.java index 7eed5f23..c6903c1d 100644 --- a/src/main/java/project/trendpick_pro/domain/cash/entity/dto/CashResponse.java +++ b/src/main/java/project/trendpick_pro/domain/cash/entity/dto/CashResponse.java @@ -7,7 +7,5 @@ @Getter @AllArgsConstructor public class CashResponse { - CashLog cashLog; - long newRestCash; } diff --git a/src/main/java/project/trendpick_pro/domain/cash/service/CashService.java b/src/main/java/project/trendpick_pro/domain/cash/service/CashService.java index 62ed08af..732005f8 100644 --- a/src/main/java/project/trendpick_pro/domain/cash/service/CashService.java +++ b/src/main/java/project/trendpick_pro/domain/cash/service/CashService.java @@ -3,9 +3,12 @@ import project.trendpick_pro.domain.cash.entity.CashLog; import project.trendpick_pro.domain.member.entity.Member; +import project.trendpick_pro.domain.rebate.entity.RebateOrderItem; +import project.trendpick_pro.domain.withdraw.entity.WithdrawApply; public interface CashService { - CashLog addCash(Member member, long price, String relTypeCode,Long relId, CashLog.EvenType eventType); + CashLog addCashLog(WithdrawApply withdrawApply); + CashLog addCashLog(RebateOrderItem rebateOrderItem); } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/cash/service/impl/CashServiceImpl.java b/src/main/java/project/trendpick_pro/domain/cash/service/impl/CashServiceImpl.java index 659744a6..1adfc5a1 100644 --- a/src/main/java/project/trendpick_pro/domain/cash/service/impl/CashServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/cash/service/impl/CashServiceImpl.java @@ -2,26 +2,35 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import project.trendpick_pro.domain.brand.entity.Brand; +import project.trendpick_pro.domain.brand.service.BrandService; import project.trendpick_pro.domain.cash.entity.CashLog; import project.trendpick_pro.domain.cash.repository.CashLogRepository; import project.trendpick_pro.domain.cash.service.CashService; import project.trendpick_pro.domain.member.entity.Member; +import project.trendpick_pro.domain.member.service.MemberService; +import project.trendpick_pro.domain.rebate.entity.RebateOrderItem; +import project.trendpick_pro.domain.withdraw.entity.WithdrawApply; + @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class CashServiceImpl implements CashService { private final CashLogRepository cashLogRepository; + @Transactional + @Override + public CashLog addCashLog(WithdrawApply withdrawApply) { + CashLog cashLog = cashLogRepository.save(CashLog.of(withdrawApply)); + return cashLog; + } - public CashLog addCash(Member member, long price, String relTypeCode, Long relId, CashLog.EvenType eventType) { - CashLog cashLog = CashLog.builder() - .member(member) - .price(price) - .relTypeCode(relTypeCode) - .relId(relId) - .eventType(eventType) - .build(); - - cashLogRepository.save(cashLog); - + @Transactional + @Override + public CashLog addCashLog(RebateOrderItem rebateOrderItem) { + CashLog cashLog = cashLogRepository.save(CashLog.of(rebateOrderItem)); return cashLog; } + + } diff --git a/src/main/java/project/trendpick_pro/domain/common/controller/CommonController.java b/src/main/java/project/trendpick_pro/domain/common/controller/CommonController.java index 3aa7ec07..d759bb0f 100644 --- a/src/main/java/project/trendpick_pro/domain/common/controller/CommonController.java +++ b/src/main/java/project/trendpick_pro/domain/common/controller/CommonController.java @@ -6,18 +6,14 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.util.UriComponentsBuilder; -import project.trendpick_pro.global.kafka.view.service.ViewService; @Slf4j @Controller @RequiredArgsConstructor public class CommonController { - private final ViewService viewService; - @GetMapping("/") public String homePage(HttpSession session) { - viewService.requestIncrementViewCount(session); String mainCategory = "전체"; return "redirect:" + UriComponentsBuilder .fromPath("/trendpick/products/list") diff --git a/src/main/java/project/trendpick_pro/domain/coupon/controller/CouponCardController.java b/src/main/java/project/trendpick_pro/domain/coupon/controller/CouponCardController.java index 04e9c1b6..febc206b 100644 --- a/src/main/java/project/trendpick_pro/domain/coupon/controller/CouponCardController.java +++ b/src/main/java/project/trendpick_pro/domain/coupon/controller/CouponCardController.java @@ -9,7 +9,6 @@ import project.trendpick_pro.domain.coupon.entity.dto.response.CouponCardByApplyResponse; import project.trendpick_pro.domain.coupon.service.CouponCardService; import project.trendpick_pro.global.util.rsData.RsData; - import java.time.LocalDateTime; import java.util.List; @@ -29,21 +28,21 @@ public String issueCoupon(@PathVariable("couponId") Long couponId, HttpServletRe } @PreAuthorize("hasRole('MEMBER')") - @GetMapping("apply") + @GetMapping("/apply") @ResponseBody public List showApplicableCoupons(@RequestParam("orderItem") Long orderItemId) { return couponCardService.showCouponCardsByOrderItem(orderItemId); } @PreAuthorize("hasRole('MEMBER')") - @PostMapping("apply") + @PostMapping("/apply") public String applyCoupon(@RequestParam("couponCard") Long couponCardId, @RequestParam("orderItem") Long orderItemId, HttpServletRequest req) { - RsData result = couponCardService.apply(couponCardId, orderItemId); + RsData result = couponCardService.apply(couponCardId, orderItemId, LocalDateTime.now()); return processRequest(result, "쿠폰이 적용되었습니다.", req); } @PreAuthorize("hasRole('MEMBER')") - @PostMapping("cancel") + @PostMapping("/cancel") public String cancelCoupon(@RequestParam("orderItem") Long orderItemId, HttpServletRequest req) { RsData result = couponCardService.cancel(orderItemId); return processRequest(result, "쿠폰 적용이 취소되었습니다.", req); diff --git a/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponCard.java b/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponCard.java index 73f0598f..171d106f 100644 --- a/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponCard.java +++ b/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponCard.java @@ -22,7 +22,7 @@ public class CouponCard extends BaseTimeEntity { private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "coupon", nullable = false) + @JoinColumn(name = "coupon") private Coupon coupon; @Column(name = "coupon_code", nullable = false, unique = true) @@ -63,24 +63,15 @@ && validateStatus() && validateMinimumPurchaseAmount(orderItem.getOrderPrice()); } - public void use(OrderItem orderItem, LocalDateTime dateTime) { - settingStatusAndDate(dateTime); - orderItem.applyCouponCard(this); - orderItem.discount( - orderItem.getOrderPrice() * (this.coupon.getDiscountPercent() / 100) - ); + public void use(LocalDateTime dateTime) { + this.status = CouponStatus.USED; + this.usedDate = dateTime; } public void cancel(OrderItem orderItem){ this.status = CouponStatus.AVAILABLE; this.usedDate = null; orderItem.cancelCouponCard(); - this.coupon.decreaseIssueCount(); - } - - private void settingStatusAndDate(LocalDateTime dateTime) { - this.status = CouponStatus.USED; - this.usedDate = dateTime; } private boolean validateMinimumPurchaseAmount(int price){ @@ -98,9 +89,10 @@ private boolean validateStatus(){ private void updateStatus(LocalDateTime dateTime) { checkDateNotNull(dateTime); - if(dateTime.isBefore(this.coupon.getExpirationPeriod().getStartDate())) { + if(dateTime.isBefore(this.coupon.getExpirationPeriod().getStartDate())) this.status = CouponStatus.NOT_YET_ACTIVE; - } this.status = CouponStatus.AVAILABLE; + else + this.status = CouponStatus.AVAILABLE; } private void checkDateNotNull(LocalDateTime dateTime) { diff --git a/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponStatus.java b/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponStatus.java index 92de62ae..53988a82 100644 --- a/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponStatus.java +++ b/src/main/java/project/trendpick_pro/domain/coupon/entity/CouponStatus.java @@ -6,7 +6,7 @@ public enum CouponStatus { USED("USED"), //사용함 AVAILABLE("AVAILABLE"), //사용가능 - EXPIRED("EXPIRED"), //기간만료 //배치로 하루마다 체크 + EXPIRED("EXPIRED"), //기간만료 //스케줄러로 하루마다 체크 NOT_YET_ACTIVE("NOT_YET_ACTIVE"); //아직 비활성화 private String value; diff --git a/src/main/java/project/trendpick_pro/domain/coupon/service/CouponCardService.java b/src/main/java/project/trendpick_pro/domain/coupon/service/CouponCardService.java index c504748c..1ff225fa 100644 --- a/src/main/java/project/trendpick_pro/domain/coupon/service/CouponCardService.java +++ b/src/main/java/project/trendpick_pro/domain/coupon/service/CouponCardService.java @@ -14,7 +14,7 @@ public interface CouponCardService { List showCouponCardsByOrderItem(Long orderItemId); - RsData apply(Long couponCardId, Long orderItemId); + RsData apply(Long couponCardId, Long orderItemId, LocalDateTime now); RsData cancel(Long orderItemId); diff --git a/src/main/java/project/trendpick_pro/domain/coupon/service/impl/CouponCardServiceImpl.java b/src/main/java/project/trendpick_pro/domain/coupon/service/impl/CouponCardServiceImpl.java index f603246f..d93d803a 100644 --- a/src/main/java/project/trendpick_pro/domain/coupon/service/impl/CouponCardServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/coupon/service/impl/CouponCardServiceImpl.java @@ -23,7 +23,6 @@ @RequiredArgsConstructor @Service public class CouponCardServiceImpl implements CouponCardService { - private final CouponCardRepository couponCardRepository; private final CouponRepository couponRepository; private final OrderItemRepository orderItemRepository; @@ -31,45 +30,47 @@ public class CouponCardServiceImpl implements CouponCardService { @Transactional @Override public RsData issue(Member member, Long couponId, LocalDateTime dateTime) { - Coupon coupon = couponRepository.findById(couponId).orElseThrow( - () -> new CouponNotFoundException("존재하지 않는 쿠폰입니다.")); + Coupon coupon = findCoupon(couponId); int count = couponCardRepository.countByCouponIdAndMemberId(couponId, member.getId()); - RsData of = validateCouponCard(count, coupon); - if (of != null) { - return of; - } + RsData validateResult = validateCouponCard(count, coupon, dateTime); + if (validateResult.isFail()) + return validateResult; - CouponCard savedCouponCard = settingCouponCard(member, dateTime, coupon); + settingCouponCard(member, dateTime, coupon); return RsData.of("S-1", coupon.getName() + " 쿠폰이 발급되었습니다."); } + @Transactional @Override - public RsData apply(Long couponCardId, Long orderItemId) { - OrderItem orderItem = orderItemRepository.findById(orderItemId).orElseThrow( - () -> new OrderItemNotFoundException("주문되지 않은 상품입니다.")); - CouponCard couponCard = couponCardRepository.findById(couponCardId).orElseThrow( - () -> new CouponNotFoundException("존재하지 않은 쿠폰입니다.")); - if (!couponCard.validate(orderItem, LocalDateTime.now())) - return RsData.of("F-1", "해당 주문상품에 적용된 쿠폰이 없습니다."); - couponCard.use(orderItem, LocalDateTime.now()); + public RsData apply(Long couponCardId, Long orderItemId, LocalDateTime dateTime) { + OrderItem orderItem = findOrderItem(orderItemId); + CouponCard couponCard = findCouponCard(couponCardId); + + if (!couponCard.validate(orderItem, dateTime)) + return RsData.of("F-1", "해당 주문상품에 해당 쿠폰을 적용할 수 없습니다."); + orderItem.applyCouponCard(couponCard); return RsData.of("S-1", "쿠폰이 적용되었습니다."); } + private CouponCard findCouponCard(Long couponCardId) { + CouponCard couponCard = couponCardRepository.findById(couponCardId).orElseThrow( + () -> new CouponNotFoundException("존재하지 않는 쿠폰입니다.")); + return couponCard; + } + @Transactional @Override public RsData cancel(Long orderItemId) { - OrderItem orderItem = orderItemRepository.findById(orderItemId).orElseThrow( - () -> new OrderItemNotFoundException("주문되지 않은 상품입니다.")); + OrderItem orderItem = findOrderItem(orderItemId); orderItem.getCouponCard().cancel(orderItem); return RsData.of("S-1", "쿠폰이 취소되었습니다."); } @Override public List showCouponCardsByOrderItem(Long orderItemId) { - OrderItem orderItem = orderItemRepository.findById(orderItemId).orElseThrow( - () -> new OrderItemNotFoundException("주문되지 않은 상품입니다.")); + OrderItem orderItem = findOrderItem(orderItemId); List couponCards = couponCardRepository.findAllByBrand(orderItem.getProduct().getProductOption().getBrand().getName()); return createCouponCardByApplyResponseList(couponCards, orderItem); } @@ -77,20 +78,19 @@ public List showCouponCardsByOrderItem(Long orderItem private CouponCard settingCouponCard(Member member, LocalDateTime dateTime, Coupon coupon) { CouponCard couponCard = new CouponCard(coupon); couponCard.updatePeriod(dateTime); + couponCard.connectMember(member); CouponCard savedCouponCard = couponCardRepository.save(couponCard); - savedCouponCard.updatePeriod(dateTime); - savedCouponCard.connectMember(member); return savedCouponCard; } - private static RsData validateCouponCard(int count, Coupon coupon) { + private RsData validateCouponCard(int count, Coupon coupon, LocalDateTime dateTime) { if(count > 0) return RsData.of("F-3", "이미 발급 받으신 쿠폰입니다."); if(!coupon.validateLimitCount()) return RsData.of("F-1", "수량이 모두 소진되었습니다."); - if(!coupon.validateLimitIssueDate(LocalDateTime.now())) + if(!coupon.validateLimitIssueDate(dateTime)) return RsData.of("F-2", "쿠폰 발급 가능 날짜가 지났습니다."); - return null; + return RsData.success(); } private List createCouponCardByApplyResponseList(List couponCards, OrderItem orderItem) { @@ -99,5 +99,15 @@ private List createCouponCardByApplyResponseList(List .map(couponCard -> CouponCardByApplyResponse.of(couponCard, orderItem)) .toList(); } + + private OrderItem findOrderItem(Long orderItemId) { + return orderItemRepository.findById(orderItemId).orElseThrow( + () -> new OrderItemNotFoundException("주문되지 않은 상품입니다.")); + } + + private Coupon findCoupon(Long couponId) { + return couponRepository.findById(couponId).orElseThrow( + () -> new CouponNotFoundException("존재하지 않는 쿠폰입니다.")); + } } diff --git a/src/main/java/project/trendpick_pro/domain/delivery/entity/Delivery.java b/src/main/java/project/trendpick_pro/domain/delivery/entity/Delivery.java index cae6b6e4..32341eb1 100644 --- a/src/main/java/project/trendpick_pro/domain/delivery/entity/Delivery.java +++ b/src/main/java/project/trendpick_pro/domain/delivery/entity/Delivery.java @@ -26,7 +26,7 @@ public class Delivery extends BaseTimeEntity { public Delivery(String address){ this.address = address; - state = DeliveryState.READY; + state = DeliveryState.COMPLETED; //원활한 테스트를 위해서 배송 완료로 임시 설정 } public void updateStatus(String status){ diff --git a/src/main/java/project/trendpick_pro/domain/member/service/MemberService.java b/src/main/java/project/trendpick_pro/domain/member/service/MemberService.java index c4b712f4..69e26d6a 100644 --- a/src/main/java/project/trendpick_pro/domain/member/service/MemberService.java +++ b/src/main/java/project/trendpick_pro/domain/member/service/MemberService.java @@ -1,14 +1,11 @@ package project.trendpick_pro.domain.member.service; -import project.trendpick_pro.domain.brand.entity.Brand; -import project.trendpick_pro.domain.cash.entity.CashLog; -import project.trendpick_pro.domain.cash.entity.dto.CashResponse; import project.trendpick_pro.domain.member.entity.Member; import project.trendpick_pro.domain.member.entity.dto.response.MemberInfoResponse; import project.trendpick_pro.domain.member.entity.form.JoinForm; import project.trendpick_pro.domain.tags.tag.entity.dto.request.TagRequest; +import project.trendpick_pro.domain.withdraw.entity.WithdrawApply; import project.trendpick_pro.global.util.rsData.RsData; - import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -19,9 +16,9 @@ public interface MemberService { void modifyTag(Member member, TagRequest tagRequest); RsData modifyAddress(Member member, String address); Member findById(Long id); - Member findByBrandMember(String name); + Member findBrandMember(String name); Optional findByEmail(String username); void updateRecentlyAccessDate(Member member, LocalDateTime dateTime); - RsData addCash(String brand, long price, Brand relEntity, CashLog.EvenType eventType); long getRestCash(Member member); + void completeWithdraw(WithdrawApply withdrawApply); } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/member/service/impl/MemberServiceImpl.java b/src/main/java/project/trendpick_pro/domain/member/service/impl/MemberServiceImpl.java index 68219913..5db30f05 100644 --- a/src/main/java/project/trendpick_pro/domain/member/service/impl/MemberServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/member/service/impl/MemberServiceImpl.java @@ -5,11 +5,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import project.trendpick_pro.domain.brand.entity.Brand; import project.trendpick_pro.domain.brand.service.BrandService; -import project.trendpick_pro.domain.cash.entity.CashLog; -import project.trendpick_pro.domain.cash.entity.dto.CashResponse; -import project.trendpick_pro.domain.cash.service.CashService; import project.trendpick_pro.domain.member.entity.Member; import project.trendpick_pro.domain.member.entity.MemberRoleType; import project.trendpick_pro.domain.member.entity.dto.response.MemberInfoResponse; @@ -22,6 +18,7 @@ import project.trendpick_pro.domain.store.service.StoreService; import project.trendpick_pro.domain.tags.favoritetag.entity.FavoriteTag; import project.trendpick_pro.domain.tags.tag.entity.dto.request.TagRequest; +import project.trendpick_pro.domain.withdraw.entity.WithdrawApply; import project.trendpick_pro.global.util.rsData.RsData; import java.time.LocalDateTime; @@ -38,7 +35,6 @@ public class MemberServiceImpl implements MemberService { private final PasswordEncoder passwordEncoder; private final RecommendService recommendService; private final StoreService storeService; - private final CashService cashService; private final BrandService brandService; @Transactional @@ -89,7 +85,7 @@ public Member findById(Long id) { } @Override - public Member findByBrandMember(String name){ + public Member findBrandMember(String name){ return memberRepository.findByBrand(name); } @@ -104,24 +100,9 @@ public void updateRecentlyAccessDate(Member member, LocalDateTime dateTime){ member.updateRecentlyAccessDate(dateTime); } - @Transactional - @Override - public RsData addCash(String brand, long price, Brand relEntity, CashLog.EvenType eventType) { - Member member=findByBrandMember(brand); - if(!member.getBrand().equals(brand)){ - return RsData.of("F-1","해당 브랜드와 관리자가 일치하지 않습니다."); - } - CashLog cashLog = cashService.addCash(member, price, relEntity.getName(),relEntity.getId(), eventType); - - long newRestCash = getRestCash(member) + cashLog.getPrice(); - member.connectCash(newRestCash); - - return RsData.of("S-1", "성공", new CashResponse(cashLog, newRestCash)); - } - @Override public long getRestCash(Member member) { - return memberRepository.findById(member.getId()).get().getRestCash(); + return member.getRestCash(); } private void checkingMemberType(Member member) { @@ -159,4 +140,12 @@ private Set createFavoriteTags(List tagList) { .map(FavoriteTag::new) .collect(Collectors.toSet()); } + + @Transactional + @Override + public void completeWithdraw(WithdrawApply withdrawApply) { + Member member = withdrawApply.getApplicant(); + long newRestCash = member.getRestCash() - withdrawApply.getPrice(); + member.connectCash(newRestCash); + } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/orders/contoller/OrderController.java b/src/main/java/project/trendpick_pro/domain/orders/contoller/OrderController.java index b2a6622c..cb380d30 100644 --- a/src/main/java/project/trendpick_pro/domain/orders/contoller/OrderController.java +++ b/src/main/java/project/trendpick_pro/domain/orders/contoller/OrderController.java @@ -7,6 +7,7 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import project.trendpick_pro.domain.member.entity.Member; +import project.trendpick_pro.domain.orders.entity.Order; import project.trendpick_pro.domain.orders.entity.dto.request.CartToOrderRequest; import project.trendpick_pro.domain.orders.entity.dto.request.ProductOrderRequest; import project.trendpick_pro.domain.orders.entity.dto.response.OrderDetailResponse; @@ -28,7 +29,10 @@ public class OrderController { @PreAuthorize("hasAuthority({'MEMBER'})") @GetMapping("{orderId}/form") public String showOrderForm(@PathVariable("orderId") Long orderId, Model model){ - model.addAttribute("order", orderService.findById(orderId)); + Order order = orderService.findById(orderId); + if(order.getPaymentKey() != null || order.getOrderState().equals("주문취소")) + return rq.redirectWithMsg("/", "이미 처리된 주문입니다."); + model.addAttribute("order", order); return "trendpick/orders/order-form"; } diff --git a/src/main/java/project/trendpick_pro/domain/orders/entity/Order.java b/src/main/java/project/trendpick_pro/domain/orders/entity/Order.java index 996f7699..8de538ac 100644 --- a/src/main/java/project/trendpick_pro/domain/orders/entity/Order.java +++ b/src/main/java/project/trendpick_pro/domain/orders/entity/Order.java @@ -39,7 +39,7 @@ public class Order extends BaseTimeEntity { @Enumerated(EnumType.STRING) private OrderStatus orderStatus; - @Column(name = "total_price", nullable = false) + @Column(name = "total_price") private int totalPrice = 0; public void connectUser(Member member) { @@ -49,7 +49,7 @@ public void connectUser(Member member) { public void connectPayment(String paymentMethod, String paymentKey) { this.paymentMethod = paymentMethod; this.paymentKey = paymentKey; - productDiscount(); + setDiscountAmount(); } public void connectDelivery(Delivery delivery) { @@ -66,6 +66,15 @@ public static Order createOrder(Member member, Delivery delivery, List orderItems) { + public void settingOrderItems(List orderItems) { for (OrderItem orderItem : orderItems) { this.addOrderItem(orderItem); - this.totalPrice += orderItem.getOrderItemByQuantity(); + this.totalPrice += orderItem.getTotalPrice(); } } @@ -129,4 +137,9 @@ private void addOrderItem(OrderItem orderItem) { orderItems.add(orderItem); orderItem.connectOrder(this); } + + //구매결정 완료된건지 (환불불가, 취소불가) + public boolean isCompletedPurchaseDecision(){ + return this.getDeliveryState().equals("배송완료") && this.getOrderState().equals("결제완료"); + } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/orders/entity/OrderItem.java b/src/main/java/project/trendpick_pro/domain/orders/entity/OrderItem.java index 61488fc0..df3d1378 100644 --- a/src/main/java/project/trendpick_pro/domain/orders/entity/OrderItem.java +++ b/src/main/java/project/trendpick_pro/domain/orders/entity/OrderItem.java @@ -8,6 +8,7 @@ import project.trendpick_pro.domain.common.base.BaseTimeEntity; import project.trendpick_pro.domain.coupon.entity.CouponCard; import project.trendpick_pro.domain.product.entity.product.Product; +import project.trendpick_pro.domain.product.exception.ProductStockOutException; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -33,8 +34,7 @@ public class OrderItem extends BaseTimeEntity { @Column(name = "order_price", nullable = false) private int orderPrice; - @Column(name = "total_price") - private int orderItemByQuantity; + private int totalPrice; //계산된 가격 (실제 지불한) @Column(name = "discount_price", nullable = false) private int discountPrice; @@ -69,25 +69,27 @@ public void connectOrder(Order order) { public void applyCouponCard(CouponCard couponCard) { this.couponCard = couponCard; + int discountPercent = couponCard.getCoupon().getDiscountPercent(); + this.discountPrice = getOrderPrice() * discountPercent / 100; //쿠폰은 상품 한 개 가격에서 할인 적용 + this.totalPrice -= this.discountPrice; } public void cancelCouponCard(){ this.couponCard = null; + this.totalPrice += this.discountPrice; this.discountPrice = 0; } - public void discount(int price){ - this.discountPrice += price; - } - private OrderItem(Product product, int quantity, String size, String color) { + if(product.getProductOption().getStock() < quantity) + throw new ProductStockOutException("상품의 재고가 부족하여 주문이 불가능합니다."); this.product = product; this.orderPrice = (product.getDiscountedPrice() > 0) ? product.getDiscountedPrice() : product.getProductOption().getPrice(); this.quantity = quantity; this.size = size; this.color = color; - this.discountPrice = 0; - this.orderItemByQuantity = this.orderPrice * this.quantity; + this.discountPrice = 0; //쿠폰으로 인한 할인가만 적용 + this.totalPrice = this.orderPrice * this.quantity; product.getProductOption().decreaseStock(quantity); } } diff --git a/src/main/java/project/trendpick_pro/domain/orders/entity/dto/response/OrderItemDto.java b/src/main/java/project/trendpick_pro/domain/orders/entity/dto/response/OrderItemDto.java index e8ae0794..22ac1e36 100644 --- a/src/main/java/project/trendpick_pro/domain/orders/entity/dto/response/OrderItemDto.java +++ b/src/main/java/project/trendpick_pro/domain/orders/entity/dto/response/OrderItemDto.java @@ -8,6 +8,7 @@ import java.util.Locale; @Getter +@Builder public class OrderItemDto { private Long productId; private String productName; @@ -17,17 +18,6 @@ public class OrderItemDto { private int price; private Long cartItemId; - @Builder - private OrderItemDto(Long productId, String productName, int quantity, String size, String color, int price, Long cartItemId) { - this.productId = productId; - this.productName = productName; - this.quantity = quantity; - this.size = size; - this.color = color; - this.price = price; - this.cartItemId = cartItemId; - } - public static OrderItemDto of(Product product, int quantity, String size, String color) { return OrderItemDto.builder() .productId(product.getId()) diff --git a/src/main/java/project/trendpick_pro/domain/orders/repository/OrderRepository.java b/src/main/java/project/trendpick_pro/domain/orders/repository/OrderRepository.java index d72dbd7f..39b272fe 100644 --- a/src/main/java/project/trendpick_pro/domain/orders/repository/OrderRepository.java +++ b/src/main/java/project/trendpick_pro/domain/orders/repository/OrderRepository.java @@ -1,7 +1,18 @@ package project.trendpick_pro.domain.orders.repository; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import project.trendpick_pro.domain.orders.entity.Order; +import java.time.LocalDateTime; +import java.util.List; + public interface OrderRepository extends JpaRepository, OrderRepositoryCustom { + + @Query("select o from Order o " + + "where o.paymentKey IS NULL " + + "and o.createdDate <= :expireDate") + List findAllByPaymentKeyIsNullAndCreatedDateIsBefore(@Param("expireDate") LocalDateTime expireDate, Pageable pageable); } diff --git a/src/main/java/project/trendpick_pro/domain/orders/service/OrderService.java b/src/main/java/project/trendpick_pro/domain/orders/service/OrderService.java index 8b79a6c0..6f6ee020 100644 --- a/src/main/java/project/trendpick_pro/domain/orders/service/OrderService.java +++ b/src/main/java/project/trendpick_pro/domain/orders/service/OrderService.java @@ -8,6 +8,7 @@ import project.trendpick_pro.domain.orders.entity.dto.request.CartToOrderRequest; import project.trendpick_pro.domain.orders.entity.dto.response.OrderDetailResponse; import project.trendpick_pro.domain.orders.entity.dto.response.OrderResponse; +import project.trendpick_pro.global.kafka.outbox.entity.OrderMaterial; import project.trendpick_pro.global.util.rsData.RsData; import java.time.LocalDate; @@ -17,7 +18,7 @@ public interface OrderService { RsData cartToOrder(Member member, CartToOrderRequest request); RsData productToOrder(Member member, Long id, int quantity, String size, String color); - void tryOrder(String id) throws JsonProcessingException; + void tryOrder(String id, List orderMaterials) throws JsonProcessingException; RsData cancel(Long orderId); void delete(Long id); RsData findOrderItems(Member member, Long orderId); diff --git a/src/main/java/project/trendpick_pro/domain/orders/service/impl/OrderServiceImpl.java b/src/main/java/project/trendpick_pro/domain/orders/service/impl/OrderServiceImpl.java index 613c6f14..eff83baf 100644 --- a/src/main/java/project/trendpick_pro/domain/orders/service/impl/OrderServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/orders/service/impl/OrderServiceImpl.java @@ -1,13 +1,12 @@ package project.trendpick_pro.domain.orders.service.impl; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; -import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.trendpick_pro.domain.cart.entity.CartItem; @@ -21,22 +20,17 @@ import project.trendpick_pro.domain.orders.entity.OrderStatus; import project.trendpick_pro.domain.orders.entity.dto.request.CartToOrderRequest; import project.trendpick_pro.domain.orders.entity.dto.request.OrderSearchCond; -import project.trendpick_pro.domain.orders.entity.dto.request.OrderStateResponse; import project.trendpick_pro.domain.orders.entity.dto.response.OrderDetailResponse; import project.trendpick_pro.domain.orders.entity.dto.response.OrderResponse; import project.trendpick_pro.domain.orders.exception.OrderNotFoundException; import project.trendpick_pro.domain.orders.repository.OrderItemRepository; import project.trendpick_pro.domain.orders.repository.OrderRepository; import project.trendpick_pro.domain.orders.service.OrderService; -import project.trendpick_pro.domain.product.entity.product.Product; -import project.trendpick_pro.domain.product.exception.ProductNotFoundException; import project.trendpick_pro.domain.product.exception.ProductStockOutException; import project.trendpick_pro.domain.product.service.ProductService; -import project.trendpick_pro.domain.tags.favoritetag.service.FavoriteTagService; -import project.trendpick_pro.domain.tags.tag.entity.TagType; import project.trendpick_pro.global.kafka.KafkaProducerService; -import project.trendpick_pro.global.kafka.kafkasave.entity.OutboxMessage; -import project.trendpick_pro.global.kafka.kafkasave.service.OutboxMessageService; +import project.trendpick_pro.global.kafka.outbox.entity.OrderMaterial; +import project.trendpick_pro.global.kafka.outbox.service.OutboxMessageService; import project.trendpick_pro.global.util.rsData.RsData; import java.time.LocalDate; @@ -58,76 +52,60 @@ public class OrderServiceImpl implements OrderService { private final CartService cartService; private final ProductService productService; - private final FavoriteTagService favoriteTagService; - private final OutboxMessageService outboxMessageService; - private final KafkaProducerService kafkaProducerService; @Transactional public RsData cartToOrder(Member member, CartToOrderRequest request) { - if(member.getAddress().trim().isEmpty()) { - return RsData.of("F-1", "주소를 알 수 없어 주문이 불가능합니다."); - } - if(request.getSelectedItems().isEmpty()){ - return RsData.of("F-1","상품을 선택한 후 주문해주세요."); - } List cartItems = cartService.getSelectedCartItems(member, request); - if (!Objects.equals(cartItems.get(0).getCart().getMember().getId(), member.getId())) { - return RsData.of("F-1", "현재 접속중인 사용자와 장바구니 사용자가 일치하지 않습니다."); - } - Order order = createOrder(member, cartItems); + RsData result = validateCartToOrder(member, request, cartItems); + if (result.isFail()) return result; - OutboxMessage message = createOutboxMessage(order); - kafkaProducerService.sendMessage(message.getId()); + Order order = orderRepository.save(Order.createTempOrder(member, new Delivery(member.getAddress()))); + String uuidCode = UUID.randomUUID().toString(); + + List orderMaterials = new ArrayList<>(); + for (CartItem cartItem : cartItems) + orderMaterials.add(new OrderMaterial(cartItem, uuidCode)); + outboxMessageService.publishOrderCreationMessage("orders", String.valueOf(order.getId()), orderMaterials, uuidCode); return RsData.of("S-1", "주문을 시작 합니다."); } @Transactional - public RsData productToOrder(Member member, Long id, int quantity, String size, String color) { - try { - Order saveOrder = orderRepository.save( - Order.createOrder(member, new Delivery(member.getAddress()), - List.of(OrderItem.of(productService.findById(id), quantity, size, color)) - ) - ); - OutboxMessage message = createOutboxMessage(saveOrder); - kafkaProducerService.sendMessage(message.getId()); - return RsData.of("S-1", "주문을 시작합니다."); - } catch (ProductNotFoundException e) { - return RsData.of("F-1", "존재하지 않는 상품입니다."); - } + public RsData productToOrder(Member member, Long productId, int quantity, String size, String color) { + String uuidCode = UUID.randomUUID().toString(); + + Order order = orderRepository.save( + Order.createTempOrder(member, new Delivery(member.getAddress()))); + + List orderMaterials = List.of(new OrderMaterial(productId, quantity, size, color, uuidCode)); + outboxMessageService.publishOrderCreationMessage("orders", String.valueOf(order.getId()), orderMaterials, uuidCode); + return RsData.of("S-1", "주문을 시작합니다."); } - @Transactional - public void tryOrder(String id) throws JsonProcessingException { - Order order = orderRepository.findById(Long.parseLong(id)) - .orElseThrow(() -> new OrderNotFoundException("존재하지 않는 주문 입니다.")); - String email = order.getMember().getEmail(); - try { - decreaseStock(order); + @Transactional //실제 주문 로직 (재고 유효성 검사, 재고 감소, 상태 변경) + public void tryOrder(String id, List orderMaterials) throws JsonProcessingException { + Order order = findById(Long.valueOf(id)); + if(order.getOrderState().equals("결제완료")) //멱등성 + throw new IllegalAccessError("이미 처리된 주문입니다."); + + try{ + List orderItems = OrderMaterialsToOrderItems(orderMaterials); + order.settingOrderItems(orderItems); order.updateStatus(OrderStatus.ORDERED); - sendMessage(order.getId(), "Success", email); - } catch (ProductStockOutException e) { - if (order.getOrderStatus() == OrderStatus.TEMP) { - order.cancelTemp(); - } - log.error("주문 실패 : {}", e.getMessage()); - sendMessage(order.getId(), "Fail", email); + outboxMessageService.publishOrderProcessMessage("standByOrder", "Success", String.valueOf(order.getId()), order.getMember().getEmail()); + } catch(ProductStockOutException e){ + log.error("error message : {} ", e.getMessage()); + kafkaProducerService.sendOrderProcessFailMessage(order.getId(), "Fail", order.getMember().getEmail()); } } @Transactional public RsData cancel(Long orderId) { - Order order = orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException("존재하지 않는 주문입니다.")); - if(order.getOrderStatus() == OrderStatus.CANCELED) { - return RsData.of("F-1", "이미 취소된 주문입니다."); - } else if (order.getDelivery().getState() == DeliveryState.COMPLETED) { - return RsData.of("F-2", "이미 배송완료된 상품은 취소가 불가능합니다."); - } - if (order.getDelivery().getState() == DeliveryState.DELIVERY_ING) { - return RsData.of("F-3", "이미 배송을 시작하여 취소가 불가능합니다."); - } + Order order = findById(orderId); + RsData result = validateAvailableCancel(order); + if (result.isFail()) return result; + order.cancel(); return RsData.of("S-1", "환불 요청이 정상적으로 진행 되었습니다. 환불까지는 최소 2일에서 최대 14일까지 소요될 수 있습니다."); } @@ -142,7 +120,6 @@ public RsData findOrderItems(Member member, Long orderId) { try { Order order = orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException("존재하지 않는 주문 입니다.")); if (order.getOrderStatus() == OrderStatus.TEMP) { - orderRepository.delete(order); return RsData.of("F-3", "존재하지 않는 주문입니다."); } if (!Objects.equals(order.getMember().getId(), member.getId())) { @@ -179,51 +156,6 @@ public List findAllByCreatedDateBetweenOrderByIdAsc(LocalDateTime fro return orderItemRepository.findAllByCreatedDateBetween(fromDate, toDate); } - private Order createOrder(Member member, List cartItems) { - List orderItems = createOrderItem(member, cartItems); - Order order = Order.createOrder(member, new Delivery(member.getAddress()), orderItems); - return orderRepository.save(order); - } - - private List createOrderItem(Member member, List cartItems) { - List orderItems = new ArrayList<>(); - for (CartItem cartItem : cartItems) { - Product product = checkingProductStock(cartItem); - favoriteTagService.updateTag(member, product, TagType.ORDER); - orderItems.add(OrderItem.of(product, cartItem)); - } - return orderItems; - } - - private Product checkingProductStock(CartItem cartItem) { - Product product = productService.findById(cartItem.getProduct().getId()); - if (product.getProductOption().getStock() < cartItem.getQuantity()) { - throw new ProductStockOutException("재고가 부족합니다."); - } - return product; - } - - private OutboxMessage createOutboxMessage(Order order) { - OutboxMessage message = new OutboxMessage("orders", - String.valueOf(order.getId()), String.valueOf(order.getId())); - outboxMessageService.save(message); - return message; - } - - private void sendMessage(Long orderId, String message, String email) throws JsonProcessingException { - kafkaProducerService.sendMessage(orderId, message, email); - } - - private static void decreaseStock(Order order) { - for (OrderItem orderItem : order.getOrderItems()) { - orderItem.getProduct().getProductOption().decreaseStock(orderItem.getQuantity()); - if (orderItem.getProduct().getProductOption().getStock() < 0) { - throw new ProductStockOutException("재고가 부족 합니다."); - } - log.info("재고 : {}", orderItem.getProduct().getProductOption().getStock()); - } - } - private Page settingOrderByMemberStatus(Member member, int offset) { Page findOrders; PageRequest pageable = PageRequest.of(offset, 10); @@ -237,9 +169,39 @@ private Page settingOrderByMemberStatus(Member member, int offset return findOrders; } - private static List checkingTempStatus(Page findOrders) { + private List checkingTempStatus(Page findOrders) { return findOrders.stream() .filter(orderResponse -> !Objects.equals(orderResponse.getOrderStatus(), OrderStatus.TEMP.toString())) .collect(Collectors.toList()); } + + private RsData validateCartToOrder(Member member, CartToOrderRequest request, List cartItems) { + if(member.getAddress().trim().isEmpty()) + return RsData.of("F-1", "주소를 알 수 없어 주문이 불가능합니다."); + + if(request.getSelectedItems().isEmpty()) + return RsData.of("F-1", "상품을 선택한 후 주문해주세요."); + + if (!Objects.equals(cartItems.get(0).getCart().getMember().getId(), member.getId())) + return RsData.of("F-1", "현재 접속중인 사용자와 장바구니 사용자가 일치하지 않습니다."); + return RsData.success(); + } + + private List OrderMaterialsToOrderItems(List orderMaterials) throws ProductStockOutException{ + List orderItems = new ArrayList<>(); + for (OrderMaterial orderMaterial : orderMaterials) { //이때 재고 감소, 재고 체크 + orderItems.add(OrderItem.of(productService.findById(orderMaterial.getProductId()), orderMaterial.getQuantity(), + orderMaterial.getSize(), orderMaterial.getColor())); + } + return orderItems; + } + + private RsData validateAvailableCancel(Order order) { + if(order.getOrderStatus() == OrderStatus.CANCELED) + return RsData.of("F-1", "이미 취소된 주문입니다."); + + if (order.getDelivery().getState() == DeliveryState.DELIVERY_ING) + return RsData.of("F-3", "이미 배송을 시작하여 취소가 불가능합니다."); + return RsData.success(); + } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/product/controller/ProductController.java b/src/main/java/project/trendpick_pro/domain/product/controller/ProductController.java index 68067a0f..ef0301f8 100644 --- a/src/main/java/project/trendpick_pro/domain/product/controller/ProductController.java +++ b/src/main/java/project/trendpick_pro/domain/product/controller/ProductController.java @@ -20,7 +20,6 @@ import project.trendpick_pro.domain.category.service.SubCategoryService; import project.trendpick_pro.global.basedata.tagname.service.impl.TagNameServiceImpl; import project.trendpick_pro.global.util.rq.Rq; -import project.trendpick_pro.global.kafka.view.service.ViewService; import project.trendpick_pro.domain.member.entity.Member; import project.trendpick_pro.domain.member.service.MemberService; import project.trendpick_pro.domain.product.entity.dto.ProductRequest; @@ -60,8 +59,6 @@ public class ProductController { private final ReviewService reviewService; private final AskService askService; - private final ViewService viewService; - private final Rq rq; @Value("${colors}") @@ -133,8 +130,6 @@ public String showAllProduct(@RequestParam(value = "page", defaultValue = "0") i @RequestParam(value = "main-category", defaultValue = "all") String mainCategory, @RequestParam(value = "sub-category", defaultValue = "전체") String subCategory, Pageable pageable, Model model, HttpSession session) { - viewService.requestIncrementViewCount(session); - model.addAttribute("totalView", viewService.getCount()); if (mainCategory.equals("recommend")) { mainCategory = "추천"; } else if (mainCategory.equals("all")) { @@ -179,7 +174,6 @@ public String showAllProductBySeller(@RequestParam("page") int offset, Model mod @GetMapping("/keyword") public String searchQuery(@RequestParam String keyword, @RequestParam(value = "page", defaultValue = "0") int offset, Model model) { Page products = productService.findAllByKeyword(keyword, offset); - model.addAttribute("totalView", viewService.getCount()); model.addAttribute("keyword", keyword); model.addAttribute("productResponses", products); return "trendpick/products/list"; diff --git a/src/main/java/project/trendpick_pro/domain/rebate/controller/AdmRebateController.java b/src/main/java/project/trendpick_pro/domain/rebate/controller/AdmRebateController.java index 2e25d1ba..363868e9 100644 --- a/src/main/java/project/trendpick_pro/domain/rebate/controller/AdmRebateController.java +++ b/src/main/java/project/trendpick_pro/domain/rebate/controller/AdmRebateController.java @@ -6,82 +6,70 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.*; +import project.trendpick_pro.domain.rebate.entity.MonthRebateData; import project.trendpick_pro.domain.rebate.entity.RebateOrderItem; import project.trendpick_pro.domain.rebate.service.RebateService; import project.trendpick_pro.global.util.rq.Rq; import project.trendpick_pro.global.util.rsData.RsData; import project.trendpick_pro.global.util.Ut; - -import java.util.Arrays; import java.util.List; @Controller @RequestMapping("/trendpick/admin") @RequiredArgsConstructor public class AdmRebateController { + private final RebateService rebateService; + private final Rq rq; - private final RebateService rebateService; - private final Rq rq; + @GetMapping("/makeData") + @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") //브랜드 관리자만 접근 가능 + public String showMakeData() { + return "trendpick/admin/makeData"; + } - @GetMapping("/makeData") - @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") - public String showMakeData() { - if(!rq.getRollMember().getRole().getValue().equals("BRAND_ADMIN")){ - return rq.historyBack("브랜드 관리자만 접근할 수 있습니다."); - } return "trendpick/admin/makeData"; + @PostMapping("/makeData") + @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") + public String makeData(String yearMonth) { + RsData makeDateRsData = rebateService.makeRebateOrderItem(rq.getBrandName(), yearMonth); + if (makeDateRsData.isFail()) { + return rq.historyBack("정산할 데이터가 없습니다."); } + return rq.redirectWithMsg("/trendpick/admin/rebateOrderItemList?yearMonth=" + yearMonth, makeDateRsData); + } - @PostMapping("/makeData") - @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") - public String makeData(String yearMonth) { - RsData makeDateRsData = rebateService.makeDate(rq.getBrandName(), yearMonth); - if(makeDateRsData.isFail()){ - return rq.historyBack("정산할 데이터가 없습니다."); - } return rq.redirectWithMsg("/trendpick/admin/rebateOrderItemList?yearMonth=" + yearMonth, makeDateRsData); - } + @GetMapping("/rebateOrderItemList") + @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") + public String showRebateOrderItemList(String yearMonth, Model model) { + if (!StringUtils.hasText(yearMonth)) + yearMonth = Ut.date.getCurrentYearMonth(); - @GetMapping("/rebateOrderItemList") - @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") - public String showRebateOrderItemList(String yearMonth, Model model) { - if(!rq.getRollMember().getRole().getValue().equals("BRAND_ADMIN")){ - return rq.historyBack("브랜드 관리자만 접근할 수 있습니다."); - } - if (!StringUtils.hasText(yearMonth)) { - yearMonth = Ut.date.getCurrentYearMonth(); - } - List items = rebateService.findRebateOrderItemsByCreatedDateIn(rq.getBrandName(),yearMonth); + List items = rebateService.findRebateOrderItemByCurrentYearMonth(rq.getBrandName(), yearMonth); + MonthRebateData monthRebateData = rebateService.findMonthRebateData(rq.getBrandName(), yearMonth); + model.addAttribute("yearMonth", yearMonth); + model.addAttribute("items", items); + model.addAttribute("monthRebateData", monthRebateData); - model.addAttribute("yearMonth", yearMonth); - model.addAttribute("items", items); - return "trendpick/admin/rebateOrderItemList"; - } + return "trendpick/admin/rebateOrderItemList"; + } - @PostMapping("/rebateOne/{orderItemId}") - @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") - public String rebateOne(@PathVariable long orderItemId, HttpServletRequest req) { - RsData rebateRsData = rebateService.rebate(orderItemId); - if(rebateRsData.isFail()){ - return rq.historyBack("정산할 수 없는 상태입니다."); - } - String yearMonth = Ut.url.getQueryParamValue(req.getHeader("Referer"), "yearMonth", ""); - return rq.redirectWithMsg("/trendpick/admin/rebateOrderItemList?yearMonth=" + yearMonth, rebateRsData); + @PostMapping("/rebateOne/{orderItemId}") + @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") + public String rebateOne(@PathVariable long orderItemId, HttpServletRequest req) { + RsData result = rebateService.rebate(rq.getBrandName(), orderItemId); + if (result.isFail()) { + return rq.historyBack(result); } + String yearMonth = Ut.url.getQueryParamValue(req.getHeader("Referer"), "yearMonth", ""); + return rq.redirectWithMsg("/trendpick/admin/rebateOrderItemList?yearMonth=" + yearMonth, result); + } - @PostMapping("/rebate") - @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") - public String rebate(String ids, HttpServletRequest req) { - String[] idsArr = ids.split(","); - Arrays.stream(idsArr) - .mapToLong(Long::parseLong) - .forEach(rebateService::rebate); - String yearMonth = Ut.url.getQueryParamValue(req.getHeader("Referer"), "yearMonth", ""); - - String redirect = "redirect:/trendpick/admin/rebateOrderItemList?yearMonth=" + yearMonth; - redirect += "&msg=" + Ut.url.encode("%d건의 정산품목을 정산처리하였습니다.".formatted(idsArr.length)); - return redirect; - } + @PostMapping("/rebate") + @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") + public String rebate(@RequestParam String yearMonth) { //전체 정산처리 + RsData result = rebateService.rebate(rq.getBrandName(), yearMonth); + if(result.isFail()) + return rq.historyBack(result); + return rq.redirectWithMsg("/trendpick/admin/rebateOrderItemList?yearMonth=" + yearMonth, result); + } } diff --git a/src/main/java/project/trendpick_pro/domain/rebate/entity/MonthRebateData.java b/src/main/java/project/trendpick_pro/domain/rebate/entity/MonthRebateData.java new file mode 100644 index 00000000..4bad2031 --- /dev/null +++ b/src/main/java/project/trendpick_pro/domain/rebate/entity/MonthRebateData.java @@ -0,0 +1,82 @@ +package project.trendpick_pro.domain.rebate.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import project.trendpick_pro.domain.common.base.BaseTimeEntity; +import project.trendpick_pro.domain.store.entity.Store; +import project.trendpick_pro.global.util.Ut; + +import java.time.LocalDateTime; +import java.util.List; + +import static jakarta.persistence.FetchType.LAZY; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class MonthRebateData extends BaseTimeEntity { //월 정산데이터 + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "month_rebate_data_id") + private Long id; + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "store_id") + private Store store; + + @Column(name = "'year_month'") + private String yearMonth; //연월 + private LocalDateTime fromDate; + private LocalDateTime toDate; + @Column(name = "total_sales") + private long totalSales; //총 매출 + @Column(name = "net_profit") + private long netProfit; //순이익 : 총 매출 - 수수료 + @Column(name = "tax") + private long tax; //수수료 + @Column(name = "sale_count") + private int saleCount; //총판매개수 + @Column(name = "total_discount") + private int totalDiscount; //총할인금액 + @Column(name = "refund_date") + private LocalDateTime refundDate; //환급날짜 + @Column(name = "refund_amount") + private Long refundAmount; + + public MonthRebateData(Store store, String yearMonth){ + this.store = store; + int monthEndDay = Ut.date.getEndDayOf(yearMonth); + String fromDateStr = yearMonth + "-01 00:00:00.000000"; + String toDateStr = yearMonth + "-%02d 23:59:59.999999".formatted(monthEndDay); + this.yearMonth = yearMonth; + this.fromDate = Ut.date.parse(fromDateStr); //yyyy-MM-dd HH:mm:ss.SSSSSS 패턴으로 만들기 + this.toDate = Ut.date.parse(toDateStr); + this.totalSales = 0; + this.netProfit = 0; + this.tax = 0; + this.saleCount = 0; + this.totalDiscount = 0; + } + + public void rebate(List rebateOrderItems){ //총정산 + for (RebateOrderItem rebateOrderItem : rebateOrderItems) { + rebate(rebateOrderItem); + } + } + + public void rebate(RebateOrderItem rebateOrderItem){ //총정산 + this.totalSales += rebateOrderItem.getTotalPrice(); + this.netProfit += rebateOrderItem.calculateRebatePrice(); + this.saleCount += rebateOrderItem.getQuantity(); + this.tax += (int)(rebateOrderItem.getTotalPrice() * 0.05); + this.totalDiscount += rebateOrderItem.getDiscountPrice(); + } + + //캐시로 환급 + public void refundDone(){ + this.refundDate = LocalDateTime.now(); + this.refundAmount = getNetProfit(); + } +} diff --git a/src/main/java/project/trendpick_pro/domain/rebate/entity/RebateOrderItem.java b/src/main/java/project/trendpick_pro/domain/rebate/entity/RebateOrderItem.java index 082cf9db..36cef48b 100644 --- a/src/main/java/project/trendpick_pro/domain/rebate/entity/RebateOrderItem.java +++ b/src/main/java/project/trendpick_pro/domain/rebate/entity/RebateOrderItem.java @@ -43,31 +43,29 @@ public class RebateOrderItem extends BaseTimeEntity { @OneToOne(fetch = LAZY) @JoinColumn(name = "coupon_card_id") private CouponCard couponCard; - @Column(name = "order_price", nullable = false) - private int orderPrice; - @Column(name = "total_price") - private int totalPrice; - + private int totalPrice; //전체금액 (할인제외) + @Column(name = "order_price", nullable = false) + private int orderPrice; //주문금액 (할인 계산된 금액) @Column(name = "discount_price") - private int discountPrice; + private int discountPrice; //할인 받은 금액 + // 상품 + @Column(name = "product_subject", nullable = false) + private String productSubject; @Column(name = "size", nullable = false) private String size; @Column(name = "color", nullable = false) private String color; - @Column(name = "count", nullable = false) + @Column(name = "quantity", nullable = false) private int quantity; - - @ManyToOne(fetch = LAZY) + @OneToOne(fetch = LAZY) @ToString.Exclude @JoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) private CashLog rebateCashLog; // 정산에 관련된 환급지급내역 private LocalDateTime rebateDate; - // 상품 - private String productSubject; // 주문품목 private LocalDateTime orderItemCreateDate; @@ -88,66 +86,66 @@ public class RebateOrderItem extends BaseTimeEntity { public RebateOrderItem(OrderItem orderItem) { this.orderItem = orderItem; - order=orderItem.getOrder(); - product=orderItem.getProduct(); - couponCard=orderItem.getCouponCard(); - orderPrice=orderItem.getOrderPrice(); - totalPrice=orderItem.getOrderItemByQuantity(); - discountPrice=orderItem.getDiscountPrice(); - size=orderItem.getSize(); - color=orderItem.getColor(); - quantity=orderItem.getQuantity(); + this.order = orderItem.getOrder(); + this.product = orderItem.getProduct(); + this.couponCard = orderItem.getCouponCard(); + this.orderPrice = orderItem.getOrderPrice(); + this.totalPrice = orderItem.getTotalPrice(); + this.discountPrice = orderItem.getDiscountPrice(); + this.size = orderItem.getSize(); + this.color = orderItem.getColor(); + this.quantity = orderItem.getQuantity(); // 상품 추가 데이터 - productSubject=orderItem.getProduct().getTitle(); + this.productSubject = orderItem.getProduct().getTitle(); // 주문 품목 추가데이터 - orderItemCreateDate=orderItem.getOrder().getCreatedDate(); + this.orderItemCreateDate = orderItem.getOrder().getCreatedDate(); // 구매자 추가 데이터 - buyer=orderItem.getOrder().getMember(); - buyerName=orderItem.getOrder().getMember().getUsername(); + this.buyer = orderItem.getOrder().getMember(); + this.buyerName = orderItem.getOrder().getMember().getUsername(); // 판매자 추가 데이터 - seller=orderItem.getProduct().getProductOption().getBrand(); - sellerName=orderItem.getProduct().getProductOption().getBrand().getName(); + this.seller = orderItem.getProduct().getProductOption().getBrand(); + this.sellerName = orderItem.getProduct().getProductOption().getBrand().getName(); } public int calculateRebatePrice() { - return (totalPrice*quantity) - (int)(totalPrice * 0.05); // 정산금액 수수료 0.05% 제외하고 계산 + return totalPrice - (int) (totalPrice * 0.05); // 정산금액 수수료 5%(임의) 제외하고 계산 } - public boolean isRebateAvailable() { - if (rebateDate != null) { + public boolean isAlreadyRebated() { + if (rebateDate == null) return false; - } return true; } - public void setRebateDone(Long cashLogId) { + public void setRebateDone(CashLog cashLog) { rebateDate = LocalDateTime.now(); - this.rebateCashLog = new CashLog(cashLogId); + this.rebateCashLog = cashLog; } public boolean isRebateDone() { return rebateDate != null; } - public void updateWith(RebateOrderItem item){ - orderItem = item.getOrderItem(); - order=item.getOrder(); - product=item.getProduct(); - couponCard=item.getCouponCard(); - orderPrice=item.getOrderPrice(); - totalPrice=item.getTotalPrice(); - discountPrice=item.getDiscountPrice(); - size=item.getSize(); - color=item.getColor(); - quantity=item.getQuantity(); - productSubject=item.getProduct().getTitle(); - orderItemCreateDate=item.getOrder().getCreatedDate(); - buyer=item.getOrder().getMember(); - buyerName=item.getOrder().getMember().getUsername(); - seller=item.getProduct().getProductOption().getBrand(); - sellerName=item.getProduct().getProductOption().getBrand().getName(); + public RebateOrderItem updateWith(RebateOrderItem item) { + this.orderItem = item.getOrderItem(); + this.order = item.getOrder(); + this.product = item.getProduct(); + this.couponCard = item.getCouponCard(); + this.orderPrice = item.getOrderPrice(); + this.totalPrice = item.getTotalPrice(); + this.discountPrice = item.getDiscountPrice(); + this.size = item.getSize(); + this.color = item.getColor(); + this.quantity = item.getQuantity(); + this.productSubject = item.getProduct().getTitle(); + this.orderItemCreateDate = item.getOrder().getCreatedDate(); + this.buyer = item.getOrder().getMember(); + this.buyerName = item.getOrder().getMember().getUsername(); + this.seller = item.getProduct().getProductOption().getBrand(); + this.sellerName = item.getProduct().getProductOption().getBrand().getName(); + return this; } } diff --git a/src/main/java/project/trendpick_pro/domain/rebate/repository/MonthRebateDataRepository.java b/src/main/java/project/trendpick_pro/domain/rebate/repository/MonthRebateDataRepository.java new file mode 100644 index 00000000..499c2c32 --- /dev/null +++ b/src/main/java/project/trendpick_pro/domain/rebate/repository/MonthRebateDataRepository.java @@ -0,0 +1,16 @@ +package project.trendpick_pro.domain.rebate.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.security.core.parameters.P; +import project.trendpick_pro.domain.rebate.entity.MonthRebateData; + +import java.util.Optional; + +public interface MonthRebateDataRepository extends JpaRepository { + @Query("select d from MonthRebateData d where d.store.brand = :storeName and d.yearMonth = :yearMonth") + Optional findByStoreNameAndYearMonth( + @Param("storeName") String storeName, @Param("yearMonth") String yearMonth); + +} diff --git a/src/main/java/project/trendpick_pro/domain/rebate/scheduler/RebateScheduler.java b/src/main/java/project/trendpick_pro/domain/rebate/scheduler/RebateScheduler.java new file mode 100644 index 00000000..c43fda38 --- /dev/null +++ b/src/main/java/project/trendpick_pro/domain/rebate/scheduler/RebateScheduler.java @@ -0,0 +1,64 @@ +package project.trendpick_pro.domain.rebate.scheduler; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Scheduled; +import project.trendpick_pro.domain.rebate.entity.MonthRebateData; +import project.trendpick_pro.domain.rebate.repository.MonthRebateDataRepository; +import project.trendpick_pro.domain.rebate.service.RebateService; +import project.trendpick_pro.domain.store.entity.Store; +import project.trendpick_pro.domain.store.repository.StoreRepository; +import project.trendpick_pro.domain.store.service.StoreService; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Configuration +public class RebateScheduler { + private final RebateService rebateService; + private final MonthRebateDataRepository monthRebateDataRepository; + private final StoreRepository storeRepository; + private final StoreService storeService; + + @Scheduled(cron = "0 0 1 * * ?") //매월 1일 객체생성 + private void createMonthRebateObject(){ + String currentYearMonth = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM")); + createEmptyRebateDataForAllStore(currentYearMonth); + } + + @Scheduled(cron = "0 0 10 * * ?") //매월 10일 (배송확정 되는데까지 시간 고려) + private void rebateFinish(){ //전체정산 (auto) + String pastYearMonth = LocalDateTime.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM")); //지난 달 + rebateForAllStore(pastYearMonth); + } + + + private void rebateForAllStore(String yearMonth) { + List stores = storeRepository.findAll(); + for (Store store : stores) { + MonthRebateData monthRebateData = rebateProcess(yearMonth, store); + refund(monthRebateData); + } + } + + private void refund(MonthRebateData monthRebateData) { + storeService.addRebateCash(monthRebateData.getStore(), monthRebateData.getNetProfit()); //캐시로 반환 + monthRebateData.refundDone(); + } + + private MonthRebateData rebateProcess(String yearMonth, Store store) { + rebateService.makeRebateOrderItem(store.getBrand(), yearMonth); + rebateService.rebate(store.getBrand(), yearMonth); + return rebateService.findMonthRebateData(store.getBrand(), yearMonth); + } + + private void createEmptyRebateDataForAllStore(String currentYearMonth) { + List data = new ArrayList<>(); + List stores = storeRepository.findAll(); + for (Store store : stores) + data.add(new MonthRebateData(store, currentYearMonth)); + monthRebateDataRepository.saveAll(data); + } +} diff --git a/src/main/java/project/trendpick_pro/domain/rebate/service/RebateService.java b/src/main/java/project/trendpick_pro/domain/rebate/service/RebateService.java index c93b224f..32e740b9 100644 --- a/src/main/java/project/trendpick_pro/domain/rebate/service/RebateService.java +++ b/src/main/java/project/trendpick_pro/domain/rebate/service/RebateService.java @@ -1,18 +1,15 @@ package project.trendpick_pro.domain.rebate.service; -import project.trendpick_pro.domain.orders.entity.OrderItem; +import project.trendpick_pro.domain.rebate.entity.MonthRebateData; import project.trendpick_pro.domain.rebate.entity.RebateOrderItem; import project.trendpick_pro.global.util.rsData.RsData; import java.util.List; public interface RebateService { - RsData makeDate(String brandName,String yearMonth); - - void makeRebateOrderItem(RebateOrderItem item); - - RebateOrderItem toRebateOrderItem(OrderItem orderItem); - - List findRebateOrderItemsByCreatedDateIn(String brandName,String yearMonth); - RsData rebate(long orderItemId); + RsData makeRebateOrderItem(String brandName, String yearMonth); + List findRebateOrderItemByCurrentYearMonth(String brandName, String yearMonth); + RsData rebate(String storeName, Long orderItemId); + RsData rebate(String brandName, String currentYearMonth); + MonthRebateData findMonthRebateData(String brandName, String yearMonth); } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/rebate/service/impl/RebateServiceImpl.java b/src/main/java/project/trendpick_pro/domain/rebate/service/impl/RebateServiceImpl.java index 33b4d147..31b16351 100644 --- a/src/main/java/project/trendpick_pro/domain/rebate/service/impl/RebateServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/rebate/service/impl/RebateServiceImpl.java @@ -4,105 +4,145 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.trendpick_pro.domain.cash.entity.CashLog; -import project.trendpick_pro.domain.member.service.MemberService; +import project.trendpick_pro.domain.cash.service.CashService; import project.trendpick_pro.domain.orders.entity.OrderItem; import project.trendpick_pro.domain.orders.service.OrderService; +import project.trendpick_pro.domain.rebate.entity.MonthRebateData; import project.trendpick_pro.domain.rebate.entity.RebateOrderItem; +import project.trendpick_pro.domain.rebate.repository.MonthRebateDataRepository; import project.trendpick_pro.domain.rebate.repository.RebateOrderItemRepository; import project.trendpick_pro.domain.rebate.service.RebateService; +import project.trendpick_pro.domain.store.service.StoreService; import project.trendpick_pro.global.util.rsData.RsData; import project.trendpick_pro.global.util.Ut; - import java.time.LocalDateTime; +import java.time.Month; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class RebateServiceImpl implements RebateService { - private final OrderService orderService; - private final MemberService memberService; private final RebateOrderItemRepository rebateOrderItemRepository; + private final MonthRebateDataRepository monthRebateDataRepository; + private final OrderService orderService; + private final CashService cashService; + private final StoreService storeService; @Transactional - public RsData makeDate(String brandName, String yearMonth) { + @Override + public RsData makeRebateOrderItem(String brandName, String yearMonth) { String fromDateStr = yearMonth + "-01 00:00:00.000000"; String toDateStr = yearMonth + "-%02d 23:59:59.999999".formatted(Ut.date.getEndDayOf(yearMonth)); List orderItems = orderService.findAllByCreatedDateBetweenOrderByIdAsc( Ut.date.parse(fromDateStr), Ut.date.parse(toDateStr) ); - if(orderItems.isEmpty()){ + if(orderItems.isEmpty()) return RsData.of("F-1","정산할 주문내역이 없습니다."); - } - List brandOrderItems=new ArrayList<>(); - for(OrderItem item: orderItems) { - if (item.getProduct().getProductOption().getBrand().getName().equals(brandName)) { - brandOrderItems.add(item); - } - } - List rebateOrderItems = brandOrderItems - .stream() - .map(this::toRebateOrderItem) - .toList(); + List rebateOrderItems = convertToRebateOrderItem(brandName, orderItems); - rebateOrderItems.forEach(this::makeRebateOrderItem); - return RsData.of("S-1", "정산데이터가 성공적으로 생성되었습니다."); + rebateOrderItems.forEach(this::updateOrCreateRebateOrderItem); + return RsData.of("S-1", "정산 데이터가 성공적으로 생성되었습니다."); } + @Transactional - public void makeRebateOrderItem(RebateOrderItem item) { - RebateOrderItem oldRebateOrderItem = rebateOrderItemRepository.findByOrderItemId(item.getOrderItem().getId()).orElse(null); - if (oldRebateOrderItem != null) { - if (oldRebateOrderItem.isRebateDone()) { - return; - } - oldRebateOrderItem.updateWith(item); - rebateOrderItemRepository.save(oldRebateOrderItem); - } else { - rebateOrderItemRepository.save(item); + @Override + public RsData rebate(String storeName, Long orderItemId) { + RebateOrderItem rebateOrderItem = rebateOrderItemRepository.findByOrderItemId(orderItemId).orElse(null); + + RsData validateResult = validateAvailableRebate(rebateOrderItem); + if (validateResult.isFail()) return validateResult; + + //캐시로그 생성 + CashLog cashLog = cashService.addCashLog(rebateOrderItem); + + //월정산 + String yearMonth = rebateOrderItem.getOrderItemCreateDate().format(DateTimeFormatter.ofPattern("yyyy-MM")); + MonthRebateData monthRebateData = findMonthRebateData(storeName, yearMonth); + monthRebateData.rebate(rebateOrderItem); + + //정산완료 + rebateOrderItem.setRebateDone(cashLog); + return RsData.of( + "S-1", + "주문품목번호 %d번에 대해서 정산을 완료하였습니다.".formatted(rebateOrderItem.getOrderItem().getId()) + ); + } + + @Override + @Transactional + public RsData rebate(String storeName, String yearMonth) { + List rebateOrderItems = findRebateOrderItemByCurrentYearMonth(storeName, yearMonth); + int count = 0; + for (RebateOrderItem rebateOrderItem : rebateOrderItems){ + if(rebate(storeName, rebateOrderItem.getOrderItem().getId()).isSuccess()) //정산 성공 개수 세기 + count++; } + + if(count == 0) + return RsData.of("F-1", "정산 처리 가능한 데이터가 존재하지 않습니다."); + + return RsData.of("S-1", "%s건의 데이터를 정산처리 하였습니다.".formatted(count)); } + + @Override @Transactional - public RebateOrderItem toRebateOrderItem(OrderItem orderItem) { - return new RebateOrderItem(orderItem); + public MonthRebateData findMonthRebateData(String brandName, String yearMonth) { + MonthRebateData monthRebateData = monthRebateDataRepository.findByStoreNameAndYearMonth(brandName, yearMonth).orElse(null); + if(monthRebateData == null) + return monthRebateDataRepository.save(new MonthRebateData(storeService.findByBrand(brandName), yearMonth)); + return monthRebateData; } - @Transactional(readOnly = true) - public List findRebateOrderItemsByCreatedDateIn(String brandName,String yearMonth) { + @Override + public List findRebateOrderItemByCurrentYearMonth(String brandName, String yearMonth) { int monthEndDay = Ut.date.getEndDayOf(yearMonth); - String fromDateStr = yearMonth + "-01 00:00:00.000000"; String toDateStr = yearMonth + "-%02d 23:59:59.999999".formatted(monthEndDay); - LocalDateTime fromDate = Ut.date.parse(fromDateStr); + LocalDateTime fromDate = Ut.date.parse(fromDateStr); //yyyy-MM-dd HH:mm:ss.SSSSSS 패턴으로 만들기 LocalDateTime toDate = Ut.date.parse(toDateStr); - return rebateOrderItemRepository.findAllByCreatedDateBetweenAndSellerNameOrderByIdAsc(fromDate, toDate,brandName); + return rebateOrderItemRepository.findAllByCreatedDateBetweenAndSellerNameOrderByIdAsc(fromDate, toDate, brandName); } - @Transactional - public RsData rebate(long orderItemId) { - RebateOrderItem rebateOrderItem = rebateOrderItemRepository.findByOrderItemId(orderItemId).get(); - - if (!rebateOrderItem.isRebateAvailable()) { - return RsData.of("F-1", "정산을 할 수 없는 상태입니다."); - } - - int calculateRebatePrice = rebateOrderItem.calculateRebatePrice(); + private static RsData validateAvailableRebate(RebateOrderItem rebateOrderItem) { + if(rebateOrderItem == null) + return RsData.of("F-2", "존재하지 않는 정산 데이터입니다."); + if (rebateOrderItem.isAlreadyRebated()) + return RsData.of("F-1", "이미 정산된 데이터입니다."); + if(!rebateOrderItem.getOrder().isCompletedPurchaseDecision()) + return RsData.of("F-3", "구매결정이 완료되지 않은 데이터입니다."); + return RsData.success(); + } - CashLog cashLog = memberService.addCash( - rebateOrderItem.getSellerName(), - calculateRebatePrice, - rebateOrderItem.getSeller(), - CashLog.EvenType.브랜드정산__예치금 - ).getData().getCashLog(); + private Long updateOrCreateRebateOrderItem(RebateOrderItem item) { + RebateOrderItem oldRebateOrderItem = rebateOrderItemRepository.findByOrderItemId(item.getOrderItem().getId()).orElse(null); + if(oldRebateOrderItem == null) //없으면 생성 + return rebateOrderItemRepository.save(item).getId(); + if (oldRebateOrderItem.isRebateDone()) //이미 정산처리 되어있다면 return + return oldRebateOrderItem.getId(); - rebateOrderItem.setRebateDone(cashLog.getId()); - return RsData.of( - "S-1", - "주문품목번호 %d번에 대해서 정산을 완료하였습니다.".formatted(rebateOrderItem.getOrderItem().getId()) - ); + return rebateOrderItemRepository.save(oldRebateOrderItem.updateWith(item)).getId(); //둘 다 아니라면 업데이트 후 리턴 + } + private RebateOrderItem toRebateOrderItem(OrderItem orderItem) { + return new RebateOrderItem(orderItem); + } + private List convertToRebateOrderItem(String brandName, List orderItems) { + List filteredOrderItem = new ArrayList<>(); + for(OrderItem item: orderItems) { + if (item.getProduct().getProductOption().getBrand().getName().equals(brandName)) + filteredOrderItem.add(item); + } + + return filteredOrderItem + .stream() + .map(this::toRebateOrderItem) + .toList(); } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/domain/recommend/repository/JdbcRecommendRepository.java b/src/main/java/project/trendpick_pro/domain/recommend/repository/JdbcRecommendRepository.java index b90aec11..96b37ef1 100644 --- a/src/main/java/project/trendpick_pro/domain/recommend/repository/JdbcRecommendRepository.java +++ b/src/main/java/project/trendpick_pro/domain/recommend/repository/JdbcRecommendRepository.java @@ -34,5 +34,4 @@ public int getBatchSize() { } ); } - } diff --git a/src/main/java/project/trendpick_pro/domain/store/entity/Store.java b/src/main/java/project/trendpick_pro/domain/store/entity/Store.java index 625c881e..2ba16500 100644 --- a/src/main/java/project/trendpick_pro/domain/store/entity/Store.java +++ b/src/main/java/project/trendpick_pro/domain/store/entity/Store.java @@ -15,10 +15,24 @@ public class Store extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(unique = true) + @Column(name = "brand", unique = true) private String brand; + //정산 데이터 생성 -> 정산처리 -> 캐시 처리 + @Column(name = "rebate_cash") + private int rebatedCash; + public Store(String brand){ this.brand = brand; } + + public void addRebatedCash(long price) { + this.rebatedCash += price; + } + + public void withdrawRebatedCash(int price){ + if(this.rebatedCash <= price) + throw new IllegalArgumentException("출금 요청한 금액이 정산 금액보다 많습니다."); + this.rebatedCash -= price; + } } diff --git a/src/main/java/project/trendpick_pro/domain/store/service/StoreService.java b/src/main/java/project/trendpick_pro/domain/store/service/StoreService.java index f0593249..7d4ebc8d 100644 --- a/src/main/java/project/trendpick_pro/domain/store/service/StoreService.java +++ b/src/main/java/project/trendpick_pro/domain/store/service/StoreService.java @@ -2,7 +2,13 @@ import project.trendpick_pro.domain.store.entity.Store; +import java.util.List; + public interface StoreService { Store save(Store store); Store findByBrand(String storeName); + void addRebateCash(Store store, long price); + int getRestCash(String storeName); + + void saveAll(List brands); } diff --git a/src/main/java/project/trendpick_pro/domain/store/service/impl/StoreServiceImpl.java b/src/main/java/project/trendpick_pro/domain/store/service/impl/StoreServiceImpl.java index aab8df02..bdf0ce85 100644 --- a/src/main/java/project/trendpick_pro/domain/store/service/impl/StoreServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/store/service/impl/StoreServiceImpl.java @@ -6,23 +6,47 @@ import project.trendpick_pro.domain.store.entity.Store; import project.trendpick_pro.domain.store.repository.StoreRepository; import project.trendpick_pro.domain.store.service.StoreService; +import java.util.ArrayList; +import java.util.List; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class StoreServiceImpl implements StoreService { private final StoreRepository storeRepository; @Override @Transactional - public Store save(Store store){ + public Store save(Store store) { return storeRepository.save(store); } @Override - @Transactional(readOnly = true) public Store findByBrand(String storeName) { return storeRepository.findByBrand(storeName).orElseThrow( () -> new IllegalArgumentException("해당 스토어는 존재하지 않는 스토어입니다.") ); } + + @Override + @Transactional + public void addRebateCash(Store store, long price) { + store.addRebatedCash(price); + storeRepository.save(store); + } + + @Override + public int getRestCash(String storeName) { + return findByBrand(storeName).getRebatedCash(); + } + + @Override + @Transactional + public void saveAll(List names) { + List list = new ArrayList<>(); + for (String name : names) { + list.add(new Store(name)); + } + storeRepository.saveAll(list); + } } diff --git a/src/main/java/project/trendpick_pro/domain/withdraw/controller/AdmWithdrawController.java b/src/main/java/project/trendpick_pro/domain/withdraw/controller/AdmWithdrawController.java index c63aaebf..31971308 100644 --- a/src/main/java/project/trendpick_pro/domain/withdraw/controller/AdmWithdrawController.java +++ b/src/main/java/project/trendpick_pro/domain/withdraw/controller/AdmWithdrawController.java @@ -26,12 +26,13 @@ public class AdmWithdrawController { @PreAuthorize("hasAuthority({'ADMIN', 'BRAND_ADMIN'})") @GetMapping("/withDrawList") public String showApplyList(Model model) { - Member member=rq.getRollMember(); + Member member= rq.getAdmin(); List withdrawApplies; + if(member.getRole().getValue().equals("ADMIN")) { withdrawApplies = withdrawService.findAll(); }else{ - withdrawApplies=withdrawService.findByWithdrawApplyId(member.getId()); + withdrawApplies=withdrawService.findAllWithdrawByStoreName(member.getBrand()); } model.addAttribute("withdrawApplies", withdrawApplies); return "trendpick/admin/withDrawList"; diff --git a/src/main/java/project/trendpick_pro/domain/withdraw/controller/WithdrawController.java b/src/main/java/project/trendpick_pro/domain/withdraw/controller/WithdrawController.java index 73570a29..b08cb9b6 100644 --- a/src/main/java/project/trendpick_pro/domain/withdraw/controller/WithdrawController.java +++ b/src/main/java/project/trendpick_pro/domain/withdraw/controller/WithdrawController.java @@ -24,30 +24,21 @@ public class WithdrawController { private final WithdrawService withdrawService; - private final MemberService memberService; private final Rq rq; @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") @GetMapping("/withDraw") - public String showApply(Model model) { - Member member = rq.getRollMember(); - if(!member.getRole().getValue().equals("BRAND_ADMIN")){ - return rq.historyBack("브랜드 관리자만 접근할 수 있습니다."); - } - model.addAttribute("actorRestCash", memberService.getRestCash(member)); + public String showApplyForm(Model model) { + model.addAttribute("actorRestCash", withdrawService.showRestCash(rq.getBrandMember())); return "trendpick/admin/withDraw"; } @PreAuthorize("hasAuthority({'BRAND_ADMIN'})") @PostMapping("/withDraw") public String apply(@Valid WithDrawApplyForm withDrawApplyForm) { - RsData rsData = withdrawService.apply( - withDrawApplyForm.getBankName(), - withDrawApplyForm.getBankAccountNo(), - withDrawApplyForm.getPrice(), - rq.getBrandMember() - ); - - return rq.redirectWithMsg("/trendpick/admin/withDrawList","출금 신청이 완료되었습니다."); + RsData result = withdrawService.apply(withDrawApplyForm, rq.getBrandMember()); + if(result.isFail()) + rq.historyBack(result); + return rq.redirectWithMsg("/trendpick/admin/withDrawList", "출금 신청이 완료되었습니다."); } } diff --git a/src/main/java/project/trendpick_pro/domain/withdraw/entity/WithdrawApply.java b/src/main/java/project/trendpick_pro/domain/withdraw/entity/WithdrawApply.java index bb624e73..2019a97c 100644 --- a/src/main/java/project/trendpick_pro/domain/withdraw/entity/WithdrawApply.java +++ b/src/main/java/project/trendpick_pro/domain/withdraw/entity/WithdrawApply.java @@ -5,6 +5,7 @@ import lombok.experimental.SuperBuilder; import project.trendpick_pro.domain.cash.entity.CashLog; import project.trendpick_pro.domain.member.entity.Member; +import project.trendpick_pro.domain.withdraw.entity.dto.WithDrawApplyForm; import java.time.LocalDateTime; @@ -24,6 +25,7 @@ public class WithdrawApply { private Long id; @ManyToOne(fetch = LAZY) private Member applicant; + private String store; private String bankName; private String bankAccountNo; private int price; @@ -35,18 +37,19 @@ public class WithdrawApply { private LocalDateTime cancelDate; private String msg; - public boolean isApplyDoneAvailable() { + public boolean checkAlreadyProcessed() { //이미 처리되었는지 확인 if (withdrawDate != null || withdrawCashLog != null || cancelDate != null) { - return false; + return true; } - return true; + return false; } - public void setApplyDone(Long cashLogId, String msg) { + public void setApplyDone(CashLog cashLog, String msg) { withdrawDate = LocalDateTime.now(); - this.withdrawCashLog = new CashLog(cashLogId); + this.withdrawCashLog = cashLog; this.msg = msg; + } public void setCancelDone(String msg) { @@ -54,8 +57,14 @@ public void setCancelDone(String msg) { this.msg = msg; } - public boolean isCancelAvailable() { - return isApplyDoneAvailable(); + static public WithdrawApply of(WithDrawApplyForm withDrawApplyForm, Member applicant){ + return WithdrawApply.builder() + .bankName(withDrawApplyForm.getBankName()) + .bankAccountNo(withDrawApplyForm.getBankAccountNo()) + .price(withDrawApplyForm.getPrice()) + .applicant(applicant) + .store(applicant.getBrand()) + .build(); } public boolean isApplyDone() { diff --git a/src/main/java/project/trendpick_pro/domain/withdraw/entity/dto/WithDrawApplyForm.java b/src/main/java/project/trendpick_pro/domain/withdraw/entity/dto/WithDrawApplyForm.java index a4f7dfb8..2f14f43f 100644 --- a/src/main/java/project/trendpick_pro/domain/withdraw/entity/dto/WithDrawApplyForm.java +++ b/src/main/java/project/trendpick_pro/domain/withdraw/entity/dto/WithDrawApplyForm.java @@ -2,9 +2,13 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@AllArgsConstructor +@NoArgsConstructor public class WithDrawApplyForm { @NotBlank private String bankName; diff --git a/src/main/java/project/trendpick_pro/domain/withdraw/repository/WithdrawApplyRepository.java b/src/main/java/project/trendpick_pro/domain/withdraw/repository/WithdrawApplyRepository.java index b4d1f9bb..3ed7348f 100644 --- a/src/main/java/project/trendpick_pro/domain/withdraw/repository/WithdrawApplyRepository.java +++ b/src/main/java/project/trendpick_pro/domain/withdraw/repository/WithdrawApplyRepository.java @@ -11,4 +11,7 @@ public interface WithdrawApplyRepository extends JpaRepository { @Query("select w from WithdrawApply w where w.applicant.id = :id") List findAllByApplicantId(@Param("id") Long id); + + @Query("select w from WithdrawApply w where w.applicant.brand = :storeName") + List findWithdrawsByStoreName(@Param("storeName") String storeName); } diff --git a/src/main/java/project/trendpick_pro/domain/withdraw/service/WithdrawService.java b/src/main/java/project/trendpick_pro/domain/withdraw/service/WithdrawService.java index 60fc2353..60a227a4 100644 --- a/src/main/java/project/trendpick_pro/domain/withdraw/service/WithdrawService.java +++ b/src/main/java/project/trendpick_pro/domain/withdraw/service/WithdrawService.java @@ -2,14 +2,16 @@ import project.trendpick_pro.domain.member.entity.Member; import project.trendpick_pro.domain.withdraw.entity.WithdrawApply; +import project.trendpick_pro.domain.withdraw.entity.dto.WithDrawApplyForm; import project.trendpick_pro.global.util.rsData.RsData; import java.util.List; public interface WithdrawService { - RsData apply(String bankName, String bankAccountNo, Integer price, Member applicant); + RsData apply(WithDrawApplyForm withDrawApplyForm, Member applicant); List findAll(); - List findByWithdrawApplyId(Long id); + List findAllWithdrawByStoreName(String storeName); RsData withdraw(Long withdrawApplyId); RsData cancelApply(Long withdrawApplyId); + int showRestCash(Member brandMember); } diff --git a/src/main/java/project/trendpick_pro/domain/withdraw/service/impl/WithdrawServiceImpl.java b/src/main/java/project/trendpick_pro/domain/withdraw/service/impl/WithdrawServiceImpl.java index 30d5349c..c457ec63 100644 --- a/src/main/java/project/trendpick_pro/domain/withdraw/service/impl/WithdrawServiceImpl.java +++ b/src/main/java/project/trendpick_pro/domain/withdraw/service/impl/WithdrawServiceImpl.java @@ -3,12 +3,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import project.trendpick_pro.domain.brand.entity.Brand; -import project.trendpick_pro.domain.brand.service.BrandService; import project.trendpick_pro.domain.cash.entity.CashLog; +import project.trendpick_pro.domain.cash.service.CashService; import project.trendpick_pro.domain.member.entity.Member; import project.trendpick_pro.domain.member.service.MemberService; +import project.trendpick_pro.domain.store.service.StoreService; import project.trendpick_pro.domain.withdraw.entity.WithdrawApply; +import project.trendpick_pro.domain.withdraw.entity.dto.WithDrawApplyForm; import project.trendpick_pro.domain.withdraw.repository.WithdrawApplyRepository; import project.trendpick_pro.domain.withdraw.service.WithdrawService; import project.trendpick_pro.global.util.rsData.RsData; @@ -18,84 +19,58 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class WithdrawServiceImpl implements WithdrawService { private final WithdrawApplyRepository withdrawApplyRepository; - + private final StoreService storeService; private final MemberService memberService; - private final BrandService brandService; + private final CashService cashService; @Transactional @Override - public RsData apply(String bankName, String bankAccountNo, Integer price, Member applicant) { - WithdrawApply withdrawApply = WithdrawApply.builder() - .bankName(bankName) - .bankAccountNo(bankAccountNo) - .price(price) - .applicant(applicant) - .build(); - withdrawApplyRepository.save(withdrawApply); + public RsData apply(WithDrawApplyForm withDrawApplyForm, Member applicant) { + RsData validateResult = validateAvailableApply(withDrawApplyForm, applicant); + if(validateResult.isFail()) + return validateResult; + WithdrawApply withdrawApply = withdrawApplyRepository.save(WithdrawApply.of(withDrawApplyForm, applicant)); return RsData.of("S-1", "출금 신청이 완료되었습니다.", withdrawApply); } - @Transactional(readOnly = true) @Override public List findAll() { return withdrawApplyRepository.findAll(); } - @Transactional(readOnly = true) @Override - public List findByWithdrawApplyId(Long id){ - return withdrawApplyRepository.findAllByApplicantId(id); + public List findAllWithdrawByStoreName(String storeName){ + return withdrawApplyRepository.findWithdrawsByStoreName(storeName); } @Transactional public RsData withdraw(Long withdrawApplyId) { WithdrawApply withdrawApply = withdrawApplyRepository.findById(withdrawApplyId).orElse(null); - if (withdrawApply == null) { - return RsData.of("F-1", "출금신청 데이터를 찾을 수 없습니다."); - } - long restCash = memberService.getRestCash(withdrawApply.getApplicant()); + RsData validateResult = validateAvailableWithdraw(withdrawApply); //캐시 출금 검증 + if (validateResult.isFail()) return validateResult; - if (!withdrawApply.isApplyDoneAvailable()) { - return RsData.of("F-2", "이미 처리되었습니다."); - } - if (withdrawApply.getPrice() > restCash) { - return RsData.of("F-3", "예치금이 부족합니다."); - } - Brand brand=brandService.findByName(withdrawApply.getApplicant().getBrand()); - - CashLog cashLog = memberService.addCash( - withdrawApply.getApplicant().getBrand(), - withdrawApply.getPrice() * -1, - brand, - CashLog.EvenType.출금__통장입금 - ) - .getData().getCashLog(); - - withdrawApply.setApplyDone(cashLog.getId(), "관리자에 의해서 처리되었습니다."); + CashLog cashLog = cashService.addCashLog(withdrawApply); + withdrawApply.setApplyDone(cashLog, "관리자에 의해서 처리되었습니다."); + memberService.completeWithdraw(withdrawApply); //출금 신청한거 완료됐어요. return RsData.of( "S-1", - "%d번 출금신청이 처리되었습니다. %s원이 출금되었습니다.".formatted(withdrawApply.getId(), Ut.nf(withdrawApply.getPrice())), - Ut.mapOf( - "cashLogId", cashLog.getId() - ) + "출금신청이 처리되었습니다. %s원이 출금되었습니다.".formatted(Ut.nf(withdrawApply.getPrice())) ); } + @Transactional public RsData cancelApply(Long withdrawApplyId) { WithdrawApply withdrawApply = withdrawApplyRepository.findById(withdrawApplyId).orElse(null); - if (withdrawApply == null) { - return RsData.of("F-1", "출금신청 데이터를 찾을 수 없습니다."); - } - if (!withdrawApply.isCancelAvailable()) { - return RsData.of("F-2", "취소가 불가능합니다."); - } + RsData validateResult = validateAvailableCancel(withdrawApply); //취소 가능한지 검증 + if (validateResult.isFail()) return validateResult; withdrawApply.setCancelDone("관리자에 의해서 취소되었습니다."); @@ -105,4 +80,34 @@ public RsData cancelApply(Long withdrawApplyId) { null ); } + + private RsData validateAvailableCancel(WithdrawApply withdrawApply) { + if (withdrawApply == null) + return RsData.of("F-1", "출금신청 데이터를 찾을 수 없습니다."); + + if (!withdrawApply.checkAlreadyProcessed()) + return RsData.of("F-2", "이미 처리된 출금 신청 입니다."); + + return RsData.success(); + } + private RsData validateAvailableWithdraw(WithdrawApply withdrawApply) { + if (withdrawApply == null) + return RsData.of("F-1", "출금신청 데이터를 찾을 수 없습니다."); + if (withdrawApply.checkAlreadyProcessed()) + return RsData.of("F-2", "이미 처리되었습니다."); + if (withdrawApply.getPrice() > storeService.getRestCash(withdrawApply.getApplicant().getBrand())) + return RsData.of("F-3", "캐시보다 더 많은 금액을 출금할 수 없습니다."); + return RsData.success(); + } + + private RsData validateAvailableApply(WithDrawApplyForm withDrawApplyForm, Member applicant) { + if(withDrawApplyForm.getPrice() > storeService.getRestCash(applicant.getBrand())) + return RsData.of("F-1", "출금 요청 금액은 잔여 캐시보다 많을 수 없습니다."); + return RsData.success(); + } + + @Override + public int showRestCash(Member brandMember) { + return storeService.getRestCash(brandMember.getBrand()); + } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/global/basedata/BaseData.java b/src/main/java/project/trendpick_pro/global/basedata/BaseData.java index 2f2630b4..93ea5dbd 100644 --- a/src/main/java/project/trendpick_pro/global/basedata/BaseData.java +++ b/src/main/java/project/trendpick_pro/global/basedata/BaseData.java @@ -23,7 +23,7 @@ import project.trendpick_pro.domain.category.service.SubCategoryService; import project.trendpick_pro.domain.common.file.CommonFile; import project.trendpick_pro.domain.coupon.entity.expirationPeriod.ExpirationType; -import project.trendpick_pro.global.kafka.view.service.ViewService; +import project.trendpick_pro.domain.store.service.StoreService; import project.trendpick_pro.domain.coupon.entity.Coupon; import project.trendpick_pro.domain.coupon.entity.dto.request.StoreCouponSaveRequest; import project.trendpick_pro.domain.coupon.repository.CouponRepository; @@ -97,7 +97,7 @@ CommandLineRunner initData( RecommendService recommendService, BrandService brandService, ProductService productService, - ViewService viewService, + StoreService storeService, ProductRepository productRepository, ReviewRepository reviewRepository, CouponRepository couponRepository, @@ -109,6 +109,8 @@ CommandLineRunner initData( public void run(String... args) { tagNameServiceImpl.saveAll(tags); + brandService.saveAll(brands); + storeService.saveAll(brands); memberService.joinAll(makeBrandMembers(brands)); mainCategoryService.saveAll(mainCategories); @@ -119,10 +121,10 @@ public void run(String... args) { accessKeyMap.put("secretKey", secretKey); accessKeyMap.put("bucket", bucket); - int memberCount = 100; - int productCount = 2000; - int reviewCount = 100; - int couponCount = 100; + int memberCount = 10; + int productCount = 200; + int reviewCount = 10; + int couponCount = 10; String brandName = "polo"; saveMembers(memberCount, tagNameServiceImpl, memberService); @@ -134,10 +136,6 @@ public void run(String... args) { saveReviews(reviewCount, productCount, accessKeyMap, memberService, productService ,reviewRepository); saveStoreCoupon(couponCount, storeRepository, couponRepository, brandService); - if (viewService.findSize() == 0) { - viewService.registerView(); - } - log.info("BASE_DATA_SUCCESS"); } }; diff --git a/src/main/java/project/trendpick_pro/global/config/KafkaConfig.java b/src/main/java/project/trendpick_pro/global/config/KafkaConfig.java index ca415336..af27a2f5 100644 --- a/src/main/java/project/trendpick_pro/global/config/KafkaConfig.java +++ b/src/main/java/project/trendpick_pro/global/config/KafkaConfig.java @@ -34,7 +34,7 @@ public class KafkaConfig { @Bean public KafkaAdmin admin() { Map configs = new HashMap<>(); - configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaHost+kafkaPort); + configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaHost+":"+kafkaPort); return new KafkaAdmin(configs); } diff --git a/src/main/java/project/trendpick_pro/global/config/RedissonConfig.java b/src/main/java/project/trendpick_pro/global/config/RedissonConfig.java index 19dc9aee..75f073d0 100644 --- a/src/main/java/project/trendpick_pro/global/config/RedissonConfig.java +++ b/src/main/java/project/trendpick_pro/global/config/RedissonConfig.java @@ -12,7 +12,7 @@ @Configuration public class RedissonConfig { - @Value("${custom.redis.host}") + @Value("${redis.host}") private String redisHost; @Value("${redis.port}") diff --git a/src/main/java/project/trendpick_pro/global/jmeter/JmeterController.java b/src/main/java/project/trendpick_pro/global/jmeter/JmeterController.java index 28779068..1eb7256a 100644 --- a/src/main/java/project/trendpick_pro/global/jmeter/JmeterController.java +++ b/src/main/java/project/trendpick_pro/global/jmeter/JmeterController.java @@ -6,7 +6,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import project.trendpick_pro.domain.member.entity.Member; @@ -20,7 +19,6 @@ import project.trendpick_pro.domain.tags.tag.entity.Tag; import project.trendpick_pro.global.kafka.KafkaProducerService; import project.trendpick_pro.global.util.rq.Rq; -import project.trendpick_pro.global.util.rsData.RsData; import java.io.IOException; import java.util.List; @@ -48,21 +46,6 @@ public ResponseEntity getMemberInfo() { return new ResponseEntity<>(memberInfoResponse, headers, HttpStatus.OK); } - @PreAuthorize("hasAuthority({'MEMBER'})") - @GetMapping("/order") - @ResponseBody - public void processOrder() { - Member member = rq.getMember(); - - Long id = 3L; - int quantity = 1; - String size = "80"; - String color = "Sliver"; - - RsData data = orderService.productToOrder(member, id, quantity, size, color); - kafkaProducerService.sendMessage(data.getData()); - } - @PostMapping("/edit") public void modifyProduct(@RequestParam("productId") Long productId, @RequestParam("mainFile") MultipartFile mainFile, diff --git a/src/main/java/project/trendpick_pro/global/job/JobConfig.java b/src/main/java/project/trendpick_pro/global/job/JobConfig.java index dc214ea2..f2d1b401 100644 --- a/src/main/java/project/trendpick_pro/global/job/JobConfig.java +++ b/src/main/java/project/trendpick_pro/global/job/JobConfig.java @@ -37,14 +37,14 @@ public class JobConfig { private final Job makeRebateDataJob; + //매일 03시에 전날에 접속 이력이 있는 회원을 대상으로 추천 상품을 갱신해준다. @Scheduled(cron = "0 0 3 * * *") public void performMakeRecommendProductJob() throws Exception { LocalDateTime fromDate = LocalDateTime.now().minusDays(1).with(LocalTime.MIN); //전날 00:00 LocalDateTime toDate = LocalDateTime.now().minusDays(1).with(LocalTime.MAX); //전날 11:59 - - fromDate = LocalDateTime.now().minusDays(2); //테스트용 - toDate = LocalDateTime.now().plusDays(2); //테스트용 +// fromDate = LocalDateTime.now().minusDays(2); //테스트용 +// toDate = LocalDateTime.now().plusDays(2); //테스트용 JobParameters param = new JobParametersBuilder() .addLocalDateTime("fromDate", fromDate) @@ -52,25 +52,25 @@ public void performMakeRecommendProductJob() throws Exception { .toJobParameters(); JobExecution execution = jobLauncher.run(makeRecommendProductJob, param); - log.debug(execution.getStatus().toString()); + log.info(execution.getStatus().toString()); } - - @Scheduled(cron = "0 0 1 * * *") + // 매일 02시에 전날 생성된 주문 객체중 결제 처리가 안된 객체를 일괄 삭제 처리한다. + @Scheduled(cron = "0 0 2 * * *") public void performCancelOrderJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { -// LocalDateTime real = LocalDateTime.now().minusDays(1).with(LocalTime.MAX); //전날 11:59 - LocalDateTime fake = LocalDateTime.now().plusDays(1); //테스트용 + LocalDateTime date = LocalDateTime.now().minusDays(1).with(LocalTime.MAX); //전날 11:59 +// LocalDateTime fake = LocalDateTime.now().plusDays(1); //테스트용 JobParameters param = new JobParametersBuilder() - .addLocalDateTime("date", fake) //전날 11:59 + .addLocalDateTime("date", date) //전날 11:59 .toJobParameters(); JobExecution execution = jobLauncher.run(cancelOrderJob, param); - System.out.println("결과 : " + execution.getStatus()); + log.info("job result : " + execution.getStatus()); } + // 매일 04시에 일일정산을 통해 월 정산을 갱신해준다. @Scheduled(cron = "0 0 4 * * *") // 실제 코드 - // @Scheduled(cron = "30 * * * * *") // 개발용 public void performMakeRebateDataJob() throws Exception { String yearMonth = getPerformMakeRebateDataJobParam1Value(); // 실제 코드 // String yearMonth = "2023-07"; // 개발용 @@ -80,7 +80,7 @@ public void performMakeRebateDataJob() throws Exception { .toJobParameters(); JobExecution execution = jobLauncher.run(makeRebateDataJob, param); - System.out.println(execution.getStatus()); + log.info(String.valueOf(execution.getStatus())); } public String getPerformMakeRebateDataJobParam1Value() { diff --git a/src/main/java/project/trendpick_pro/global/job/JobTestController.java b/src/main/java/project/trendpick_pro/global/job/JobTestController.java deleted file mode 100644 index 9f446c69..00000000 --- a/src/main/java/project/trendpick_pro/global/job/JobTestController.java +++ /dev/null @@ -1,47 +0,0 @@ -package project.trendpick_pro.global.job; - -import lombok.RequiredArgsConstructor; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; -import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; -import org.springframework.batch.core.repository.JobRestartException; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -@RequiredArgsConstructor -@RequestMapping("/job") -public class JobTestController { - - private final JobConfig jobConfig; - - @GetMapping("/test") - @ResponseBody - public String test(){ - try { - jobConfig.performMakeRecommendProductJob(); - } catch (Exception e) { - throw new RuntimeException("추천 잡실행 실패"); - } - return "추천 잡실행 성공"; - } - - @GetMapping("/order") - @ResponseBody - public String cancelOrderJob(){ - try { - jobConfig.performCancelOrderJob(); - } catch (JobInstanceAlreadyCompleteException e) { - throw new RuntimeException("주문삭제 잡실패"); - } catch (JobExecutionAlreadyRunningException e) { - throw new RuntimeException("주문삭제 잡실패"); - } catch (JobParametersInvalidException e) { - throw new RuntimeException("주문삭제 잡실패"); - } catch (JobRestartException e) { - throw new RuntimeException("주문삭제 잡실패"); - } - return "주문삭제 잡성공"; - } -} diff --git a/src/main/java/project/trendpick_pro/global/job/cancelOrderJobConfig/CancelOrderJobConfig.java b/src/main/java/project/trendpick_pro/global/job/cancelOrderJobConfig/CancelOrderJobConfig.java index 21b834b8..db3d0df6 100644 --- a/src/main/java/project/trendpick_pro/global/job/cancelOrderJobConfig/CancelOrderJobConfig.java +++ b/src/main/java/project/trendpick_pro/global/job/cancelOrderJobConfig/CancelOrderJobConfig.java @@ -71,9 +71,9 @@ public RepositoryItemReader orderReader( return new RepositoryItemReaderBuilder() .name("orderReader") .repository(orderRepository) - .methodName("findAllByStatusAndCreatedDateIsBefore") + .methodName("findAllByPaymentKeyIsNullAndCreatedDateIsBefore") .pageSize(100) - .arguments(Arrays.asList(OrderStatus.TEMP, date)) + .arguments(Arrays.asList(date)) .sorts(Collections.singletonMap("id", Sort.Direction.ASC)) .build(); } diff --git a/src/main/java/project/trendpick_pro/global/job/makeRebateData/MakeRebateDataJobConfig.java b/src/main/java/project/trendpick_pro/global/job/makeRebateData/MakeRebateDataJobConfig.java index 6787dc90..4cae3964 100644 --- a/src/main/java/project/trendpick_pro/global/job/makeRebateData/MakeRebateDataJobConfig.java +++ b/src/main/java/project/trendpick_pro/global/job/makeRebateData/MakeRebateDataJobConfig.java @@ -32,7 +32,7 @@ @Configuration @RequiredArgsConstructor @Slf4j -public class MakeRebateDataJobConfig { +public class MakeRebateDataJobConfig { //모든 주문 품목에 대해서 복사해 놓는 job private final OrderItemRepository orderItemRepository; private final RebateOrderItemRepository rebateOrderItemRepository; private final JobRepository jobRepository; diff --git a/src/main/java/project/trendpick_pro/global/kafka/KafkaConsumerService.java b/src/main/java/project/trendpick_pro/global/kafka/KafkaConsumerService.java index 12f9b3ec..14134d0d 100644 --- a/src/main/java/project/trendpick_pro/global/kafka/KafkaConsumerService.java +++ b/src/main/java/project/trendpick_pro/global/kafka/KafkaConsumerService.java @@ -10,8 +10,8 @@ import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; +import project.trendpick_pro.domain.orders.service.OrderService; import project.trendpick_pro.global.redis.redisson.service.RedissonService; -import project.trendpick_pro.global.kafka.view.service.ViewService; import project.trendpick_pro.domain.orders.entity.dto.request.OrderStateResponse; @Slf4j @@ -20,19 +20,18 @@ public class KafkaConsumerService { private final RedissonService redissonService; - private final ViewService viewService; - private final SimpMessagingTemplate messagingTemplate; private final ObjectMapper objectMapper; - + private final OrderService orderService; @KafkaListener(topics = "orders", groupId = "order") public void orderToOrder(ConsumerRecord message, Acknowledgment ack) { try { - redissonService.processOrderWithLock(message.key()); - ack.acknowledge(); + redissonService.processOrderWithLock(message.key(), message.value()); } catch (Exception e) { - log.error(e.getMessage()); + log.error("orderToOrder process error : {} ", e.getMessage()); + } finally { + ack.acknowledge(); } } @@ -41,9 +40,4 @@ public void message(@Payload String json) throws JsonProcessingException { OrderStateResponse response = objectMapper.readValue(json, OrderStateResponse.class); messagingTemplate.convertAndSendToUser(response.getEmail(), "/topic/standByOrder", json); } - - @KafkaListener(topicPattern = "views", groupId = "view") - public void handleIncrementViewCount(@Payload String viewId) { - viewService.incrementViewCount(viewId); - } } diff --git a/src/main/java/project/trendpick_pro/global/kafka/KafkaProducerService.java b/src/main/java/project/trendpick_pro/global/kafka/KafkaProducerService.java index b3708125..fcae5ba1 100644 --- a/src/main/java/project/trendpick_pro/global/kafka/KafkaProducerService.java +++ b/src/main/java/project/trendpick_pro/global/kafka/KafkaProducerService.java @@ -6,13 +6,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.retry.support.RetryTemplate; -import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.trendpick_pro.domain.orders.entity.dto.request.OrderStateResponse; -import project.trendpick_pro.global.kafka.kafkasave.entity.OutboxMessage; -import project.trendpick_pro.global.kafka.kafkasave.service.OutboxMessageService; +import project.trendpick_pro.global.kafka.outbox.entity.OutboxMessage; +import project.trendpick_pro.global.kafka.outbox.service.OutboxMessageService; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Slf4j @@ -28,12 +30,47 @@ public class KafkaProducerService { private final ObjectMapper objectMapper; @Transactional - public void sendMessage(Long id) { - OutboxMessage outboxMessage = outboxMessageService.findById(id); + @Scheduled(fixedRate = 100) + public void sendMessage() throws JsonProcessingException { + List outboxMessages = outboxMessageService.findAll(); + + List completedMessages = new ArrayList<>(); + for (OutboxMessage outboxMessage : outboxMessages) { + if(outboxMessage.getTopic().equals("standByOrder")) //주문 처리 성공 메시지 + sendOrderProcessSuccessMessage(completedMessages, outboxMessage); + + else if(outboxMessage.getTopic().equals("orders")) + sendCompleteOrderCreationMessage(completedMessages, outboxMessage); //주문 객체 생성 완료 메시지 + } + + if(!completedMessages.isEmpty()) + outboxMessageService.deleteAllById(completedMessages); + } + + private void sendCompleteOrderCreationMessage(List completedMessages, OutboxMessage outboxMessage) { retryTemplate.execute(context -> { - kafkaTemplate.send(outboxMessage.getTopic(), outboxMessage.getPayload(), outboxMessage.getMessage()); - log.info("Message sent: {}", outboxMessage.getMessage()); - outboxMessageService.delete(outboxMessage); + kafkaTemplate.send(outboxMessage.getTopic(), outboxMessage.getPayload(), outboxMessage.getCode()); + outboxMessage.disconnectOrderMaterial(); + completedMessages.add(outboxMessage.getId()); + log.info("Message sent: {}", outboxMessage.getMessage()); + return null; + }, context -> { + log.error("Failed to send message: {}", context.getLastThrowable().getMessage()); + return null; + }); + } + + @Transactional + public void sendOrderProcessFailMessage(Long orderId, String message, String email) throws JsonProcessingException { + OrderStateResponse response = OrderStateResponse.builder() + .orderId(orderId) + .message(message) + .email(email) + .build(); + String json = objectMapper.writeValueAsString(response); + retryTemplate.execute(context -> { + kafkaTemplate.send("standByOrder", UUID.randomUUID().toString(), json); + log.info("Message sent: {}", message); return null; }, context -> { log.error("Failed to send message: {}", context.getLastThrowable().getMessage()); @@ -42,20 +79,23 @@ public void sendMessage(Long id) { } @Transactional - public void sendMessage(Long orderId, String message, String email) throws JsonProcessingException { + public void sendOrderProcessSuccessMessage(List completedMessages, OutboxMessage outboxMessage) throws JsonProcessingException { OrderStateResponse response = OrderStateResponse.builder() - .orderId(orderId) - .message(message) - .email(email) + .orderId(Long.valueOf(outboxMessage.getPayload())) + .message(outboxMessage.getMessage()) + .email( outboxMessage.getEmail()) .build(); String json = objectMapper.writeValueAsString(response); retryTemplate.execute(context -> { kafkaTemplate.send("standByOrder", UUID.randomUUID().toString(), json); - log.info("Message sent: {}", message); + completedMessages.add(outboxMessage.getId()); + log.info("Message sent: {}", outboxMessage.getMessage()); return null; }, context -> { log.error("Failed to send message: {}", context.getLastThrowable().getMessage()); return null; }); } + + } diff --git a/src/main/java/project/trendpick_pro/global/kafka/kafkasave/entity/OutboxMessage.java b/src/main/java/project/trendpick_pro/global/kafka/kafkasave/entity/OutboxMessage.java deleted file mode 100644 index 448da8fa..00000000 --- a/src/main/java/project/trendpick_pro/global/kafka/kafkasave/entity/OutboxMessage.java +++ /dev/null @@ -1,29 +0,0 @@ -package project.trendpick_pro.global.kafka.kafkasave.entity; - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class OutboxMessage { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "topic", nullable = false) - private String topic; - - @Column(name = "message", nullable = false) - private String message; - - @Column(name = "payload", nullable = false) - private String payload; - - public OutboxMessage(String topic, String message, String payload) { - this.topic = topic; - this.message = message; - this.payload = payload; - } -} diff --git a/src/main/java/project/trendpick_pro/global/kafka/kafkasave/repository/OutboxMessageRepository.java b/src/main/java/project/trendpick_pro/global/kafka/kafkasave/repository/OutboxMessageRepository.java deleted file mode 100644 index 5b97efb9..00000000 --- a/src/main/java/project/trendpick_pro/global/kafka/kafkasave/repository/OutboxMessageRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package project.trendpick_pro.global.kafka.kafkasave.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import project.trendpick_pro.global.kafka.kafkasave.entity.OutboxMessage; - -public interface OutboxMessageRepository extends JpaRepository { -} diff --git a/src/main/java/project/trendpick_pro/global/kafka/kafkasave/service/OutboxMessageService.java b/src/main/java/project/trendpick_pro/global/kafka/kafkasave/service/OutboxMessageService.java deleted file mode 100644 index 89bb9b1e..00000000 --- a/src/main/java/project/trendpick_pro/global/kafka/kafkasave/service/OutboxMessageService.java +++ /dev/null @@ -1,31 +0,0 @@ -package project.trendpick_pro.global.kafka.kafkasave.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import project.trendpick_pro.global.kafka.kafkasave.entity.OutboxMessage; -import project.trendpick_pro.global.kafka.kafkasave.repository.OutboxMessageRepository; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class OutboxMessageService { - - private final OutboxMessageRepository outboxMessageRepository; - - @Transactional - public void save(OutboxMessage outboxMessage) { - outboxMessageRepository.save(outboxMessage); - } - - @Transactional - public void delete(OutboxMessage outboxMessage) { - outboxMessageRepository.delete(outboxMessage); - } - - @Transactional(readOnly = true) - public OutboxMessage findById(Long id) { - return outboxMessageRepository.findById(id).get(); - } -} diff --git a/src/main/java/project/trendpick_pro/global/kafka/outbox/entity/OrderMaterial.java b/src/main/java/project/trendpick_pro/global/kafka/outbox/entity/OrderMaterial.java new file mode 100644 index 00000000..a20e7bc7 --- /dev/null +++ b/src/main/java/project/trendpick_pro/global/kafka/outbox/entity/OrderMaterial.java @@ -0,0 +1,37 @@ +package project.trendpick_pro.global.kafka.outbox.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import project.trendpick_pro.domain.cart.entity.CartItem; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Entity +public class OrderMaterial { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private Long productId; + private Integer quantity; + private String size; + private String color; + private String code; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "outbox_message_id") + private OutboxMessage outboxMessage; + + public OrderMaterial(Long productId, Integer quantity, String size, String color, String code) { + this.productId = productId; + this.quantity = quantity; + this.size = size; + this.color = color; + this.code = code; + } + + public OrderMaterial(CartItem cartItem, String code){ + new OrderMaterial(cartItem.getProduct().getId(), cartItem.getQuantity(), cartItem.getSize(), cartItem.getColor(), code); + } +} diff --git a/src/main/java/project/trendpick_pro/global/kafka/outbox/entity/OutboxMessage.java b/src/main/java/project/trendpick_pro/global/kafka/outbox/entity/OutboxMessage.java new file mode 100644 index 00000000..c739f7b1 --- /dev/null +++ b/src/main/java/project/trendpick_pro/global/kafka/outbox/entity/OutboxMessage.java @@ -0,0 +1,55 @@ +package project.trendpick_pro.global.kafka.outbox.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import project.trendpick_pro.domain.orders.entity.dto.response.OrderItemDto; +import project.trendpick_pro.domain.product.entity.product.Product; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class OutboxMessage { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "topic", nullable = false) + private String topic; + + @Column(name = "message", nullable = false) + private String message; + + @Column(name = "payload", nullable = false) + private String payload; + + private String code; + + private String email; + + @OneToMany(mappedBy = "outboxMessage", cascade = CascadeType.PERSIST) + private List orderMaterials = new ArrayList<>(); + + public void disconnectOrderMaterial(){ + this.orderMaterials = null; + } + + public OutboxMessage (String topic, String message, String payload, + List orderMaterial, String code) { + this.topic = topic; + this.message = message; + this.payload = payload; + this.orderMaterials = orderMaterial; + this.code = code; + } + + public OutboxMessage (String topic, String message, String payload, String email) { + this.topic = topic; + this.message = message; + this.payload = payload; + this.email = email; + } +} diff --git a/src/main/java/project/trendpick_pro/global/kafka/outbox/repository/OutboxMessageRepository.java b/src/main/java/project/trendpick_pro/global/kafka/outbox/repository/OutboxMessageRepository.java new file mode 100644 index 00000000..14bb6975 --- /dev/null +++ b/src/main/java/project/trendpick_pro/global/kafka/outbox/repository/OutboxMessageRepository.java @@ -0,0 +1,16 @@ +package project.trendpick_pro.global.kafka.outbox.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import project.trendpick_pro.global.kafka.outbox.entity.OrderMaterial; +import project.trendpick_pro.global.kafka.outbox.entity.OutboxMessage; + +import java.util.List; +import java.util.Optional; + +public interface OutboxMessageRepository extends JpaRepository { + + @Query("select o from OrderMaterial o where o.code = :code") + List findAllOrderMaterialByCode(@Param("code") String code); +} diff --git a/src/main/java/project/trendpick_pro/global/kafka/outbox/service/OutboxMessageService.java b/src/main/java/project/trendpick_pro/global/kafka/outbox/service/OutboxMessageService.java new file mode 100644 index 00000000..cd9f802e --- /dev/null +++ b/src/main/java/project/trendpick_pro/global/kafka/outbox/service/OutboxMessageService.java @@ -0,0 +1,56 @@ +package project.trendpick_pro.global.kafka.outbox.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import project.trendpick_pro.domain.orders.entity.Order; +import project.trendpick_pro.global.kafka.outbox.entity.OrderMaterial; +import project.trendpick_pro.global.kafka.outbox.entity.OutboxMessage; +import project.trendpick_pro.global.kafka.outbox.repository.OutboxMessageRepository; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class OutboxMessageService { + + private final OutboxMessageRepository outboxMessageRepository; + + public OutboxMessage findById(Long id) { + return outboxMessageRepository.findById(id).get(); + } + + public List findAll() { + return outboxMessageRepository.findAll(); + } + + @Transactional + public void save(OutboxMessage outboxMessage) { + outboxMessageRepository.save(outboxMessage); + } + + @Transactional + public void delete(OutboxMessage outboxMessage) { + outboxMessageRepository.delete(outboxMessage); + } + + @Transactional + public void deleteAllById(List completedMessages) { + outboxMessageRepository.deleteAllById(completedMessages); + } + + public List findOrderMaterial(String code){ + return outboxMessageRepository.findAllOrderMaterialByCode(code); + } + + //주문 객쳉 생성 메시지 발행 + public void publishOrderCreationMessage(String topic, String key, List orderMaterials, String code) { + outboxMessageRepository.save(new OutboxMessage(topic, key, key, orderMaterials, code)); + } + + //주문 처리 메시지 발행 + public void publishOrderProcessMessage(String topic, String message, String payload, String email) { + outboxMessageRepository.save(new OutboxMessage(topic, message, payload, email)); + } +} diff --git a/src/main/java/project/trendpick_pro/global/kafka/view/entity/View.java b/src/main/java/project/trendpick_pro/global/kafka/view/entity/View.java deleted file mode 100644 index 6366ce14..00000000 --- a/src/main/java/project/trendpick_pro/global/kafka/view/entity/View.java +++ /dev/null @@ -1,23 +0,0 @@ -package project.trendpick_pro.global.kafka.view.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor -public class View { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private Long count = 0L; - - public void increment() { - this.count++; - } -} diff --git a/src/main/java/project/trendpick_pro/global/kafka/view/repository/ViewRepository.java b/src/main/java/project/trendpick_pro/global/kafka/view/repository/ViewRepository.java deleted file mode 100644 index d972c81c..00000000 --- a/src/main/java/project/trendpick_pro/global/kafka/view/repository/ViewRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package project.trendpick_pro.global.kafka.view.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import project.trendpick_pro.global.kafka.view.entity.View; - -public interface ViewRepository extends JpaRepository { -} diff --git a/src/main/java/project/trendpick_pro/global/kafka/view/service/ViewService.java b/src/main/java/project/trendpick_pro/global/kafka/view/service/ViewService.java deleted file mode 100644 index 37c417b2..00000000 --- a/src/main/java/project/trendpick_pro/global/kafka/view/service/ViewService.java +++ /dev/null @@ -1,46 +0,0 @@ -package project.trendpick_pro.global.kafka.view.service; - -import jakarta.servlet.http.HttpSession; -import lombok.RequiredArgsConstructor; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import project.trendpick_pro.global.kafka.view.entity.View; -import project.trendpick_pro.global.kafka.view.repository.ViewRepository; - -@Transactional(readOnly = true) -@Service -@RequiredArgsConstructor -public class ViewService { - - private final ViewRepository viewRepository; - - private final KafkaTemplate kafkaTemplate; - - @Transactional - public void registerView() { - viewRepository.save(new View()); - } - - public int findSize() { - return viewRepository.findAll().size(); - } - - @Async - public void requestIncrementViewCount(HttpSession session) { - if (session.getAttribute("visited") == null) { - kafkaTemplate.send("views", "increment", "1"); - session.setAttribute("visited", true); - } - } - - @Transactional - public void incrementViewCount(String viewId) { - viewRepository.findById(Long.valueOf(viewId)).get().increment(); - } - - public Long getCount() { - return viewRepository.findById(1L).get().getCount(); - } -} diff --git a/src/main/java/project/trendpick_pro/global/redis/redisson/service/RedissonService.java b/src/main/java/project/trendpick_pro/global/redis/redisson/service/RedissonService.java index 8d27d775..d0d265d5 100644 --- a/src/main/java/project/trendpick_pro/global/redis/redisson/service/RedissonService.java +++ b/src/main/java/project/trendpick_pro/global/redis/redisson/service/RedissonService.java @@ -1,46 +1,73 @@ package project.trendpick_pro.global.redis.redisson.service; -import com.fasterxml.jackson.core.JsonProcessingException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.stereotype.Service; +import project.trendpick_pro.domain.orders.entity.Order; import project.trendpick_pro.domain.orders.service.OrderService; -import project.trendpick_pro.domain.product.exception.ProductStockOutException; +import project.trendpick_pro.global.kafka.outbox.entity.OrderMaterial; +import project.trendpick_pro.global.kafka.outbox.service.OutboxMessageService; import project.trendpick_pro.global.redis.exception.RedisLockException; - +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j @Service @RequiredArgsConstructor public class RedissonService { - private final RedissonClient redissonClient; private final OrderService orderService; + private final OutboxMessageService outboxMessageService; - public void processOrderWithLock(String orderKey) throws InterruptedException { - RLock lock = getDistributedLock(orderKey); - if (lock.tryLock(3, 3, TimeUnit.SECONDS)) { - try { - log.info(lock.getName()); - orderService.tryOrder(orderKey); - } catch (JsonProcessingException e) { + public void processOrderWithLock(String orderId, String orderMaterialCode) throws InterruptedException { + Order order = orderService.findById(Long.valueOf(orderId)); + List orderMaterials = outboxMessageService.findOrderMaterial(orderMaterialCode); + List locks = getLocks(orderMaterials); //주문 상품들에 대한 락 + if (isCompleteLock(locks)) + try{ + orderService.tryOrder(orderId, orderMaterials); + } catch (Exception e){ log.error("Error processing order", e); - } finally {; + } finally { try { - lock.unlock(); + unlock(locks); } catch (IllegalMonitorStateException e) { log.info("Redisson Lock Already UnLocked"); } } - } else { - throw new RedisLockException("해당 락이 사용중입니다"); + else throw new RedisLockException("해당 락이 사용중입니다"); + } + + private List getLocks(List orderMaterials) { + List lockList = new ArrayList<>(); + for(OrderMaterial orderMaterial : orderMaterials) + lockList.add(getDistributedLock(String.valueOf(orderMaterial.getProductId()))); + return lockList; + } + + private boolean isCompleteLock(List lockList) throws InterruptedException { + for (RLock lock : lockList) { + if(!lock.tryLock(3, 1, TimeUnit.SECONDS)){ + return false; + } + } + return true; + } + + public void unlock(List lockList){ + for (RLock lock : lockList){ + try { + lock.unlock(); + } catch (IllegalMonitorStateException e) { + log.info("Redisson Lock Already UnLocked"); + } } } private RLock getDistributedLock(String key) { - return redissonClient.getLock("ORD_" + key); + return redissonClient.getLock("PRODUCT_" + key); } } diff --git a/src/main/java/project/trendpick_pro/global/tosspayment/controller/PaymentController.java b/src/main/java/project/trendpick_pro/global/tosspayment/controller/PaymentController.java index 38a17085..606d136a 100644 --- a/src/main/java/project/trendpick_pro/global/tosspayment/controller/PaymentController.java +++ b/src/main/java/project/trendpick_pro/global/tosspayment/controller/PaymentController.java @@ -7,12 +7,9 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import project.trendpick_pro.domain.notification.service.NotificationService; -import project.trendpick_pro.domain.orders.entity.Order; import project.trendpick_pro.domain.orders.service.OrderService; import project.trendpick_pro.global.util.rq.Rq; import project.trendpick_pro.global.util.rsData.RsData; -import project.trendpick_pro.global.tosspayment.dto.PaymentResultResponse; import project.trendpick_pro.global.tosspayment.service.PaymentService; @Controller @@ -22,11 +19,8 @@ public class PaymentController { private final PaymentService paymentService; private final OrderService orderService; - private final NotificationService notificationService; - private final Rq rq; - @Transactional @GetMapping(value = "/{id}/success") public String PaymentTry( @PathVariable("id") Long id, @@ -34,25 +28,31 @@ public String PaymentTry( @RequestParam(value = "amount") Integer amount, @RequestParam(value = "paymentKey") String paymentKey) { - PaymentResultResponse response = paymentService.requestPayment(paymentKey, orderId, amount); - Order order = orderService.findById(id); - - if (response.getStatus().equals("DONE")) { - order.connectPayment( - response.getPaymentKey(), - "TossPayments " + response.getMethod() - ); - notificationService.create(rq.getMember(), order.getId()); - return rq.redirectWithMsg("/trendpick/orders/%s".formatted(id), "주문이 완료되었습니다."); - } else { - return rq.historyBack(RsData.of("F-1", "주문이 완료되지 않았습니다.")); - } + RsData result = paymentService.processPayment(paymentKey, id, amount, orderId); + + if (result.isSuccess()) + return rq.redirectWithMsg("/trendpick/orders/%s".formatted(id), "결제를 성공적으로 완료했습니다."); + + orderService.cancel(id); + return rq.historyBack(RsData.of("F-1", "결제 도중에 오류가 발생했습니다.")); + + } + + @Transactional + @GetMapping(value = "/{id}/fail") + public String PaymentFail( + @PathVariable("id") Long id, + @RequestParam(value = "orderId") String orderId, + @RequestParam(value = "amount", required = false) Integer amount, + @RequestParam(value = "paymentKey", required = false) String paymentKey) { + + orderService.cancel(id); + return rq.redirectWithMsg("/trendpick/products/list?main-category=전체", "결제에 실패했습니다."); } @GetMapping(value = "/{id}/cancel") public String paymentCancel(@PathVariable("id") Long id) { orderService.cancel(id); - notificationService.create(rq.getMember(),id); paymentService.cancelPayment(orderService.findById(id).getPaymentKey()); return rq.redirectWithMsg("/trendpick/orders/usr", "주문을 취소했습니다."); } diff --git a/src/main/java/project/trendpick_pro/global/tosspayment/service/PaymentService.java b/src/main/java/project/trendpick_pro/global/tosspayment/service/PaymentService.java index b59dcf9f..e40fe465 100644 --- a/src/main/java/project/trendpick_pro/global/tosspayment/service/PaymentService.java +++ b/src/main/java/project/trendpick_pro/global/tosspayment/service/PaymentService.java @@ -1,20 +1,30 @@ package project.trendpick_pro.global.tosspayment.service; +import lombok.RequiredArgsConstructor; import net.minidev.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; +import project.trendpick_pro.domain.orders.entity.Order; +import project.trendpick_pro.domain.orders.entity.OrderItem; +import project.trendpick_pro.domain.orders.service.OrderService; +import project.trendpick_pro.domain.tags.favoritetag.service.FavoriteTagService; +import project.trendpick_pro.domain.tags.tag.entity.TagType; import project.trendpick_pro.global.tosspayment.dto.PaymentResultResponse; +import project.trendpick_pro.global.util.rsData.RsData; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; import java.util.Base64; import java.util.Collections; @Service +@RequiredArgsConstructor public class PaymentService { @Value("${toss.secretKey}") @@ -23,7 +33,34 @@ public class PaymentService { @Value("${toss.url}") private String tossURL; - public PaymentResultResponse requestPayment(String paymentKey, String orderId, Integer amount) { + private final OrderService orderService; + private final FavoriteTagService favoriteTagService; + + @Transactional + public RsData processPayment(String paymentKey, Long orderId, Integer amount, String ORD_ID){ + PaymentResultResponse response = requestPayment(paymentKey, ORD_ID, amount); + + Order order = orderService.findById(orderId); + validatePayment(order); + + if (response.getStatus().equals("DONE")) { + successPaymentProcess(response.getMethod(), response.getPaymentKey(), order); + return RsData.of("S-1", "결제를 성공적으로 완료했습니다."); + } + + return RsData.of("F-1", "결제 도중에 오류가 발생했습니다."); + } + + private void successPaymentProcess(String method, String paymentKey, Order order) { + order.connectPayment("TossPayments" + method, paymentKey); + + for (OrderItem orderItem : order.getOrderItems()) { + favoriteTagService.updateTag(order.getMember(), orderItem.getProduct(), TagType.ORDER); + orderItem.getCouponCard().use(LocalDateTime.now()); //실제 쿠폰 사용 처리 + } + } + + private PaymentResultResponse requestPayment(String paymentKey, String orderId, Integer amount) { RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); String secret = new String(Base64.getEncoder().encode((secretKey + ":").getBytes(StandardCharsets.UTF_8))); @@ -60,4 +97,9 @@ public void cancelPayment(String paymentKey) { PaymentResultResponse.class ); } + + private void validatePayment(Order order) { + if(order.getPaymentKey() != null)//멱등성 + throw new IllegalArgumentException("이미 결제 완료된 주문입니다."); + } } \ No newline at end of file diff --git a/src/main/java/project/trendpick_pro/global/util/rq/Rq.java b/src/main/java/project/trendpick_pro/global/util/rq/Rq.java index 855c3617..396fbf98 100644 --- a/src/main/java/project/trendpick_pro/global/util/rq/Rq.java +++ b/src/main/java/project/trendpick_pro/global/util/rq/Rq.java @@ -79,15 +79,7 @@ public Boolean admin() { return getLogin().getRole().equals(MemberRoleType.ADMIN); } public Member getRollMember(){ - Member member=getLogin(); - if(member.getRole().equals(MemberRoleType.MEMBER)){ - return member; - } else if(member.getRole().equals(MemberRoleType.BRAND_ADMIN)){ - return member; - } else if(member.getRole().equals(MemberRoleType.ADMIN)){ - return member; - } - throw new MemberNotMatchException("허용된 권한이 아닙니다."); + return getLogin(); } public Member getBrandMember() { Member member = getLogin(); diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index e7110b0c..62ad5c72 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,26 +1,25 @@ spring: datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:test; - username: sa - password: + driver-class-name: org.mariadb.jdbc.Driver + url: jdbc:${dev.dataSource.kind}://${dev.dataSource.host}:${dev.dataSource.port}/${dev.dataSource.db} + username: ${dev.dataSource.username} + password: ${dev.dataSource.password} jpa: hibernate: ddl-auto: create data: redis: - host: ${redis.devHost} + host: ${dev.redis.host} kafka: - bootstrap-servers: localhost:${custom.kafka.devPort} + bootstrap-servers: ${dev.kafka.host}:${dev.kafka.port} logging.level: org: hibernate.SQL: error -custom: - redis: - host: ${redis.devHost} - kafka: - host: "localhost:" - port: ${custom.kafka.devPort} \ No newline at end of file + host: ${dev.kafka.host} + port: ${dev.kafka.port} +redis: + host: ${dev.redis.host} + port: ${dev.redis.port} \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 522a3d6c..c02047b3 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -15,9 +15,9 @@ spring: timeout-per-shutdown-phase: 1m datasource: driver-class-name: org.mariadb.jdbc.Driver - url: jdbc:${custom.dataSource.kind}://${custom.dataSource.host}:${custom.dataSource.port}/${custom.dataSource.db} - username: ${custom.dataSource.username} - password: ${custom.dataSource.password} + url: jdbc:${prod.dataSource.kind}://${prod.dataSource.host}:${prod.dataSource.port}/${prod.dataSource.db} + username: ${prod.dataSource.username} + password: ${prod.dataSource.password} jpa: hibernate: ddl-auto: update @@ -26,11 +26,11 @@ spring: host: ${redis.host} port: ${redis.port} kafka: - bootstrap-servers: ${custom.kafka.host}:${custom.kafka.port} + bootstrap-servers: ${prod.kafka.host}:${prod.kafka.port} -custom: - redis: - host: ${redis.host} kafka: - host: "my-cluster-kafka-bootstrap" - port: ${custom.kafka.port} \ No newline at end of file + host: ${prod.kafka.host} + port: ${prod.kafka.port} +redis: + host: ${prod.redis.host} + port: ${prod.redis.port} \ No newline at end of file diff --git a/src/main/resources/application-secret.yml.default b/src/main/resources/application-secret.yml.default new file mode 100644 index 00000000..a08649e8 --- /dev/null +++ b/src/main/resources/application-secret.yml.default @@ -0,0 +1,64 @@ +spring: + config: + activate: + on-profile: secret + +# S3 연결 환경변수 +cloud: + aws: + s3: + bucket: ${S3_BUCKET} + region: + static: ${S3_REGION} + credentials: + accessKey: ${SERVER_ACCESSKEY} + secretKey: ${SERVER_SECRETKEY} + endPoint: ${SERVER_ENDPOINT} + +# 토스 페이먼츠 환경변수 +toss: + clientKey: ${TOSS_CLIENTKEY} + secretKey: ${TOSS_SECRETKEY} + url: ${TOSS_URL} + +# 운영용 환경변수 (db, kafka, redis) +prod: + dataSource: + kind: ${PROD_DATABASE_KIND} + host: ${PROD_DATABASE_HOST} + port: ${PROD_DATABASE_PORT} + db: ${PROD_DATABASE_NAME} + username: ${PROD_DATABASE_USERNAME} + password: ${PROD_DATABASE_PASSWORD} + kafka: + host: ${PROD_KAFKA_HOST} + port: ${PROD_KAFKA_PORT} + redis: + host: ${PROD_REDIS_HOST} + port: ${PROD_REDIS_PORT} + +# 개발용 환경변수 (db, kafka, redis) +dev: + dataSource: + kind: ${DEV_DATABASE_KIND} + host: ${DEV_DATABASE_HOST} + port: ${DEV_DATABASE_PORT} + db: ${DEV_DATABASE_NAME} + username: ${DEV_DATABASE_USERNAME} + password: ${DEV_DATABASE_PASSWORD} + kafka: + host: ${DEV_KAFKA_HOST} + port: ${DEV_KAFKA_PORT} + redis: + host: ${DEV_REDIS_HOST} + port: ${DEV_REDIS_PORT} + +# 테스트용 환경변수 (kafka, redis) +# h2 내장 DB 사용 +test: + kafka: + host: ${TEST_KAFKA_HOST} + port: ${TEST_KAFKA_PORT} + redis: + host: ${TEST_REDIS_HOST} + port: ${TEST_REDIS_PORT} \ No newline at end of file diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index e7b1b635..f2b90ff5 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -11,13 +11,13 @@ spring: ddl-auto: create data: redis: - host: ${redis.devHost} + host: ${test.redis.host} kafka: - bootstrap-servers: localhost:${custom.kafka.devPort} + bootstrap-servers: ${test.kafka.host}:${test.kafka.port} -custom: - redis: - host: ${redis.host} kafka: - host: "localhost:" - port: ${custom.kafka.port} \ No newline at end of file + host: ${test.kafka.host} + port: ${test.kafka.port} +redis: + host: ${test.redis.host} + port: ${test.redis.port} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 97b2f1e8..ade431a4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,11 +1,7 @@ -server: - tomcat: - threads: - max: 300 spring: profiles: active: dev - include: secret, db, data + include: secret, data batch: job: name: ${job.name:NONE} @@ -39,4 +35,4 @@ spring: consumer: key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.apache.kafka.common.serialization.StringDeserializer - max-poll-records: 1000 \ No newline at end of file + max-poll-records: 100 \ No newline at end of file diff --git a/src/main/resources/templates/trendpick/admin/rebateOrderItemList.html b/src/main/resources/templates/trendpick/admin/rebateOrderItemList.html index 26519453..80b545cd 100644 --- a/src/main/resources/templates/trendpick/admin/rebateOrderItemList.html +++ b/src/main/resources/templates/trendpick/admin/rebateOrderItemList.html @@ -14,22 +14,34 @@

정산

-
- - +
+
+ + +
+
+
+
월 총매출
+
+
+
+
월 정산금액
+
+
+
@@ -43,8 +55,11 @@

주문품목번호 결제날짜 상품명 - 결제가격 - 판매자 + 객단가 + 구매수량 + 쿠폰할인금액 + 결제금액 + 정산수수료 정산금액 정산날짜 정산 @@ -54,18 +69,22 @@

- - - + + + + + - 정산완료 + 정산 @@ -75,14 +94,10 @@

-
- - 선택정산 - @@ -108,34 +123,14 @@

function RebateForm__submit() { if (RebateForm__submitDone) return; - const form = document.rebateForm; - - const $checked = $('.orderItemCheckbox:checked'); - - if ($checked.length == 0) { - alert('정산할 주문품목을 선택해주세요.'); - return; - } - - var confirmAction = confirm("선택 상품들을 정산하시겠습니까?" + - "\n(한 번 정산하면 취소할 수 없습니다.)"); + var confirmAction = confirm("정산처리 하시겠습니까?" + + "\n(명월 10일에 자동 정산처리됩니다.)"); if (confirmAction) { - // 폼 전송 - //return true; - const ids = $checked.map((index, el) => $(el).val()).get(); - form.ids.value = ids; - form.submit(); - RebateForm__submitDone = true; return true; } else { - // 폼 전송 후 페이지 이동 취소 - setTimeout(function () { - window.stop(); - }, 0); return false; } } -

diff --git a/src/main/resources/templates/trendpick/admin/sales.html b/src/main/resources/templates/trendpick/admin/sales.html index 491f0202..2dbafa72 100644 --- a/src/main/resources/templates/trendpick/admin/sales.html +++ b/src/main/resources/templates/trendpick/admin/sales.html @@ -10,7 +10,7 @@

판매내역

- +
diff --git a/src/main/resources/templates/trendpick/orders/order-form.html b/src/main/resources/templates/trendpick/orders/order-form.html index ff5aebe5..2bb3df72 100644 --- a/src/main/resources/templates/trendpick/orders/order-form.html +++ b/src/main/resources/templates/trendpick/orders/order-form.html @@ -109,7 +109,7 @@

주문상품 정보

- 원 +
@@ -171,8 +171,8 @@

결제 정보