사이드 프로젝트/CafeMate프로젝트(SpringBoot)

14주차 CafeMate 주문 관리 프로세스 구현

EVO. 2023. 5. 26. 01:19

어느새 인증 구현, 메뉴구현에 이어 가장 중요하다 싶은 주문 관리 페이지를 만들어 보도록 하겠습니다.

 

시나리오

1. 고객이 주문을 했을 때, 직원이 주문 등록 페이지에서 쉽게 주문 정보를 입력한다.

2. 직원은 간단한 폼을 사용하여 필요한 정보(이름,연락처,주문내역 등)을 입력하도록 한다.

3. 주문 등록 페이지에 주문 상태(조리중, 조리완료, 배달중,배달완료)를 등록하고 추후 변경할 수 있도록 합니다.

4. 메뉴 품절정보를 표시하여 직원들이 주문시 품절된 품목을 확인할 수 있도록 합니다.

 


 

일단 UI를 보면 로직을 짜기 쉽기에 이런식으로 만들면 될듯 합니다.

 

 

다시 정확하게 컨트롤러에 넘길 데이터를 알기 위해

주문 테이블 스키마를 자세히 살펴보겠습니다.

팀원이 만든 스키마 일부

 

그리고 팀원이 짠 OrderService 코드중 주문생성 로직 입니다.

@Transactional
    public OrderDto createOrder(String customerId, OrderDto orderDto, List<MenuDto> menuList){
        Optional<Customer> foundCustomer =
                customerRepository.findByCustomerId(customerId);

        if(foundCustomer.isEmpty()){
            throw new IllegalStateException("존재하지 않는 고객입니다.");
        }

        Long customerPK = foundCustomer.get().getId();
        orderDto.setCustomerId(customerPK);
        Order order = orderDtoToEntity(orderDto);
        Long orderId = orderRepository.save(order).getId();

        List<Long> menuIds = getMenuIds(menuList);
        List<OrderMenu> orderMenuList = getOrderMenuList(orderId, menuIds);
        orderRepository.saveMenus(order, orderMenuList)
                .addMenu(orderMenuList);

        return orderEntityToDto(order);
    }

 

코드리뷰했을때 이미 들었지만 다시 복기 하기 위해 해당 메소드의 로직을 설명해보겠습니다.

createOrder의 함수에 

회원아이디, 주문Dto(회원id(회원아이디아님),주문수량,결재액수,포인트사용량,처리유무(아마주문상태?-0,1),주문일자),주문한 메뉴들 리스트 를 컨트롤러에서 넘깁니다.

 

그것들을 받은 함수는 회원아이디로 DB에서 검색해 각종 회원정보가 들어있는 Customer 도메인으로 반환 받습니다.

그리고 그 도메인에서 회원에 대한 id(회원아이디가 아님)를 찾습니다.

그리고 그 회원id(회원아이디 아님)를 orderDto에 저장합니다. 이제 그 orderDto를 order도메인으로 바꿉니다.

이제 그 order도메인을 DB에 저장합니다. 그리고 반환된 orderId를 받습니다.

주문한 메뉴들의 id들을 받습니다.

주문한 메뉴들의 id들과 주문id를 각각을 매칭시켜 OrderMenu 객체들로 리스트로 묶어 반환합니다

private List<OrderMenu> getOrderMenuList(Long orderId, List<Long> menuIds){
       return menuIds.stream()
               .map(menuId->new OrderMenu(orderId, menuId))
               .collect(Collectors.toList());
    }

그리고 DB에 이 order들을 각각으로 나눠 저장합니다.

 public Order saveMenus(Order order, List<OrderMenu> menuList){
        for (OrderMenu orderMenu : menuList) {
            orderMenuRepository.save(orderMenu);
        }

        order.addMenu(menuList);

        return order;
    }

 

보시다 싶이 로직이 조금 불필요한 부분(밑줄친곳)이 있습니다. 코드리뷰를 하면서 팀원과 이 문제들을 직시하고 제가 한번 입맛에 맞게 고쳐보도록 하겠습니다.

그다음 문제는 DB스키마 문제인 것 같습니다. 주문Dto에는 수량만 저장되있고 메뉴Dto와 합쳐 주문메뉴Dto에서 주문과 메아이디를 연결하는데 그 수량이 어느 주문의 수량인지 알수가 없습니다.

예를들어 아이스커피 2잔 , 크루아상 1개를 회원 1이 주문을 하였다면 주문 Dto에 회원아이디1과 수량에 어떤걸 집어넣을지 알수가 없습니다.

아이스커피 2잔인 2를 집어넣으면

주문(주문아이디1,회원1,주문수량2,..) 메뉴(메뉴아이디1,아이스커피,...) 메뉴(메뉴아이디2,크루아상,...) 

주문메뉴 (주문메뉴아이디1,주문아이디1,메뉴아이디1) 주문메뉴(주문메뉴아이디2,주문아이디1,메뉴아이디2)

이런식으로 결과가 나올텐데

아무튼 주문에 저장된 주문아이디1에는 아이스커피의 수량 2일뿐 크루아상 수량1을 알 방법이 없습니다.

이에 대한 문제를 방금 제시 했고 팀원이 빠른 시간내에 해결을 하고 있습니다.

 

대화내용 일부 캡쳐

 

 


 

문제 해결

팀원이 그 수량문제에 대해 해결을 위해  작성한 클래스 다이어그램 입니다

저 역시 빨리 이 기능을 구현하기 위해 다이어그램만을 보고 작업을 진행하도록 하겠습니다

제가 만들 컨트롤러는 OrderService에게

고객아이디(id 아님)orderDto(회원id(회원아이디아님),주문수량,결재액수,포인트사용량,처리유무(아마주문상태?-0,1),주문일자)와 MenuDto와 Integer(아마 수량?)을 매칭한 Map 형태로 orderMenuInfo에 전달하는 것으로 보입니다.

그 뒤 작업은 추후에 팀원이 작업한 코드를 봐야 알거같습니다. 

 

일단 저의 역할은 컨트롤러와 프론트단에서 데이터를 던져주는 작업을 하는 것이기에 세가지 인자를 어떻게 보낼 지만 결정하면 됩니다

 


로직 구현

 

1. 주문 등록 페이지가 반환이 되면 현재 판매중인 메뉴들이 나오도록 하고 

2. 고객 이름(고객 아이디)를 선택하면 연락처와 포인트가 나타나도록 하겠습니다.

3. 주문추가 버튼을 누르면 JSON 객체 배열로 컨트롤러 단에 보낼 수 있도록 하겠습니다.

 

1. 현재 판매중인 메뉴들을 가져오기

 

기존에 사용했던 메뉴리스트 가져오기로 데이터를 일단 받습니다. 

이번엔 주문 리스트 구현이 좀 어렵기에 공개 안했던 코드 모두 쓰도록 하겠습니다.

    //(1)메뉴 리스트 가져오기
    function menuLoad(){
      //get은 디폴트
      $.ajax({
        url:`/api/menu`,
        dataType:"json"
      }).done(res=>{
        console.log("성공" , res);

        res.data.forEach((menu)=>{
           console.log(menu);

        });

      }).fail(error=>{
        console.log("오류",error);
      })
    }

    menuLoad();

 

정상적으로 데이터가 들어오는지 확인하기위해 메뉴페이지를 요청하도록 OrderController를 만들겠습니다.

@RequiredArgsConstructor
@Controller
public class OrderController {

    //주문 등록 페이지 응답
    @GetMapping("/admin/order")
    public String showOrderRegistrationForm(){
        return "order/orderRegisterForm";
    }

}

 

정상적으로 가져옵니다. 이제 받아온 JSON 객체들을 랜더링 하겠습니다.

진행중에 +와 -버튼의 이벤트가 작동하지 않아 꽤 시간을 썼습니다 거기에 시간을 쓴 이유가

JavaScript의 정확한 지식을 가지고 있지 않아 그렇습니다. 작동하지 않은 이유가 다음과 같았습니다.

 

https://babgeuleus.tistory.com/64

 

이벤트 처리기가 동적으로 생성된 요소에 이벤트를 처리 못했던 이유

https://babgeuleus.tistory.com/58 14주차 CafeMate 주문 관리 프로세스 구현 babgeuleus.tistory.com 다음 글에서 진행중 메뉴들을 동적으로 받아오고 동적으로 받아온 메뉴들에 +와 -버튼에 클릭이벤트를 달아주

babgeuleus.tistory.com

제가 정리한 글이며 아무튼 해결이 되었습니다.

 

 

$(document).ready(function (){
  //(1)메뉴 리스트 가져오기
  function menuLoad(){
    //get은 디폴트
    $.ajax({
      url:`/api/menu`,
      dataType:"json"
    }).done(res=>{
      //console.log("성공" , res);

      res.data.forEach((menu)=>{
        let menuItem = getMenuItem(menu)
        $("#menu_item_container").append(menuItem);
      });

    }).fail(error=>{
      console.log("오류",error);
    })
  }

  menuLoad();


  //(2)메뉴리스트 랜더링
  function  getMenuItem(menu){
    //판매중인 메뉴만 보이도록 함
    if(menu.on_sale){
      let item = `
                        <div class="col-md-4">
                            <div class="card mb-4">
                                <div class="card-body">
                                    <h5 class="card-title">${menu.product_name} <br>  ${menu.price}원</h5>
                                    <div class="input-group">
                                        <button class="btn btn-outline-secondary decrease-qty" type="button" id="${menu.id}-minus" data-price="${menu.price}">&minus;</button>
                                        <input type="text" class="form-control text-center qty-input" id="${menu.id}-qty" value="0" readonly>
                                        <button class="btn btn-outline-secondary increase-qty" type="button" id="${menu.id}-plus" data-price="${menu.price}">&plus;</button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    `;
      return item;
    }
  }

 

받은 json 객체를 하나씩 렌더링해서 판매중일때만 카드를 만들도록 합니다.

이제 정상적으로 판매중인 메뉴들을 가져오도록 하였습니다. 

 

2. 고객 아이디를 모두 가져오도록 하고 고객 아이디를 선택할때마다 고객에 보유하고 있는 포인트와 번호가 나오도록 하기

 

고객아이디를 가져오는 것은 메뉴를 가져오는 것과 같은 방법으로 하면 됩니다.

 

다음 고객아이디를 선택하면 고객에 대한 포인트와 번호를 가지고 오는 방법은 여러가지 있습니다.

 

그중 하나는 아까 고객아이디를 가져올 때 이런식으로 모든 고객의 정보를  RespDto에 담아 보냈습니다.

//고객리스트 데이터들 불러오기
    @GetMapping("/api/customers")
    public ResponseEntity<?>customerList(){
        List<CustomerDto> customers = customerService.findAll();
        return new ResponseEntity<>(new RespDto<>(1,"성공",customers), HttpStatus.OK);
    }

이런식으로 JSON 객체 형태로 보낸 고객 정보들을

하나씩 <option>태그에 고객아이디와 함께 만들면서 랜더링을 하게되는데 이 고객 정보들을 자바스크립트 배열에 담아 어떤 아이디를 선택하면 이벤트를 발생시켜

이 자바스크립트 배열에 담긴 것을 모두 탐색하면서 이벤트를 발생시킨 아이디와 매칭되는것을 뽑아 포인트와 번호를 뽑아오는 방식입니다.

 

제가 생각하는 이 장점은 이미 서버를 통해 요청한 모든 고객들의 정보를 담아왔기 때문에 또다시 서버에 요청하는 것 없이 프론트단에서 잘 활용하면 됩니다. 응답속도면에서 빠를 것 같습니다.

 

두번째방식은 option태그에 단 value가 고객아이디의 id로 되있으므로 option를 택했을때 서버에 해당 id를 보내 DB에서 그 id를 찾아 해당 고객 정보를 가져오는 것 입니다. 

 

이 두번째 방식은 응답속도 면에서 불리해 보이지만 좀더 프론트단에서 추가 작업 보다 로직면에서 깔끔해 보이긴 합니다

 

하지만 불필요하게 서버에 재요청하는 것은 아니다 싶어

첫번째 방식으로 진행을 하겠습니다.

 

$("#customer_name").on("change", function(){
    //고객의 id를 가져오기
    let id = $(this).val();
    //그 id와 매칭되는것을 찾는다
    customerArray.forEach((customer)=>{
      //일치한다면 포인트와 연락처 출력
      if(customer.id==id){
        $("#customer_phone").val(customer.phoneNumber);
        $("#points").val(customer.savedPoint);
      }
    });


  });

 

실제로 선택할때마다 출력이 잘되고 있습니다.

 

 

3. 주문추가 버튼을 누르면 해당 모든 정보들을 JSON 객체에 담아 컨트롤러에 보낸다

 

이제 주문 등록 페이지에서 입력한 정보들을 서버에 잘 보내주도록 하겠습니다.

일단 보시면 고객아이디,연락처,포인트,포인트사용여부,결제금액은 form 형태로 보낼 수 있지만

수량은 보내기가 좀 어려워 보입니다.

수량 역시 input으로 되있지만 해당 메뉴이름도 보내야하기 때문입니다. 그리고 애초에 요즘은 form으로 보내지 않습니다. form의 content-type의 형식은 application/x-www-form-urlencoded이기 때문입니다.

따라서 ajax형태로 json 형식으로 request 하도록 합시다.

 

컨트롤러에 보낼 http body의 json데이터는 다음과 같습니다.

{
  [
    {"menu": "아이스 아메리카노", "qty": "2"},
    {"menu": "맘모스빵", "qty": "2"}
  ],
  
    "customerId": "cos1234",
    "total_amount": "19600"
  
  
    "point": "0"
  
}

JSON 데이터에는 다음과 같은 세가지 종류의 데이터를 포함하고 있습니다.

1. 메뉴정보(menu,qty)

2. 고객 정보(customerId,total_amount)

3. 포인트 정보(point)

 

//주문추가 버튼 클릭시
  $("#add_order").click(function() {
    let orderData = {};
    let menuData = [];
    let sumQty = 0;
    console.log(menuItems);
    //메뉴와 수량 집어넣기
    $(".qty-input").each(function() {
      let menuValue = $(this).attr("id").replace("-qty", "");



      let menuName = menuItems.find(item => item.value == menuValue).name;
      let quantity = $(this).val();

      if (quantity > 0) {
        sumQty+=quantity;
        menuData.push({
          menu: menuName,
          qty: quantity
        });
      }
    });
    console.log(menuData);
    orderData.quantity = sumQty;
    orderData.menuData=menuData;
    //포인트 사용,아이디,결제금액 푸시

    orderData.customerId = $("#customer_name option:selected").text();
    orderData.total_amount = $("#total_amount").val();

    //포인트 사용 체크여부 확인하고 푸시
    let checkbox = document.getElementById("use-points");
    if(checkbox.checked){
      let points = document.getElementById("points").value;
      orderData.point = points;
    }else{
      orderData.point = 0;
    }





    //JSON으로 잘 변환되는 지 테스트
    let jsonOrderData = JSON.stringify(orderData);

    console.log(jsonOrderData);

      $.ajax({
          url: "/api/order",
          method: "POST",
          dataType: "json",
          contentType: "application/json; charset=UTF-8",
          data: jsonOrderData,
          success: function(data) {
              console.log("JSON 데이터 전송 성공");
          },
          error: function(error) {
              console.log("JSON 데이터 전송 오류", error);
          }
      });


  });

 

 

잘 json으로 파싱된 jsonOrderData를 컨트롤러에서 받아주면 됩니다.

잘 받아주기 위해서는 새로운 dto가 필요하기 때문에 OrderRequestDto를 만들었습니다. 자바스크립트에서 보시다싶이 menuData는 배열로 이루어져있습니다. 따라서 List형태로 받아야 합니다.

그리고 그 배열안에는 menu,qty로 이루어져 있으므로 역시 그것도 잘 받기 위해 MenuItem 클래스를 제작했습니다.

 

MenuItem

@Data
public class MenuItem {

        private String menu;
        private Integer qty;

        // 기본 생성자
        public MenuItem() {}

        // 인자가 있는 생성자
        public MenuItem(String menu, int qty) {
            this.menu = menu;
            this.qty = qty;
        }

   

}

 

 

OrderRequestDto

@Data
public class OrderRequestDto {
    private int quantity;
    private List<MenuItem> menuData;
    private String customerId;
    private int total_amount;
    private int point;
}

 

 

OrderApiController

    @PostMapping("/api/order")
    public ResponseEntity<?> orderRegisteration(@RequestBody OrderRequestDto orderRequestDto){

        //데이터 제대로 받아오는 지 체크
        /*

        System.out.println("sssss");
        List<MenuItem> menuItems = orderRequestDto.getMenuData();

        for(MenuItem item : menuItems){
            System.out.println(item.getMenu()+item.getQty());
        }

        */
        //고객아이디 받아오기
        String customerId = orderRequestDto.getCustomerId();
        //주문데이터 받아오기
        OrderDto orderDto = new OrderDto(orderRequestDto.getQuantity(), orderRequestDto.getTotal_amount(),orderRequestDto.getPoint(),false,null);
        //메뉴와 수량 받아오기
        Map<MenuDto,Integer> orderMenuInfo = new HashMap<MenuDto,Integer>();
        List<MenuItem> menuItems = orderRequestDto.getMenuData();
        for(MenuItem item : menuItems){
            String menuName = item.getMenu();
            int qty = item.getQty();

            MenuDto menuDto = new MenuDto(menuName);

            orderMenuInfo.put(menuDto,qty);
        }

        //주문생성 서비스 실행
        orderService.createOrder(customerId,orderDto,orderMenuInfo);

        return new ResponseEntity<>(new HashMap<>(), HttpStatus.OK);


    }

 

더 자세한 정보는 아래 주소에서 확인해주시길 바랍니다.

https://github.com/minsang-alt/CafeMate

 

GitHub - minsang-alt/CafeMate: 소상공인 프로젝트

소상공인 프로젝트. Contribute to minsang-alt/CafeMate development by creating an account on GitHub.

github.com