Merge branch 'master' into owner_dashboard

This commit is contained in:
Sangbum Park
2022-03-23 15:56:21 +09:00
committed by GitHub
29 changed files with 895 additions and 159 deletions

View File

@@ -67,6 +67,8 @@ domain-httpRequestCode-etc
== 주문
=== 주문 수정
operation::order-patch[snippets='curl-request,http-request,http-response,path-parameters,request-fields,response-fields']
=== 주문 상세보기
operation::api-orderDetail[snippets='curl-request,http-request,http-response,path-parameters,response-fields']
== 점주 서비스
=== 주문 페이지

View File

@@ -0,0 +1,122 @@
package com.justpickup.orderservice.domain.order.dto;
import com.justpickup.orderservice.domain.order.entity.Order;
import com.justpickup.orderservice.domain.order.entity.OrderStatus;
import com.justpickup.orderservice.domain.orderItem.entity.OrderItem;
import com.justpickup.orderservice.domain.orderItemOption.entity.OrderItemOption;
import com.justpickup.orderservice.global.client.store.OptionType;
import lombok.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderDetailDto {
private Long id;
private LocalDateTime orderTime;
private Long orderPrice;
private OrderStatus orderStatus;
private String storeName;
private OrderDetailUser user;
private List<OrderDetailItem> orderItems = new ArrayList<>();
@Builder
public OrderDetailDto(Long id, LocalDateTime orderTime, Long orderPrice, OrderStatus orderStatus,
String storeName, OrderDetailUser user, List<OrderDetailItem> orderItems) {
this.id = id;
this.orderTime = orderTime;
this.orderPrice = orderPrice;
this.orderStatus = orderStatus;
this.storeName = storeName;
this.user = user;
this.orderItems = orderItems;
}
public static OrderDetailDto of(Order order, String storeName,
List<OrderDetailItem> orderItems, OrderDetailUser orderDetailUser) {
OrderDetailDto orderDetailDto = new OrderDetailDto();
orderDetailDto.id = order.getId();
orderDetailDto.orderTime = order.getOrderTime();
orderDetailDto.orderPrice = order.getOrderPrice();
orderDetailDto.orderStatus = order.getOrderStatus();
orderDetailDto.storeName = storeName;
orderDetailDto.user = orderDetailUser;
orderDetailDto.orderItems = orderItems;
return orderDetailDto;
}
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class OrderDetailUser {
private Long id;
private String name;
private String phoneNumber;
@Builder
public OrderDetailUser(Long id, String name, String phoneNumber) {
this.id = id;
this.name = name;
this.phoneNumber = phoneNumber;
}
}
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class OrderDetailItem {
private Long id;
private Long itemId;
private long totalPrice;
private long count;
private String name;
private List<OrderDetailItemOption> options = new ArrayList<>();
@Builder
public OrderDetailItem(Long id, Long itemId, long totalPrice, long count, String name, List<OrderDetailItemOption> options) {
this.id = id;
this.itemId = itemId;
this.totalPrice = totalPrice;
this.count = count;
this.name = name;
this.options = options;
}
public static OrderDetailItem of(OrderItem orderItem, String name, List<OrderDetailItemOption> orderDetailItemOption) {
OrderDetailItem orderDetailItem = new OrderDetailItem();
orderDetailItem.id = orderItem.getId();
orderDetailItem.itemId = orderItem.getItemId();
orderDetailItem.totalPrice = orderItem.getTotalPrice();
orderDetailItem.count = orderItem.getCount();
orderDetailItem.name = name;
orderDetailItem.options = orderDetailItemOption;
return orderDetailItem;
}
}
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class OrderDetailItemOption {
private Long id;
private Long itemOptionId;
private String name;
private OptionType optionType;
@Builder
public OrderDetailItemOption(Long id, Long itemOptionId, String name, OptionType optionType) {
this.id = id;
this.itemOptionId = itemOptionId;
this.name = name;
this.optionType = optionType;
}
public static OrderDetailItemOption of(OrderItemOption orderItemOption, String name, OptionType optionType) {
OrderDetailItemOption orderDetailItemOption = new OrderDetailItemOption();
orderDetailItemOption.id = orderItemOption.getId();
orderDetailItemOption.itemOptionId = orderItemOption.getItemOptionId();
orderDetailItemOption.name = name;
orderDetailItemOption.optionType = optionType;
return orderDetailItemOption;
}
}
}

View File

@@ -112,9 +112,4 @@ public class Order extends BaseEntity {
public void fail() {
this.orderStatus = OrderStatus.FAILED;
}
public void changeOrderDate(LocalDateTime orderTime){
this.orderTime = orderTime;
}
}

View File

@@ -80,6 +80,7 @@ public class OrderRepositoryCustom {
.leftJoin(order.transaction)
.where(
order.orderTime.between(search.getStartDateTime(), search.getEndDateTime()),
order.orderStatus.ne(OrderStatus.PENDING),
order.storeId.eq(storeId)
)
.fetchOne();

View File

@@ -16,4 +16,5 @@ public interface OrderService {
FetchOrderDto fetchOrder(Long userId);
void saveOrder(Long userId);
void modifyOrder(Long userId, OrderStatus orderStatus);
OrderDetailDto findOrderDetail(Long orderId);
}

View File

@@ -8,11 +8,10 @@ import com.justpickup.orderservice.domain.order.repository.OrderRepository;
import com.justpickup.orderservice.domain.order.repository.OrderRepositoryCustom;
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
import com.justpickup.orderservice.domain.orderItem.entity.OrderItem;
import com.justpickup.orderservice.domain.orderItem.repository.OrderItemRepositoryCustom;
import com.justpickup.orderservice.domain.orderItemOption.entity.OrderItemOption;
import com.justpickup.orderservice.global.client.store.GetItemResponse;
import com.justpickup.orderservice.global.client.store.GetStoreResponse;
import com.justpickup.orderservice.global.client.store.StoreByUserIdResponse;
import com.justpickup.orderservice.global.client.store.StoreClient;
import com.justpickup.orderservice.global.client.store.*;
import com.justpickup.orderservice.global.client.user.GetCustomerResponse;
import com.justpickup.orderservice.global.client.user.UserClient;
import com.justpickup.orderservice.global.dto.Result;
import lombok.RequiredArgsConstructor;
@@ -27,7 +26,9 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
import static com.justpickup.orderservice.domain.order.dto.OrderDetailDto.*;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
@Service
@RequiredArgsConstructor
@@ -37,6 +38,7 @@ public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final OrderRepositoryCustom orderRepositoryCustom;
private final OrderItemRepositoryCustom orderItemRepositoryCustom;
private final StoreClient storeClient;
private final UserClient userClient;
@@ -266,4 +268,70 @@ public class OrderServiceImpl implements OrderService {
return DashBoardDto.of(orderPrices , bestSellItem, sellAmountAWeeks);
}
@Override
public OrderDetailDto findOrderDetail(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderException(orderId + "는 없는 주문 번호입니다."));
List<OrderItem> orderItemsWithOptions = orderItemRepositoryCustom.getOrderItemsWithOptions(order.getId());
Set<Long> itemIds = new HashSet<>();
// 아이템 이름 및 옵션 이름 가져오기
for (OrderItem orderItem : orderItemsWithOptions) {
itemIds.add(orderItem.getItemId());
}
Map<Long, GetItemResponse> itemAndItemOptionMap = storeClient.getItemAndItemOptionMap(itemIds);
List<OrderDetailItem> orderDetailItems = orderItemsWithOptions.stream()
.map(orderItem -> {
// 주문 상세 옵션 생성
GetItemResponse itemResponse = itemAndItemOptionMap.get(orderItem.getItemId());
// 아이템 옵션 맵 생성
Map<Long, GetItemResponse.ItemOptionDto> itemOptionMap = itemResponse.getItemOptions().stream()
.collect(
toMap(GetItemResponse.ItemOptionDto::getId, itemOptionDto -> itemOptionDto)
);
List<OrderDetailItemOption> orderDetailItemOptions = orderItem.getOrderItemOptions()
.stream()
.map(orderItemOption -> {
// 옵션 아이디에 해당하는 아이템 옵션 객체 가져오기
GetItemResponse.ItemOptionDto itemOptionDto =
itemOptionMap.get(orderItemOption.getItemOptionId());
return OrderDetailItemOption.of(
orderItemOption,
itemOptionDto.getName(),
itemOptionDto.getOptionType()
);
})
.collect(toList());
// 아이템 아이디에 해당하는 아이템 이름 가져오기
String itemName = itemResponse.getName();
return OrderDetailItem.of(orderItem, itemName, orderDetailItemOptions);
})
.collect(toList());
// 고객 정보 가져오기
GetCustomerResponse customerInfo = userClient.getCustomerById(order.getUserId()).getData();
// 주문한 사용자 정보 생성
OrderDetailUser orderDetailUser = OrderDetailUser.builder()
.id(customerInfo.getUserId())
.phoneNumber(customerInfo.getPhoneNumber())
.name(customerInfo.getUserName())
.build();
// 매장 정보 가져오기
GetStoreResponse storeInfo = storeClient.getStore(String.valueOf(order.getStoreId())).getData();
return OrderDetailDto.of(order, storeInfo.getName(), orderDetailItems, orderDetailUser);
}
}

View File

@@ -1,8 +1,10 @@
package com.justpickup.orderservice.domain.order.web;
import com.justpickup.orderservice.domain.order.dto.OrderDetailDto;
import com.justpickup.orderservice.domain.order.entity.OrderStatus;
import com.justpickup.orderservice.domain.order.exception.OrderException;
import com.justpickup.orderservice.domain.order.service.OrderService;
import com.justpickup.orderservice.global.client.store.OptionType;
import com.justpickup.orderservice.global.dto.Result;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -10,10 +12,12 @@ import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequiredArgsConstructor
@@ -26,7 +30,7 @@ public class OrderController {
public ResponseEntity<Result> patchOrder(@PathVariable("orderId") Long orderId,
@RequestBody PatchOrderRequest patchOrderRequest) {
OrderStatus orderStatus = patchOrderRequest.getOrderStatus();
if (orderStatus == OrderStatus.PENDING && orderStatus != OrderStatus.FAILED) {
if (orderStatus == OrderStatus.PENDING || orderStatus == OrderStatus.FAILED) {
throw new OrderException(orderStatus.getMessage() + "는 변경 불가능합니다.");
}
@@ -39,4 +43,83 @@ public class OrderController {
static class PatchOrderRequest {
private OrderStatus orderStatus;
}
@GetMapping("/api/order-detail/{orderId}")
public ResponseEntity<Result> getOrderDetail(@PathVariable Long orderId) {
OrderDetailDto orderDetail = orderService.findOrderDetail(orderId);
return ResponseEntity.ok(Result.createSuccessResult(new OrderDetailResponse(orderDetail)));
}
@Data @NoArgsConstructor @AllArgsConstructor
static class OrderDetailResponse {
private Long id;
private OrderStatus orderStatus;
private String orderTime;
private Long orderPrice;
private String storeName;
private OrderDetailUserResponse user;
private List<OrderDetailItemResponse> orderItems = new ArrayList<>();
public OrderDetailResponse(OrderDetailDto dto) {
this.id = dto.getId();
this.orderStatus = dto.getOrderStatus();
this.orderTime = dto.getOrderTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
this.orderPrice = dto.getOrderPrice();
this.storeName = dto.getStoreName();
this.user = new OrderDetailUserResponse(dto.getUser());
this.orderItems = dto.getOrderItems().stream()
.map(OrderDetailItemResponse::new)
.collect(Collectors.toList());
}
@Data
static class OrderDetailUserResponse {
private Long id;
private String name;
private String phoneNumber;
public OrderDetailUserResponse(OrderDetailDto.OrderDetailUser user) {
this.id = user.getId();
this.name = user.getName();
this.phoneNumber = user.getPhoneNumber();
}
}
@Data @NoArgsConstructor
static class OrderDetailItemResponse {
private Long id;
private Long itemId;
private long totalPrice;
private long count;
private String name;
private List<OrderDetailItemOptionResponse> options = new ArrayList<>();
public OrderDetailItemResponse(OrderDetailDto.OrderDetailItem orderDetailItem) {
this.id = orderDetailItem.getId();
this.itemId = orderDetailItem.getItemId();
this.totalPrice = orderDetailItem.getTotalPrice();
this.count = orderDetailItem.getCount();
this.name = orderDetailItem.getName();
this.options = orderDetailItem.getOptions().stream()
.map(OrderDetailItemOptionResponse::new)
.collect(Collectors.toList());
}
}
@Data @NoArgsConstructor
static class OrderDetailItemOptionResponse {
private Long id;
private Long itemOptionId;
private String name;
private OptionType optionType;
public OrderDetailItemOptionResponse(OrderDetailDto.OrderDetailItemOption itemOption) {
this.id = itemOption.getId();
this.itemOptionId = itemOption.getItemOptionId();
this.name = itemOption.getName();
this.optionType = itemOption.getOptionType();
}
}
}
}

View File

@@ -0,0 +1,27 @@
package com.justpickup.orderservice.domain.orderItem.repository;
import com.justpickup.orderservice.domain.orderItem.entity.OrderItem;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.justpickup.orderservice.domain.orderItem.entity.QOrderItem.orderItem;
@Repository
@RequiredArgsConstructor
public class OrderItemRepositoryCustom {
private final JPAQueryFactory queryFactory;
public List<OrderItem> getOrderItemsWithOptions(Long orderId) {
return queryFactory.selectFrom(orderItem)
.leftJoin(orderItem.orderItemOptions).fetchJoin()
.where(
orderItem.order.id.eq(orderId)
)
.distinct()
.fetch();
}
}

View File

@@ -4,7 +4,6 @@ import com.justpickup.orderservice.global.entity.Yn;
import lombok.*;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor

View File

@@ -0,0 +1,10 @@
package com.justpickup.orderservice.global.client.store;
import lombok.Data;
@Data
public class ItemOptionsResponse {
private Long id;
private OptionType optionType;
private String name;
}

View File

@@ -32,7 +32,7 @@ public interface StoreClient {
Result<GetStoreResponse> getStore(@PathVariable(value = "storeId") String storeId);
@GetMapping("/api/customer/items/{itemId}")
Result<List<GetItemResponse>> getItemAndItemOptions(@PathVariable(value = "itemId") List<Long> itemIds);
Result<List<GetItemResponse>> getItemAndItemOptions(@PathVariable(value = "itemId") Iterable<Long> itemIds);
default Map<Long, String> getStoreNameMap(Set<Long> storeIds) {
List<GetStoreResponse> storeResponses = this.getStoreAllById(storeIds).getData();
@@ -50,5 +50,11 @@ public interface StoreClient {
);
}
default Map<Long, GetItemResponse> getItemAndItemOptionMap(Iterable<Long> itemIds) {
List<GetItemResponse> responses = this.getItemAndItemOptions(itemIds).getData();
return responses.stream()
.collect(
toMap(GetItemResponse::getId, getItemsResponse -> getItemsResponse)
);
}
}

View File

@@ -19,6 +19,8 @@ public class GlobalExceptionHandler {
HttpStatus status = ce.getStatus();
Result errorResult = ce.getErrorResult();
log.warn("[CustomException] {}, {}", status, errorResult);
return ResponseEntity.status(status)
.body(errorResult);
}

View File

@@ -1,12 +1,12 @@
package com.justpickup.orderservice.domain.order.web;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.justpickup.orderservice.config.TestConfig;
import com.justpickup.orderservice.domain.order.dto.OrderDetailDto;
import com.justpickup.orderservice.domain.order.entity.OrderStatus;
import com.justpickup.orderservice.domain.order.repository.OrderRepository;
import com.justpickup.orderservice.domain.order.service.OrderService;
import com.justpickup.orderservice.domain.order.web.OrderController.PatchOrderRequest;
import com.justpickup.orderservice.global.client.store.OptionType;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,11 +18,14 @@ import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import static org.junit.jupiter.api.Assertions.*;
import java.time.LocalDateTime;
import java.util.List;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
@@ -77,4 +80,98 @@ class OrderControllerTest {
;
}
@Test
@DisplayName("[GET] 주문 상세 내역 가져오기")
void getOrderDetail() throws Exception {
// GIVEN
Long orderId = 1589L;
given(orderService.findOrderDetail(orderId)).willReturn(getOrderDetailWillReturn(orderId));
// THEN
ResultActions actions = mockMvc.perform(get("/api/order-detail/{orderId}", String.valueOf(orderId)));
// WHEN
actions.andExpect(status().isOk())
.andDo(print())
.andDo(document("api-orderDetail",
pathParameters(
parameterWithName("orderId").description("주문 고유번호")
),
responseFields(
fieldWithPath("code").description("결과코드 SUCCESS/ERROR"),
fieldWithPath("message").description("메세지"),
fieldWithPath("data.id").description("주문 고유번호"),
fieldWithPath("data.orderTime").description("주문 시간 [yyy-MM-dd]"),
fieldWithPath("data.orderPrice").description("주문 금액"),
fieldWithPath("data.user.id").description("주문한 회원 고유번호"),
fieldWithPath("data.user.name").description("주문한 회원 이름"),
fieldWithPath("data.user.phoneNumber").description("주문한 회원 전화번호"),
fieldWithPath("data.orderItems[*].id").description("주문아이템 고유번호"),
fieldWithPath("data.orderItems[*].itemId").description("아이템 고유번호"),
fieldWithPath("data.orderItems[*].totalPrice").description("주문아이템 총합계"),
fieldWithPath("data.orderItems[*].count").description("주문아이템 수량"),
fieldWithPath("data.orderItems[*].name").description("아이템 이름"),
fieldWithPath("data.orderItems[*].options[*].id").description("주문아이템옵션 고유번호"),
fieldWithPath("data.orderItems[*].options[*].itemOptionId").description("아이템옵션 고유번호"),
fieldWithPath("data.orderItems[*].options[*].name").description("아이템옵션 이름"),
fieldWithPath("data.orderItems[*].options[*].optionType").description("아이템옵션 타입")
)
))
;
}
private OrderDetailDto getOrderDetailWillReturn(Long orderId) {
OrderDetailDto.OrderDetailUser orderDetailUser = OrderDetailDto.OrderDetailUser.builder()
.id(6L)
.name("박상범")
.phoneNumber("010-1234-5678")
.build();
long id = 11371L;
long itemOptionId = 40L;
OrderDetailDto.OrderDetailItemOption ice = OrderDetailDto.OrderDetailItemOption.builder()
.id(id++)
.itemOptionId(itemOptionId++)
.name("ICE")
.optionType(OptionType.REQUIRED)
.build();
OrderDetailDto.OrderDetailItemOption hot = OrderDetailDto.OrderDetailItemOption.builder()
.id(id++)
.itemOptionId(itemOptionId++)
.name("HOT")
.optionType(OptionType.REQUIRED)
.build();
OrderDetailDto.OrderDetailItemOption plus = OrderDetailDto.OrderDetailItemOption.builder()
.id(id++)
.itemOptionId(itemOptionId++)
.name("시럽 추가")
.optionType(OptionType.OTHER)
.build();
OrderDetailDto.OrderDetailItem 카페라떼 = OrderDetailDto.OrderDetailItem.builder()
.id(id++)
.itemId(itemOptionId++)
.count(2)
.name("카페라떼")
.options(List.of(hot, plus))
.build();
OrderDetailDto.OrderDetailItem 아메리카노 = OrderDetailDto.OrderDetailItem.builder()
.id(id++)
.itemId(itemOptionId++)
.count(2)
.options(List.of(ice))
.name("아메리카노")
.build();
return OrderDetailDto.builder()
.id(orderId)
.orderTime(LocalDateTime.now())
.orderPrice(76600L)
.user(orderDetailUser)
.orderItems(List.of(카페라떼, 아메리카노))
.build();
}
}