WEB JAVA SPRING/PROJECT

7.주문 기능

sshhhh 2023. 9. 12. 17:02

고객이 상품 주문하는 기능 구현

주문하기 클릭 시
비회원은 주문하지 못한다.
재고 없을시
수량 0개로 주문 시

 

 

 

1.OutOfStiockException

/**
 * - 고객이 상품을 주문하면 현재 상품의 재고에서 주문수량만큼 재고를 감소시켜야한다.
 * - 주문수량 > 재고수량 == 주문x 이럴때 발생시킬 exception 정의
 */

package com.shop.exception;

public class OutOfStiockException extends RuntimeException {

    public OutOfStiockException(String message){
        super(message);
    }
}

 

 

 

2.Item

//상품 재고 감소 로직

//엔티티 안에 비즈니스 로직 작성하는 이유 : 코드의 재사용, 데이터의 변경 포인트 한군데로 모음

public void removeStock(int stockNumber){
    int restStock =this.stockNumber -stockNumber; //상품 재고수량 - 주문 = 남은 재고 수량

    //주문수량 > 재고
    if(restStock<0){
        throw new OutOfStiockException("상품의 재고가 부족합니다. 현재 재고 수량 : " + this.stockNumber);
    }
    //상품의 현재 재고값으로 할당 <= 주문 후 남은 재고 수량을
    this.stockNumber = restStock;
}

 

 

 

3.OrderItem

//상품 주문

//주문 가격 *수량 = 총 가격

//상품 주문
public static OrderItem createOrderItem(Item item, int count){
    OrderItem orderItem = new OrderItem();

    orderItem.setItem(item); //주문 상품
    orderItem.setCount(count); //주문 수량 세팅
    orderItem.setOrderPrice(item.getPrice()); //나중에 쿠폰이나 할인 설정

    //주문한 수량만큼 재고 감소
    item.removeStock(count);
    return orderItem;
}

//주문 가격 *수량 = 총 가격
public int getTotalPrice(){
    return orderPrice*count;
}

 

 

 

4.Order

//주문 객체 만들기

//주문 객체 만들기
public void addOrderItem(OrderItem orderItem) { //주문 상품 정보 담아줌
    orderItems.add(orderItem); //orderItem 객체를 order 객체의 orderItems에 추가합니다.
    orderItem.setOrder(this);
}


public static Order createOrder(Member member, List<OrderItem> orderItemList){
    Order order = new Order();
    order.setMember(member); //상품을 주문한 회원의 정보를 세팅합니다.

    /*
    상품 페이지에서는 1개의 상품을 주문하지만,
    장바구니 페이지에서는 한번에 여러개의 상품을 주문한다.
    따라서 여러개의 주문 상품을 담을 수 있도록 리스트 형태로 파라미터 값을 받으며
    주문 객체에 ordrItem 객체 추가
     */
    for(OrderItem orderItem : orderItemList){
        order.addOrderItem(orderItem);
    }
    order.setOrderStatus(OrderStatus.ORDER); //주문 상태 세팅
    order.setOrderDate(LocalDateTime.now()); //현재 시간을 주문 시간으로 세팅팅

    return order;
}

//총 주문 금액
public int getTotalPrice(){
    int totalPrice = 0;
    for(OrderItem orderItem : orderItems){
        totalPrice +=orderItem.getTotalPrice();
    }
    return  totalPrice;
}

 

 

 

5.OrderDto

/**
상품 상세 페이지에서 주문할 상품의 아이디와 주문 수량을 전달받음
 */

package com.shop.dto;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;


@Getter @Setter
public class OrderDto {

    @NotNull(message = "상품 아이디는 필수 입력 값입니다.")
    private Long itemId;

    @Min(value = 1, message = "최소 주문 수량은 1개 입니다.")
    @Max(value = 999, message = "최대 주문 수량은 999개 입니다.")
    private int count;


}

 

 

 

6.OrderService

//주문로직

public Long order(OrderDto orderDto, String email){
    Item item =itemRepository.findById(orderDto.getItemId()) //주문할 상품 조회
            .orElseThrow(EntityExistsException::new);
    Member member =memberRepository.findByEmail(email); //현재 로그인한 회원의 이메일 정보를 이용해서 회원 정보 조회

    List<OrderItem> orderItemList = new ArrayList<>();
    // 주문 상품 엔티티 생성 (주문할 상품 엔티티와 주문 수량을 이용)
    OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount());
    orderItemList.add(orderItem);

    //주문 엔티티 생성 (회원 정보와 주문할 상품 리스트 정보를 이용)
    Order order = Order.createOrder(member, orderItemList);
    orderRepository.save(order); //생성한 주문 엔티티 저장

    return order.getId();
}

 

 

 

7.OrderController

/**
 * 주문 관련 요청 처리
 * 상품 주문에서 웹페이지의 새로고침 없이 서버에 주문을 요청하기 위해
 * 비동기방식 사용
 * - @RequestBody : HTTP 요청의 본문 body에 담긴 내용을 자바 객체로 전달
 * - @ResponseBody : 자바 객체를 HTTP 요청의 body로 전달
 */

//주문
@PostMapping(value = "/order")
public @ResponseBody
ResponseEntity order(@RequestBody @Valid OrderDto orderDto
        , BindingResult bindingResult, Principal principal) {

    if (bindingResult.hasErrors()) {
        StringBuilder sb = new StringBuilder();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();

        for (FieldError fieldError : fieldErrors) {
            sb.append(fieldError.getDefaultMessage());
        }
        return new ResponseEntity<String>(sb.toString(), HttpStatus.BAD_REQUEST); //에러 정보를 ResponseEntity 객체에 담아서 반환
    }

    String email = principal.getName();
    Long orderId;

    try {
        orderId = orderService.order(orderDto, email);//화면으로부터 넘어오는 주문정보와 회원의 이메일 정보를 이용해 주문로직 호출
    } catch (Exception e) {
        return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }

    return new ResponseEntity<Long>(orderId, HttpStatus.OK); //결과값으로 생성된 주문번호와 요청이 성공했다는 HTTP 응답 상태 코드를 반환
}

 

 

 

 

 


itemDtl.html

$(document).ready(function(){

    calculateToalPrice();

    $("#count").change( function(){
        calculateToalPrice();
    });
});

function calculateToalPrice(){
    var count = $("#count").val(); //수량
    var price = $("#price").val(); //가격
    var totalPrice = price*count; //결제 금액
    $("#totalPrice").html(totalPrice + '원');
}

 

// 주문 ajax
function order(){
    //시큐리티 사용할 경우 post방식의 데이터 전송에는 csrf 토큰 값이 필요
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");

    var url = "/order";
    var paramData = { //전달할 객체 생성
        itemId : $("#itemId").val(), //주문할 상품의 아이디
        count : $("#count").val() //주문 수량 데이터
    };

    var param = JSON.stringify(paramData);

    $.ajax({
        url      : url,
        type     : "POST",
        contentType : "application/json", //서버에 데이터 보낼 형식
        data     : param,
        beforeSend : function(xhr){
            /!* 데이터를 전송하기 전에 헤더에 csrf값을 설정 *!/
            xhr.setRequestHeader(header, token);
        },
        dataType : "json", //서버에서 결과값으로 받을 데이터 타입
        cache   : false,
        success  : function(result, status){ //주문 로직 호출 성공시
            alert("주문이 완료 되었습니다.");
            location.href='/'; //메인으로
        },
        error : function(jqXHR, status, error){

            if(jqXHR.status == '401'){
                alert('로그인 후 이용해주세요');
                location.href='/members/login';
            } else{
                alert(jqXHR.responseText); //에러 발생시 해당 메세지
            }

        }
    });
}
<div layout:fragment="content" style="margin-left:25%;margin-right:25%">

    <input type="hidden" id="itemId" th:value="${item.id}">

    <div class="d-flex">
        <div class="repImgDiv">
            <img th:src="${item.itemImgDtoList[0].imgUrl}" class = "rounded repImg" th:alt="${item.itemNm}">
        </div>
        <div class="wd50">
            <span th:if="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="badge badge-primary mgb-15">
                판매중
            </span>
            <span th:unless="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="badge btn-danger mgb-15" >
                품절
            </span>
            <div class="h4" th:text="${item.itemNm}"></div>
            <hr class="my-4">

            <div class="text-right">
                <div class="h4 text-danger text-left">
                    <input type="hidden" th:value="${item.price}" id="price" name="price">
                    <span th:text="${item.price}"></span>원
                </div>
                <div class="input-group w-50">
                    <div class="input-group-prepend">
                        <span class="input-group-text">수량</span>
                    </div>
                    <input type="number" name="count" id="count" class="form-control" value="1" min="1">
                </div>
            </div>
            <hr class="my-4">

            <div class="text-right mgt-50">
                <h5>결제 금액</h5>
                <h3 name="totalPrice" id="totalPrice" class="font-weight-bold"></h3>
            </div>
            <div th:if="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="text-right">
                <button type="button" class="btn btn-light border border-primary btn-lg" onclick="addCart()">장바구니 담기</button>
                <button type="button" class="btn btn-primary btn-lg" onclick="order()">주문하기</button>
            </div>
            <div th:unless="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL}" class="text-right">
                <button type="button" class="btn btn-danger btn-lg">품절</button>
            </div>
        </div>
    </div>

    <div class="jumbotron jumbotron-fluid mgt-30">
        <div class="container">
            <h4 class="display-5">상품 상세 설명</h4>
            <hr class="my-4">
            <p class="lead" th:text="${item.itemDetail}"></p>
        </div>
    </div>

    <!--등록된 상품 이미지-->
    <div th:each="itemImg : ${item.itemImgDtoList}" class="text-center">
        <img th:if="${not #strings.isEmpty(itemImg.imgUrl)}" th:src="${itemImg.imgUrl}" class="rounded mgb-15" width="800">
    </div>

</div>