일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 백엔드
- django
- 웹동아리
- ㅏㄴ
- 멋쟁이사자처럼 서류
- 멋사10기
- 파이썬 크롤링
- API
- 디스코드봇
- 멋사12
- 파이썬
- 멋사 서류평가
- 알림봇
- 멋사11기
- 멋사 면접
- 멋사 10기
- 멋쟁이사자처럼10기
- 코딩동아리
- 멋사 합격
- 멋쟁이 사자처럼
- 깃허브
- 멋쟁이사자처럼11기
- IT동아리
- 기사 제목 크롤링
- 멋사 서류
- 멋쟁이사자처럼
- 멋쟁이사자처럼대학
- 크롤링
- discord
- 멋사
- Today
- Total
ACHO.pk devlog
[Springboot] 주문 이력 조회 및 주문 취소하기 본문
1. 주문 내역 조회하기
조회할 주문 데이터를 화면에 보낼 때 사용할 DTO 클래스를 만들어보자.
dto > OrderItemDto
package com.shop.shop.dto;
import com.shop.shop.entity.OrderItem;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OrderItemDto {
public OrderItemDto(OrderItem orderItem, String imgUrl){
this.itemNm = orderItem.getItem().getItemNm();
this.count = orderItem.getCount();
this.orderPrice = orderItem.getOrderPrice();
this.imgUrl = imgUrl;
}
private String itemNm; //상품명
private int count; //주문 수량
private int orderPrice; //주문 금액
private String imgUrl; //상품 이미지 경로
}
▹OrderItemDto 클래스의 생성자로 orderItem 객체와 이미지 경로를 파라미터로 받아서 멤버 변수 값을 세팅한다.
public OrderItemDto(OrderItem orderItem, String imgUrl){
주문 정보를 담을 클래스를 생성한다.
dto > OrderHistDto
package com.shop.shop.dto;
import com.shop.shop.constant.OrderStatus;
import com.shop.shop.entity.Order;
import lombok.Getter;
import lombok.Setter;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class OrderHistDto {
public OrderHistDto(Order order){
this.orderId = order.getId();
this.orderDate = order.getOrderDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
this.orderStatus = order.getOrderStatus();
}
private Long orderId; //주문아이디
private String orderDate; //주문날짜
private OrderStatus orderStatus; //주문 상태
private List<OrderItemDto> orderItemDtoList = new ArrayList<>();
//주문 상품리스트
public void addOrderItemDto(OrderItemDto orderItemDto){
orderItemDtoList.add(orderItemDto);
}
}
▹OrderHistDto 클래스의 생성자로 order 객체를 파라미터로 받아서 멤버 변수 값을 세팅한다. 주문 날짜의 경우 화면에 "yyyy-MM-dd HH:mm" 형태로 전달하기 위해서 포맷을 수정한다.
public OrderHistDto(Order order){
▹orderItemDto 객체를 주문 상품 리스트에 추가하는 메소드이다.
public void addOrderItemDto(OrderItemDto orderItemDto){
@Query 어노테이션을 이용하여 주문 이력을 조회하는 쿼리를 작성하자.
@Query 안에 들어가는 문법은 JPQL이다.
repository > OrderRepository
package com.shop.shop.repository;
import com.shop.shop.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("select o from Order o " +
"where o.member.email = :email " +
"order by o.orderDate desc"
)
List<Order> findOrders(@Param("email") String email, Pageable pageable);
@Query("select count(o) from Order o " +
"where o.member.email = :email"
)
Long countOrder(@Param("email") String email);
}
▹현재 로그인한 사용자의 주문 데이터를 페이징 조건에 맞춰서 조회한다.
List<Order> findOrders(@Param("email") String email, Pageable pageable);
▹현재 로그인한 회원의 주문 개수가 몇 개인지 조회한다.
Long countOrder(@Param("email") String email);
상품의 대표 이미지를 찾는 쿼리 메소드를 추가한다.
구매 이력 페이지에서 주문 상품의 대표 이미지를 보여주기 위함이다.
repository > ItemImgRepository
package com.shop.shop.repository;
import com.shop.shop.entity.ItemImg;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {
List<ItemImg> findByItemIdOrderByIdAsc(Long iemId);
ItemImg findByItemIdAndRepimgYn(Long itemId, String repimgYn);
}
주문 목록을 조회하는 로직을 구현해보자.
service > OrderService
package com.shop.shop.service;
import com.shop.shop.dto.OrderItemDto;
import com.shop.shop.entity.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.shop.shop.dto.OrderDto;
import com.shop.shop.dto.OrderHistDto;
import com.shop.shop.repository.ItemImgRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import com.shop.shop.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;
@Service
@Transactional
@RequiredArgsConstructor
public class OrderService {
private final ItemRepository itemRepository;
private final MemberRepository memberRepository;
private final OrderRepository orderRepository;
private final ItemImgRepository itemImgRepository;
...생략...
@Transactional(readOnly = true)
public Page<OrderHistDto> getOrderList(String email, Pageable pageable) {
List<Order> orders = orderRepository.findOrders(email, pageable);
Long totalCount = orderRepository.countOrder(email);
List<OrderHistDto> orderHistDtos = new ArrayList<>();
for (Order order : orders) {
OrderHistDto orderHistDto = new OrderHistDto(order);
List<OrderItem> orderItems = order.getOrderItems();
for (OrderItem orderItem : orderItems) {
ItemImg itemImg = itemImgRepository.findByItemIdAndRepimgYn
(orderItem.getItem().getId(), "Y");
OrderItemDto orderItemDto =
new OrderItemDto(orderItem, itemImg.getImgUrl());
orderHistDto.addOrderItemDto(orderItemDto);
}
orderHistDtos.add(orderHistDto);
}
return new PageImpl<OrderHistDto>(orderHistDtos, pageable, totalCount);
}
}
▹유저의 아디이와 페이징 조건을 이용하여 주문 목록을 조회한다.
List<Order> orders = orderRepository.findOrders(email, pageable);
▹유저의 주문 총 개수를 구한다.
Long totalCount = orderRepository.countOrder(email);
▹주문 리스트를 순회하면서 구매 이력 페이지에 전달할 DTO를 생성한다.
for (Order order : orders) {
▹주문한 상품의 대표 이미지를 조회한다.
ItemImg itemImg = itemImgRepository.findByItemIdAndRepimgYn
(orderItem.getItem().getId(), "Y");
▹페이지 구현 객체를 생성하여 반환한다.
return new PageImpl<OrderHistDto>(orderHistDtos, pageable, totalCount);
구매이력을 조회할 수 있도록 지금까지 구현한 로직을 호출하는 메소드를 만들어보자.
controller > OrderController
package com.shop.shop.controller;
import com.shop.shop.dto.OrderDto;
import com.shop.shop.dto.OrderHistDto;
import com.shop.shop.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
@Controller
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
...생략...
@GetMapping(value = {"/orders", "/orders/{page}"})
public String orderHist(@PathVariable("page") Optional<Integer> page, Principal principal, Model model){
Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 4);
Page<OrderHistDto> ordersHistDtoList = orderService.getOrderList(principal.getName(), pageable);
model.addAttribute("orders", ordersHistDtoList);
model.addAttribute("page", pageable.getPageNumber());
model.addAttribute("maxPage", 5);
return "order/orderHist";
}
}
▹한 번에 가지고 올 주문의 개수는 4개로 설정한다.
Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 4);
▹현재 로그인한 회원은 이메일과 페이징 객체를 파라미터로 전달하여 화면에 전달한 주문 목록 데이터를 리턴 값으로 받는다.
Page<OrderHistDto> ordersHistDtoList = orderService.getOrderList(principal.getName(), pageable);
구매 이력 페이지를 만들어서 주문 목록 데이터를 보여주는 html 파일을 만들어보자.
templates > order > orderHist.html
아래 링크에서 코드를 가져오면 된다.
https://github.com/roadbook2/shop/blob/master/src/main/resources/templates/order/orderHist.html
여기까지 하면 지금까지 주문했던 상품들의 목록이 화면에 보인다.
2. 주문 취소하기
상품의 재고를 더해주기 위해서 Item 클래스 addStock 메소드를 생성한다.
entity > Item
package com.shop.shop.entity;
import com.shop.shop.constant.ItemSellStatus;
import com.shop.shop.dto.ItemFormDto;
import com.shop.shop.exception.OutOfStockException;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name="item")
@Getter
@Setter
@ToString
public class Item extends BaseEntity{
...생략...
public void addStock(int stockNumber){
this.stockNumber += stockNumber;
}
}
▹상품의 재고를 증가시키는 메소드이다.
public void addStock(int stockNumber){
주문을 취소할 경우 주문 수량만큼 상품의 재고를 증가시키는 메소드를 구현해보자.
entity > OrderItem
package com.shop.shop.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
public class OrderItem extends BaseEntity{
...생략...
public void cancel() {
this.getItem().addStock(count);
}
}
▹주문 취소 시 주문 수량만큼 상품의 재고를 더해준다.
public void cancel() {
Item 클래스에 주문 취소 시 주문 수량을 상품의 재고에 더해주는 로직과 주문 상태를 취소 상태로 바꿔주는 메소드를 구현하자.
entity > Order
package com.shop.shop.entity;
import com.shop.shop.constant.OrderStatus;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order extends BaseEntity{
...생략...
public void cancelOrder() {
this.orderStatus = OrderStatus.CANCEL;
for (OrderItem orderItem : orderItems) {
orderItem.cancel();
}
}
}
주문을 취소하는 로직을 구현해보자.
service > OrderService
package com.shop.shop.service;
import com.shop.shop.dto.OrderItemDto;
import com.shop.shop.entity.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.shop.shop.dto.OrderDto;
import com.shop.shop.dto.OrderHistDto;
import com.shop.shop.repository.ItemImgRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import com.shop.shop.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;
@Service
@Transactional
@RequiredArgsConstructor
public class OrderService {
private final ItemRepository itemRepository;
private final MemberRepository memberRepository;
private final OrderRepository orderRepository;
private final ItemImgRepository itemImgRepository;
...생략...
@Transactional(readOnly = true)
public boolean validateOrder(Long orderId, String email){
Member curMember = memberRepository.findByEmail(email);
Order order = orderRepository.findById(orderId)
.orElseThrow(EntityNotFoundException::new);
Member savedMember = order.getMember();
if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){
return false;
}
return true;
}
public void cancelOrder(Long orderId){
Order order = orderRepository.findById(orderId)
.orElseThrow(EntityNotFoundException::new);
order.cancelOrder();
}
}
▹현재 로그인한 사용자와 주문 데이터를 생성한 사용자가 같은지 검사를 한다. 같을 때는 true를 반환하고 같지 않을 경우는 false를 반환한다.
public boolean validateOrder(Long orderId, String email){
▹주문 취소 상태로 변경하면 변경 감지 기능에 의해서 트랜잭션이 끝날 때 update 쿼리가 실행된다.
order.cancelOrder();
주문번호를 받아서 주문 취소 로직을 호출하는 메소드를 만들어보자. 상품을 장바구니에 담았을 때처럼 비동기 요청을 받아서 처리하는 것이다.
controller > OrderController
package com.shop.shop.controller;
import com.shop.shop.dto.OrderDto;
import com.shop.shop.dto.OrderHistDto;
import com.shop.shop.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
@Controller
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
...생략...
@PostMapping("/order/{orderId}/cancel")
public @ResponseBody ResponseEntity cancelOrder(@PathVariable("orderId") Long orderId , Principal principal){
if(!orderService.validateOrder(orderId, principal.getName())){
return new ResponseEntity<String>("주문 취소 권한이 없습니다.", HttpStatus.FORBIDDEN);
}
orderService.cancelOrder(orderId);
return new ResponseEntity<Long>(orderId, HttpStatus.OK);
}
}
▹자바스크립트에서 취소할 주문 번호는 조작이 가능하므로 다른 사람의 주문을 취소하지 못하도록 주문 취소 권한 검사를 한다.
if(!orderService.validateOrder(orderId, principal.getName())){
▹주문 취소 로직을 호출한다.
orderService.cancelOrder(orderId);
2-1. 테스트 코드 작성
주문 취소 로직이 제대로 동작하는지 확인해야 한다.
package com.shop.shop.service;
import com.shop.shop.constant.ItemSellStatus;
import com.shop.shop.constant.OrderStatus;
import com.shop.shop.dto.OrderDto;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.Member;
import com.shop.shop.entity.Order;
import com.shop.shop.entity.OrderItem;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import com.shop.shop.repository.OrderRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
@Autowired
ItemRepository itemRepository;
@Autowired
MemberRepository memberRepository;
...생략...
@Test
@DisplayName("주문 취소 테스트")
public void cancelOrder(){
Item item = saveItem();
Member member = saveMember();
OrderDto orderDto = new OrderDto();
orderDto.setCount(10);
orderDto.setItemId(item.getId());
Long orderId = orderService.order(orderDto, member.getEmail());
Order order = orderRepository.findById(orderId)
.orElseThrow(EntityNotFoundException::new);
orderService.cancelOrder(orderId);
assertEquals(OrderStatus.CANCEL, order.getOrderStatus());
assertEquals(100, item.getStockNumber());
}
}
▹테스트를 위해서 상품과 회원 데이터를 생성한다. 생성한 상품의 재고는 100개다.
Item item = saveItem();
Member member = saveMember();
▹테스트를 위해서 주문 데이터를 생성한다. 주문 개수는 총 10개다.
Long orderId = orderService.order(orderDto, member.getEmail());
▹생성한 주문 엔티티를 조회한다.
Order order = orderRepository.findById(orderId)
.orElseThrow(EntityNotFoundException::new);
▹해당 주문을 취소한다.
orderService.cancelOrder(orderId);
▹주문의 상태가 취소 상태라면 테스트가 통과한다.
assertEquals(OrderStatus.CANCEL, order.getOrderStatus());
▹취소 후 상품의 재고가 처음 재고 개수인 100개와 동일하다면 테스트가 통과한다.
assertEquals(100, item.getStockNumber());
테스트 실행 결과 정상적으로 작동한다.
2-3. 자바스크립트 함수 만들기
구현한 주문 취소 기능을 호출하는 자바스크립트 함수를 만들어보자.
templates > order > orderHist.html
▹취소할 주문 번호를 파라미터로 넘겨준다.
var paramData = {
▹주문이 정상적으로 취소됐으면 현재 페이지로 다시 redirect 한다.
success : function(result, status){
주문취소 기능 구현이 완료됐다. 버튼을 클릭하면 "취소 완료"라는 텍스트 메시지가 뜬다.
'프레임워크 > Springboot' 카테고리의 다른 글
[Springboot] 장바구니 기능 (0) | 2023.03.27 |
---|---|
[Springboot] 상품 주문 기능 구현하기 (0) | 2023.03.20 |
[Springboot] 등록 상품 메인 화면에서 보기 / 상품 상세 페이지 (0) | 2023.03.15 |
[Springboot] 상품 관리하기 (2) | 2023.03.14 |
[Springboot] 상품 수정하기 (0) | 2023.03.13 |