Skip to content

Commit

Permalink
Feat/#241 order create (#255)
Browse files Browse the repository at this point in the history
* [FEAT] 동시성을 고려하지 않은 임시 order id generator 생성

* [FEAT] DeliveryInfo 생성 로직 구현

* [FEAT] DeliveryOption (배송비, 배송방법) 관련 로직 구현

* [FEAT] 필드 변경에 따른 메서드명 변경

* [RENAME] 폴더 이동

* [REFACT] Order 에서 사용을 위한 접근제한자 변경

* [REFACT] OrderSheet 유효성 검증 로직 분리

* [FEAT] Order 생성 로직 구현

* [FEAT] Order 생성 로직에 필요한 ErrorCode 추가

* [FIX] PaymentInfo 미구현으로 인한 컴파일 에러 수정

* [FIX] 순환참조로인한 OrderPointService 분리

* [FIX] 테이블 변화로 test용 ddl 업데이트

* [FIX] DeliveryOptions 추가사항 작성
  • Loading branch information
happyjamy authored Jan 18, 2024
1 parent 79e4564 commit 445bf42
Show file tree
Hide file tree
Showing 24 changed files with 442 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public enum ErrorCode {
COUPON_ALREADY_USED(BAD_REQUEST, "이미 사용 완료한 쿠폰입니다."),
COUPON_NOT_AVAILABLE(BAD_REQUEST, "유효하지 않은 쿠폰입니다."),
COUPON_STOCK_INVALID(BAD_REQUEST,"쿠폰 수량은 0개 이상이어야 합니다."),
// TODO: DELIVERY_FEE_COUPON_ALREADY_APPLIED 통합 논의 필요
COUPON_ALREADY_APPLIED_PRODUCT(BAD_REQUEST,"이미 쿠폰이 적용된 상품입니다"),
COUPON_BENEFIT_VALUE_EXCEED(BAD_REQUEST,"쿠폰의 할인값은 최대할인값을 초과할 수 없습니다."),
COUPON_PRICE_NOT_ENOUGH(BAD_REQUEST,"쿠폰의 최수 주문 금액 미만입니다."),
Expand Down Expand Up @@ -68,6 +69,9 @@ public enum ErrorCode {
PRODUCT_NOT_AVAILABLE_ORDER(BAD_REQUEST, "주문할 수 없는 상품입니다."),
UPDATED_POINT_VALUE_INVALID(BAD_REQUEST, "갱신할 포인트의 값이 유효하지 않습니다."),
ORDERSHEET_ALREADY_ORDERED(BAD_REQUEST, "이미 주문이 완료된 주문서입니다."),
DELIVERY_FEE_COUPON_ALREADY_APPLIED(BAD_REQUEST, "이미 적용된 배송비 쿠폰입니다."),
// TODO: 적용 할 수 없는 쿠폰 에러 메시지 통합 논의 필요
DELIVERY_FEE_COUPON_NOT_APPLICABLE(BAD_REQUEST, "배송비가 없는 상품입니다."),
// 401
AUTH_MISSING_CREDENTIALS(UNAUTHORIZED, "사용자의 인증 정보를 찾을 수 없습니다."),
SECURITY_UNAUTHORIZED(UNAUTHORIZED, "인증 정보가 유효하지 않습니다"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public OrderSheetCouponInfo getOrderSheetCouponInfoWithSelectedCoupons(Long user
}

// 장바구니 쿠폰 가져오기
Coupon cartCoupon = couponJpaRepository.findById(selectedCouponsRequest.getSelectedCartCoupons())
Coupon cartCoupon = couponJpaRepository.findById(selectedCouponsRequest.getSelectedCartCouponId())
.orElseThrow(()->new CouponException(ErrorCode.COUPON_NOT_FOUND));

// 할인 금액 적용 결과
Expand Down Expand Up @@ -131,7 +131,7 @@ public Map<Long, List<CouponApplyResult>> calCouponApplyResult(

// 장바구니 쿠폰 가져오기
Coupon cartCoupon = couponJpaRepository.findById(
selectedCouponsRequest.getSelectedCartCoupons())
selectedCouponsRequest.getSelectedCartCouponId())
.orElseThrow(() -> new CouponException(ErrorCode.COUPON_NOT_FOUND));

// 최대 할인 쿠폰 적용 결과 + 적용한 쿠폰 가져오기
Expand All @@ -152,7 +152,7 @@ private SelectedCoupons getSelectedCoupons(SelectedCouponsRequest selectedCoupon
selectedProductDuplicateCouponsByOrderedProductId.put(orderedProductId, CouponResponse.from(coupon));
} else selectedProductCouponListsByOrderedProductId.put(orderedProductId, CouponResponse.from(coupon));
}
CouponResponse cartCoupon = CouponResponse.from(getCouponJpaRepositoryById(selectedCoupons.getSelectedCartCoupons()));
CouponResponse cartCoupon = CouponResponse.from(getCouponJpaRepositoryById(selectedCoupons.getSelectedCartCouponId()));

return new SelectedCoupons(selectedProductCouponListsByOrderedProductId, selectedProductDuplicateCouponsByOrderedProductId, cartCoupon);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.programmers.smrtstore.domain.orderManagement.order.domain.entity;
package com.programmers.smrtstore.domain.orderManagement.delivery.entity;

import com.programmers.smrtstore.domain.orderManagement.order.domain.entity.vo.DeliveryAddress;
import com.programmers.smrtstore.domain.orderManagement.order.domain.entity.vo.ReceiverInfo;
import com.programmers.smrtstore.domain.orderManagement.delivery.entity.vo.DeliveryAddress;
import com.programmers.smrtstore.domain.orderManagement.delivery.entity.vo.ReceiverInfo;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
Expand All @@ -10,6 +10,7 @@
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -33,7 +34,14 @@ public class DeliveryInfo {
@Column(name = "delivery_request")
private String deliveryRequest;

@Column(name = "delivery_fee")
private Integer deliveryFee;

@Builder
public DeliveryInfo(
Long id, String address1Depth, String address2Depth, String zipCode,
String receiverName, String receiverPhone, String deliveryRequest
) {
this.id = id;
this.deliveryAddress = new DeliveryAddress(address1Depth, address2Depth, zipCode);
this.receiverInfo = new ReceiverInfo(receiverName, receiverPhone);
this.deliveryRequest = deliveryRequest;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.programmers.smrtstore.domain.orderManagement.order.domain.entity.vo;
package com.programmers.smrtstore.domain.orderManagement.delivery.entity.vo;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
Expand All @@ -10,7 +10,7 @@
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class DeliveryAddress {

// TODO 대문자로 변경
@Column(name = "address_1depth")
private String address1depth;

Expand All @@ -19,4 +19,10 @@ public class DeliveryAddress {

@Column(name = "zip_code")
private String zipCode;

public DeliveryAddress(String address1depth, String address2depth, String zipCode) {
this.address1depth = address1depth;
this.address2depth = address2depth;
this.zipCode = zipCode;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.programmers.smrtstore.domain.orderManagement.order.domain.entity.vo;
package com.programmers.smrtstore.domain.orderManagement.delivery.entity.vo;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
Expand All @@ -16,4 +16,9 @@ public class ReceiverInfo {

@Column(name = "receiver_phone")
private String receiverPhone;

public ReceiverInfo(String receiverName, String receiverPhone) {
this.receiverName = receiverName;
this.receiverPhone = receiverPhone;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.programmers.smrtstore.domain.orderManagement.delivery.infrastructure;

import com.programmers.smrtstore.domain.orderManagement.delivery.entity.DeliveryInfo;
import org.springframework.data.jpa.repository.JpaRepository;

public interface DeliveryInfoJpaRepository extends JpaRepository<DeliveryInfo, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.programmers.smrtstore.domain.orderManagement.order.application;

import static com.programmers.smrtstore.core.properties.ErrorCode.ORDER_NOT_FOUND;
import static com.programmers.smrtstore.core.properties.ErrorCode.USER_NOT_FOUND;

import com.programmers.smrtstore.domain.orderManagement.order.domain.entity.Order;
import com.programmers.smrtstore.domain.orderManagement.order.exception.OrderException;
import com.programmers.smrtstore.domain.orderManagement.order.infrastructure.OrderJpaRepository;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.res.OrderedProductResponse;
import com.programmers.smrtstore.domain.user.domain.entity.User;
import com.programmers.smrtstore.domain.user.exception.UserException;
import com.programmers.smrtstore.domain.user.infrastructure.UserJpaRepository;
import com.programmers.smrtstore.util.DateTimeUtils;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrderPointService {
private final UserJpaRepository userJpaRepository;
private final OrderJpaRepository orderJpaRepository;

public Integer calculateUserMonthlyTotalSpending(Long userId, int month, int year) {
checkUserExistence(userId);
DateTimeUtils.validateMonth(month);
DateTimeUtils.validateYear(year);
return orderJpaRepository.calculateMonthlyTotalSpending(userId, month, year);
}

public Integer getTotalPriceByOrderId(String orderId) {
Order order = orderJpaRepository.findByIdIncludeDeleted(orderId)
.orElseThrow(() -> new OrderException(ORDER_NOT_FOUND, String.valueOf(orderId)));
return order.getTotalPrice();
}

public List<OrderedProductResponse> getProductsForOrder(String orderId) {
return orderJpaRepository.findByIdWithOrderSheetIncludeDeleted(orderId)
.orElseThrow(() -> new OrderException(ORDER_NOT_FOUND, String.valueOf(orderId)))
.getOrderSheet().getOrderedProducts().stream()
.map(OrderedProductResponse::from)
.toList();
}

private User checkUserExistence(Long userId) {
return userJpaRepository.findById(userId)
.orElseThrow(() -> new UserException(USER_NOT_FOUND, String.valueOf(userId)));
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.programmers.smrtstore.domain.orderManagement.order.application;

import com.programmers.smrtstore.domain.orderManagement.order.domain.entity.enums.OrderStatus;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.res.CreateOrderResponse;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.req.CreateOrderRequest;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.req.UpdateOrderRequest;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.res.OrderPreviewResponse;
Expand All @@ -11,20 +10,14 @@

public interface OrderService {

CreateOrderResponse createOrder(CreateOrderRequest request);
String createOrder(Long userId, CreateOrderRequest request);

OrderResponse getOrderById(Long orderId);

Long cancelOrder(Long orderId);

Long updateOrder(Long orderId, UpdateOrderRequest request);

Integer calculateUserMonthlyTotalSpending(Long userId, int month, int year);

Integer getTotalPriceByOrderId(String orderId);

List<OrderedProductResponse> getProductsForOrder(String orderId);

List<OrderPreviewResponse> getOrderPreviewsByUserId(Long userId);

List<OrderPreviewResponse> getOrderPreviewsByUserIdAndStatus(Long userId, List<OrderStatus> statuses);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
package com.programmers.smrtstore.domain.orderManagement.order.application;

import static com.programmers.smrtstore.core.properties.ErrorCode.ORDERSHEET_NOT_FOUND;
import static com.programmers.smrtstore.core.properties.ErrorCode.ORDER_NOT_FOUND;
import static com.programmers.smrtstore.core.properties.ErrorCode.SHIPPING_ADDRESS_NOT_FOUND;
import static com.programmers.smrtstore.core.properties.ErrorCode.USER_NOT_FOUND;

import com.programmers.smrtstore.domain.coupon.application.OrderCouponService;
import com.programmers.smrtstore.domain.orderManagement.delivery.entity.DeliveryInfo;
import com.programmers.smrtstore.domain.orderManagement.delivery.infrastructure.DeliveryInfoJpaRepository;
import com.programmers.smrtstore.domain.orderManagement.order.domain.entity.Order;
import com.programmers.smrtstore.domain.orderManagement.order.domain.entity.enums.OrderStatus;
import com.programmers.smrtstore.domain.orderManagement.order.exception.OrderException;
import com.programmers.smrtstore.domain.orderManagement.order.infrastructure.OrderJpaRepository;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.req.CreateOrderRequest;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.req.UpdateOrderRequest;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.res.CreateOrderResponse;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.res.OrderPreviewResponse;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.res.OrderResponse;
import com.programmers.smrtstore.domain.orderManagement.order.presentation.dto.res.OrderedProductResponse;
import com.programmers.smrtstore.domain.orderManagement.orderSheet.application.OrderSheetService;
import com.programmers.smrtstore.domain.orderManagement.orderSheet.domain.entity.OrderSheet;
import com.programmers.smrtstore.domain.orderManagement.orderSheet.infrastructure.OrderSheetJpaRepository;
import com.programmers.smrtstore.domain.orderManagement.orderSheet.presentation.dto.vo.CouponApplyResult;
import com.programmers.smrtstore.domain.orderManagement.orderedProduct.domain.entity.OrderedProduct;
import com.programmers.smrtstore.domain.point.application.PointDetailService;
import com.programmers.smrtstore.domain.point.application.PointService;
import com.programmers.smrtstore.domain.point.application.dto.req.PointDetailRequest;
import com.programmers.smrtstore.domain.point.application.dto.req.UsePointRequest;
import com.programmers.smrtstore.domain.user.domain.entity.ShippingAddress;
import com.programmers.smrtstore.domain.user.domain.entity.User;
import com.programmers.smrtstore.domain.user.exception.UserException;
import com.programmers.smrtstore.domain.user.infrastructure.ShippingAddressJpaRepository;
import com.programmers.smrtstore.domain.user.infrastructure.UserJpaRepository;
import com.programmers.smrtstore.util.DateTimeUtils;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -26,14 +43,87 @@
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrderServiceImpl implements OrderService {

private final UserJpaRepository userJpaRepository;
private final OrderJpaRepository orderJpaRepository;
private final OrderSheetJpaRepository orderSheetJpaRepository;
private final ShippingAddressJpaRepository shippingAddressJpaRepository;
private final DeliveryInfoJpaRepository deliveryInfoJpaRepository;
// TODO: validateOrderSheet() 메소드 도메인 내부로 분리
private final OrderSheetService orderSheetService;
private final PointDetailService pointDetailService;
private final PointService pointService;

private final OrderCouponService orderCouponService;

@Transactional
@Override
public CreateOrderResponse createOrder(CreateOrderRequest request) {
return null;
public String createOrder(Long userId, CreateOrderRequest request) {
User user = checkUserExistence(userId);

OrderSheet orderSheet = orderSheetJpaRepository.findById(request.getOrderSheetId())
.orElseThrow(() -> new OrderException(ORDERSHEET_NOT_FOUND,
String.valueOf(request.getOrderSheetId())));

// 주문서 유효성 검증
orderSheetService.validateAvailableOrderSheet(orderSheet, user);

List<OrderedProduct> orderedProducts = orderSheet.getOrderedProducts();

// 최종적으로 적용된 쿠폰에 대해 쿠폰 적용 결과 산출
Map<Long, List<CouponApplyResult>> finalCouponAppliedResult = orderCouponService.calCouponApplyResult(
orderedProducts, request.getSelectedCoupons());
orderedProducts.forEach(orderedProduct -> orderedProduct.updateCouponDiscount(
finalCouponAppliedResult.get(orderedProduct.getId()).stream()
.map(CouponApplyResult::getDiscountAmount).reduce(0, Integer::sum))
);

// 최종적으로 포인트 사용액에 대해 포인트 적용 결과 산출
Map<Long, Integer> finalPointUsedResult = pointDetailService.getPointPiecesByOrderedProduct(
orderedProducts, request.getUsedPoint());
orderedProducts.forEach(orderedProduct -> orderedProduct.updatePointDiscount(
finalPointUsedResult.get(orderedProduct.getId()))
);

// 배송비 쿠폰 적용
if (request.getSelectedCoupons().getSelectedDeliveryFeeCouponId() != null) {
orderSheet.getDeliveryOptions().useDeliveryFeeCoupon();
}

// delivery entity 생성
ShippingAddress shippingAddress = shippingAddressJpaRepository.findById(
request.getDeliveryAddress().getShippingAddressId())
.orElseThrow(() -> new OrderException(SHIPPING_ADDRESS_NOT_FOUND,
String.valueOf(request.getDeliveryAddress().getShippingAddressId())));

DeliveryInfo deliveryInfo = request.getDeliveryAddress()
.createDeliveryInfoFrom(shippingAddress);
deliveryInfoJpaRepository.save(deliveryInfo);

// order 생성
// 주문서 상태 변경 (order 넣기)
// TODO: 동시성 고려해서 id 생성
Order order = Order.builder()
.id(SimpleIdGenerator.generateId())
.orderSheet(orderSheet)
.orderDate(LocalDateTime.now())
.deliveryInfo(deliveryInfo)
.build();
orderJpaRepository.save(order);

// TODO: 쿠폰 적용 트랜잭션

// 포인트 적용 트랜잭션
Long pointId = pointService.usePoint(UsePointRequest.builder()
.userId(userId)
.orderId(orderSheet.getOrder().getId())
.pointAmount(request.getUsedPoint())
.build());
pointDetailService.saveUseHistory(PointDetailRequest.builder()
.userId(userId)
.pointId(pointId)
.build());

return order.getId();
}

@Override
Expand All @@ -55,30 +145,6 @@ public Long updateOrder(Long orderId, UpdateOrderRequest request) {
return null;
}

@Override
public Integer calculateUserMonthlyTotalSpending(Long userId, int month, int year) {
checkUserExistence(userId);
DateTimeUtils.validateMonth(month);
DateTimeUtils.validateYear(year);
return orderJpaRepository.calculateMonthlyTotalSpending(userId, month, year);
}

@Override
public Integer getTotalPriceByOrderId(String orderId) {
Order order = orderJpaRepository.findByIdIncludeDeleted(orderId)
.orElseThrow(() -> new OrderException(ORDER_NOT_FOUND, String.valueOf(orderId)));
return order.getTotalPrice();
}

@Override
public List<OrderedProductResponse> getProductsForOrder(String orderId) {
return orderJpaRepository.findByIdWithOrderSheetIncludeDeleted(orderId)
.orElseThrow(() -> new OrderException(ORDER_NOT_FOUND, String.valueOf(orderId)))
.getOrderSheet().getOrderedProducts().stream()
.map(OrderedProductResponse::from)
.toList();
}

@Override
public List<OrderPreviewResponse> getOrderPreviewsByUserId(Long userId) {
checkUserExistence(userId);
Expand Down
Loading

0 comments on commit 445bf42

Please sign in to comment.