diff --git a/README.md b/README.md index 4a91e3f71..f39202405 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ docker compose -p kitchenpos up -d - 상품을 등록할 수 있다. - 상품의 가격이 올바르지 않으면 등록할 수 없다. - - 상품의 가격은 0원 이상이어야 한다. + - 상품의 가격은 0원 이상이어야 한다. - 상품의 이름이 올바르지 않으면 등록할 수 없다. - - 상품의 이름에는 비속어가 포함될 수 없다. + - 상품의 이름에는 비속어가 포함될 수 없다. - 상품의 가격을 변경할 수 있다. - 상품의 가격이 올바르지 않으면 변경할 수 없다. - - 상품의 가격은 0원 이상이어야 한다. + - 상품의 가격은 0원 이상이어야 한다. - 상품의 가격이 변경될 때 메뉴의 가격이 메뉴에 속한 상품 금액의 합보다 크면 메뉴가 숨겨진다. - 상품의 목록을 조회할 수 있다. @@ -26,7 +26,8 @@ docker compose -p kitchenpos up -d - 메뉴 그룹을 등록할 수 있다. - 메뉴 그룹의 이름이 올바르지 않으면 등록할 수 없다. - - 메뉴 그룹의 이름은 비워 둘 수 없다. + - 메뉴 그룹의 이름은 비워 둘 수 없다. + - 메뉴 그룹의 이름에는 비속어가 포함될 수 없다. - 메뉴 그룹의 목록을 조회할 수 있다. ### 메뉴 @@ -35,15 +36,14 @@ docker compose -p kitchenpos up -d - 상품이 없으면 등록할 수 없다. - 메뉴에 속한 상품의 수량은 0 이상이어야 한다. - 메뉴의 가격이 올바르지 않으면 등록할 수 없다. - - 메뉴의 가격은 0원 이상이어야 한다. + - 메뉴의 가격은 0원 이상이어야 한다. - 메뉴에 속한 상품 금액의 합은 메뉴의 가격보다 크거나 같아야 한다. - 메뉴는 특정 메뉴 그룹에 속해야 한다. - 메뉴의 이름이 올바르지 않으면 등록할 수 없다. - - 메뉴의 이름에는 비속어가 포함될 수 없다. + - 메뉴의 이름에는 비속어가 포함될 수 없다. - 메뉴의 가격을 변경할 수 있다. - 메뉴의 가격이 올바르지 않으면 변경할 수 없다. - - 메뉴의 가격은 0원 이상이어야 한다. -- 메뉴에 속한 상품 금액의 합은 메뉴의 가격보다 크거나 같아야 한다. + - 메뉴의 가격은 0원 이상이어야 한다. - 메뉴를 노출할 수 있다. - 메뉴의 가격이 메뉴에 속한 상품 금액의 합보다 높을 경우 메뉴를 노출할 수 없다. - 메뉴를 숨길 수 있다. @@ -53,13 +53,13 @@ docker compose -p kitchenpos up -d - 주문 테이블을 등록할 수 있다. - 주문 테이블의 이름이 올바르지 않으면 등록할 수 없다. - - 주문 테이블의 이름은 비워 둘 수 없다. + - 주문 테이블의 이름은 비워 둘 수 없다. - 빈 테이블을 해지할 수 있다. - 빈 테이블로 설정할 수 있다. - 완료되지 않은 주문이 있는 주문 테이블은 빈 테이블로 설정할 수 없다. - 방문한 손님 수를 변경할 수 있다. - 방문한 손님 수가 올바르지 않으면 변경할 수 없다. - - 방문한 손님 수는 0 이상이어야 한다. + - 방문한 손님 수는 0 이상이어야 한다. - 빈 테이블은 방문한 손님 수를 변경할 수 없다. - 주문 테이블의 목록을 조회할 수 있다. @@ -73,7 +73,7 @@ docker compose -p kitchenpos up -d - 매장 주문은 주문 항목의 수량이 0 미만일 수 있다. - 매장 주문을 제외한 주문의 경우 주문 항목의 수량은 0 이상이어야 한다. - 배달 주소가 올바르지 않으면 배달 주문을 등록할 수 없다. - - 배달 주소는 비워 둘 수 없다. + - 배달 주소는 비워 둘 수 없다. - 빈 테이블에는 매장 주문을 등록할 수 없다. - 숨겨진 메뉴는 주문할 수 없다. - 주문한 메뉴의 가격은 실제 메뉴 가격과 일치해야 한다. @@ -94,108 +94,367 @@ docker compose -p kitchenpos up -d - 완료되지 않은 매장 주문이 있는 주문 테이블은 빈 테이블로 설정하지 않는다. - 주문 목록을 조회할 수 있다. -## 용어 사전 +--- 이전 단계에서 만든 요구사항 --- -### 상품 +### 1. 메뉴(menu) 요구 사항 -| 한글명 | 영문명 | 설명 | -| --- | --- | --- | -| 상품 | product | 메뉴를 관리하는 기준이 되는 데이터 | -| 이름 | displayed name | 음식을 상상하게 만드는 중요한 요소 | + - [] : 메뉴는 등록할 수 있다. + - [] : 메뉴를 등록할 때, 메뉴 그룹에게 메뉴 검증을 요청한다. + - [] : 메뉴는 하나 이상의 메뉴 그룹으로 등록할 수 있다. + - [] : 메뉴의 가격을 변경할 수 있다. + - [] : 전체 메뉴를 볼 수 있다. + - [] : 메뉴의 가격은 0원 이상이어야 한다. + - [] : 메뉴에는 이름이 존재한다. + - [] : 판매 가격은 마진이 남아야 한다. + - [] : 메뉴는 게시 여부를 정할 수 있다. + - [] : 메뉴는 하나의 카테고리에 속한다. + - [] : 메뉴 이름에는 비속어를 넣을 수 없다. -### 메뉴 +### 1-1. 메뉴 상품(menu-product) 요구 사항 + + - [] : 메뉴 상품은 메뉴 검증을 할 수 있다. + - [] : 메뉴 상품은 메뉴와 수량으로 이루어져 있다. + - [] : 메뉴 상품의 메뉴 수량은 0보다 커야 한다. + +### 2. 카테고리(menu-group) 요구 사항 + + - [] : 메뉴 카테고리는 이름을 가지고 있다. + - [] : 전체 메뉴 카테고리를 볼 수 있다. + - [] : 메뉴 카테고리를 등록할 수 있다. + - [] : 메뉴 카테고리에는 비속어를 넣을 수 없다. + +### 3. 상품(product) 요구 사항 + + - [] : 상품을 등록할 수 있다. + - [] : 상품의 가격을 변경할 수 있다. + - [] : 전체 상품을 볼 수 있다. + - [] : 상품에는 가격와 이름이 있다. + - [] : 상품의 가격은 0원 이상이어야 한다. + - [] : 상품 이름에는 비속어를 넣을 수 없다. + - [] : 상품 가격이 변하면, 메뉴에 마진이 남는지 물어본다. + - [] : 마진이 남지 않는다면, 메뉴 게시를 중단한다. + +### 4. 주문(order) 요구 사항 + + - [] : 주문은 하나 이상의 주문 내역으로 한다. + - [] : 주문이 정상적인지 확인하기 위해 주문 내역에게 주문 검증을 요청한다. + - [] : 주문을 할 때는 게시된 메뉴만 주문할 수 있다. + - [] : 모든 주문을 볼 수 있다. + - [] : 주문은 3가지 종류가 존재한다. (배달, 포장, 매장 식사) + - [] : 주문에는 6가지 주문 상태가 존재한다. (주문 대기 중, 주문 접수 완료, 서빙 완료, 배달 중, 배달 완료, 주문 완료) + +### 4-1. 배달 주문 요구 사항 + + - [] : 배달은 주소로 배달을 요청한다. + - [] : 배달 시 주소는 꼭 필요하다. + + - 배달 주문의 전체적인 순서 + - [] : 1. 주문 대기 중 (waiting) => 주문 대기 중일 때만 주문을 접수할 수 있다. + - [] : 2. 주문 접수 완료 (accepted) => 배달부에게 받아야 할 돈, 주소를 알려주고 배달해줄 것을 요청한다. + - [] : 3. 배달 중 (delivering) + - [] : 4. 배달 완료 (delivered) + - [] : 5. 주문 완료 (completed) + +### 4-2. 포장 주문 요구 사항 + + - [] : 포장은 아무때나 가능하다. + + - 포장 주문의 전체적인 순서 + - [] : 1. 주문 대기 중 (waiting) => 주문 대기 중일 때만 주문을 접수할 수 있다. + - [] : 2. 주문 접수 완료 (accepted) + - [] : 3. 서빙 완료 (served) + - [] : 4. 주문 완료 (completed) + +### 4-3. 매장 식사 주문 요구 사항 -| 한글명 | 영문명 | 설명 | -| --- | --- | --- | -| 금액 | amount | 가격 * 수량 | -| 메뉴 | menu | 메뉴 그룹에 속하는 실제 주문 가능 단위 | -| 메뉴 그룹 | menu group | 각각의 메뉴를 성격에 따라 분류하여 묶어둔 그룹 | -| 메뉴 상품 | menu product | 메뉴에 속하는 수량이 있는 상품 | -| 숨겨진 메뉴 | not displayed menu | 주문할 수 없는 숨겨진 메뉴 | -| 이름 | displayed name | 음식을 상상하게 만드는 중요한 요소 | - -### 매장 주문 - -| 한글명 | 영문명 | 설명 | -| --- | --- | --- | -| 방문한 손님 수 | number of guests | 식기가 필요한 사람 수. 필수 사항은 아니며 주문은 0명으로 등록할 수 있다. | -| 빈 테이블 | empty table | 주문을 등록할 수 없는 주문 테이블 | -| 서빙 | served | 조리가 완료되어 음식이 나갈 수 있는 단계 | -| 완료 | completed | 고객이 모든 식사를 마치고 결제를 완료한 단계 | -| 접수 | accepted | 주문을 받고 음식을 조리하는 단계 | -| 접수 대기 | waiting | 주문이 생성되어 매장으로 전달된 단계 | -| 주문 | order | 매장에서 식사하는 고객 대상. 손님들이 매장에서 먹을 수 있도록 조리된 음식을 가져다준다. | -| 주문 상태 | order status | 주문이 생성되면 매장에서 주문을 접수하고 고객이 음식을 받기까지의 단계를 표시한다. | -| 주문 테이블 | order table | 매장에서 주문이 발생하는 영역 | -| 주문 항목 | order line item | 주문에 속하는 수량이 있는 메뉴 | - -### 배달 주문 - -| 한글명 | 영문명 | 설명 | -| --- | --- | --- | -| 배달 | delivering | 배달원이 매장을 방문하여 배달 음식의 픽업을 완료하고 배달을 시작하는 단계 | -| 배달 대행사 | delivery agency | 준비한 음식을 고객에게 직접 배달하는 서비스 | -| 배달 완료 | delivered | 배달원이 주문한 음식을 고객에게 배달 완료한 단계 | -| 서빙 | served | 조리가 완료되어 음식이 나갈 수 있는 단계 | -| 완료 | completed | 배달 및 결제 완료 단계 | -| 접수 | accepted | 주문을 받고 음식을 조리하는 단계 | -| 접수 대기 | waiting | 주문이 생성되어 매장으로 전달된 단계 | -| 주문 | order | 집이나 직장 등 고객이 선택한 주소로 음식을 배달한다. | -| 주문 상태 | order status | 주문이 생성되면 매장에서 주문을 접수하고 고객이 음식을 받기까지의 단계를 표시한다. | -| 주문 항목 | order line item | 주문에 속하는 수량이 있는 메뉴 | - -### 포장 주문 - -| 한글명 | 영문명 | 설명 | -| --- | --- | --- | -| 서빙 | served | 조리가 완료되어 음식이 나갈 수 있는 단계 | -| 완료 | completed | 고객이 음식을 수령하고 결제를 완료한 단계 | -| 접수 | accepted | 주문을 받고 음식을 조리하는 단계 | -| 접수 대기 | waiting | 주문이 생성되어 매장으로 전달된 단계 | -| 주문 | order | 포장하는 고객 대상. 고객이 매장에서 직접 음식을 수령한다. | -| 주문 상태 | order status | 주문이 생성되면 매장에서 주문을 접수하고 고객이 음식을 받기까지의 단계를 표시한다. | -| 주문 항목 | order line item | 주문에 속하는 수량이 있는 메뉴 | + - [] : 자신이 이용하고 있는(앉아있는) 매장 테이블에서만 매장 식사를 할 수 있다. + - [] : 매장 이용 시 주문한 테이블에 앉아 있어야 한다. + + - 매장 식사 주문의 전체적인 순서 + - [] : 1. 주문 대기 중 (waiting) => 주문 대기 중일 때만 주문을 접수할 수 있다. + - [] : 2. 주문 접수 완료 (accepted) + - [] : 3. 서빙 완료 (served) + - [] : 4. 주문 완료 (completed) => 주문 완료를 하면 테이블에 주문이 남아있는지 확인하고 없으면 테이블을 비운다. + +### 5. 주문 내역(order-line) 요구 사항 + + - [] : 주문 내역은 주문 검증을 할 수 있다. + - [] : 주문 내역에는 메뉴와 수량, 가격으로 이루어져 있다. + - [] : 주문 내역의 메뉴 수량은 1 이상이어야 한다. + - [] : 주문 내역의 메뉴는 게시 상태여야 한다. + - [] : 주문 시점의 가격과 메뉴의 가격은 동일해야 한다. + +### 6. 매장 테이블(order-table) 요구 사항 + + - [] : 매장 테이블을 만들 수 있다. + - [] : 매장 테이블에는 이름이 존재한다. + - [] : 전체 매장 테이블을 볼 수 있다. + - [] : 빈 테이블만 이용할(앉을) 수 있다. + - [] : 모든 주문이 완료되면, 테이블을 정리할 수 있다. + - [] : 테이블에 손님이 있는 경우에만 앉아 있는 손님의 수가 바뀔 수 있다. + +## 용어 사전 + +### 상품 (Product) + +| 한글명 | 영문명 | 설명 | +|-----|---------|------------------------| +| 상품 | product | `상품`은 `메뉴`를 구성하는 단위이다. | + +### 메뉴 카테고리 (Menu Group) + +| 한글명 | 영문명 | 설명 | +|---------|------------|-------------| +| 메뉴 카테고리 | menu-group | 메뉴의 카테고리이다. | + +### 메뉴 (Menu) + +| 한글명 | 영문명 | 설명 | +|-------|-----------------|-------------------------------------------------| +| 메뉴 | menu | `주문`을 구성하는 최소한의 단위이다. | +| 메뉴 게시 | menu display | `메뉴`를 `주문`할 수 있는지에 대한 여부이다. | +| 마진 | margin | `메뉴`가격이 `메뉴`를 이루는 `상품`들의 가격의 총합보다 높은 것을 말한다. | +| 메뉴 검증 | menu validation | `메뉴`의 `상품`들과 메뉴 상품의 구성이 동일하게 존재하는지 확인하는 것을 말한다. | +| 메뉴 상품 | menu-product | 단일 `상품`의 그룹이다. | + +### 매장 테이블 (Order Table) + +| 한글명 | 영문명 | 설명 | +|--------------|-----------------|-------------------------------------| +| 매장 테이블 | order-table | `매장 테이블`은 `매장 주문`을 할 수 있는 장소이다. | +| 앉기 | sit | `매장 테이블`을 `손님`이 점유한다. | +| 점유 상태 해지 | release table | `매장 주문 완료`시에 `매장 테이블의 점유 상태`를 해지한다. | +| 매장 테이블 점유 상태 | table occupancy | `매장 테이블`을 `손님`이 점유한 상태를 말한다. | + +### 주문 (Order) + +| 한글명 | 영문명 | 설명 | +|----------|------------------|---------------------------------------------------------------------| +| 주문 | order | `주문`은 `게시`된 `메뉴`를 구매하는 것이다. | +| 주문 유형 | order type | `배달 주문`, `포장 주문`, `매장 주문`로 구성된 `주문`의 유형이다. | +| 주문 상태 | order status | 현재 확인 가능한 `주문`의 상태이다.(주문 대기 중, 주문 접수 완료, 서빙 완료, 배달 중, 배달 완료, 주문 완료) | +| 주문 검증 | order validation | `주문` 내역에 수량, 가격 구성과 `주문`한 `메뉴`들의 수량, 가격 구성이 동일하게 존재하는지 검증한다. | +| 손님 | guest | `주문`을 목적으로 하는 사람이다. | +| 주문 생성 시간 | order time | `손님`이 주문한 시간이다. | + +### 유저 (User) + +| 한글명 | 영문명 | 설명 | +|-----|-------------|---------------------| +| 손님 | guest | `주문`을 목적으로 하는 사람이다. | +| 사장님 | store owner | `메뉴`를 만드는 사람이다. | + +### 배달 주문 (Delivery) + +| 한글명 | 영문명 | 설명 | +|-------------|---------------------------|---------------------------------------------------------------------------------------| +| 배달 주문 | delivery order | `주문 유형`이 `배달 주문`인 `주문`을 말한다. | +| 배달 순서 | delivery order flow | `배달 주문`의 과정의 흐름을 말한다.(배달 주문 대기 - 배달 주문 접수 완료 - 배달 주문 서빙 완료 - 배달 중 - 배달 완료 - 배달 주문 완료) | +| 배달 주문 대기 | delivery order waiting | `손님`이 `배달 주문`을 한 상태이다. | +| 배달 주문 접수 완료 | delivery order accepted | `배달 기사`가 `배달 주문`을 수락한 상태이다. | +| 배달 주문 서빙 완료 | delivery order served | 조리가 완료된 음식을 `배달 기사`에게 제공한 상태이다. | +| 배달 중 | delivery order delivering | `배달 기사`가 배달을 진행하고 있는 상태이다. | +| 배달 완료 | delivery order delivered | `배달 기사`가 `손님`의 배달을 완료한 상태이다. | +| 배달 주문 완료 | delivery order completed | `배달 순서`가 종료된 상태이다. | + +### 포장 주문 (Takeout) + +| 한글명 | 영문명 | 설명 | +|-------------|-------------------------|------------------------------------------------------------------------| +| 포장 주문 | takeout order | `주문 유형`이 `포장 주문`인 `주문`을 말한다. | +| 포장 순서 | takeout order flow | `포장 주문`의 과정의 흐름을 말한다.(포장 주문 대기 → 포장 주문 접수 완료 → 포장 주문 서빙 완료 → 포장 주문 완료) | +| 포장 주문 대기 | takeout order waiting | `손님`이 `포장 주문`을 한 상태이다. | +| 포장 주문 접수 완료 | takeout order accepted | `포장 주문`을 수락한 상태이다. | +| 포장 주문 서빙 완료 | takeout order served | `손님`에게 조리가 완료된 음식을 제공한 상태이다. | +| 포장 주문 완료 | takeout order completed | `포장 순서`가 종료된 상태이다. | + +### 매장 주문 (Eat-in) + +| 한글명 | 영문명 | 설명 | +|-------------|------------------------|------------------------------------------------------------------------| +| 매장 주문 | eat-in order | `주문 유형`이 `매장 주문`인 `주문`을 말한다. | +| 매장 주문 순서 | eat-in order flow | `매장 주문`의 과정의 흐름을 말한다.(매장 주문 대기 - 매장 주문 접수 완료 - 매장 주문 서빙 완료 - 매장 주문 완료) | +| 매장 주문 대기 | eat-in order waiting | `매장 테이블`을 `점유`하고 있는 `손님`이 `매장 주문`을 한 상태이다. | +| 매장 주문 접수 완료 | eat-in order accepted | `매장 주문`을 수락한 상태이다. | +| 매장 주문 서빙 완료 | eat-in order served | `손님`에게 조리가 완료된 음식을 제공한 상태이다. | +| 매장 주문 완료 | eat-in order completed | `매장 순서`가 종료된 상태로 `매장 테이블`의 `점유 상태를 해지`한다. | + +### 주문 내역 (Order Line Item) + +| 한글명 | 영문명 | 설명 | +|-----------|--------------------------|---------------------------------| +| 주문 내역 | order line item | `주문 내역`은 단일`주문`한 `메뉴`에 대한 그룹이다. | +| 주문 내역의 가격 | order line item price | 단일`주문` 메뉴의 `수량`에 따른 총 가격이다. | +| 주문 내역의 수량 | order line item quantity | 단일`주문` 메뉴의 총 수량이다. | + +### 외부 시스템 (external System) + +| 한글명 | 영문명 | 설명 | +|------------|-----------------------|-----------------| +| 비속어 검증 시스템 | purgomalum client | 단어가 비속어인지 검증한다. | +| 배달 기사 | kitchen riders client | 배달 서비스를 제공한다. | ## 모델링 -### 상품 +### 1. 메뉴 + +- `메뉴`는 여러 `상품` 과 `메뉴 게시`를 가진다. +- `메뉴`는 상품의 가격과 수량을 토대로 `마진`을 검증한다. + +### 2. 외부 시스템 + +- `비속어 검증 시스템`은 비속어를 검증한다. +- `배달 기사`는 배달 정보를 받아 배달을 간다. + +### 3. 주문 + +- `주문`은 주문 방식을 구별할 수 있는 `주문 유형`을 가진다. +- `주문`은 여러 `메뉴`를 가진다. +- `주문`은 손님이 원하는 `주문 유형`에 따라 `주문`을 수행한다. +- `주문`은 `주문 내역`을 토대로 `주문`을 검증한다. + +### 4. 유저 + +- `손님`은 `주문`을 한다. +- `손님`은 `매장 테이블`을 `점유`한다. +- `사장님`은 `메뉴`를 만든다. + +### 5. 배달 주문 + +- `배달 주문`은 `배달 기사`에게 배달을 요청한다. +- `배달 주문`은 현재 `배달 주문 상태`를 기반으로 `배달 주문 순서`에게 다음 순서의 `배달 주문`을 요청한다. +- `배달 주문 순서`는 이전 `배달 주문 상태`와 `배달 주문 순서`를 고려해 다음 순서의 `배달 주문`을 수행한다. + +### 6. 포장 주문 + +- `포장 주문`은 `손님`에게 조리된 음식을 포장 해준다. +- `포장 주문`은 현재 `포장 주문 상태`를 기반으로 `포장 주문 순서`에게 다음 순서의 `포장 주문`을 요청한다. +- `포장 주문 순서`는 이전 `포장 주문 상태`와 `포장 주문 순서`를 고려해 다음 순서의 `포장 주문`을 수행한다. + +### 7. 매장 주문 + +- `매장 주문`은 `매장 테이블을 점유`한 `손님`에게 조리된 음식을 제공한다. +- `매장 주문`은 현재 `매장 주문 상태`를 기반으로 `매장 주문 순서`에게 다음 순서의 `매장 주문`을 요청한다. +- `매장 주문 순서`는 이전 `매장 주문 상태`와 `매장 주문 순서`를 고려해 다음 순서의 `매장 주문`을 수행한다. + +### 8. 매장 테이블 + +- `매장 테이블`은 `매장 주문이 완료`되면 `테이블 점유를 해지`한다. + +### Kitchen Pos 모델링 + +```mermaid +flowchart TD + guest + store_owner +%% 외부 시스템 (ID: externalSystem) + subgraph externalSystem [외부 시스템] + KitchenRidersClient + PurgomalumClient + end + + style externalSystem stroke-dasharray: 5 +%% 메뉴 + subgraph 메뉴 + style 주문 stroke-dasharray: 5 + menu + product + menu --> product + end + +%% 주문 + subgraph 주문 + style 주문 stroke-dasharray: 5 + order + deliveryOrder + takeOutOrder + eatInOrder + DeliveryOrderFlow + TakeOutOrderFlow + EatInOrderFlow + OrderTable + order -- case:delivery orderType --> deliveryOrder + order -- case:takeOut orderType --> takeOutOrder + order -- case:eatIn orderType --> eatInOrder + deliveryOrder -- 배달 주문 시작 --> DeliveryOrderFlow + takeOutOrder -- 포장 주문 시작 --> TakeOutOrderFlow + eatInOrder -- 매장 주문 시작 --> EatInOrderFlow + EatInOrderFlow -- 매장 테이블 해지 요청 --> OrderTable + end + + guest -- <순서 2> : 주문 요청 --> order + guest -- 매장 테이블 점유 --> OrderTable + guest -- <순서 1> : 메뉴 선택 --> menu + store_owner -- 메뉴 생성 --> menu + DeliveryOrderFlow -- 배달 요청 --> KitchenRidersClient + menu -- 비속어 검증 --> PurgomalumClient + product -- 비속어 검증 --> PurgomalumClient -- `Product`는 식별자와 `DisplayedName`, 가격을 가진다. -- `DisplayedName`에는 `Profanity`가 포함될 수 없다. +``` -### 메뉴 +### 배달 주문 순서 + +```mermaid +sequenceDiagram + participant Guest + participant Delivery Order + participant Delivery Order Flow + participant Kitchen Riders Client + Guest ->> Delivery Order: 배달 주문 요청 + Delivery Order ->> Delivery Order Flow: 배달 주문 프로세스 시작 + Delivery Order Flow ->> Delivery Order: 배달 주문 대기 중 + Delivery Order ->> Delivery Order Flow: 배달 주문 접수 요청 + Delivery Order Flow -->> Kitchen Riders Client: 배달 요청 + Delivery Order Flow ->> Delivery Order: 배달 접수 완료 + Kitchen Riders Client -->> Delivery Order Flow: 배달 요청 확인 + Delivery Order ->> Delivery Order Flow: 배달 주문 서빙 요청 + Delivery Order Flow ->> Delivery Order: 배달 주문 서빙 완료 + Delivery Order ->> Delivery Order Flow: 배달 중 요청 + Delivery Order Flow ->> Delivery Order: 배달 중 + Delivery Order ->> Delivery Order Flow: 배달 완료 요청 + Delivery Order Flow ->> Delivery Order: 배달 완료 + Delivery Order ->> Delivery Order Flow: 배달 주문 완료 요청 + Delivery Order Flow ->> Delivery Order: 배달 주문 완료 + +``` + +### 포장 주문 순서 + +```mermaid +sequenceDiagram + participant Guest + participant TakeOut Order + participant TakeOut Order Flow + Guest ->> TakeOut Order: 포장 주문 요청 + TakeOut Order ->> TakeOut Order Flow: 포장 주문 프로세스 시작 + TakeOut Order Flow ->> TakeOut Order: 포장 주문 대기 중 + TakeOut Order ->> TakeOut Order Flow: 포장 주문 접수 요청 + TakeOut Order Flow ->> TakeOut Order: 포장 접수 완료 + TakeOut Order ->> TakeOut Order Flow: 포장 주문 서빙 요청 + TakeOut Order Flow ->> TakeOut Order: 포장 주문 서빙 완료 + TakeOut Order ->> TakeOut Order Flow: 포장 주문 완료 요청 + TakeOut Order Flow ->> TakeOut Order: 포장 주문 완료 -- `MenuGroup`은 식별자와 이름을 가진다. -- `Menu`는 식별자와 `Displayed Name`, 가격, `MenuProducts`를 가진다. -- `Menu`는 특정 `MenuGroup`에 속한다. -- `Menu`의 가격은 `MenuProducts`의 금액의 합보다 적거나 같아야 한다. -- `Menu`의 가격이 `MenuProducts`의 금액의 합보다 크면 `NotDisplayedMenu`가 된다. -- `MenuProduct`는 가격과 수량을 가진다. - -### 매장 주문 - -- `OrderTable`은 식별자와 이름, `NumberOfGuests`를 가진다. -- `OrderTable`의 추가 `Order`는 `OrderTable`에 계속 쌓이며 모든 `Order`가 완료되면 `EmptyTable`이 된다. -- `EmptyTable`인 경우 `NumberOfGuests`는 0이며 변경할 수 없다. -- `Order`는 식별자와 `OrderStatus`, 주문 시간, `OrderLineItems`를 가진다. -- 메뉴가 노출되고 있으며 판매되는 메뉴 가격과 일치하면 `Order`가 생성된다. -- `Order`는 접수 대기 ➜ 접수 ➜ 서빙 ➜ 계산 완료 순서로 진행된다. -- `OrderLineItem`는 가격과 수량을 가진다. -- `OrderLineItem`의 수량은 기존 `Order`를 취소하거나 변경해도 수정되지 않기 때문에 0보다 적을 수 있다. - -### 배달 주문 - -- `Order`는 식별자와 `OrderStatus`, 주문 시간, 배달 주소, `OrderLineItems`를 가진다. -- 메뉴가 노출되고 있으며 판매되는 메뉴 가격과 일치하면 `Order`가 생성된다. -- `Order`는 접수 대기 ➜ 접수 ➜ 서빙 ➜ 배달 ➜ 배달 완료 ➜ 계산 완료 순서로 진행된다. -- `Order`가 접수되면 `DeliveryAgency`가 호출된다. -- `OrderLineItem`는 가격과 수량을 가진다. -- `OrderLineItem`의 수량은 1보다 커야 한다. - -### 포장 주문 - -- `Order`는 식별자와 `OrderStatus`, 주문 시간, `OrderLineItems`를 가진다. -- 메뉴가 노출되고 있으며 판매되는 메뉴 가격과 일치하면 `Order`가 생성된다. -- `Order`는 접수 대기 ➜ 접수 ➜ 서빙 ➜ 계산 완료 순서로 진행된다. -- `OrderLineItem`는 가격과 수량을 가진다. -- `OrderLineItem`의 수량은 1보다 커야 한다. +``` + +### 매장 주문 순서 + +```mermaid +sequenceDiagram + participant Guest + participant EatIn Order + participant EatIn Order Flow + participant OrderTable + Guest ->> EatIn Order: 매장 주문 요청 + EatIn Order ->> EatIn Order Flow: 매장 주문 프로세스 시작 + EatIn Order Flow ->> EatIn Order: 매장 주문 대기 중 + EatIn Order ->> EatIn Order Flow: 매장 주문 접수 요청 + EatIn Order Flow ->> EatIn Order: 매장 접수 완료 + EatIn Order ->> EatIn Order Flow: 매장 주문 서빙 요청 + EatIn Order Flow ->> EatIn Order: 매장 주문 서빙 완료 + EatIn Order ->> EatIn Order Flow: 매장 주문 완료 요청 + EatIn Order Flow -->> OrderTable: 매장 테이블 점유 해제 요청 + EatIn Order Flow ->> EatIn Order: 매장 주문 완료 + OrderTable -->> EatIn Order Flow: 매장 테이블 점유 해제 + +``` diff --git a/build.gradle.kts b/build.gradle.kts index 67ab16154..743f6f0cb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { kotlin("jvm") version "1.9.23" kotlin("plugin.spring") version "1.9.23" kotlin("plugin.jpa") version "1.9.23" + kotlin("kapt") version "1.9.23" id("org.flywaydb.flyway") version "7.12.0" } @@ -32,6 +33,11 @@ dependencies { runtimeOnly("com.h2database:h2") runtimeOnly("com.mysql:mysql-connector-j") testImplementation("org.springframework.boot:spring-boot-starter-test") + + implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta") + kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") + kapt("jakarta.persistence:jakarta.persistence-api") + kapt("jakarta.annotation:jakarta.annotation-api") } tasks.withType { diff --git a/src/main/java/kitchenpos/products/infra/PurgomalumClient.java b/src/main/java/kitchenpos/common/application/PurgomalumClient.java similarity index 68% rename from src/main/java/kitchenpos/products/infra/PurgomalumClient.java rename to src/main/java/kitchenpos/common/application/PurgomalumClient.java index 4002a2bb8..47c31c355 100644 --- a/src/main/java/kitchenpos/products/infra/PurgomalumClient.java +++ b/src/main/java/kitchenpos/common/application/PurgomalumClient.java @@ -1,4 +1,4 @@ -package kitchenpos.products.infra; +package kitchenpos.common.application; public interface PurgomalumClient { boolean containsProfanity(String text); diff --git a/src/main/java/kitchenpos/common/config/QuerydslConfig.java b/src/main/java/kitchenpos/common/config/QuerydslConfig.java new file mode 100644 index 000000000..259af694d --- /dev/null +++ b/src/main/java/kitchenpos/common/config/QuerydslConfig.java @@ -0,0 +1,21 @@ +package kitchenpos.common.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + + private final EntityManager entityManager; + + public QuerydslConfig(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/kitchenpos/common/exception/GlobalExceptionHandler.java b/src/main/java/kitchenpos/common/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..353d5b25d --- /dev/null +++ b/src/main/java/kitchenpos/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,19 @@ +package kitchenpos.common.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity handleIllegalState(IllegalStateException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } +} diff --git a/src/main/java/kitchenpos/products/infra/DefaultPurgomalumClient.java b/src/main/java/kitchenpos/common/infra/external/DefaultPurgomalumClient.java similarity index 80% rename from src/main/java/kitchenpos/products/infra/DefaultPurgomalumClient.java rename to src/main/java/kitchenpos/common/infra/external/DefaultPurgomalumClient.java index 87dba885c..a4d421c88 100644 --- a/src/main/java/kitchenpos/products/infra/DefaultPurgomalumClient.java +++ b/src/main/java/kitchenpos/common/infra/external/DefaultPurgomalumClient.java @@ -1,12 +1,12 @@ -package kitchenpos.products.infra; +package kitchenpos.common.infra.external; +import java.net.URI; +import kitchenpos.common.application.PurgomalumClient; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; -import java.net.URI; - @Component public class DefaultPurgomalumClient implements PurgomalumClient { private final RestTemplate restTemplate; @@ -18,9 +18,9 @@ public DefaultPurgomalumClient(final RestTemplateBuilder restTemplateBuilder) { @Override public boolean containsProfanity(final String text) { final URI url = UriComponentsBuilder.fromUriString("https://www.purgomalum.com/service/containsprofanity") - .queryParam("text", text) - .build() - .toUri(); + .queryParam("text", text) + .build() + .toUri(); return Boolean.parseBoolean(restTemplate.getForObject(url, String.class)); } } diff --git a/src/main/java/kitchenpos/eatinorders/application/OrderTableService.java b/src/main/java/kitchenpos/eatinorders/application/OrderTableService.java deleted file mode 100644 index 1df7e345f..000000000 --- a/src/main/java/kitchenpos/eatinorders/application/OrderTableService.java +++ /dev/null @@ -1,78 +0,0 @@ -package kitchenpos.eatinorders.application; - -import kitchenpos.eatinorders.domain.OrderRepository; -import kitchenpos.eatinorders.domain.OrderStatus; -import kitchenpos.eatinorders.domain.OrderTable; -import kitchenpos.eatinorders.domain.OrderTableRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.UUID; - -@Service -public class OrderTableService { - private final OrderTableRepository orderTableRepository; - private final OrderRepository orderRepository; - - public OrderTableService(final OrderTableRepository orderTableRepository, final OrderRepository orderRepository) { - this.orderTableRepository = orderTableRepository; - this.orderRepository = orderRepository; - } - - @Transactional - public OrderTable create(final OrderTable request) { - final String name = request.getName(); - if (Objects.isNull(name) || name.isEmpty()) { - throw new IllegalArgumentException(); - } - final OrderTable orderTable = new OrderTable(); - orderTable.setId(UUID.randomUUID()); - orderTable.setName(name); - orderTable.setNumberOfGuests(0); - orderTable.setOccupied(false); - return orderTableRepository.save(orderTable); - } - - @Transactional - public OrderTable sit(final UUID orderTableId) { - final OrderTable orderTable = orderTableRepository.findById(orderTableId) - .orElseThrow(NoSuchElementException::new); - orderTable.setOccupied(true); - return orderTable; - } - - @Transactional - public OrderTable clear(final UUID orderTableId) { - final OrderTable orderTable = orderTableRepository.findById(orderTableId) - .orElseThrow(NoSuchElementException::new); - if (orderRepository.existsByOrderTableAndStatusNot(orderTable, OrderStatus.COMPLETED)) { - throw new IllegalStateException(); - } - orderTable.setNumberOfGuests(0); - orderTable.setOccupied(false); - return orderTable; - } - - @Transactional - public OrderTable changeNumberOfGuests(final UUID orderTableId, final OrderTable request) { - final int numberOfGuests = request.getNumberOfGuests(); - if (numberOfGuests < 0) { - throw new IllegalArgumentException(); - } - final OrderTable orderTable = orderTableRepository.findById(orderTableId) - .orElseThrow(NoSuchElementException::new); - if (!orderTable.isOccupied()) { - throw new IllegalStateException(); - } - orderTable.setNumberOfGuests(numberOfGuests); - return orderTable; - } - - @Transactional(readOnly = true) - public List findAll() { - return orderTableRepository.findAll(); - } -} diff --git a/src/main/java/kitchenpos/eatinorders/domain/OrderLineItem.java b/src/main/java/kitchenpos/eatinorders/domain/OrderLineItem.java deleted file mode 100644 index a5fe38278..000000000 --- a/src/main/java/kitchenpos/eatinorders/domain/OrderLineItem.java +++ /dev/null @@ -1,85 +0,0 @@ -package kitchenpos.eatinorders.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.persistence.Transient; -import kitchenpos.menus.domain.Menu; - -import java.math.BigDecimal; -import java.util.UUID; - -@Table(name = "order_line_item") -@Entity -public class OrderLineItem { - @Column(name = "seq") - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Id - private Long seq; - - @ManyToOne(optional = false) - @JoinColumn( - name = "menu_id", - columnDefinition = "binary(16)", - foreignKey = @ForeignKey(name = "fk_order_line_item_to_menu") - ) - private Menu menu; - - @Column(name = "quantity", nullable = false) - private long quantity; - - @Transient - private UUID menuId; - - @Transient - private BigDecimal price; - - public OrderLineItem() { - } - - public Long getSeq() { - return seq; - } - - public void setSeq(final Long seq) { - this.seq = seq; - } - - public Menu getMenu() { - return menu; - } - - public void setMenu(final Menu menu) { - this.menu = menu; - } - - public long getQuantity() { - return quantity; - } - - public void setQuantity(final long quantity) { - this.quantity = quantity; - } - - public UUID getMenuId() { - return menuId; - } - - public void setMenuId(final UUID menuId) { - this.menuId = menuId; - } - - public BigDecimal getPrice() { - return price; - } - - public void setPrice(final BigDecimal price) { - this.price = price; - } -} diff --git a/src/main/java/kitchenpos/eatinorders/domain/OrderTable.java b/src/main/java/kitchenpos/eatinorders/domain/OrderTable.java deleted file mode 100644 index bdd3dd23c..000000000 --- a/src/main/java/kitchenpos/eatinorders/domain/OrderTable.java +++ /dev/null @@ -1,60 +0,0 @@ -package kitchenpos.eatinorders.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -import java.util.UUID; - -@Table(name = "order_table") -@Entity -public class OrderTable { - @Column(name = "id", columnDefinition = "binary(16)") - @Id - private UUID id; - - @Column(name = "name", nullable = false) - private String name; - - @Column(name = "number_of_guests", nullable = false) - private int numberOfGuests; - - @Column(name = "occupied", nullable = false) - private boolean occupied; - - public OrderTable() { - } - - public UUID getId() { - return id; - } - - public void setId(final UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public int getNumberOfGuests() { - return numberOfGuests; - } - - public void setNumberOfGuests(final int numberOfGuests) { - this.numberOfGuests = numberOfGuests; - } - - public boolean isOccupied() { - return occupied; - } - - public void setOccupied(final boolean occupied) { - this.occupied = occupied; - } -} diff --git a/src/main/java/kitchenpos/eatinorders/ui/OrderTableRestController.java b/src/main/java/kitchenpos/eatinorders/ui/OrderTableRestController.java deleted file mode 100644 index 1ceabe86f..000000000 --- a/src/main/java/kitchenpos/eatinorders/ui/OrderTableRestController.java +++ /dev/null @@ -1,56 +0,0 @@ -package kitchenpos.eatinorders.ui; - -import kitchenpos.eatinorders.application.OrderTableService; -import kitchenpos.eatinorders.domain.OrderTable; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.net.URI; -import java.util.List; -import java.util.UUID; - -@RequestMapping("/api/order-tables") -@RestController -public class OrderTableRestController { - private final OrderTableService orderTableService; - - public OrderTableRestController(final OrderTableService orderTableService) { - this.orderTableService = orderTableService; - } - - @PostMapping - public ResponseEntity create(@RequestBody final OrderTable request) { - final OrderTable response = orderTableService.create(request); - return ResponseEntity.created(URI.create("/api/order-tables/" + response.getId())) - .body(response); - } - - @PutMapping("/{orderTableId}/sit") - public ResponseEntity sit(@PathVariable final UUID orderTableId) { - return ResponseEntity.ok(orderTableService.sit(orderTableId)); - } - - @PutMapping("/{orderTableId}/clear") - public ResponseEntity clear(@PathVariable final UUID orderTableId) { - return ResponseEntity.ok(orderTableService.clear(orderTableId)); - } - - @PutMapping("/{orderTableId}/number-of-guests") - public ResponseEntity changeNumberOfGuests( - @PathVariable final UUID orderTableId, - @RequestBody final OrderTable request - ) { - return ResponseEntity.ok(orderTableService.changeNumberOfGuests(orderTableId, request)); - } - - @GetMapping - public ResponseEntity> findAll() { - return ResponseEntity.ok(orderTableService.findAll()); - } -} diff --git a/src/main/java/kitchenpos/menu/application/MenuGroupService.java b/src/main/java/kitchenpos/menu/application/MenuGroupService.java new file mode 100644 index 000000000..0671e11e1 --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/MenuGroupService.java @@ -0,0 +1,38 @@ +package kitchenpos.menu.application; + +import java.util.List; +import kitchenpos.menu.application.dto.CreateMenuGroupServiceRq; +import kitchenpos.menu.application.dto.MenuGroupServiceRs; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuGroupName; +import kitchenpos.menu.domain.model.MenuGroupNameCreationService; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class MenuGroupService { + private final MenuGroupRepository menuGroupRepository; + private final MenuGroupNameCreationService menuGroupNameCreationService; + + public MenuGroupService(final MenuGroupRepository menuGroupRepository, + MenuGroupNameCreationService menuGroupNameCreationService) { + this.menuGroupRepository = menuGroupRepository; + this.menuGroupNameCreationService = menuGroupNameCreationService; + } + + @Transactional + public MenuGroupServiceRs create(final CreateMenuGroupServiceRq request) { + final String name = request.getName(); + MenuGroupName menuGroupName = menuGroupNameCreationService.createName(name); + MenuGroup menuGroup = menuGroupRepository.save(new MenuGroup(menuGroupName)); + return new MenuGroupServiceRs(menuGroup); + } + + @Transactional(readOnly = true) + public List findAll() { + return menuGroupRepository.findAll().stream() + .map(MenuGroupServiceRs::new) + .toList(); + } +} diff --git a/src/main/java/kitchenpos/menu/application/MenuQueryService.java b/src/main/java/kitchenpos/menu/application/MenuQueryService.java new file mode 100644 index 000000000..4aa61fb4c --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/MenuQueryService.java @@ -0,0 +1,19 @@ +package kitchenpos.menu.application; + +import java.util.List; +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.domain.repository.MenuQueryRepository; +import org.springframework.stereotype.Service; + +@Service +public class MenuQueryService { + private final MenuQueryRepository menuQueryRepository; + + public MenuQueryService(MenuQueryRepository menuQueryRepository) { + this.menuQueryRepository = menuQueryRepository; + } + + public List findAll() { + return menuQueryRepository.findAll(); + } +} diff --git a/src/main/java/kitchenpos/menu/application/MenuService.java b/src/main/java/kitchenpos/menu/application/MenuService.java new file mode 100644 index 000000000..0175f66b6 --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/MenuService.java @@ -0,0 +1,127 @@ +package kitchenpos.menu.application; + +import static kitchenpos.menu.exception.MenuExceptionMessage.NONE_MARGIN_EXCEPTION; + +import java.math.BigDecimal; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import kitchenpos.menu.application.dto.ChangeMenuPriceServiceRq; +import kitchenpos.menu.application.dto.CreateMenuServiceRq; +import kitchenpos.menu.application.dto.CreateMenuServiceRq.MenuProductServiceRq; +import kitchenpos.menu.application.dto.MenuServiceRs; +import kitchenpos.menu.application.dto.SimpleMenuServiceRs; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuName; +import kitchenpos.menu.domain.model.MenuNameCreationService; +import kitchenpos.menu.domain.model.MenuPrice; +import kitchenpos.menu.domain.model.MenuProduct; +import kitchenpos.menu.domain.model.MenuProductQuantity; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.domain.service.MarginValidator; +import kitchenpos.menu.domain.service.MenuProductValidator; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class MenuService { + private final MenuRepository menuRepository; + private final MenuGroupRepository menuGroupRepository; + private final ProductRepository productRepository; + private final MarginValidator marginValidator; + private final MenuProductValidator menuProductValidator; + private final MenuNameCreationService menuNameCreationService; + + public MenuService( + final MenuRepository menuRepository, + final MenuGroupRepository menuGroupRepository, + final ProductRepository productRepository, + MarginValidator marginValidator, + MenuProductValidator menuProductValidator, + MenuNameCreationService menuNameCreationService + ) { + this.menuRepository = menuRepository; + this.menuGroupRepository = menuGroupRepository; + this.productRepository = productRepository; + this.marginValidator = marginValidator; + this.menuProductValidator = menuProductValidator; + this.menuNameCreationService = menuNameCreationService; + } + + @Transactional + public MenuServiceRs create(final CreateMenuServiceRq request) { + final BigDecimal price = request.getPrice(); + final MenuGroup menuGroup = menuGroupRepository.findById(request.getMenuGroupId()) + .orElseThrow(NoSuchElementException::new); + + List menuProductRequests = request.getMenuProductDtos(); + final List menuProducts = createMenuProductsByRequest(menuProductRequests); + menuProductValidator.validateMenuProduct(menuProducts); + + final String name = request.getName(); + MenuName menuName = menuNameCreationService.createName(name); + + final Menu menu = new Menu(menuName, new MenuPrice(price), menuGroup, request.isDisplayed(), menuProducts, + menuGroup.getId()); + validateMargin(menu); + menuRepository.save(menu); + + return new MenuServiceRs(menu); + } + + private List createMenuProductsByRequest(List menuProductRequests) { + return menuProductRequests.stream().map(this::createMenuProductByRequest).toList(); + } + + private MenuProduct createMenuProductByRequest(MenuProductServiceRq request) { + final long quantity = request.getQuantity(); + final Product product = productRepository.findById(request.getProductId()) + .orElseThrow(NoSuchElementException::new); + return new MenuProduct(product, new MenuProductQuantity(quantity), product.getId()); + } + + private void validateMargin(Menu menu) { + boolean hasMargin = marginValidator.checkMargin(menu); + if (!hasMargin) { + throw new IllegalStateException(NONE_MARGIN_EXCEPTION.getMessage()); + } + } + + @Transactional + public SimpleMenuServiceRs changePrice(final UUID menuId, final ChangeMenuPriceServiceRq request) { + final BigDecimal price = request.getPrice(); + final Menu menu = menuRepository.findById(menuId) + .orElseThrow(NoSuchElementException::new); + menu.changePrice(price); + validateMargin(menu); + return new SimpleMenuServiceRs(menu); + } + + @Transactional + public SimpleMenuServiceRs display(final UUID menuId) { + final Menu menu = menuRepository.findById(menuId) + .orElseThrow(NoSuchElementException::new); + validateMargin(menu); + menu.changeDisplay(true); + return new SimpleMenuServiceRs(menu); + } + + @Transactional + public SimpleMenuServiceRs hide(final UUID menuId) { + final Menu menu = menuRepository.findById(menuId) + .orElseThrow(NoSuchElementException::new); + menu.changeDisplay(false); + return new SimpleMenuServiceRs(menu); + } + + @Transactional(readOnly = true) + public List findAll() { + return menuRepository.findAll().stream() + .map(MenuServiceRs::new) + .toList(); + } +} diff --git a/src/main/java/kitchenpos/menu/application/dto/ChangeMenuPriceServiceRq.java b/src/main/java/kitchenpos/menu/application/dto/ChangeMenuPriceServiceRq.java new file mode 100644 index 000000000..4ce401270 --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/dto/ChangeMenuPriceServiceRq.java @@ -0,0 +1,18 @@ +package kitchenpos.menu.application.dto; + +import java.math.BigDecimal; + +public class ChangeMenuPriceServiceRq { + private BigDecimal price; + + public ChangeMenuPriceServiceRq(BigDecimal price) { + this.price = price; + } + + public ChangeMenuPriceServiceRq() { + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/menu/application/dto/CreateMenuGroupServiceRq.java b/src/main/java/kitchenpos/menu/application/dto/CreateMenuGroupServiceRq.java new file mode 100644 index 000000000..3919c09cf --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/dto/CreateMenuGroupServiceRq.java @@ -0,0 +1,16 @@ +package kitchenpos.menu.application.dto; + +public class CreateMenuGroupServiceRq { + private String name; + + public CreateMenuGroupServiceRq(String name) { + this.name = name; + } + + public CreateMenuGroupServiceRq() { + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/kitchenpos/menu/application/dto/CreateMenuServiceRq.java b/src/main/java/kitchenpos/menu/application/dto/CreateMenuServiceRq.java new file mode 100644 index 000000000..41c55ff32 --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/dto/CreateMenuServiceRq.java @@ -0,0 +1,66 @@ +package kitchenpos.menu.application.dto; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +public class CreateMenuServiceRq { + private String name; + private BigDecimal price; + private boolean isDisplayed; + private UUID menuGroupId; + private List menuProductServiceRqs; + + public CreateMenuServiceRq(String name, BigDecimal price, boolean isDisplayed, UUID menuGroupId, + List menuProductServiceRqs) { + this.name = name; + this.price = price; + this.isDisplayed = isDisplayed; + this.menuGroupId = menuGroupId; + this.menuProductServiceRqs = menuProductServiceRqs; + } + + public CreateMenuServiceRq() { + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } + + public UUID getMenuGroupId() { + return menuGroupId; + } + + public List getMenuProductDtos() { + return menuProductServiceRqs; + } + + public static class MenuProductServiceRq { + private UUID productId; + private long quantity; + + public MenuProductServiceRq(UUID productId, long quantity) { + this.productId = productId; + this.quantity = quantity; + } + + public MenuProductServiceRq() { + } + + public UUID getProductId() { + return productId; + } + + public long getQuantity() { + return quantity; + } + } +} diff --git a/src/main/java/kitchenpos/menu/application/dto/MenuGroupServiceRs.java b/src/main/java/kitchenpos/menu/application/dto/MenuGroupServiceRs.java new file mode 100644 index 000000000..3d2bee3b4 --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/dto/MenuGroupServiceRs.java @@ -0,0 +1,27 @@ +package kitchenpos.menu.application.dto; + +import java.util.UUID; +import kitchenpos.menu.domain.model.MenuGroup; + +public class MenuGroupServiceRs { + private UUID id; + private String name; + + public MenuGroupServiceRs(UUID id, String name) { + this.id = id; + this.name = name; + } + + public MenuGroupServiceRs(MenuGroup menuGroup) { + this.id = menuGroup.getId(); + this.name = menuGroup.getName(); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/kitchenpos/menu/application/dto/MenuServiceRs.java b/src/main/java/kitchenpos/menu/application/dto/MenuServiceRs.java new file mode 100644 index 000000000..6c0658b6c --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/dto/MenuServiceRs.java @@ -0,0 +1,81 @@ +package kitchenpos.menu.application.dto; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; + +public class MenuServiceRs { + private UUID id; + private String name; + private BigDecimal price; + private boolean isDisplayed; + private UUID menuGroupId; + private List menuProductServiceRsList; + + public MenuServiceRs(UUID id, String name, BigDecimal price, boolean isDisplayed, UUID menuGroupId, + List menuProductServiceRsList) { + this.id = id; + this.name = name; + this.price = price; + this.isDisplayed = isDisplayed; + this.menuGroupId = menuGroupId; + this.menuProductServiceRsList = menuProductServiceRsList; + } + + public MenuServiceRs(Menu menu) { + this.id = menu.getId(); + this.name = menu.getInnerName(); + this.price = menu.getInnerPrice(); + this.isDisplayed = menu.isDisplayed(); + this.menuGroupId = menu.getMenuGroupId(); + this.menuProductServiceRsList = menu.getMenuProducts().stream() + .map(mp -> new MenuProductServiceRs(mp.getProductId(), mp.getInnerQuantity())) + .toList(); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } + + public UUID getMenuGroupId() { + return menuGroupId; + } + + public List getMenuProductServiceRsList() { + return menuProductServiceRsList; + } + + public static class MenuProductServiceRs { + private UUID productId; + private long quantity; + + public MenuProductServiceRs(UUID productId, long quantity) { + this.productId = productId; + this.quantity = quantity; + } + + public MenuProductServiceRs() { + } + + public UUID getProductId() { + return productId; + } + + public long getQuantity() { + return quantity; + } + } +} diff --git a/src/main/java/kitchenpos/menu/application/dto/SimpleMenuServiceRs.java b/src/main/java/kitchenpos/menu/application/dto/SimpleMenuServiceRs.java new file mode 100644 index 000000000..375f03853 --- /dev/null +++ b/src/main/java/kitchenpos/menu/application/dto/SimpleMenuServiceRs.java @@ -0,0 +1,42 @@ +package kitchenpos.menu.application.dto; + +import java.math.BigDecimal; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; + +public class SimpleMenuServiceRs { + private UUID id; + private String name; + private BigDecimal price; + private boolean isDisplayed; + + public SimpleMenuServiceRs(UUID id, String name, BigDecimal price, boolean isDisplayed) { + this.id = id; + this.name = name; + this.price = price; + this.isDisplayed = isDisplayed; + } + + public SimpleMenuServiceRs(Menu menu) { + this.id = menu.getId(); + this.name = menu.getInnerName(); + this.price = menu.getInnerPrice(); + this.isDisplayed = menu.isDisplayed(); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } +} diff --git a/src/main/java/kitchenpos/menu/domain/model/Menu.java b/src/main/java/kitchenpos/menu/domain/model/Menu.java new file mode 100644 index 000000000..c66b5336d --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/Menu.java @@ -0,0 +1,163 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_GROUP_EXISTS_EXCEPTION; +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_PRODUCTS_EXISTS_EXCEPTION; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import java.math.BigDecimal; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import org.springframework.data.domain.AbstractAggregateRoot; + +@Table(name = "menu") +@Entity +public class Menu extends AbstractAggregateRoot { + @Column(name = "id", columnDefinition = "binary(16)") + @Id + private UUID id; + + @Embedded + private MenuName name; + + @Embedded + private MenuPrice price; + + @ManyToOne(optional = false) + @JoinColumn( + name = "menu_group_id", + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_menu_to_menu_group") + ) + private MenuGroup menuGroup; + + @Column(name = "displayed", nullable = false) + private boolean displayed; + + @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @JoinColumn( + name = "menu_id", + nullable = false, + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_menu_product_to_menu") + ) + private List menuProducts; + + @Transient + private UUID menuGroupId; + + public Menu() { + } + + public Menu(String name, BigDecimal price, boolean displayed) { + this.id = UUID.randomUUID(); + this.name = new MenuName(name); + this.price = new MenuPrice(price); + this.displayed = displayed; + // test 용 생성자 +// registerEvent(MenuSummaryEvent.from(this)); + } + + public Menu(MenuName name, MenuPrice price, MenuGroup menuGroup, boolean displayed, + List menuProducts, UUID menuGroupId) { + validateMenuGroupExists(menuGroup); + validateMenuProductsExists(menuProducts); + this.id = UUID.randomUUID(); + this.name = name; + this.price = price; + this.menuGroup = menuGroup; + this.displayed = displayed; + this.menuProducts = menuProducts; + this.menuGroupId = menuGroupId; + registerEvent(MenuSummaryEvent.from(this)); + } + + public Menu(String name, BigDecimal price, boolean displayed, List menuProducts, + MenuGroup menuGroup, + UUID menuGroupId) { + this(new MenuName(name), new MenuPrice(price), menuGroup, displayed, menuProducts, menuGroupId); + } + + private void validateMenuGroupExists(MenuGroup menuGroup) { + if (menuGroup == null) { + throw new NoSuchElementException(MENU_GROUP_EXISTS_EXCEPTION.getMessage()); + } + } + + private void validateMenuProductsExists(List menuProducts) { + if (menuProducts == null || menuProducts.isEmpty()) { + throw new NoSuchElementException(MENU_PRODUCTS_EXISTS_EXCEPTION.getMessage()); + } + } + + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + registerEvent(MenuSummaryEvent.from(this)); + } + + public String getInnerName() { + return name.getValue(); + } + + public MenuName getName() { + return name; + } + + public void setName(final String name) { + this.name = new MenuName(name); + registerEvent(MenuSummaryEvent.from(this)); + } + + public BigDecimal getInnerPrice() { + return price.getValue(); + } + + public MenuPrice getPrice() { + return price; + } + + public void changePrice(final BigDecimal price) { + this.price = new MenuPrice(price); + registerEvent(MenuSummaryEvent.from(this)); + } + + public MenuGroup getMenuGroup() { + return menuGroup; + } + + public boolean isDisplayed() { + return displayed; + } + + public void changeDisplay(final boolean displayed) { + this.displayed = displayed; + registerEvent(MenuSummaryEvent.from(this)); + } + + public void changeDisplay() { + this.displayed = !this.displayed; + registerEvent(MenuSummaryEvent.from(this)); + } + + public List getMenuProducts() { + return menuProducts; + } + + public UUID getMenuGroupId() { + return menuGroupId; + } +} diff --git a/src/main/java/kitchenpos/menus/domain/MenuGroup.java b/src/main/java/kitchenpos/menu/domain/model/MenuGroup.java similarity index 56% rename from src/main/java/kitchenpos/menus/domain/MenuGroup.java rename to src/main/java/kitchenpos/menu/domain/model/MenuGroup.java index 70e0b8e23..526e1ae31 100644 --- a/src/main/java/kitchenpos/menus/domain/MenuGroup.java +++ b/src/main/java/kitchenpos/menu/domain/model/MenuGroup.java @@ -1,10 +1,10 @@ -package kitchenpos.menus.domain; +package kitchenpos.menu.domain.model; import jakarta.persistence.Column; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; - import java.util.UUID; @Table(name = "menu_group") @@ -14,10 +14,20 @@ public class MenuGroup { @Id private UUID id; - @Column(name = "name", nullable = false) - private String name; + @Embedded + private MenuGroupName name; - public MenuGroup() { + public MenuGroup(MenuGroupName name) { + this.name = name; + this.id = UUID.randomUUID(); + } + + public MenuGroup(String name) { + this.name = new MenuGroupName(name); + this.id = UUID.randomUUID(); + } + + protected MenuGroup() { } public UUID getId() { @@ -29,10 +39,6 @@ public void setId(final UUID id) { } public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; + return name.getValue(); } } diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuGroupName.java b/src/main/java/kitchenpos/menu/domain/model/MenuGroupName.java new file mode 100644 index 000000000..5afd18e61 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuGroupName.java @@ -0,0 +1,51 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_CATEGORY_NAME_CREATION_EXCEPTION; + +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class MenuGroupName { + @Column(name = "name", nullable = false) + private final String value; + + protected MenuGroupName(String value) { + validateMenuGroupName(value); + this.value = value; + } + + protected MenuGroupName() { + this.value = null; + } + + private void validateMenuGroupName(String value) { + if (Objects.isNull(value) || value.isEmpty()) { + throw new IllegalArgumentException(MENU_CATEGORY_NAME_CREATION_EXCEPTION.getMessage()); + } + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuGroupName that = (MenuGroupName) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuGroupNameCreationService.java b/src/main/java/kitchenpos/menu/domain/model/MenuGroupNameCreationService.java new file mode 100644 index 000000000..8b66c6ec1 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuGroupNameCreationService.java @@ -0,0 +1,22 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_GROUP_NAME_VALIDATION_EXCEPTION; + +import kitchenpos.common.application.PurgomalumClient; +import org.springframework.stereotype.Service; + +@Service +public class MenuGroupNameCreationService { + private final PurgomalumClient purgomalumClient; + + public MenuGroupNameCreationService(PurgomalumClient purgomalumClient) { + this.purgomalumClient = purgomalumClient; + } + + public MenuGroupName createName(String name) { + if (purgomalumClient.containsProfanity(name)) { + throw new IllegalArgumentException(MENU_GROUP_NAME_VALIDATION_EXCEPTION.getMessage()); + } + return new MenuGroupName(name); + } +} diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuName.java b/src/main/java/kitchenpos/menu/domain/model/MenuName.java new file mode 100644 index 000000000..e9ab5d663 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuName.java @@ -0,0 +1,51 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_NAME_CREATION_EXCEPTION; + +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class MenuName { + @Column(name = "name", nullable = false) + private final String value; + + public MenuName(String value) { + validateName(value); + this.value = value; + } + + protected MenuName() { + this.value = null; + } + + private void validateName(String name) { + if (Objects.isNull(name) || name.isEmpty()) { + throw new IllegalArgumentException(MENU_NAME_CREATION_EXCEPTION.getMessage()); + } + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuName menuName = (MenuName) o; + return Objects.equals(value, menuName.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuNameCreationService.java b/src/main/java/kitchenpos/menu/domain/model/MenuNameCreationService.java new file mode 100644 index 000000000..1b49f1dff --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuNameCreationService.java @@ -0,0 +1,22 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_NAME_VALIDATION_EXCEPTION; + +import kitchenpos.common.application.PurgomalumClient; +import org.springframework.stereotype.Service; + +@Service +public class MenuNameCreationService { + private final PurgomalumClient purgomalumClient; + + public MenuNameCreationService(PurgomalumClient purgomalumClient) { + this.purgomalumClient = purgomalumClient; + } + + public MenuName createName(String name) { + if (purgomalumClient.containsProfanity(name)) { + throw new IllegalArgumentException(MENU_NAME_VALIDATION_EXCEPTION.getMessage()); + } + return new MenuName(name); + } +} diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuPrice.java b/src/main/java/kitchenpos/menu/domain/model/MenuPrice.java new file mode 100644 index 000000000..27af93625 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuPrice.java @@ -0,0 +1,52 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_PRICE_CREATION_EXCEPTION; + +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.math.BigDecimal; +import java.util.Objects; + +@Embeddable +public class MenuPrice { + @Column(name = "price", nullable = false) + private final BigDecimal value; + + public MenuPrice(BigDecimal value) { + validatePrice(value); + this.value = value; + } + + protected MenuPrice() { + this.value = null; + } + + private void validatePrice(BigDecimal value) { + if (Objects.isNull(value) || value.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException(MENU_PRICE_CREATION_EXCEPTION.getMessage()); + } + } + + @JsonValue + public BigDecimal getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuPrice menuPrice = (MenuPrice) o; + return Objects.equals(value, menuPrice.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/src/main/java/kitchenpos/menus/domain/MenuProduct.java b/src/main/java/kitchenpos/menu/domain/model/MenuProduct.java similarity index 60% rename from src/main/java/kitchenpos/menus/domain/MenuProduct.java rename to src/main/java/kitchenpos/menu/domain/model/MenuProduct.java index b47ca26cb..09e095bb1 100644 --- a/src/main/java/kitchenpos/menus/domain/MenuProduct.java +++ b/src/main/java/kitchenpos/menu/domain/model/MenuProduct.java @@ -1,6 +1,7 @@ -package kitchenpos.menus.domain; +package kitchenpos.menu.domain.model; import jakarta.persistence.Column; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; import jakarta.persistence.GeneratedValue; @@ -10,9 +11,8 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.Transient; -import kitchenpos.products.domain.Product; - import java.util.UUID; +import kitchenpos.product.domain.model.Product; @Table(name = "menu_product") @Entity @@ -24,14 +24,14 @@ public class MenuProduct { @ManyToOne(optional = false) @JoinColumn( - name = "product_id", - columnDefinition = "binary(16)", - foreignKey = @ForeignKey(name = "fk_menu_product_to_product") + name = "product_id", + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_menu_product_to_product") ) private Product product; - @Column(name = "quantity", nullable = false) - private long quantity; + @Embedded + private MenuProductQuantity quantity; @Transient private UUID productId; @@ -39,12 +39,14 @@ public class MenuProduct { public MenuProduct() { } - public Long getSeq() { - return seq; + public MenuProduct(Product product, MenuProductQuantity quantity, UUID productId) { + this.product = product; + this.quantity = quantity; + this.productId = productId; } - public void setSeq(final Long seq) { - this.seq = seq; + public MenuProduct(long quantity, Product product, UUID productId) { + this(product, new MenuProductQuantity(quantity), productId); } public Product getProduct() { @@ -55,19 +57,15 @@ public void setProduct(final Product product) { this.product = product; } - public long getQuantity() { - return quantity; - } - - public void setQuantity(final long quantity) { - this.quantity = quantity; + public long getInnerQuantity() { + return quantity.getValue(); } public UUID getProductId() { return productId; } - public void setProductId(final UUID productId) { - this.productId = productId; + public MenuProductQuantity getQuantity() { + return quantity; } } diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuProductQuantity.java b/src/main/java/kitchenpos/menu/domain/model/MenuProductQuantity.java new file mode 100644 index 000000000..28cf9d252 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuProductQuantity.java @@ -0,0 +1,48 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.menu.exception.MenuExceptionMessage.MENU_PRODUCT_QUANTITY_CREATION_EXCEPTION; + +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class MenuProductQuantity { + @Column(name = "quantity", nullable = false) + private final Long value; + + public MenuProductQuantity(long value) { + validateMenuProductQuantity(value); + this.value = value; + } + + protected MenuProductQuantity() { + this.value = null; + } + + private void validateMenuProductQuantity(long value) { + if (value <= 0) { + throw new IllegalArgumentException(MENU_PRODUCT_QUANTITY_CREATION_EXCEPTION.getMessage()); + } + } + + @JsonValue + public Long getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuProductQuantity that = (MenuProductQuantity) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuSummary.java b/src/main/java/kitchenpos/menu/domain/model/MenuSummary.java new file mode 100644 index 000000000..5b33c2a78 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuSummary.java @@ -0,0 +1,83 @@ +package kitchenpos.menu.domain.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import kitchenpos.product.domain.model.ProductSummary; + +@Entity +public class MenuSummary { + + @Id + @Column(name = "id") + private UUID id; + private String menuName; + private BigDecimal price; + private boolean isDisplayed; + private UUID menuGroupId; + private String menuGroupName; + + @OneToMany + @JoinColumn(name = "menu_summary_id") + private List productSummaries = new ArrayList<>(); + + public MenuSummary(UUID menuId, String menuName, BigDecimal price, boolean isDisplayed, UUID menuGroupId, + String menuGroupName, List productSummaries) { + this.id = menuId; + this.menuName = menuName; + this.price = price; + this.isDisplayed = isDisplayed; + this.menuGroupId = menuGroupId; + this.menuGroupName = menuGroupName; + this.productSummaries = productSummaries; + } + + protected MenuSummary() { + } + + public MenuSummary(MenuSummaryEvent event) { + this.id = event.getId(); + this.menuName = event.getMenuName(); + this.price = event.getPrice(); + this.isDisplayed = event.isDisplayed(); + this.menuGroupId = event.getMenuGroupId(); + this.menuGroupName = event.getMenuGroupName(); + this.productSummaries = event.getProductSummaries(); + } + + public UUID getId() { + return id; + } + + public String getMenuName() { + return menuName; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } + + public UUID getMenuGroupId() { + return menuGroupId; + } + + public String getMenuGroupName() { + return menuGroupName; + } + + public List getProductSummaries() { + return productSummaries; + } +} + + diff --git a/src/main/java/kitchenpos/menu/domain/model/MenuSummaryEvent.java b/src/main/java/kitchenpos/menu/domain/model/MenuSummaryEvent.java new file mode 100644 index 000000000..f30be0c09 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/model/MenuSummaryEvent.java @@ -0,0 +1,71 @@ +package kitchenpos.menu.domain.model; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; +import kitchenpos.product.domain.model.ProductSummary; + +public class MenuSummaryEvent { + + private UUID id; + private String menuName; + private BigDecimal price; + private boolean isDisplayed; + private UUID menuGroupId; + private String menuGroupName; + private List productSummaries; + + private MenuSummaryEvent(UUID id, String menuName, BigDecimal price, boolean isDisplayed, UUID menuGroupId, + String menuGroupName, List productSummaries) { + this.id = id; + this.menuName = menuName; + this.price = price; + this.isDisplayed = isDisplayed; + this.menuGroupId = menuGroupId; + this.menuGroupName = menuGroupName; + this.productSummaries = productSummaries; + } + + public static MenuSummaryEvent from(Menu menu) { + return new MenuSummaryEvent( + menu.getId(), + menu.getInnerName(), + menu.getInnerPrice(), + menu.isDisplayed(), + menu.getMenuGroupId(), + menu.getMenuGroup().getName(), + menu.getMenuProducts().stream() + .map(mp -> new ProductSummary(mp.getProductId(), mp.getProduct().getInnerName(), + mp.getInnerQuantity())) + .toList() + ); + } + + public UUID getId() { + return id; + } + + public String getMenuName() { + return menuName; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } + + public UUID getMenuGroupId() { + return menuGroupId; + } + + public String getMenuGroupName() { + return menuGroupName; + } + + public List getProductSummaries() { + return productSummaries; + } +} diff --git a/src/main/java/kitchenpos/menus/domain/MenuGroupRepository.java b/src/main/java/kitchenpos/menu/domain/repository/MenuGroupRepository.java similarity index 72% rename from src/main/java/kitchenpos/menus/domain/MenuGroupRepository.java rename to src/main/java/kitchenpos/menu/domain/repository/MenuGroupRepository.java index b25e6acbc..cfb05d515 100644 --- a/src/main/java/kitchenpos/menus/domain/MenuGroupRepository.java +++ b/src/main/java/kitchenpos/menu/domain/repository/MenuGroupRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.menus.domain; +package kitchenpos.menu.domain.repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import kitchenpos.menu.domain.model.MenuGroup; public interface MenuGroupRepository { MenuGroup save(MenuGroup menuGroup); diff --git a/src/main/java/kitchenpos/menu/domain/repository/MenuQueryRepository.java b/src/main/java/kitchenpos/menu/domain/repository/MenuQueryRepository.java new file mode 100644 index 000000000..8ba9227a4 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/repository/MenuQueryRepository.java @@ -0,0 +1,11 @@ +package kitchenpos.menu.domain.repository; + +import java.util.List; +import kitchenpos.menu.domain.model.MenuSummary; +import org.springframework.stereotype.Repository; + +@Repository +public interface MenuQueryRepository { + + List findAll(); +} diff --git a/src/main/java/kitchenpos/menus/domain/MenuRepository.java b/src/main/java/kitchenpos/menu/domain/repository/MenuRepository.java similarity index 78% rename from src/main/java/kitchenpos/menus/domain/MenuRepository.java rename to src/main/java/kitchenpos/menu/domain/repository/MenuRepository.java index 5fbaab864..f83243f3d 100644 --- a/src/main/java/kitchenpos/menus/domain/MenuRepository.java +++ b/src/main/java/kitchenpos/menu/domain/repository/MenuRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.menus.domain; +package kitchenpos.menu.domain.repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; public interface MenuRepository { Menu save(Menu menu); diff --git a/src/main/java/kitchenpos/menu/domain/repository/MenuSummaryRepository.java b/src/main/java/kitchenpos/menu/domain/repository/MenuSummaryRepository.java new file mode 100644 index 000000000..c07758e9b --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/repository/MenuSummaryRepository.java @@ -0,0 +1,9 @@ +package kitchenpos.menu.domain.repository; + +import kitchenpos.menu.domain.model.MenuSummary; +import org.springframework.stereotype.Repository; + +@Repository +public interface MenuSummaryRepository { + MenuSummary save(MenuSummary menuSummary); +} diff --git a/src/main/java/kitchenpos/menu/domain/service/MarginValidator.java b/src/main/java/kitchenpos/menu/domain/service/MarginValidator.java new file mode 100644 index 000000000..b4bc47931 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/service/MarginValidator.java @@ -0,0 +1,42 @@ +package kitchenpos.menu.domain.service; + +import java.math.BigDecimal; +import java.util.List; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuProduct; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.product.domain.model.Product; +import org.springframework.stereotype.Service; + +@Service +public class MarginValidator { + + private final MenuRepository menuRepository; + + public MarginValidator(MenuRepository menuRepository) { + this.menuRepository = menuRepository; + } + + public void checkMargin(Product product) { + List menus = menuRepository.findAllByProductId(product.getId()); + for (Menu menu : menus) { + checkMargin(menu); + } + } + + public boolean checkMargin(Menu menu) { + BigDecimal sum = BigDecimal.ZERO; + for (final MenuProduct menuProduct : menu.getMenuProducts()) { + sum = sum.add( + menuProduct.getProduct() + .getInnerPrice() + .multiply(BigDecimal.valueOf(menuProduct.getInnerQuantity())) + ); + } + if (menu.getInnerPrice().compareTo(sum) < 0) { + menu.changeDisplay(false); + return false; + } + return true; + } +} diff --git a/src/main/java/kitchenpos/menu/domain/service/MenuProductValidator.java b/src/main/java/kitchenpos/menu/domain/service/MenuProductValidator.java new file mode 100644 index 000000000..060b1a5c7 --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/service/MenuProductValidator.java @@ -0,0 +1,29 @@ +package kitchenpos.menu.domain.service; + +import java.util.List; +import kitchenpos.menu.domain.model.MenuProduct; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import org.springframework.stereotype.Service; + +@Service +public class MenuProductValidator { + private static final String MENU_PRODUCT_VALIDATION_EXCEPTION = "메뉴 상품에 들어갈 상품 수와 실제 상품 수가 다릅니다!"; + + private final ProductRepository productRepository; + + public MenuProductValidator(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + public void validateMenuProduct(List menuProducts) { + final List products = productRepository.findAllByIdIn( + menuProducts.stream() + .map(MenuProduct::getProductId) + .toList() + ); + if (products.size() != menuProducts.size()) { + throw new IllegalArgumentException(MENU_PRODUCT_VALIDATION_EXCEPTION); + } + } +} diff --git a/src/main/java/kitchenpos/menu/domain/service/MenuQueryModelSynchronizer.java b/src/main/java/kitchenpos/menu/domain/service/MenuQueryModelSynchronizer.java new file mode 100644 index 000000000..77c9f741b --- /dev/null +++ b/src/main/java/kitchenpos/menu/domain/service/MenuQueryModelSynchronizer.java @@ -0,0 +1,29 @@ +package kitchenpos.menu.domain.service; + +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.domain.model.MenuSummaryEvent; +import kitchenpos.menu.domain.repository.MenuSummaryRepository; +import kitchenpos.product.domain.model.ProductSummary; +import kitchenpos.product.domain.repository.ProductSummaryRepository; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +@Service +public class MenuQueryModelSynchronizer { + private final MenuSummaryRepository menuSummaryRepository; + private final ProductSummaryRepository productSummaryRepository; // 이 레포지토리 추가 + + public MenuQueryModelSynchronizer(MenuSummaryRepository menuSummaryRepository, + ProductSummaryRepository productSummaryRepository) { + this.menuSummaryRepository = menuSummaryRepository; + this.productSummaryRepository = productSummaryRepository; + } + + @EventListener + public void updateReadModel(MenuSummaryEvent event) { + for (ProductSummary productSummary : event.getProductSummaries()) { + productSummaryRepository.save(productSummary); + } + menuSummaryRepository.save(new MenuSummary(event)); + } +} diff --git a/src/main/java/kitchenpos/menu/exception/MenuExceptionMessage.java b/src/main/java/kitchenpos/menu/exception/MenuExceptionMessage.java new file mode 100644 index 000000000..a4e29192c --- /dev/null +++ b/src/main/java/kitchenpos/menu/exception/MenuExceptionMessage.java @@ -0,0 +1,24 @@ +package kitchenpos.menu.exception; + +public enum MenuExceptionMessage { + + NONE_MARGIN_EXCEPTION("마진이 남지 않습니다! 마진을 남기게 만들어주세요!"), + MENU_PRODUCTS_EXISTS_EXCEPTION("메뉴 상품이 존재하지 않습니다!"), + MENU_GROUP_EXISTS_EXCEPTION("메뉴 그룹이 존재하지 않습니다!"), + MENU_CATEGORY_NAME_CREATION_EXCEPTION("메뉴 카테고리 이름을 채워주세요!"), + MENU_GROUP_NAME_VALIDATION_EXCEPTION("메뉴 카테고리 이름에 비속어가 존재합니다. 비속어를 제외해주세요!"), + MENU_NAME_CREATION_EXCEPTION("메뉴 이름을 채워주세요!"), + MENU_NAME_VALIDATION_EXCEPTION("메뉴 이름에 비속어가 존재합니다. 비속어를 제외해주세요!"), + MENU_PRICE_CREATION_EXCEPTION("메뉴 가격을 채워주세요!"), + MENU_PRODUCT_QUANTITY_CREATION_EXCEPTION("메뉴 상품의 수량은 0보다 커야 합니다!"); + + private final String message; + + MenuExceptionMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kitchenpos/menus/domain/JpaMenuGroupRepository.java b/src/main/java/kitchenpos/menu/infra/persistence/JpaMenuGroupRepository.java similarity index 55% rename from src/main/java/kitchenpos/menus/domain/JpaMenuGroupRepository.java rename to src/main/java/kitchenpos/menu/infra/persistence/JpaMenuGroupRepository.java index 233488198..13d3e1fdb 100644 --- a/src/main/java/kitchenpos/menus/domain/JpaMenuGroupRepository.java +++ b/src/main/java/kitchenpos/menu/infra/persistence/JpaMenuGroupRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.menus.domain; - -import org.springframework.data.jpa.repository.JpaRepository; +package kitchenpos.menu.infra.persistence; import java.util.UUID; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import org.springframework.data.jpa.repository.JpaRepository; public interface JpaMenuGroupRepository extends MenuGroupRepository, JpaRepository { } diff --git a/src/main/java/kitchenpos/menus/domain/JpaMenuRepository.java b/src/main/java/kitchenpos/menu/infra/persistence/JpaMenuRepository.java similarity index 77% rename from src/main/java/kitchenpos/menus/domain/JpaMenuRepository.java rename to src/main/java/kitchenpos/menu/infra/persistence/JpaMenuRepository.java index 796499c30..9f9ec4437 100644 --- a/src/main/java/kitchenpos/menus/domain/JpaMenuRepository.java +++ b/src/main/java/kitchenpos/menu/infra/persistence/JpaMenuRepository.java @@ -1,12 +1,13 @@ -package kitchenpos.menus.domain; +package kitchenpos.menu.infra.persistence; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.UUID; - public interface JpaMenuRepository extends MenuRepository, JpaRepository { @Query("select m from Menu m join m.menuProducts mp where mp.product.id = :productId") @Override diff --git a/src/main/java/kitchenpos/menu/infra/persistence/JpaMenuSummaryRepository.java b/src/main/java/kitchenpos/menu/infra/persistence/JpaMenuSummaryRepository.java new file mode 100644 index 000000000..8f418cd3f --- /dev/null +++ b/src/main/java/kitchenpos/menu/infra/persistence/JpaMenuSummaryRepository.java @@ -0,0 +1,11 @@ +package kitchenpos.menu.infra.persistence; + +import java.util.UUID; +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.domain.repository.MenuSummaryRepository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface JpaMenuSummaryRepository extends JpaRepository, MenuSummaryRepository { +} diff --git a/src/main/java/kitchenpos/menu/infra/persistence/QuerydslMenuQueryRepository.java b/src/main/java/kitchenpos/menu/infra/persistence/QuerydslMenuQueryRepository.java new file mode 100644 index 000000000..419111581 --- /dev/null +++ b/src/main/java/kitchenpos/menu/infra/persistence/QuerydslMenuQueryRepository.java @@ -0,0 +1,28 @@ +package kitchenpos.menu.infra.persistence; + +import static kitchenpos.menu.domain.model.QMenuSummary.menuSummary; +import static kitchenpos.product.domain.model.QProductSummary.productSummary; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.domain.repository.MenuQueryRepository; +import org.springframework.stereotype.Repository; + +@Repository +public class QuerydslMenuQueryRepository implements MenuQueryRepository { + private final JPAQueryFactory queryFactory; + + public QuerydslMenuQueryRepository(JPAQueryFactory queryFactory) { + this.queryFactory = queryFactory; + } + + @Override + public List findAll() { + return queryFactory + .selectFrom(menuSummary) + .distinct() + .leftJoin(menuSummary.productSummaries, productSummary).fetchJoin() + .fetch(); + } +} diff --git a/src/main/java/kitchenpos/menus/ui/MenuGroupRestController.java b/src/main/java/kitchenpos/menu/ui/MenuGroupRestController.java similarity index 50% rename from src/main/java/kitchenpos/menus/ui/MenuGroupRestController.java rename to src/main/java/kitchenpos/menu/ui/MenuGroupRestController.java index 30c38b4a1..a1c779708 100644 --- a/src/main/java/kitchenpos/menus/ui/MenuGroupRestController.java +++ b/src/main/java/kitchenpos/menu/ui/MenuGroupRestController.java @@ -1,7 +1,12 @@ -package kitchenpos.menus.ui; +package kitchenpos.menu.ui; -import kitchenpos.menus.application.MenuGroupService; -import kitchenpos.menus.domain.MenuGroup; +import java.net.URI; +import java.util.List; +import kitchenpos.menu.application.MenuGroupService; +import kitchenpos.menu.application.dto.CreateMenuGroupServiceRq; +import kitchenpos.menu.application.dto.MenuGroupServiceRs; +import kitchenpos.menu.ui.dto.CreateMenuGroupRq; +import kitchenpos.menu.ui.dto.MenuGroupRs; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -9,9 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.net.URI; -import java.util.List; - @RequestMapping("/api/menu-groups") @RestController public class MenuGroupRestController { @@ -22,14 +24,19 @@ public MenuGroupRestController(final MenuGroupService menuGroupService) { } @PostMapping - public ResponseEntity create(@RequestBody final MenuGroup request) { - final MenuGroup response = menuGroupService.create(request); + public ResponseEntity create(@RequestBody final CreateMenuGroupRq request) { + MenuGroupServiceRs response = menuGroupService.create( + new CreateMenuGroupServiceRq(request.getName())); return ResponseEntity.created(URI.create("/api/menu-groups/" + response.getId())) - .body(response); + .body(new MenuGroupRs(response)); } @GetMapping - public ResponseEntity> findAll() { - return ResponseEntity.ok(menuGroupService.findAll()); + public ResponseEntity> findAll() { + return ResponseEntity.ok( + menuGroupService.findAll().stream() + .map(MenuGroupRs::new) + .toList() + ); } } diff --git a/src/main/java/kitchenpos/menu/ui/MenuRestController.java b/src/main/java/kitchenpos/menu/ui/MenuRestController.java new file mode 100644 index 000000000..e8aaa710f --- /dev/null +++ b/src/main/java/kitchenpos/menu/ui/MenuRestController.java @@ -0,0 +1,69 @@ +package kitchenpos.menu.ui; + +import java.net.URI; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.application.MenuQueryService; +import kitchenpos.menu.application.MenuService; +import kitchenpos.menu.application.dto.ChangeMenuPriceServiceRq; +import kitchenpos.menu.application.dto.MenuServiceRs; +import kitchenpos.menu.application.dto.SimpleMenuServiceRs; +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.ui.dto.ChangeMenuPriceRq; +import kitchenpos.menu.ui.dto.CreateMenuRq; +import kitchenpos.menu.ui.dto.MenuRs; +import kitchenpos.menu.ui.dto.SimpleMenuRs; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/menus") +@RestController +public class MenuRestController { + private final MenuService menuService; + private final MenuQueryService menuQueryService; + + public MenuRestController(final MenuService menuService, MenuQueryService menuQueryService) { + this.menuService = menuService; + this.menuQueryService = menuQueryService; + } + + @PostMapping + public ResponseEntity create(@RequestBody final CreateMenuRq request) { + MenuServiceRs response = menuService.create(request.toServiceRq()); + return ResponseEntity.created(URI.create("/api/menus/" + response.getId())) + .body(new MenuRs(response)); + } + + @PutMapping("/{menuId}/price") + public ResponseEntity changePrice(@PathVariable("menuId") final UUID menuId, + @RequestBody final ChangeMenuPriceRq request) { + SimpleMenuServiceRs response = menuService.changePrice( + menuId, + new ChangeMenuPriceServiceRq(request.getPrice()) + ); + return ResponseEntity.ok(new SimpleMenuRs(response)); + } + + @PutMapping("/{menuId}/display") + public ResponseEntity display(@PathVariable("menuId") final UUID menuId) { + SimpleMenuServiceRs response = menuService.display(menuId); + return ResponseEntity.ok(new SimpleMenuRs(response)); + } + + @PutMapping("/{menuId}/hide") + public ResponseEntity hide(@PathVariable("menuId") final UUID menuId) { + SimpleMenuServiceRs response = menuService.hide(menuId); + return ResponseEntity.ok(new SimpleMenuRs(response)); + } + + @GetMapping + public ResponseEntity> findAll() { + return ResponseEntity.ok(menuQueryService.findAll()); + } +} diff --git a/src/main/java/kitchenpos/menu/ui/dto/ChangeMenuPriceRq.java b/src/main/java/kitchenpos/menu/ui/dto/ChangeMenuPriceRq.java new file mode 100644 index 000000000..a9c7f6c07 --- /dev/null +++ b/src/main/java/kitchenpos/menu/ui/dto/ChangeMenuPriceRq.java @@ -0,0 +1,18 @@ +package kitchenpos.menu.ui.dto; + +import java.math.BigDecimal; + +public class ChangeMenuPriceRq { + private BigDecimal price; + + public ChangeMenuPriceRq(BigDecimal price) { + this.price = price; + } + + public ChangeMenuPriceRq() { + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/menu/ui/dto/CreateMenuGroupRq.java b/src/main/java/kitchenpos/menu/ui/dto/CreateMenuGroupRq.java new file mode 100644 index 000000000..1d9094385 --- /dev/null +++ b/src/main/java/kitchenpos/menu/ui/dto/CreateMenuGroupRq.java @@ -0,0 +1,20 @@ +package kitchenpos.menu.ui.dto; + +public class CreateMenuGroupRq { + private String name; + + public CreateMenuGroupRq(String name) { + this.name = name; + } + + public CreateMenuGroupRq(CreateMenuGroupRq menuGroup) { + this.name = menuGroup.getName(); + } + + public CreateMenuGroupRq() { + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/kitchenpos/menu/ui/dto/CreateMenuRq.java b/src/main/java/kitchenpos/menu/ui/dto/CreateMenuRq.java new file mode 100644 index 000000000..6bb6f0479 --- /dev/null +++ b/src/main/java/kitchenpos/menu/ui/dto/CreateMenuRq.java @@ -0,0 +1,80 @@ +package kitchenpos.menu.ui.dto; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.application.dto.CreateMenuServiceRq; +import kitchenpos.menu.application.dto.CreateMenuServiceRq.MenuProductServiceRq; + +public class CreateMenuRq { + private String name; + private BigDecimal price; + private boolean isDisplayed; + private UUID menuGroupId; + private List menuProductRqs; + + public CreateMenuRq(String name, BigDecimal price, boolean isDisplayed, UUID menuGroupId, + List menuProductRqs) { + this.name = name; + this.price = price; + this.isDisplayed = isDisplayed; + this.menuGroupId = menuGroupId; + this.menuProductRqs = menuProductRqs; + } + + public CreateMenuRq() { + } + + public CreateMenuServiceRq toServiceRq() { + return new CreateMenuServiceRq( + this.name, + this.price, + this.isDisplayed, + this.menuGroupId, + this.menuProductRqs.stream() + .map(mp -> new MenuProductServiceRq(mp.productId, mp.quantity)) + .toList() + ); + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } + + public UUID getMenuGroupId() { + return menuGroupId; + } + + public List getMenuProductRqs() { + return menuProductRqs; + } + + public static class MenuProductRq { + private UUID productId; + private long quantity; + + public MenuProductRq(UUID productId, long quantity) { + this.productId = productId; + this.quantity = quantity; + } + + public MenuProductRq() { + } + + public UUID getProductId() { + return productId; + } + + public long getQuantity() { + return quantity; + } + } +} diff --git a/src/main/java/kitchenpos/menu/ui/dto/MenuGroupRs.java b/src/main/java/kitchenpos/menu/ui/dto/MenuGroupRs.java new file mode 100644 index 000000000..4f77100ee --- /dev/null +++ b/src/main/java/kitchenpos/menu/ui/dto/MenuGroupRs.java @@ -0,0 +1,27 @@ +package kitchenpos.menu.ui.dto; + +import java.util.UUID; +import kitchenpos.menu.application.dto.MenuGroupServiceRs; + +public class MenuGroupRs { + private UUID id; + private String name; + + public MenuGroupRs(UUID id, String name) { + this.id = id; + this.name = name; + } + + public MenuGroupRs(MenuGroupServiceRs menuGroup) { + this.id = menuGroup.getId(); + this.name = menuGroup.getName(); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/kitchenpos/menu/ui/dto/MenuRs.java b/src/main/java/kitchenpos/menu/ui/dto/MenuRs.java new file mode 100644 index 000000000..e7eb2a2f9 --- /dev/null +++ b/src/main/java/kitchenpos/menu/ui/dto/MenuRs.java @@ -0,0 +1,81 @@ +package kitchenpos.menu.ui.dto; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.application.dto.MenuServiceRs; + +public class MenuRs { + private UUID id; + private String name; + private BigDecimal price; + private boolean isDisplayed; + private UUID menuGroupId; + private List menuProductRsList; + + public MenuRs(UUID id, String name, BigDecimal price, boolean isDisplayed, UUID menuGroupId, + List menuProductRsList) { + this.id = id; + this.name = name; + this.price = price; + this.isDisplayed = isDisplayed; + this.menuGroupId = menuGroupId; + this.menuProductRsList = menuProductRsList; + } + + public MenuRs(MenuServiceRs rs) { + this.id = rs.getId(); + this.name = rs.getName(); + this.price = rs.getPrice(); + this.isDisplayed = rs.isDisplayed(); + this.menuGroupId = rs.getMenuGroupId(); + this.menuProductRsList = rs.getMenuProductServiceRsList().stream() + .map(mpsrs -> new MenuProductRs(mpsrs.getProductId(), mpsrs.getQuantity())) + .toList(); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } + + public UUID getMenuGroupId() { + return menuGroupId; + } + + public List getMenuProductRsList() { + return menuProductRsList; + } + + public static class MenuProductRs { + private UUID productId; + private long quantity; + + public MenuProductRs(UUID productId, long quantity) { + this.productId = productId; + this.quantity = quantity; + } + + public MenuProductRs() { + } + + public UUID getProductId() { + return productId; + } + + public long getQuantity() { + return quantity; + } + } +} diff --git a/src/main/java/kitchenpos/menu/ui/dto/SimpleMenuRs.java b/src/main/java/kitchenpos/menu/ui/dto/SimpleMenuRs.java new file mode 100644 index 000000000..57053558e --- /dev/null +++ b/src/main/java/kitchenpos/menu/ui/dto/SimpleMenuRs.java @@ -0,0 +1,42 @@ +package kitchenpos.menu.ui.dto; + +import java.math.BigDecimal; +import java.util.UUID; +import kitchenpos.menu.application.dto.SimpleMenuServiceRs; + +public class SimpleMenuRs { + private UUID id; + private String name; + private BigDecimal price; + private boolean isDisplayed; + + public SimpleMenuRs(UUID id, String name, BigDecimal price, boolean isDisplayed) { + this.id = id; + this.name = name; + this.price = price; + this.isDisplayed = isDisplayed; + } + + public SimpleMenuRs(SimpleMenuServiceRs rs) { + this.id = rs.getId(); + this.name = rs.getName(); + this.price = rs.getPrice(); + this.isDisplayed = rs.isDisplayed(); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public boolean isDisplayed() { + return isDisplayed; + } +} diff --git a/src/main/java/kitchenpos/menus/application/MenuGroupService.java b/src/main/java/kitchenpos/menus/application/MenuGroupService.java deleted file mode 100644 index c468893a2..000000000 --- a/src/main/java/kitchenpos/menus/application/MenuGroupService.java +++ /dev/null @@ -1,36 +0,0 @@ -package kitchenpos.menus.application; - -import kitchenpos.menus.domain.MenuGroup; -import kitchenpos.menus.domain.MenuGroupRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -@Service -public class MenuGroupService { - private final MenuGroupRepository menuGroupRepository; - - public MenuGroupService(final MenuGroupRepository menuGroupRepository) { - this.menuGroupRepository = menuGroupRepository; - } - - @Transactional - public MenuGroup create(final MenuGroup request) { - final String name = request.getName(); - if (Objects.isNull(name) || name.isEmpty()) { - throw new IllegalArgumentException(); - } - final MenuGroup menuGroup = new MenuGroup(); - menuGroup.setId(UUID.randomUUID()); - menuGroup.setName(name); - return menuGroupRepository.save(menuGroup); - } - - @Transactional(readOnly = true) - public List findAll() { - return menuGroupRepository.findAll(); - } -} diff --git a/src/main/java/kitchenpos/menus/application/MenuService.java b/src/main/java/kitchenpos/menus/application/MenuService.java deleted file mode 100644 index abefa5bcf..000000000 --- a/src/main/java/kitchenpos/menus/application/MenuService.java +++ /dev/null @@ -1,149 +0,0 @@ -package kitchenpos.menus.application; - -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuGroup; -import kitchenpos.menus.domain.MenuGroupRepository; -import kitchenpos.menus.domain.MenuProduct; -import kitchenpos.menus.domain.MenuRepository; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; -import kitchenpos.products.infra.PurgomalumClient; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.UUID; - -@Service -public class MenuService { - private final MenuRepository menuRepository; - private final MenuGroupRepository menuGroupRepository; - private final ProductRepository productRepository; - private final PurgomalumClient purgomalumClient; - - public MenuService( - final MenuRepository menuRepository, - final MenuGroupRepository menuGroupRepository, - final ProductRepository productRepository, - final PurgomalumClient purgomalumClient - ) { - this.menuRepository = menuRepository; - this.menuGroupRepository = menuGroupRepository; - this.productRepository = productRepository; - this.purgomalumClient = purgomalumClient; - } - - @Transactional - public Menu create(final Menu request) { - final BigDecimal price = request.getPrice(); - if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); - } - final MenuGroup menuGroup = menuGroupRepository.findById(request.getMenuGroupId()) - .orElseThrow(NoSuchElementException::new); - final List menuProductRequests = request.getMenuProducts(); - if (Objects.isNull(menuProductRequests) || menuProductRequests.isEmpty()) { - throw new IllegalArgumentException(); - } - final List products = productRepository.findAllByIdIn( - menuProductRequests.stream() - .map(MenuProduct::getProductId) - .toList() - ); - if (products.size() != menuProductRequests.size()) { - throw new IllegalArgumentException(); - } - final List menuProducts = new ArrayList<>(); - BigDecimal sum = BigDecimal.ZERO; - for (final MenuProduct menuProductRequest : menuProductRequests) { - final long quantity = menuProductRequest.getQuantity(); - if (quantity < 0) { - throw new IllegalArgumentException(); - } - final Product product = productRepository.findById(menuProductRequest.getProductId()) - .orElseThrow(NoSuchElementException::new); - sum = sum.add( - product.getPrice() - .multiply(BigDecimal.valueOf(quantity)) - ); - final MenuProduct menuProduct = new MenuProduct(); - menuProduct.setProduct(product); - menuProduct.setQuantity(quantity); - menuProducts.add(menuProduct); - } - if (price.compareTo(sum) > 0) { - throw new IllegalArgumentException(); - } - final String name = request.getName(); - if (Objects.isNull(name) || purgomalumClient.containsProfanity(name)) { - throw new IllegalArgumentException(); - } - final Menu menu = new Menu(); - menu.setId(UUID.randomUUID()); - menu.setName(name); - menu.setPrice(price); - menu.setMenuGroup(menuGroup); - menu.setDisplayed(request.isDisplayed()); - menu.setMenuProducts(menuProducts); - return menuRepository.save(menu); - } - - @Transactional - public Menu changePrice(final UUID menuId, final Menu request) { - final BigDecimal price = request.getPrice(); - if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); - } - final Menu menu = menuRepository.findById(menuId) - .orElseThrow(NoSuchElementException::new); - BigDecimal sum = BigDecimal.ZERO; - for (final MenuProduct menuProduct : menu.getMenuProducts()) { - sum = sum.add( - menuProduct.getProduct() - .getPrice() - .multiply(BigDecimal.valueOf(menuProduct.getQuantity())) - ); - } - if (price.compareTo(sum) > 0) { - throw new IllegalArgumentException(); - } - menu.setPrice(price); - return menu; - } - - @Transactional - public Menu display(final UUID menuId) { - final Menu menu = menuRepository.findById(menuId) - .orElseThrow(NoSuchElementException::new); - BigDecimal sum = BigDecimal.ZERO; - for (final MenuProduct menuProduct : menu.getMenuProducts()) { - sum = sum.add( - menuProduct.getProduct() - .getPrice() - .multiply(BigDecimal.valueOf(menuProduct.getQuantity())) - ); - } - if (menu.getPrice().compareTo(sum) > 0) { - throw new IllegalStateException(); - } - menu.setDisplayed(true); - return menu; - } - - @Transactional - public Menu hide(final UUID menuId) { - final Menu menu = menuRepository.findById(menuId) - .orElseThrow(NoSuchElementException::new); - menu.setDisplayed(false); - return menu; - } - - @Transactional(readOnly = true) - public List findAll() { - return menuRepository.findAll(); - } -} diff --git a/src/main/java/kitchenpos/menus/domain/Menu.java b/src/main/java/kitchenpos/menus/domain/Menu.java deleted file mode 100644 index 8d1cc1139..000000000 --- a/src/main/java/kitchenpos/menus/domain/Menu.java +++ /dev/null @@ -1,112 +0,0 @@ -package kitchenpos.menus.domain; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.Transient; - -import java.math.BigDecimal; -import java.util.List; -import java.util.UUID; - -@Table(name = "menu") -@Entity -public class Menu { - @Column(name = "id", columnDefinition = "binary(16)") - @Id - private UUID id; - - @Column(name = "name", nullable = false) - private String name; - - @Column(name = "price", nullable = false) - private BigDecimal price; - - @ManyToOne(optional = false) - @JoinColumn( - name = "menu_group_id", - columnDefinition = "binary(16)", - foreignKey = @ForeignKey(name = "fk_menu_to_menu_group") - ) - private MenuGroup menuGroup; - - @Column(name = "displayed", nullable = false) - private boolean displayed; - - @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) - @JoinColumn( - name = "menu_id", - nullable = false, - columnDefinition = "binary(16)", - foreignKey = @ForeignKey(name = "fk_menu_product_to_menu") - ) - private List menuProducts; - - @Transient - private UUID menuGroupId; - - public Menu() { - } - - public UUID getId() { - return id; - } - - public void setId(final UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public BigDecimal getPrice() { - return price; - } - - public void setPrice(final BigDecimal price) { - this.price = price; - } - - public MenuGroup getMenuGroup() { - return menuGroup; - } - - public void setMenuGroup(final MenuGroup menuGroup) { - this.menuGroup = menuGroup; - } - - public boolean isDisplayed() { - return displayed; - } - - public void setDisplayed(final boolean displayed) { - this.displayed = displayed; - } - - public List getMenuProducts() { - return menuProducts; - } - - public void setMenuProducts(final List menuProducts) { - this.menuProducts = menuProducts; - } - - public UUID getMenuGroupId() { - return menuGroupId; - } - - public void setMenuGroupId(final UUID menuGroupId) { - this.menuGroupId = menuGroupId; - } -} diff --git a/src/main/java/kitchenpos/menus/ui/MenuRestController.java b/src/main/java/kitchenpos/menus/ui/MenuRestController.java deleted file mode 100644 index 626b214fc..000000000 --- a/src/main/java/kitchenpos/menus/ui/MenuRestController.java +++ /dev/null @@ -1,53 +0,0 @@ -package kitchenpos.menus.ui; - -import kitchenpos.menus.application.MenuService; -import kitchenpos.menus.domain.Menu; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.net.URI; -import java.util.List; -import java.util.UUID; - -@RequestMapping("/api/menus") -@RestController -public class MenuRestController { - private final MenuService menuService; - - public MenuRestController(final MenuService menuService) { - this.menuService = menuService; - } - - @PostMapping - public ResponseEntity create(@RequestBody final Menu request) { - final Menu response = menuService.create(request); - return ResponseEntity.created(URI.create("/api/menus/" + response.getId())) - .body(response); - } - - @PutMapping("/{menuId}/price") - public ResponseEntity changePrice(@PathVariable final UUID menuId, @RequestBody final Menu request) { - return ResponseEntity.ok(menuService.changePrice(menuId, request)); - } - - @PutMapping("/{menuId}/display") - public ResponseEntity display(@PathVariable final UUID menuId) { - return ResponseEntity.ok(menuService.display(menuId)); - } - - @PutMapping("/{menuId}/hide") - public ResponseEntity hide(@PathVariable final UUID menuId) { - return ResponseEntity.ok(menuService.hide(menuId)); - } - - @GetMapping - public ResponseEntity> findAll() { - return ResponseEntity.ok(menuService.findAll()); - } -} diff --git a/src/main/java/kitchenpos/eatinorders/application/OrderService.java b/src/main/java/kitchenpos/order/common/application/OrderService.java similarity index 78% rename from src/main/java/kitchenpos/eatinorders/application/OrderService.java rename to src/main/java/kitchenpos/order/common/application/OrderService.java index 1159a0ba0..88994b97d 100644 --- a/src/main/java/kitchenpos/eatinorders/application/OrderService.java +++ b/src/main/java/kitchenpos/order/common/application/OrderService.java @@ -1,17 +1,4 @@ -package kitchenpos.eatinorders.application; - -import kitchenpos.deliveryorders.infra.KitchenridersClient; -import kitchenpos.eatinorders.domain.Order; -import kitchenpos.eatinorders.domain.OrderLineItem; -import kitchenpos.eatinorders.domain.OrderRepository; -import kitchenpos.eatinorders.domain.OrderStatus; -import kitchenpos.eatinorders.domain.OrderTable; -import kitchenpos.eatinorders.domain.OrderTableRepository; -import kitchenpos.eatinorders.domain.OrderType; -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +package kitchenpos.order.common.application; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -20,6 +7,18 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.order.common.model.Order; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.common.model.OrderStatus; +import kitchenpos.order.common.model.OrderType; +import kitchenpos.order.common.repository.OrderRepository; +import kitchenpos.order.deliveryorder.infra.external.KitchenridersClient; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class OrderService { @@ -29,10 +28,10 @@ public class OrderService { private final KitchenridersClient kitchenridersClient; public OrderService( - final OrderRepository orderRepository, - final MenuRepository menuRepository, - final OrderTableRepository orderTableRepository, - final KitchenridersClient kitchenridersClient + final OrderRepository orderRepository, + final MenuRepository menuRepository, + final OrderTableRepository orderTableRepository, + final KitchenridersClient kitchenridersClient ) { this.orderRepository = orderRepository; this.menuRepository = menuRepository; @@ -51,9 +50,9 @@ public Order create(final Order request) { throw new IllegalArgumentException(); } final List menus = menuRepository.findAllByIdIn( - orderLineItemRequests.stream() - .map(OrderLineItem::getMenuId) - .toList() + orderLineItemRequests.stream() + .map(OrderLineItem::getMenuId) + .toList() ); if (menus.size() != orderLineItemRequests.size()) { throw new IllegalArgumentException(); @@ -67,16 +66,16 @@ public Order create(final Order request) { } } final Menu menu = menuRepository.findById(orderLineItemRequest.getMenuId()) - .orElseThrow(NoSuchElementException::new); + .orElseThrow(NoSuchElementException::new); if (!menu.isDisplayed()) { throw new IllegalStateException(); } - if (menu.getPrice().compareTo(orderLineItemRequest.getPrice()) != 0) { + if (menu.getInnerPrice().compareTo(orderLineItemRequest.getPrice()) != 0) { throw new IllegalArgumentException(); } final OrderLineItem orderLineItem = new OrderLineItem(); - orderLineItem.setMenu(menu); - orderLineItem.setQuantity(quantity); + orderLineItem.addMenu(menu); + orderLineItem.addQuantity(quantity); orderLineItems.add(orderLineItem); } Order order = new Order(); @@ -94,7 +93,7 @@ public Order create(final Order request) { } if (type == OrderType.EAT_IN) { final OrderTable orderTable = orderTableRepository.findById(request.getOrderTableId()) - .orElseThrow(NoSuchElementException::new); + .orElseThrow(NoSuchElementException::new); if (!orderTable.isOccupied()) { throw new IllegalStateException(); } @@ -106,7 +105,7 @@ public Order create(final Order request) { @Transactional public Order accept(final UUID orderId) { final Order order = orderRepository.findById(orderId) - .orElseThrow(NoSuchElementException::new); + .orElseThrow(NoSuchElementException::new); if (order.getStatus() != OrderStatus.WAITING) { throw new IllegalStateException(); } @@ -114,8 +113,8 @@ public Order accept(final UUID orderId) { BigDecimal sum = BigDecimal.ZERO; for (final OrderLineItem orderLineItem : order.getOrderLineItems()) { sum = orderLineItem.getMenu() - .getPrice() - .multiply(BigDecimal.valueOf(orderLineItem.getQuantity())); + .getInnerPrice() + .multiply(BigDecimal.valueOf(orderLineItem.getQuantity())); } kitchenridersClient.requestDelivery(orderId, sum, order.getDeliveryAddress()); } @@ -126,7 +125,7 @@ public Order accept(final UUID orderId) { @Transactional public Order serve(final UUID orderId) { final Order order = orderRepository.findById(orderId) - .orElseThrow(NoSuchElementException::new); + .orElseThrow(NoSuchElementException::new); if (order.getStatus() != OrderStatus.ACCEPTED) { throw new IllegalStateException(); } @@ -137,7 +136,7 @@ public Order serve(final UUID orderId) { @Transactional public Order startDelivery(final UUID orderId) { final Order order = orderRepository.findById(orderId) - .orElseThrow(NoSuchElementException::new); + .orElseThrow(NoSuchElementException::new); if (order.getType() != OrderType.DELIVERY) { throw new IllegalStateException(); } @@ -151,7 +150,7 @@ public Order startDelivery(final UUID orderId) { @Transactional public Order completeDelivery(final UUID orderId) { final Order order = orderRepository.findById(orderId) - .orElseThrow(NoSuchElementException::new); + .orElseThrow(NoSuchElementException::new); if (order.getStatus() != OrderStatus.DELIVERING) { throw new IllegalStateException(); } @@ -162,7 +161,7 @@ public Order completeDelivery(final UUID orderId) { @Transactional public Order complete(final UUID orderId) { final Order order = orderRepository.findById(orderId) - .orElseThrow(NoSuchElementException::new); + .orElseThrow(NoSuchElementException::new); final OrderType type = order.getType(); final OrderStatus status = order.getStatus(); if (type == OrderType.DELIVERY) { @@ -179,8 +178,7 @@ public Order complete(final UUID orderId) { if (type == OrderType.EAT_IN) { final OrderTable orderTable = order.getOrderTable(); if (!orderRepository.existsByOrderTableAndStatusNot(orderTable, OrderStatus.COMPLETED)) { - orderTable.setNumberOfGuests(0); - orderTable.setOccupied(false); + orderTable.releaseTable(); } } return order; diff --git a/src/main/java/kitchenpos/order/common/exception/OrderLineItemExceptionMessage.java b/src/main/java/kitchenpos/order/common/exception/OrderLineItemExceptionMessage.java new file mode 100644 index 000000000..945ca0378 --- /dev/null +++ b/src/main/java/kitchenpos/order/common/exception/OrderLineItemExceptionMessage.java @@ -0,0 +1,18 @@ +package kitchenpos.order.common.exception; + +public enum OrderLineItemExceptionMessage { + ORDER_LINE_ITEM_QUANTITY_EXCEPTION("주문 내역의 메뉴 수량이 비어있습니다!"), + ORDER_LINE_ITEM_MENU_DISPLAY_EXCEPTION("주문 내역의 메뉴가 게시되어 있지 않습니다!"), + ORDER_LINE_ITEM_PRICE_EXCEPTION("주문 내역의 가격이 메뉴의 가격과 다릅니다!"), + ORDER_LINE_ITEM_INCORRECT_MENU_INFO_EXCEPTION("주문 내역의 메뉴 정보가 올바르지 않습니다."); + + private final String message; + + OrderLineItemExceptionMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kitchenpos/eatinorders/domain/JpaOrderRepository.java b/src/main/java/kitchenpos/order/common/infra/persistence/JpaOrderRepository.java similarity index 53% rename from src/main/java/kitchenpos/eatinorders/domain/JpaOrderRepository.java rename to src/main/java/kitchenpos/order/common/infra/persistence/JpaOrderRepository.java index 01c825c45..22b55e7a9 100644 --- a/src/main/java/kitchenpos/eatinorders/domain/JpaOrderRepository.java +++ b/src/main/java/kitchenpos/order/common/infra/persistence/JpaOrderRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.eatinorders.domain; - -import org.springframework.data.jpa.repository.JpaRepository; +package kitchenpos.order.common.infra.persistence; import java.util.UUID; +import kitchenpos.order.common.model.Order; +import kitchenpos.order.common.repository.OrderRepository; +import org.springframework.data.jpa.repository.JpaRepository; public interface JpaOrderRepository extends OrderRepository, JpaRepository { } diff --git a/src/main/java/kitchenpos/eatinorders/domain/JpaOrderTableRepository.java b/src/main/java/kitchenpos/order/common/infra/persistence/JpaOrderTableRepository.java similarity index 51% rename from src/main/java/kitchenpos/eatinorders/domain/JpaOrderTableRepository.java rename to src/main/java/kitchenpos/order/common/infra/persistence/JpaOrderTableRepository.java index 84c0d3c6f..975ca8c6c 100644 --- a/src/main/java/kitchenpos/eatinorders/domain/JpaOrderTableRepository.java +++ b/src/main/java/kitchenpos/order/common/infra/persistence/JpaOrderTableRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.eatinorders.domain; - -import org.springframework.data.jpa.repository.JpaRepository; +package kitchenpos.order.common.infra.persistence; import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import org.springframework.data.jpa.repository.JpaRepository; public interface JpaOrderTableRepository extends OrderTableRepository, JpaRepository { } diff --git a/src/main/java/kitchenpos/eatinorders/domain/Order.java b/src/main/java/kitchenpos/order/common/model/Order.java similarity index 68% rename from src/main/java/kitchenpos/eatinorders/domain/Order.java rename to src/main/java/kitchenpos/order/common/model/Order.java index 4a5991301..fb29c3bc2 100644 --- a/src/main/java/kitchenpos/eatinorders/domain/Order.java +++ b/src/main/java/kitchenpos/order/common/model/Order.java @@ -1,4 +1,4 @@ -package kitchenpos.eatinorders.domain; +package kitchenpos.order.common.model; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -12,10 +12,10 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.Transient; - import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.OrderTable; @Table(name = "orders") @Entity @@ -37,10 +37,10 @@ public class Order { @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn( - name = "order_id", - nullable = false, - columnDefinition = "binary(16)", - foreignKey = @ForeignKey(name = "fk_order_line_item_to_orders") + name = "order_id", +// nullable = false, + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_order_line_item_to_orders") ) private List orderLineItems; @@ -49,9 +49,9 @@ public class Order { @ManyToOne @JoinColumn( - name = "order_table_id", - columnDefinition = "binary(16)", - foreignKey = @ForeignKey(name = "fk_orders_to_order_table") + name = "order_table_id", + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_orders_to_order_table") ) private OrderTable orderTable; @@ -61,6 +61,23 @@ public class Order { public Order() { } + public Order(UUID id, OrderType type, OrderStatus status, LocalDateTime orderDateTime, + List orderLineItems, String deliveryAddress, OrderTable orderTable, UUID orderTableId) { + this.id = id; + this.type = type; + this.status = status; + this.orderDateTime = orderDateTime; + this.orderLineItems = orderLineItems; + this.deliveryAddress = deliveryAddress; + this.orderTable = orderTable; + this.orderTableId = orderTableId; + } + + public Order(OrderType type, OrderStatus status, LocalDateTime orderDateTime, + List orderLineItems, String deliveryAddress, OrderTable orderTable, UUID orderTableId) { + this(UUID.randomUUID(), type, status, orderDateTime, orderLineItems, deliveryAddress, orderTable, orderTableId); + } + public UUID getId() { return id; } @@ -85,10 +102,6 @@ public void setStatus(final OrderStatus status) { this.status = status; } - public LocalDateTime getOrderDateTime() { - return orderDateTime; - } - public void setOrderDateTime(final LocalDateTime orderDateTime) { this.orderDateTime = orderDateTime; } @@ -120,8 +133,4 @@ public void setOrderTable(final OrderTable orderTable) { public UUID getOrderTableId() { return orderTableId; } - - public void setOrderTableId(final UUID orderTableId) { - this.orderTableId = orderTableId; - } } diff --git a/src/main/java/kitchenpos/order/common/model/OrderLineItem.java b/src/main/java/kitchenpos/order/common/model/OrderLineItem.java new file mode 100644 index 000000000..38fc23846 --- /dev/null +++ b/src/main/java/kitchenpos/order/common/model/OrderLineItem.java @@ -0,0 +1,104 @@ +package kitchenpos.order.common.model; + +import static kitchenpos.order.common.exception.OrderLineItemExceptionMessage.ORDER_LINE_ITEM_MENU_DISPLAY_EXCEPTION; +import static kitchenpos.order.common.exception.OrderLineItemExceptionMessage.ORDER_LINE_ITEM_PRICE_EXCEPTION; +import static kitchenpos.order.common.exception.OrderLineItemExceptionMessage.ORDER_LINE_ITEM_QUANTITY_EXCEPTION; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import java.math.BigDecimal; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; + +@Table(name = "order_line_item") +@Entity +public class OrderLineItem { + @Column(name = "seq") + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long seq; + + @ManyToOne(optional = false) + @JoinColumn( + name = "menu_id", + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_order_line_item_to_menu") + ) + private Menu menu; + + @Column(name = "quantity", nullable = false) + private long quantity; + + @Transient + private UUID menuId; + + @Transient + private BigDecimal price; + + public OrderLineItem() { + } + + public OrderLineItem(Menu menu, long quantity, UUID menuId, BigDecimal price) { + validateQuantity(quantity); + validateMenuDisplay(menu); + validatePrice(menu, price); + this.menu = menu; + this.quantity = quantity; + this.menuId = menuId; + this.price = price; + } + + private void validateQuantity(long quantity) { + if (quantity <= 0) { + throw new IllegalArgumentException(ORDER_LINE_ITEM_QUANTITY_EXCEPTION.getMessage()); + } + } + + private void validateMenuDisplay(Menu menu) { + if (!menu.isDisplayed()) { + throw new IllegalStateException(ORDER_LINE_ITEM_MENU_DISPLAY_EXCEPTION.getMessage()); + } + } + + private void validatePrice(Menu menu, BigDecimal price) { + if (menu.getInnerPrice().compareTo(price) != 0) { + throw new IllegalArgumentException(ORDER_LINE_ITEM_PRICE_EXCEPTION.getMessage()); + } + } + + public Menu getMenu() { + return menu; + } + + public void addMenu(final Menu menu) { + this.menu = menu; + } + + public long getQuantity() { + return quantity; + } + + public void addQuantity(final long quantity) { + this.quantity = quantity; + } + + public UUID getMenuId() { + return menuId; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(final BigDecimal price) { + this.price = price; + } +} diff --git a/src/main/java/kitchenpos/order/common/model/OrderLineItemValidator.java b/src/main/java/kitchenpos/order/common/model/OrderLineItemValidator.java new file mode 100644 index 000000000..86d85a0e4 --- /dev/null +++ b/src/main/java/kitchenpos/order/common/model/OrderLineItemValidator.java @@ -0,0 +1,37 @@ +package kitchenpos.order.common.model; + +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.order.common.exception.OrderLineItemExceptionMessage; +import org.springframework.stereotype.Service; + +@Service +public class OrderLineItemValidator { + + private final MenuRepository menuRepository; + + public OrderLineItemValidator(MenuRepository menuRepository) { + this.menuRepository = menuRepository; + } + + public void validate(List orderLineItems) { + final List menuIds = findMenuIdsBy(orderLineItems); + final List menus = menuRepository.findAllByIdIn(menuIds); + validateMenuExists(orderLineItems, menus); + } + + private List findMenuIdsBy(List orderLineItems) { + return orderLineItems.stream() + .map(OrderLineItem::getMenuId) + .toList(); + } + + private void validateMenuExists(List orderLineItems, List menus) { + if (menus.size() != orderLineItems.size()) { + throw new IllegalArgumentException( + OrderLineItemExceptionMessage.ORDER_LINE_ITEM_INCORRECT_MENU_INFO_EXCEPTION.getMessage()); + } + } +} diff --git a/src/main/java/kitchenpos/eatinorders/domain/OrderStatus.java b/src/main/java/kitchenpos/order/common/model/OrderStatus.java similarity index 70% rename from src/main/java/kitchenpos/eatinorders/domain/OrderStatus.java rename to src/main/java/kitchenpos/order/common/model/OrderStatus.java index fe0f76c7d..ae2e45364 100644 --- a/src/main/java/kitchenpos/eatinorders/domain/OrderStatus.java +++ b/src/main/java/kitchenpos/order/common/model/OrderStatus.java @@ -1,4 +1,4 @@ -package kitchenpos.eatinorders.domain; +package kitchenpos.order.common.model; public enum OrderStatus { WAITING, ACCEPTED, SERVED, DELIVERING, DELIVERED, COMPLETED diff --git a/src/main/java/kitchenpos/eatinorders/domain/OrderType.java b/src/main/java/kitchenpos/order/common/model/OrderType.java similarity index 59% rename from src/main/java/kitchenpos/eatinorders/domain/OrderType.java rename to src/main/java/kitchenpos/order/common/model/OrderType.java index 0e3133e69..d68c3bbcf 100644 --- a/src/main/java/kitchenpos/eatinorders/domain/OrderType.java +++ b/src/main/java/kitchenpos/order/common/model/OrderType.java @@ -1,4 +1,4 @@ -package kitchenpos.eatinorders.domain; +package kitchenpos.order.common.model; public enum OrderType { DELIVERY, TAKEOUT, EAT_IN diff --git a/src/main/java/kitchenpos/eatinorders/domain/OrderRepository.java b/src/main/java/kitchenpos/order/common/repository/OrderRepository.java similarity index 60% rename from src/main/java/kitchenpos/eatinorders/domain/OrderRepository.java rename to src/main/java/kitchenpos/order/common/repository/OrderRepository.java index f98d45c15..843816c3c 100644 --- a/src/main/java/kitchenpos/eatinorders/domain/OrderRepository.java +++ b/src/main/java/kitchenpos/order/common/repository/OrderRepository.java @@ -1,8 +1,11 @@ -package kitchenpos.eatinorders.domain; +package kitchenpos.order.common.repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import kitchenpos.order.common.model.Order; +import kitchenpos.order.common.model.OrderStatus; +import kitchenpos.order.eatinorder.domain.model.OrderTable; public interface OrderRepository { Order save(Order order); diff --git a/src/main/java/kitchenpos/eatinorders/ui/OrderRestController.java b/src/main/java/kitchenpos/order/common/ui/OrderRestController.java similarity index 74% rename from src/main/java/kitchenpos/eatinorders/ui/OrderRestController.java rename to src/main/java/kitchenpos/order/common/ui/OrderRestController.java index dbb5568ad..7fa233224 100644 --- a/src/main/java/kitchenpos/eatinorders/ui/OrderRestController.java +++ b/src/main/java/kitchenpos/order/common/ui/OrderRestController.java @@ -1,7 +1,10 @@ -package kitchenpos.eatinorders.ui; +package kitchenpos.order.common.ui; -import kitchenpos.eatinorders.application.OrderService; -import kitchenpos.eatinorders.domain.Order; +import java.net.URI; +import java.util.List; +import java.util.UUID; +import kitchenpos.order.common.application.OrderService; +import kitchenpos.order.common.model.Order; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -11,10 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.net.URI; -import java.util.List; -import java.util.UUID; - @RequestMapping("/api/orders") @RestController public class OrderRestController { @@ -28,31 +27,31 @@ public OrderRestController(final OrderService orderService) { public ResponseEntity create(@RequestBody final Order request) { final Order response = orderService.create(request); return ResponseEntity.created(URI.create("/api/orders/" + response.getId())) - .body(response); + .body(response); } @PutMapping("/{orderId}/accept") - public ResponseEntity accept(@PathVariable final UUID orderId) { + public ResponseEntity accept(@PathVariable("orderId") final UUID orderId) { return ResponseEntity.ok(orderService.accept(orderId)); } @PutMapping("/{orderId}/serve") - public ResponseEntity serve(@PathVariable final UUID orderId) { + public ResponseEntity serve(@PathVariable("orderId") final UUID orderId) { return ResponseEntity.ok(orderService.serve(orderId)); } @PutMapping("/{orderId}/start-delivery") - public ResponseEntity startDelivery(@PathVariable final UUID orderId) { + public ResponseEntity startDelivery(@PathVariable("orderId") final UUID orderId) { return ResponseEntity.ok(orderService.startDelivery(orderId)); } @PutMapping("/{orderId}/complete-delivery") - public ResponseEntity completeDelivery(@PathVariable final UUID orderId) { + public ResponseEntity completeDelivery(@PathVariable("orderId") final UUID orderId) { return ResponseEntity.ok(orderService.completeDelivery(orderId)); } @PutMapping("/{orderId}/complete") - public ResponseEntity complete(@PathVariable final UUID orderId) { + public ResponseEntity complete(@PathVariable("orderId") final UUID orderId) { return ResponseEntity.ok(orderService.complete(orderId)); } diff --git a/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrder.java b/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrder.java new file mode 100644 index 000000000..17495dd38 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrder.java @@ -0,0 +1,45 @@ +package kitchenpos.order.deliveryorder.domain.model; + +import java.time.LocalDateTime; +import java.util.UUID; + +//@Entity +public class DeliveryOrder { + + // @Id +// @GeneratedValue + private UUID id; + + // @Enumerated + private DeliveryOrderFlow orderFlow; + + // @Column(name = "order_date_time", nullable = false) + private LocalDateTime orderDateTime; + +// @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) +// @JoinColumn( +// name = "order_id", +// nullable = false, +// columnDefinition = "binary(16)", +// foreignKey = @ForeignKey(name = "fk_order_line_item_to_orders") +// ) +// private List orderLineItems; + + // @Column(name = "delivery_address") + private String deliveryAddress; + + public boolean validateOrderFlowAndFindNextStep(DeliveryOrderStatus orderStatus) { + if (orderFlow.validateOrderStatus(orderStatus)) { + throw new IllegalArgumentException(); + } + return orderFlow.isRiderNecessary(orderStatus); + } + + public UUID getId() { + return id; + } + + public String getDeliveryAddress() { + return deliveryAddress; + } +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrderFlow.java b/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrderFlow.java new file mode 100644 index 000000000..36181def6 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrderFlow.java @@ -0,0 +1,31 @@ +package kitchenpos.order.deliveryorder.domain.model; + +import java.util.Arrays; + +public enum DeliveryOrderFlow { + ACCEPTED(DeliveryOrderStatus.ACCEPTED, DeliveryOrderStatus.WAITING), + SERVED(DeliveryOrderStatus.SERVED, DeliveryOrderStatus.ACCEPTED), + DELIVERING(DeliveryOrderStatus.DELIVERING, DeliveryOrderStatus.SERVED), + DELIVERED(DeliveryOrderStatus.DELIVERED, DeliveryOrderStatus.DELIVERING), + COMPLETED(DeliveryOrderStatus.COMPLETED, DeliveryOrderStatus.DELIVERED); + + private final DeliveryOrderStatus nextStatus; + private final DeliveryOrderStatus previousStatus; + + DeliveryOrderFlow(DeliveryOrderStatus nextStatus, DeliveryOrderStatus previousStatus) { + this.nextStatus = nextStatus; + this.previousStatus = previousStatus; + } + + public boolean validateOrderStatus(DeliveryOrderStatus nextOrderStatus) { + DeliveryOrderFlow nextStatus = Arrays.stream(values()) + .filter(v -> v.nextStatus == nextOrderStatus) + .findFirst() + .orElseThrow(); + return nextStatus.previousStatus != (this.nextStatus); + } + + public boolean isRiderNecessary(DeliveryOrderStatus orderStatus) { + return orderStatus == DeliveryOrderStatus.SERVED; + } +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrderStatus.java b/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrderStatus.java new file mode 100644 index 000000000..44dbfdfe9 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/domain/model/DeliveryOrderStatus.java @@ -0,0 +1,5 @@ +package kitchenpos.order.deliveryorder.domain.model; + +public enum DeliveryOrderStatus { + WAITING, ACCEPTED, SERVED, DELIVERING, DELIVERED, COMPLETED +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/domain/port/RiderPort.java b/src/main/java/kitchenpos/order/deliveryorder/domain/port/RiderPort.java new file mode 100644 index 000000000..13d8e0bec --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/domain/port/RiderPort.java @@ -0,0 +1,10 @@ +package kitchenpos.order.deliveryorder.domain.port; + +import kitchenpos.order.deliveryorder.domain.model.DeliveryOrder; +import org.springframework.stereotype.Component; + +@Component +public interface RiderPort { + + void requestRider(DeliveryOrder order); +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/domain/repository/DeliveryRepository.java b/src/main/java/kitchenpos/order/deliveryorder/domain/repository/DeliveryRepository.java new file mode 100644 index 000000000..ce903f938 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/domain/repository/DeliveryRepository.java @@ -0,0 +1,4 @@ +package kitchenpos.order.deliveryorder.domain.repository; + +public interface DeliveryRepository { +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/domain/service/DeliveryOrderDomainService.java b/src/main/java/kitchenpos/order/deliveryorder/domain/service/DeliveryOrderDomainService.java new file mode 100644 index 000000000..36d804f40 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/domain/service/DeliveryOrderDomainService.java @@ -0,0 +1,23 @@ +package kitchenpos.order.deliveryorder.domain.service; + +import kitchenpos.order.deliveryorder.domain.model.DeliveryOrder; +import kitchenpos.order.deliveryorder.domain.model.DeliveryOrderStatus; +import kitchenpos.order.deliveryorder.domain.port.RiderPort; + +public class DeliveryOrderDomainService { + + private final RiderPort riderPort; + private final DeliveryOrder deliveryOrder; + + public DeliveryOrderDomainService(RiderPort riderPort, DeliveryOrder deliveryOrder) { + this.riderPort = riderPort; + this.deliveryOrder = deliveryOrder; + } + + public void doRide(DeliveryOrderStatus orderStatus) { + boolean existsNextStep = deliveryOrder.validateOrderFlowAndFindNextStep(orderStatus); + if (existsNextStep) { + riderPort.requestRider(deliveryOrder); + } + } +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/infra/adaptor/RiderAdaptor.java b/src/main/java/kitchenpos/order/deliveryorder/infra/adaptor/RiderAdaptor.java new file mode 100644 index 000000000..faf88dabd --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/infra/adaptor/RiderAdaptor.java @@ -0,0 +1,16 @@ +package kitchenpos.order.deliveryorder.infra.adaptor; + +import java.math.BigDecimal; +import kitchenpos.order.deliveryorder.domain.model.DeliveryOrder; +import kitchenpos.order.deliveryorder.domain.port.RiderPort; +import kitchenpos.order.deliveryorder.infra.external.KitchenridersClient; + +public class RiderAdaptor implements RiderPort { + + private KitchenridersClient kitchenridersClient; + + @Override + public void requestRider(DeliveryOrder order) { + kitchenridersClient.requestDelivery(order.getId(), BigDecimal.ONE, order.getDeliveryAddress()); + } +} diff --git a/src/main/java/kitchenpos/deliveryorders/infra/DefaultKitchenridersClient.java b/src/main/java/kitchenpos/order/deliveryorder/infra/external/DefaultKitchenridersClient.java similarity index 85% rename from src/main/java/kitchenpos/deliveryorders/infra/DefaultKitchenridersClient.java rename to src/main/java/kitchenpos/order/deliveryorder/infra/external/DefaultKitchenridersClient.java index 31d6d8cee..580d076a1 100644 --- a/src/main/java/kitchenpos/deliveryorders/infra/DefaultKitchenridersClient.java +++ b/src/main/java/kitchenpos/order/deliveryorder/infra/external/DefaultKitchenridersClient.java @@ -1,9 +1,8 @@ -package kitchenpos.deliveryorders.infra; - -import org.springframework.stereotype.Component; +package kitchenpos.order.deliveryorder.infra.external; import java.math.BigDecimal; import java.util.UUID; +import org.springframework.stereotype.Component; @Component public class DefaultKitchenridersClient implements KitchenridersClient { diff --git a/src/main/java/kitchenpos/deliveryorders/infra/KitchenridersClient.java b/src/main/java/kitchenpos/order/deliveryorder/infra/external/KitchenridersClient.java similarity index 76% rename from src/main/java/kitchenpos/deliveryorders/infra/KitchenridersClient.java rename to src/main/java/kitchenpos/order/deliveryorder/infra/external/KitchenridersClient.java index 0c8278791..061d45428 100644 --- a/src/main/java/kitchenpos/deliveryorders/infra/KitchenridersClient.java +++ b/src/main/java/kitchenpos/order/deliveryorder/infra/external/KitchenridersClient.java @@ -1,4 +1,4 @@ -package kitchenpos.deliveryorders.infra; +package kitchenpos.order.deliveryorder.infra.external; import java.math.BigDecimal; import java.util.UUID; diff --git a/src/main/java/kitchenpos/order/deliveryorder/infra/persistence/JpaDeliveryOrderRepository.java b/src/main/java/kitchenpos/order/deliveryorder/infra/persistence/JpaDeliveryOrderRepository.java new file mode 100644 index 000000000..19240e443 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/infra/persistence/JpaDeliveryOrderRepository.java @@ -0,0 +1,6 @@ +package kitchenpos.order.deliveryorder.infra.persistence; + +import kitchenpos.order.deliveryorder.domain.repository.DeliveryRepository; + +public interface JpaDeliveryOrderRepository extends DeliveryRepository { +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/service/DeliveryOrderService.java b/src/main/java/kitchenpos/order/deliveryorder/service/DeliveryOrderService.java new file mode 100644 index 000000000..55fa1b586 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/service/DeliveryOrderService.java @@ -0,0 +1,4 @@ +package kitchenpos.order.deliveryorder.service; + +public class DeliveryOrderService { +} diff --git a/src/main/java/kitchenpos/order/deliveryorder/ui/DeliveryOrderController.java b/src/main/java/kitchenpos/order/deliveryorder/ui/DeliveryOrderController.java new file mode 100644 index 000000000..217461485 --- /dev/null +++ b/src/main/java/kitchenpos/order/deliveryorder/ui/DeliveryOrderController.java @@ -0,0 +1,4 @@ +package kitchenpos.order.deliveryorder.ui; + +public class DeliveryOrderController { +} diff --git a/src/main/java/kitchenpos/order/eatinorder/application/EatInOrderService.java b/src/main/java/kitchenpos/order/eatinorder/application/EatInOrderService.java new file mode 100644 index 000000000..2f5b41d1b --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/application/EatInOrderService.java @@ -0,0 +1,84 @@ +package kitchenpos.order.eatinorder.application; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.eatinorder.application.dto.CreateEatInOrderServiceRq; +import kitchenpos.order.eatinorder.application.dto.CreateEatInOrderServiceRq.OrderLineItemServiceDto; +import kitchenpos.order.eatinorder.application.dto.EatInOrderServiceRs; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFactory; +import kitchenpos.order.eatinorder.domain.model.EatInOrderStatus; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class EatInOrderService { + private final EatInOrderRepository eatInOrderRepository; + private final MenuRepository menuRepository; + private final EatInOrderFactory eatInOrderFactory; + + public EatInOrderService( + final EatInOrderRepository eatInOrderRepository, + final MenuRepository menuRepository, + final EatInOrderFactory eatInOrderFactory + ) { + this.eatInOrderRepository = eatInOrderRepository; + this.menuRepository = menuRepository; + this.eatInOrderFactory = eatInOrderFactory; + } + + @Transactional + public EatInOrderServiceRs create(final CreateEatInOrderServiceRq request) { + final List orderLineItems = toOrderLineItems(request.getOrderLineItemDtos()); + EatInOrder eatInOrder = eatInOrderFactory.create(orderLineItems, request.getOrderTableId()); + eatInOrderRepository.save(eatInOrder); + return new EatInOrderServiceRs(eatInOrder); + } + + private List toOrderLineItems(List request) { + return request.stream() + .map(itemRq -> { + final Menu menu = menuRepository.findById(itemRq.getMenuId()) + .orElseThrow(NoSuchElementException::new); + return new OrderLineItem(menu, itemRq.getQuantity(), menu.getId(), itemRq.getPrice()); + }) + .toList(); + } + + @Transactional + public EatInOrderServiceRs accept(final UUID orderId) { + final EatInOrder eatInOrder = eatInOrderRepository.findById(orderId) + .orElseThrow(NoSuchElementException::new); + eatInOrder.processOrderFlow(EatInOrderStatus.ACCEPTED); + return new EatInOrderServiceRs(eatInOrder); + } + + @Transactional + public EatInOrderServiceRs serve(final UUID orderId) { + final EatInOrder eatInOrder = eatInOrderRepository.findById(orderId) + .orElseThrow(NoSuchElementException::new); + eatInOrder.processOrderFlow(EatInOrderStatus.SERVED); + return new EatInOrderServiceRs(eatInOrder); + } + + @Transactional + public EatInOrderServiceRs complete(final UUID orderId) { + final EatInOrder eatInOrder = eatInOrderRepository.findById(orderId) + .orElseThrow(NoSuchElementException::new); + eatInOrder.processOrderFlow(EatInOrderStatus.COMPLETED); + + return new EatInOrderServiceRs(eatInOrder); + } + + @Transactional(readOnly = true) + public List findAll() { + return eatInOrderRepository.findAll().stream() + .map(EatInOrderServiceRs::new) + .toList(); + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/application/OrderTableService.java b/src/main/java/kitchenpos/order/eatinorder/application/OrderTableService.java new file mode 100644 index 000000000..6c02172e7 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/application/OrderTableService.java @@ -0,0 +1,65 @@ +package kitchenpos.order.eatinorder.application; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import kitchenpos.order.eatinorder.application.dto.CreateOrderTableServiceRq; +import kitchenpos.order.eatinorder.application.dto.OrderTableServiceRs; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.model.OrderTableName; +import kitchenpos.order.eatinorder.domain.model.ReleaseOrderTableEvent; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import kitchenpos.order.eatinorder.domain.service.OrderTableOccupationManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class OrderTableService { + private final OrderTableRepository orderTableRepository; + private final OrderTableOccupationManager orderTableOccupationManager; + + public OrderTableService(final OrderTableRepository orderTableRepository, + final OrderTableOccupationManager orderTableOccupationManager) { + this.orderTableRepository = orderTableRepository; + this.orderTableOccupationManager = orderTableOccupationManager; + } + + @Transactional + public OrderTableServiceRs create(final CreateOrderTableServiceRq request) { + final String name = request.getName(); + final OrderTable orderTable = orderTableRepository.save(new OrderTable(new OrderTableName(name))); + return new OrderTableServiceRs(orderTable); + } + + @Transactional + public OrderTableServiceRs sit(final UUID orderTableId) { + final OrderTable orderTable = orderTableRepository.findById(orderTableId) + .orElseThrow(NoSuchElementException::new); + orderTable.occupyTable(); + return new OrderTableServiceRs(orderTable); + } + + @Transactional + public OrderTableServiceRs clear(final UUID orderTableId) { + final OrderTable orderTable = orderTableRepository.findById(orderTableId) + .orElseThrow(NoSuchElementException::new); + orderTableOccupationManager.release(new ReleaseOrderTableEvent(orderTable)); + return new OrderTableServiceRs(orderTable); + } + + @Transactional + public OrderTableServiceRs changeNumberOfGuests(final UUID orderTableId, final OrderTable request) { + final int numberOfGuests = request.getNumberOfGuests(); + final OrderTable orderTable = orderTableRepository.findById(orderTableId) + .orElseThrow(NoSuchElementException::new); + orderTable.changeNumberOfGuests(numberOfGuests); + return new OrderTableServiceRs(orderTable); + } + + @Transactional(readOnly = true) + public List findAll() { + return orderTableRepository.findAll().stream() + .map(OrderTableServiceRs::new) + .toList(); + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/application/dto/CreateEatInOrderServiceRq.java b/src/main/java/kitchenpos/order/eatinorder/application/dto/CreateEatInOrderServiceRq.java new file mode 100644 index 000000000..1867ba201 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/application/dto/CreateEatInOrderServiceRq.java @@ -0,0 +1,54 @@ +package kitchenpos.order.eatinorder.application.dto; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +public class CreateEatInOrderServiceRq { + private List orderLineItemDtos; + private UUID orderTableId; + + public CreateEatInOrderServiceRq(List orderLineItemDtos, UUID orderTableId) { + this.orderLineItemDtos = orderLineItemDtos; + this.orderTableId = orderTableId; + } + + public CreateEatInOrderServiceRq() { + } + + public List getOrderLineItemDtos() { + return orderLineItemDtos; + } + + public UUID getOrderTableId() { + return orderTableId; + } + + public static class OrderLineItemServiceDto { + private UUID menuId; + private long quantity; + private BigDecimal price; + + public OrderLineItemServiceDto(UUID menuId, long quantity, BigDecimal price) { + this.menuId = menuId; + this.quantity = quantity; + this.price = price; + } + + public OrderLineItemServiceDto() { + } + + public UUID getMenuId() { + return menuId; + } + + public long getQuantity() { + return quantity; + } + + public BigDecimal getPrice() { + return price; + } + } + +} diff --git a/src/main/java/kitchenpos/order/eatinorder/application/dto/CreateOrderTableServiceRq.java b/src/main/java/kitchenpos/order/eatinorder/application/dto/CreateOrderTableServiceRq.java new file mode 100644 index 000000000..b671bbcb7 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/application/dto/CreateOrderTableServiceRq.java @@ -0,0 +1,16 @@ +package kitchenpos.order.eatinorder.application.dto; + +public class CreateOrderTableServiceRq { + private String name; + + public CreateOrderTableServiceRq(String name) { + this.name = name; + } + + public CreateOrderTableServiceRq() { + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/application/dto/EatInOrderServiceRs.java b/src/main/java/kitchenpos/order/eatinorder/application/dto/EatInOrderServiceRs.java new file mode 100644 index 000000000..2d1a3a184 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/application/dto/EatInOrderServiceRs.java @@ -0,0 +1,28 @@ +package kitchenpos.order.eatinorder.application.dto; + +import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; + +public class EatInOrderServiceRs { + private UUID eatInOrderId; + private EatInOrderFlow eatInOrderFlow; + + public EatInOrderServiceRs(EatInOrder eatInOrder) { + this.eatInOrderId = eatInOrder.getId(); + this.eatInOrderFlow = eatInOrder.getEatInOrderFlow(); + } + + public EatInOrderServiceRs(UUID eatInOrderId, EatInOrderFlow eatInOrderFlow) { + this.eatInOrderId = eatInOrderId; + this.eatInOrderFlow = eatInOrderFlow; + } + + public UUID getEatInOrderId() { + return eatInOrderId; + } + + public EatInOrderFlow getEatInOrderFlow() { + return eatInOrderFlow; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/application/dto/OrderTableServiceRs.java b/src/main/java/kitchenpos/order/eatinorder/application/dto/OrderTableServiceRs.java new file mode 100644 index 000000000..280a1296d --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/application/dto/OrderTableServiceRs.java @@ -0,0 +1,41 @@ +package kitchenpos.order.eatinorder.application.dto; + +import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.OrderTable; + +public class OrderTableServiceRs { + private UUID id; + private String name; + private int numberOfGuests; + private boolean occupied; + + public OrderTableServiceRs(UUID id, String name, int numberOfGuests, boolean occupied) { + this.id = id; + this.name = name; + this.numberOfGuests = numberOfGuests; + this.occupied = occupied; + } + + public OrderTableServiceRs(OrderTable orderTable) { + this.id = orderTable.getId(); + this.name = orderTable.getInnerName(); + this.numberOfGuests = orderTable.getNumberOfGuests(); + this.occupied = orderTable.isOccupied(); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public int getNumberOfGuests() { + return numberOfGuests; + } + + public boolean isOccupied() { + return occupied; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrder.java b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrder.java new file mode 100644 index 000000000..941a48871 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrder.java @@ -0,0 +1,115 @@ +package kitchenpos.order.eatinorder.domain.model; + + +import static kitchenpos.order.eatinorder.exception.EatInOrderExceptionMessage.EAT_IN_ORDER_EMPTY_ORDER_LINE_ITEM_EXCEPTION; +import static kitchenpos.order.eatinorder.exception.EatInOrderExceptionMessage.EAT_IN_ORDER_FLOW_EXCEPTION; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import kitchenpos.order.common.model.OrderLineItem; +import org.springframework.data.domain.AbstractAggregateRoot; + +@Table(name = "eat_in_orders") +@Entity +public class EatInOrder extends AbstractAggregateRoot { + @Column(name = "id", columnDefinition = "binary(16)") + @Id + private UUID id; + + @Column(name = "order_date_time", nullable = false) + private LocalDateTime orderDateTime; + + @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @JoinColumn( + name = "eat_in_order_id", + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_order_line_item_to_eat_in_orders") + ) + private List orderLineItems; + + @ManyToOne + @JoinColumn( + name = "order_table_id", + columnDefinition = "binary(16)", + foreignKey = @ForeignKey(name = "fk_eat_in_orders_to_order_table") + ) + private OrderTable orderTable; + + @Enumerated + private EatInOrderFlow eatInOrderFlow; + + @Transient + private UUID orderTableId; + + public EatInOrder() { + } + + public EatInOrder(LocalDateTime orderDateTime, + List orderLineItems, EatInOrderFlow eatInOrderFlow) { + validateOrderLineItemIsEmpty(orderLineItems); + this.id = UUID.randomUUID(); + this.orderDateTime = orderDateTime; + this.orderLineItems = orderLineItems; + this.eatInOrderFlow = eatInOrderFlow; + } + + private void validateOrderLineItemIsEmpty(List orderLineItems) { + if (Objects.isNull(orderLineItems) || orderLineItems.isEmpty()) { + throw new IllegalArgumentException(EAT_IN_ORDER_EMPTY_ORDER_LINE_ITEM_EXCEPTION.getMessage()); + } + } + + public void processOrderFlow(EatInOrderStatus orderStatus) { + if (!eatInOrderFlow.validateOrderStatus(orderStatus)) { + throw new IllegalStateException(EAT_IN_ORDER_FLOW_EXCEPTION.getMessage()); + } + this.eatInOrderFlow = EatInOrderFlow.findByOrderStatus(orderStatus); + + if (this.eatInOrderFlow == EatInOrderFlow.COMPLETED) { + registerEvent(new ReleaseOrderTableEvent(this.orderTable)); + } + } + + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + } + + public EatInOrderFlow getEatInOrderFlow() { + return eatInOrderFlow; + } + + public List getOrderLineItems() { + return orderLineItems; + } + + public OrderTable getOrderTable() { + return orderTable; + } + + public void occupyOrderTable(final OrderTable orderTable) { + orderTable.validateTableIsOccupied(); + this.orderTable = orderTable; + this.orderTableId = orderTable.getId(); + } + + public UUID getOrderTableId() { + return orderTableId; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFactory.java b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFactory.java new file mode 100644 index 000000000..ffb8b68fd --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFactory.java @@ -0,0 +1,34 @@ +package kitchenpos.order.eatinorder.domain.model; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.common.model.OrderLineItemValidator; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import org.springframework.stereotype.Component; + +@Component +public class EatInOrderFactory { + + private final OrderLineItemValidator orderLineItemValidator; + private final OrderTableRepository orderTableRepository; + + public EatInOrderFactory(OrderLineItemValidator orderLineItemValidator, OrderTableRepository orderTableRepository) { + this.orderLineItemValidator = orderLineItemValidator; + this.orderTableRepository = orderTableRepository; + } + + public EatInOrder create(List orderLineItems, UUID orderTableId) { + orderLineItemValidator.validate(orderLineItems); + + EatInOrder eatInOrder = new EatInOrder(LocalDateTime.now(), + orderLineItems, EatInOrderFlow.WAITING); + + final OrderTable orderTable = orderTableRepository.findById(orderTableId) + .orElseThrow(NoSuchElementException::new); + eatInOrder.occupyOrderTable(orderTable); + return eatInOrder; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFlow.java b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFlow.java new file mode 100644 index 000000000..84c3dfa74 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFlow.java @@ -0,0 +1,30 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static kitchenpos.order.eatinorder.exception.EatInOrderExceptionMessage.EAT_IN_ORDER_FLOW_NOT_FOUND_EXCEPTION; + +import java.util.Arrays; + +public enum EatInOrderFlow { + WAITING(EatInOrderStatus.NONE), + ACCEPTED(EatInOrderStatus.WAITING), + SERVED(EatInOrderStatus.ACCEPTED), + COMPLETED(EatInOrderStatus.SERVED); + + private final EatInOrderStatus previousOrderStatus; + + EatInOrderFlow(EatInOrderStatus previousOrderStatus) { + this.previousOrderStatus = previousOrderStatus; + } + + public static EatInOrderFlow findByOrderStatus(EatInOrderStatus nextOrderStatus) { + return Arrays.stream(values()) + .filter(flow -> flow.name().equals(nextOrderStatus.name())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(EAT_IN_ORDER_FLOW_NOT_FOUND_EXCEPTION.getMessage())); + } + + public boolean validateOrderStatus(EatInOrderStatus nextOrderStatus) { + EatInOrderFlow nextOrderFlow = findByOrderStatus(nextOrderStatus); + return this.name().equals(nextOrderFlow.previousOrderStatus.name()); + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderStatus.java b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderStatus.java new file mode 100644 index 000000000..e4faddea0 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/model/EatInOrderStatus.java @@ -0,0 +1,5 @@ +package kitchenpos.order.eatinorder.domain.model; + +public enum EatInOrderStatus { + NONE, WAITING, ACCEPTED, SERVED, COMPLETED +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/model/OrderTable.java b/src/main/java/kitchenpos/order/eatinorder/domain/model/OrderTable.java new file mode 100644 index 000000000..f5fa82f2c --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/model/OrderTable.java @@ -0,0 +1,98 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static kitchenpos.order.eatinorder.exception.EatInOrderExceptionMessage.EMPTY_ORDER_TABLE_EXCEPTION; +import static kitchenpos.order.eatinorder.exception.EatInOrderExceptionMessage.NUMBER_OF_GUESTS_EXCEPTION; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; + +@Table(name = "order_table") +@Entity +public class OrderTable { + @Column(name = "id", columnDefinition = "binary(16)") + @Id + private UUID id; + + @Enumerated + private OrderTableName name; + + @Column(name = "number_of_guests", nullable = false) + private int numberOfGuests; + + @Column(name = "occupied", nullable = false) + private boolean occupied; + + public OrderTable() { + } + + public OrderTable(OrderTableName name, int numberOfGuests, boolean occupied) { + validateNumberOfGuests(numberOfGuests); + this.id = UUID.randomUUID(); + this.name = name; + this.numberOfGuests = numberOfGuests; + this.occupied = occupied; + } + + public OrderTable(OrderTableName name) { + this(name, 0, false); + } + + public OrderTable(String name, int numberOfGuests, boolean occupied) { + this(new OrderTableName(name), numberOfGuests, occupied); + } + + private void validateNumberOfGuests(int numberOfGuests) { + if (numberOfGuests < 0) { + throw new IllegalArgumentException(NUMBER_OF_GUESTS_EXCEPTION.getMessage()); + } + } + + public void changeNumberOfGuests(final int numberOfGuests) { + validateTableIsOccupied(); + validateNumberOfGuests(numberOfGuests); + this.numberOfGuests = numberOfGuests; + } + + public void validateTableIsOccupied() { + if (!isOccupied()) { + throw new IllegalStateException(EMPTY_ORDER_TABLE_EXCEPTION.getMessage()); + } + } + + public void releaseTable() { + this.numberOfGuests = 0; + this.occupied = false; + } + + public void occupyTable() { + this.occupied = true; + } + + public boolean isOccupied() { + return occupied; + } + + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + } + + public String getInnerName() { + return name.getValue(); + } + + public int getNumberOfGuests() { + return numberOfGuests; + } + + public OrderTableName getName() { + return name; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/model/OrderTableName.java b/src/main/java/kitchenpos/order/eatinorder/domain/model/OrderTableName.java new file mode 100644 index 000000000..8626c018c --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/model/OrderTableName.java @@ -0,0 +1,47 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static kitchenpos.order.eatinorder.exception.EatInOrderExceptionMessage.ORDER_TABLE_NAME_CREATION_EXCEPTION; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class OrderTableName { + + @Column(name = "name", nullable = false) + private final String value; + + protected OrderTableName() { + this.value = null; + } + + public OrderTableName(String value) { + validateOrderTableName(value); + this.value = value; + } + + private void validateOrderTableName(String value) { + if (Objects.isNull(value) || value.isEmpty()) { + throw new IllegalArgumentException(ORDER_TABLE_NAME_CREATION_EXCEPTION.getMessage()); + } + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + OrderTableName that = (OrderTableName) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/model/ReleaseOrderTableEvent.java b/src/main/java/kitchenpos/order/eatinorder/domain/model/ReleaseOrderTableEvent.java new file mode 100644 index 000000000..918c9af15 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/model/ReleaseOrderTableEvent.java @@ -0,0 +1,4 @@ +package kitchenpos.order.eatinorder.domain.model; + +public record ReleaseOrderTableEvent(OrderTable orderTable) { +} diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/repository/EatInOrderRepository.java b/src/main/java/kitchenpos/order/eatinorder/domain/repository/EatInOrderRepository.java new file mode 100644 index 000000000..294fbaa77 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/repository/EatInOrderRepository.java @@ -0,0 +1,18 @@ +package kitchenpos.order.eatinorder.domain.repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; + +public interface EatInOrderRepository { + Optional findById(UUID eatInOrderId); + + EatInOrder save(EatInOrder eatInOrder); + + boolean existsByOrderTableAndEatInOrderFlowNot(OrderTable orderTable, EatInOrderFlow eatInOrderFlow); + + List findAll(); +} diff --git a/src/main/java/kitchenpos/eatinorders/domain/OrderTableRepository.java b/src/main/java/kitchenpos/order/eatinorder/domain/repository/OrderTableRepository.java similarity index 67% rename from src/main/java/kitchenpos/eatinorders/domain/OrderTableRepository.java rename to src/main/java/kitchenpos/order/eatinorder/domain/repository/OrderTableRepository.java index 1e9047d43..86a420535 100644 --- a/src/main/java/kitchenpos/eatinorders/domain/OrderTableRepository.java +++ b/src/main/java/kitchenpos/order/eatinorder/domain/repository/OrderTableRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.eatinorders.domain; +package kitchenpos.order.eatinorder.domain.repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.OrderTable; public interface OrderTableRepository { OrderTable save(OrderTable orderTable); diff --git a/src/main/java/kitchenpos/order/eatinorder/domain/service/OrderTableOccupationManager.java b/src/main/java/kitchenpos/order/eatinorder/domain/service/OrderTableOccupationManager.java new file mode 100644 index 000000000..54fbe7436 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/domain/service/OrderTableOccupationManager.java @@ -0,0 +1,29 @@ +package kitchenpos.order.eatinorder.domain.service; + +import static kitchenpos.order.eatinorder.exception.EatInOrderExceptionMessage.RELEASE_ORDER_TABLE_EXCEPTION; + +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.model.ReleaseOrderTableEvent; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Service +public class OrderTableOccupationManager { + private final EatInOrderRepository eatInOrderRepository; + + public OrderTableOccupationManager(EatInOrderRepository eatInOrderRepository) { + this.eatInOrderRepository = eatInOrderRepository; + } + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void release(ReleaseOrderTableEvent event) { + OrderTable orderTable = event.orderTable(); + if (eatInOrderRepository.existsByOrderTableAndEatInOrderFlowNot(orderTable, EatInOrderFlow.COMPLETED)) { + throw new IllegalStateException(RELEASE_ORDER_TABLE_EXCEPTION.getMessage()); + } + orderTable.releaseTable(); + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/exception/EatInOrderExceptionMessage.java b/src/main/java/kitchenpos/order/eatinorder/exception/EatInOrderExceptionMessage.java new file mode 100644 index 000000000..37fa6c573 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/exception/EatInOrderExceptionMessage.java @@ -0,0 +1,21 @@ +package kitchenpos.order.eatinorder.exception; + +public enum EatInOrderExceptionMessage { + ORDER_TABLE_NAME_CREATION_EXCEPTION("주문 테이블 이름을 채워주세요!"), + EMPTY_ORDER_TABLE_EXCEPTION("주문 테이블이 비어있습니다!"), + NUMBER_OF_GUESTS_EXCEPTION("손님 수가 음수일 수 없습니다!"), + EAT_IN_ORDER_EMPTY_ORDER_LINE_ITEM_EXCEPTION("매장 주문에 주문 내역이 비어있습니다!"), + EAT_IN_ORDER_FLOW_EXCEPTION("잘못된 매장 주문 순서입니다. 주문 순서를 지켜주세요!"), + EAT_IN_ORDER_FLOW_NOT_FOUND_EXCEPTION("매장 주문 상태에 맞는 주문 순서를 찾을 수 없습니다."), + RELEASE_ORDER_TABLE_EXCEPTION("주문이 완료되지 않은 매장 테이블은 정리할 수 없습니다."); + + private final String message; + + EatInOrderExceptionMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/infra/persistence/JpaEatInOrderRepository.java b/src/main/java/kitchenpos/order/eatinorder/infra/persistence/JpaEatInOrderRepository.java new file mode 100644 index 000000000..6444e1ca8 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/infra/persistence/JpaEatInOrderRepository.java @@ -0,0 +1,9 @@ +package kitchenpos.order.eatinorder.infra.persistence; + +import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaEatInOrderRepository extends EatInOrderRepository, JpaRepository { +} diff --git a/src/main/java/kitchenpos/order/eatinorder/infra/persistence/JpaOrderTableRepository.java b/src/main/java/kitchenpos/order/eatinorder/infra/persistence/JpaOrderTableRepository.java new file mode 100644 index 000000000..c27f00b03 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/infra/persistence/JpaOrderTableRepository.java @@ -0,0 +1,4 @@ +package kitchenpos.order.eatinorder.infra.persistence; + +public interface JpaOrderTableRepository { +} diff --git a/src/main/java/kitchenpos/order/eatinorder/ui/EatInOrderRestController.java b/src/main/java/kitchenpos/order/eatinorder/ui/EatInOrderRestController.java new file mode 100644 index 000000000..d51af37c0 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/ui/EatInOrderRestController.java @@ -0,0 +1,62 @@ +package kitchenpos.order.eatinorder.ui; + +import java.net.URI; +import java.util.List; +import java.util.UUID; +import kitchenpos.order.eatinorder.application.EatInOrderService; +import kitchenpos.order.eatinorder.application.dto.EatInOrderServiceRs; +import kitchenpos.order.eatinorder.ui.dto.CreateEatInOrderRq; +import kitchenpos.order.eatinorder.ui.dto.EatInOrderRs; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/eatInOrder") +@RestController +public class EatInOrderRestController { + private final EatInOrderService eatInOrderService; + + public EatInOrderRestController(final EatInOrderService eatInOrderService) { + this.eatInOrderService = eatInOrderService; + } + + @PostMapping + public ResponseEntity create(@RequestBody final CreateEatInOrderRq request) { + EatInOrderServiceRs response = eatInOrderService.create(request.createServiceRq()); + return ResponseEntity.created(URI.create("/api/orders/" + response.getEatInOrderId())) + .body(new EatInOrderRs(response)); + } + + @PutMapping("/{eatInOrderId}/accept") + public ResponseEntity accept(@PathVariable("eatInOrderId") final UUID eatInOrderId) { + EatInOrderServiceRs response = eatInOrderService.accept(eatInOrderId); + return ResponseEntity.ok(new EatInOrderRs(response)); + } + + @PutMapping("/{eatInOrderId}/serve") + public ResponseEntity serve(@PathVariable("eatInOrderId") final UUID eatInOrderId) { + EatInOrderServiceRs response = eatInOrderService.serve(eatInOrderId); + return ResponseEntity.ok(new EatInOrderRs(response)); + } + + @PutMapping("/{eatInOrderId}/complete") + public ResponseEntity complete(@PathVariable("eatInOrderId") final UUID eatInOrderId) { + EatInOrderServiceRs response = eatInOrderService.complete(eatInOrderId); + return ResponseEntity.ok(new EatInOrderRs(response)); + } + + @GetMapping + public ResponseEntity> findAll() { + List response = eatInOrderService.findAll(); + return ResponseEntity.ok( + response.stream() + .map(EatInOrderRs::new) + .toList() + ); + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/ui/OrderTableRestController.java b/src/main/java/kitchenpos/order/eatinorder/ui/OrderTableRestController.java new file mode 100644 index 000000000..efba17170 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/ui/OrderTableRestController.java @@ -0,0 +1,68 @@ +package kitchenpos.order.eatinorder.ui; + +import java.net.URI; +import java.util.List; +import java.util.UUID; +import kitchenpos.order.eatinorder.application.OrderTableService; +import kitchenpos.order.eatinorder.application.dto.CreateOrderTableServiceRq; +import kitchenpos.order.eatinorder.application.dto.OrderTableServiceRs; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.ui.dto.CreateOrderTableRq; +import kitchenpos.order.eatinorder.ui.dto.OrderTableRs; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/order-tables") +@RestController +public class OrderTableRestController { + private final OrderTableService orderTableService; + + public OrderTableRestController(final OrderTableService orderTableService) { + this.orderTableService = orderTableService; + } + + @PostMapping + public ResponseEntity create(@RequestBody final CreateOrderTableRq request) { + OrderTableServiceRs response = orderTableService.create( + new CreateOrderTableServiceRq(request.getName())); + return ResponseEntity.created(URI.create("/api/order-tables/" + response.getId())) + .body(new OrderTableRs(response)); + } + + @PutMapping("/{orderTableId}/sit") + public ResponseEntity sit(@PathVariable("orderTableId") final UUID orderTableId) { + OrderTableServiceRs response = orderTableService.sit(orderTableId); + return ResponseEntity.ok(new OrderTableRs(response)); + } + + @PutMapping("/{orderTableId}/clear") + public ResponseEntity clear(@PathVariable("orderTableId") final UUID orderTableId) { + OrderTableServiceRs response = orderTableService.clear(orderTableId); + return ResponseEntity.ok(new OrderTableRs(response)); + } + + @PutMapping("/{orderTableId}/number-of-guests") + public ResponseEntity changeNumberOfGuests( + @PathVariable("orderTableId") final UUID orderTableId, + @RequestBody final OrderTable request + ) { + OrderTableServiceRs response = orderTableService.changeNumberOfGuests(orderTableId, request); + return ResponseEntity.ok(new OrderTableRs(response)); + } + + @GetMapping + public ResponseEntity> findAll() { + List response = orderTableService.findAll(); + return ResponseEntity.ok( + response.stream() + .map(OrderTableRs::new) + .toList() + ); + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/ui/dto/CreateEatInOrderRq.java b/src/main/java/kitchenpos/order/eatinorder/ui/dto/CreateEatInOrderRq.java new file mode 100644 index 000000000..b92aa1004 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/ui/dto/CreateEatInOrderRq.java @@ -0,0 +1,64 @@ +package kitchenpos.order.eatinorder.ui.dto; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; +import kitchenpos.order.eatinorder.application.dto.CreateEatInOrderServiceRq; +import kitchenpos.order.eatinorder.application.dto.CreateEatInOrderServiceRq.OrderLineItemServiceDto; + +public class CreateEatInOrderRq { + private List orderLineItemDtos; + private UUID orderTableId; + + public CreateEatInOrderRq(List orderLineItemDtos, UUID orderTableId) { + this.orderLineItemDtos = orderLineItemDtos; + this.orderTableId = orderTableId; + } + + public CreateEatInOrderRq() { + } + + public List getOrderLineItemDtos() { + return orderLineItemDtos; + } + + public UUID getOrderTableId() { + return orderTableId; + } + + public CreateEatInOrderServiceRq createServiceRq() { + return new CreateEatInOrderServiceRq( + this.orderLineItemDtos.stream() + .map(o -> new OrderLineItemServiceDto(o.menuId, o.quantity, o.price)) + .toList(), + orderTableId + ); + } + + public static class OrderLineItemDto { + private UUID menuId; + private long quantity; + private BigDecimal price; + + public OrderLineItemDto(UUID menuId, long quantity, BigDecimal price) { + this.menuId = menuId; + this.quantity = quantity; + this.price = price; + } + + public OrderLineItemDto() { + } + + public UUID getMenuId() { + return menuId; + } + + public long getQuantity() { + return quantity; + } + + public BigDecimal getPrice() { + return price; + } + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/ui/dto/CreateOrderTableRq.java b/src/main/java/kitchenpos/order/eatinorder/ui/dto/CreateOrderTableRq.java new file mode 100644 index 000000000..27aa61dc5 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/ui/dto/CreateOrderTableRq.java @@ -0,0 +1,16 @@ +package kitchenpos.order.eatinorder.ui.dto; + +public class CreateOrderTableRq { + private String name; + + public CreateOrderTableRq(String name) { + this.name = name; + } + + public CreateOrderTableRq() { + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/ui/dto/EatInOrderRs.java b/src/main/java/kitchenpos/order/eatinorder/ui/dto/EatInOrderRs.java new file mode 100644 index 000000000..22b0eae46 --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/ui/dto/EatInOrderRs.java @@ -0,0 +1,26 @@ +package kitchenpos.order.eatinorder.ui.dto; + +import java.util.UUID; +import kitchenpos.order.eatinorder.application.dto.EatInOrderServiceRs; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; + +public class EatInOrderRs { + private UUID eatInOrderId; + private EatInOrderFlow eatInOrderFlow; + + public EatInOrderRs(EatInOrderServiceRs eatInOrder) { + this.eatInOrderId = eatInOrder.getEatInOrderId(); + this.eatInOrderFlow = eatInOrder.getEatInOrderFlow(); + } + + public EatInOrderRs() { + } + + public UUID getEatInOrderId() { + return eatInOrderId; + } + + public EatInOrderFlow getEatInOrderFlow() { + return eatInOrderFlow; + } +} diff --git a/src/main/java/kitchenpos/order/eatinorder/ui/dto/OrderTableRs.java b/src/main/java/kitchenpos/order/eatinorder/ui/dto/OrderTableRs.java new file mode 100644 index 000000000..5735f8e2b --- /dev/null +++ b/src/main/java/kitchenpos/order/eatinorder/ui/dto/OrderTableRs.java @@ -0,0 +1,44 @@ +package kitchenpos.order.eatinorder.ui.dto; + +import java.util.UUID; +import kitchenpos.order.eatinorder.application.dto.OrderTableServiceRs; + +public class OrderTableRs { + private UUID id; + private String name; + private int numberOfGuests; + private boolean occupied; + + public OrderTableRs(UUID id, String name, int numberOfGuests, boolean occupied) { + this.id = id; + this.name = name; + this.numberOfGuests = numberOfGuests; + this.occupied = occupied; + } + + public OrderTableRs(OrderTableServiceRs rs) { + this.id = rs.getId(); + this.name = rs.getName(); + this.numberOfGuests = rs.getNumberOfGuests(); + this.occupied = rs.isOccupied(); + } + + public OrderTableRs() { + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public int getNumberOfGuests() { + return numberOfGuests; + } + + public boolean isOccupied() { + return occupied; + } +} diff --git a/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrder.java b/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrder.java new file mode 100644 index 000000000..75c618f66 --- /dev/null +++ b/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrder.java @@ -0,0 +1,4 @@ +package kitchenpos.order.takeoutorder.domain.model; + +public class TakeOutOrder { +} diff --git a/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrderFlow.java b/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrderFlow.java new file mode 100644 index 000000000..d6c42e97c --- /dev/null +++ b/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrderFlow.java @@ -0,0 +1,4 @@ +package kitchenpos.order.takeoutorder.domain.model; + +public class TakeOutOrderFlow { +} diff --git a/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrderStatus.java b/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrderStatus.java new file mode 100644 index 000000000..da4549de4 --- /dev/null +++ b/src/main/java/kitchenpos/order/takeoutorder/domain/model/TakeOutOrderStatus.java @@ -0,0 +1,5 @@ +package kitchenpos.order.takeoutorder.domain.model; + +public enum TakeOutOrderStatus { + WAITING, ACCEPTED, SERVED, DELIVERING, DELIVERED, COMPLETED +} diff --git a/src/main/java/kitchenpos/order/takeoutorder/domain/repository/TakeOutOrderRepository.java b/src/main/java/kitchenpos/order/takeoutorder/domain/repository/TakeOutOrderRepository.java new file mode 100644 index 000000000..1f2c5ec68 --- /dev/null +++ b/src/main/java/kitchenpos/order/takeoutorder/domain/repository/TakeOutOrderRepository.java @@ -0,0 +1,4 @@ +package kitchenpos.order.takeoutorder.domain.repository; + +public class TakeOutOrderRepository { +} diff --git a/src/main/java/kitchenpos/order/takeoutorder/infra/persistence/JpaTakeOutOrderRepository.java b/src/main/java/kitchenpos/order/takeoutorder/infra/persistence/JpaTakeOutOrderRepository.java new file mode 100644 index 000000000..4c46dbae7 --- /dev/null +++ b/src/main/java/kitchenpos/order/takeoutorder/infra/persistence/JpaTakeOutOrderRepository.java @@ -0,0 +1,4 @@ +package kitchenpos.order.takeoutorder.infra.persistence; + +public interface JpaTakeOutOrderRepository { +} diff --git a/src/main/java/kitchenpos/order/takeoutorder/service/TakeOutOrderService.java b/src/main/java/kitchenpos/order/takeoutorder/service/TakeOutOrderService.java new file mode 100644 index 000000000..ef337e9a1 --- /dev/null +++ b/src/main/java/kitchenpos/order/takeoutorder/service/TakeOutOrderService.java @@ -0,0 +1,4 @@ +package kitchenpos.order.takeoutorder.service; + +public class TakeOutOrderService { +} diff --git a/src/main/java/kitchenpos/order/takeoutorder/ui/TakeOutOrderController.java b/src/main/java/kitchenpos/order/takeoutorder/ui/TakeOutOrderController.java new file mode 100644 index 000000000..a20e071f4 --- /dev/null +++ b/src/main/java/kitchenpos/order/takeoutorder/ui/TakeOutOrderController.java @@ -0,0 +1,4 @@ +package kitchenpos.order.takeoutorder.ui; + +public class TakeOutOrderController { +} diff --git a/src/main/java/kitchenpos/product/application/ProductService.java b/src/main/java/kitchenpos/product/application/ProductService.java new file mode 100644 index 000000000..e7afcdc01 --- /dev/null +++ b/src/main/java/kitchenpos/product/application/ProductService.java @@ -0,0 +1,61 @@ +package kitchenpos.product.application; + +import java.math.BigDecimal; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import kitchenpos.menu.domain.service.MarginValidator; +import kitchenpos.product.application.dto.ChangeProductPriceServiceRq; +import kitchenpos.product.application.dto.CreateProductServiceRq; +import kitchenpos.product.application.dto.ProductServiceRs; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.model.ProductName; +import kitchenpos.product.domain.model.ProductNameCreationService; +import kitchenpos.product.domain.model.ProductPrice; +import kitchenpos.product.domain.repository.ProductRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ProductService { + private final ProductRepository productRepository; + private final ProductNameCreationService productNameCreationService; + private final MarginValidator marginValidator; + + public ProductService( + final ProductRepository productRepository, + final ProductNameCreationService productNameCreationService, + final MarginValidator marginValidator + ) { + this.productRepository = productRepository; + this.productNameCreationService = productNameCreationService; + this.marginValidator = marginValidator; + } + + @Transactional + public ProductServiceRs create(final CreateProductServiceRq request) { + final BigDecimal price = request.getPrice(); + final String name = request.getName(); + final ProductName validProductName = productNameCreationService.createName(name); + final Product product = new Product(validProductName, new ProductPrice(price)); + productRepository.save(product); + return new ProductServiceRs(product); + } + + @Transactional + public ProductServiceRs changePrice(final UUID productId, final ChangeProductPriceServiceRq request) { + final BigDecimal price = request.getPrice(); + final Product product = productRepository.findById(productId) + .orElseThrow(NoSuchElementException::new); + product.changePrice(price); + marginValidator.checkMargin(product); + return new ProductServiceRs(product); + } + + @Transactional(readOnly = true) + public List findAll() { + return productRepository.findAll().stream() + .map(ProductServiceRs::new) + .toList(); + } +} diff --git a/src/main/java/kitchenpos/product/application/dto/ChangeProductPriceServiceRq.java b/src/main/java/kitchenpos/product/application/dto/ChangeProductPriceServiceRq.java new file mode 100644 index 000000000..2ba3b64a8 --- /dev/null +++ b/src/main/java/kitchenpos/product/application/dto/ChangeProductPriceServiceRq.java @@ -0,0 +1,18 @@ +package kitchenpos.product.application.dto; + +import java.math.BigDecimal; + +public class ChangeProductPriceServiceRq { + private BigDecimal price; + + public ChangeProductPriceServiceRq(BigDecimal price) { + this.price = price; + } + + public ChangeProductPriceServiceRq() { + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/product/application/dto/CreateProductServiceRq.java b/src/main/java/kitchenpos/product/application/dto/CreateProductServiceRq.java new file mode 100644 index 000000000..82496bfa1 --- /dev/null +++ b/src/main/java/kitchenpos/product/application/dto/CreateProductServiceRq.java @@ -0,0 +1,24 @@ +package kitchenpos.product.application.dto; + +import java.math.BigDecimal; + +public class CreateProductServiceRq { + private String name; + private BigDecimal price; + + public CreateProductServiceRq(String name, BigDecimal price) { + this.name = name; + this.price = price; + } + + public CreateProductServiceRq() { + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/product/application/dto/ProductServiceRs.java b/src/main/java/kitchenpos/product/application/dto/ProductServiceRs.java new file mode 100644 index 000000000..8952b0dbb --- /dev/null +++ b/src/main/java/kitchenpos/product/application/dto/ProductServiceRs.java @@ -0,0 +1,32 @@ +package kitchenpos.product.application.dto; + +import java.math.BigDecimal; +import java.util.UUID; +import kitchenpos.product.domain.model.Product; + +public class ProductServiceRs { + private UUID id; + private String name; + private BigDecimal price; + + public ProductServiceRs(Product product) { + this.id = product.getId(); + this.name = product.getInnerName(); + this.price = product.getInnerPrice(); + } + + public ProductServiceRs() { + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/product/domain/model/Product.java b/src/main/java/kitchenpos/product/domain/model/Product.java new file mode 100644 index 000000000..9c7a8cc7a --- /dev/null +++ b/src/main/java/kitchenpos/product/domain/model/Product.java @@ -0,0 +1,65 @@ +package kitchenpos.product.domain.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import java.util.UUID; + +@Table(name = "product") +@Entity +public class Product { + @Column(name = "id", columnDefinition = "binary(16)") + @Id + private UUID id; + + @Embedded + private ProductName name; + + @Embedded + private ProductPrice price; + + public Product(ProductName name, ProductPrice price) { + this.name = name; + this.price = price; + this.id = UUID.randomUUID(); + } + + protected Product() { + + } + + public Product(String name, BigDecimal price) { + this(new ProductName(name), new ProductPrice(price)); + } + + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + } + + public String getInnerName() { + return name.getValue(); + } + + public ProductName getName() { + return name; + } + + public BigDecimal getInnerPrice() { + return price.getValue(); + } + + public ProductPrice getPrice() { + return price; + } + + public void changePrice(BigDecimal price) { + this.price = new ProductPrice(price); + } +} diff --git a/src/main/java/kitchenpos/product/domain/model/ProductName.java b/src/main/java/kitchenpos/product/domain/model/ProductName.java new file mode 100644 index 000000000..d5d42dc7c --- /dev/null +++ b/src/main/java/kitchenpos/product/domain/model/ProductName.java @@ -0,0 +1,49 @@ +package kitchenpos.product.domain.model; + +import static kitchenpos.product.exception.ProductExceptionMessage.PRODUCT_NAME_CREATION_EXCEPTION; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class ProductName { + @Column(name = "name", nullable = false) + private final String value; + + protected ProductName(String value) { + validateName(value); + this.value = value; + } + + protected ProductName() { + this.value = null; + } + + private void validateName(String name) { + if (Objects.isNull(name) || name.isEmpty()) { + throw new IllegalArgumentException(PRODUCT_NAME_CREATION_EXCEPTION.getMessage()); + } + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProductName productName = (ProductName) o; + return Objects.equals(value, productName.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/src/main/java/kitchenpos/product/domain/model/ProductNameCreationService.java b/src/main/java/kitchenpos/product/domain/model/ProductNameCreationService.java new file mode 100644 index 000000000..c32098139 --- /dev/null +++ b/src/main/java/kitchenpos/product/domain/model/ProductNameCreationService.java @@ -0,0 +1,22 @@ +package kitchenpos.product.domain.model; + +import static kitchenpos.product.exception.ProductExceptionMessage.PRODUCT_NAME_VALIDATION_EXCEPTION; + +import kitchenpos.common.application.PurgomalumClient; +import org.springframework.stereotype.Service; + +@Service +public class ProductNameCreationService { + private final PurgomalumClient purgomalumClient; + + public ProductNameCreationService(PurgomalumClient purgomalumClient) { + this.purgomalumClient = purgomalumClient; + } + + public ProductName createName(String name) { + if (purgomalumClient.containsProfanity(name)) { + throw new IllegalArgumentException(PRODUCT_NAME_VALIDATION_EXCEPTION.getMessage()); + } + return new ProductName(name); + } +} diff --git a/src/main/java/kitchenpos/product/domain/model/ProductPrice.java b/src/main/java/kitchenpos/product/domain/model/ProductPrice.java new file mode 100644 index 000000000..137a63621 --- /dev/null +++ b/src/main/java/kitchenpos/product/domain/model/ProductPrice.java @@ -0,0 +1,50 @@ +package kitchenpos.product.domain.model; + +import static kitchenpos.product.exception.ProductExceptionMessage.PRODUCT_PRICE_CREATION_EXCEPTION; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.math.BigDecimal; +import java.util.Objects; + +@Embeddable +public class ProductPrice { + @Column(name = "price", nullable = false) + private final BigDecimal value; + + public ProductPrice(BigDecimal value) { + validatePrice(value); + this.value = value; + } + + protected ProductPrice() { + this.value = null; + } + + private void validatePrice(BigDecimal value) { + if (Objects.isNull(value) || value.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException(PRODUCT_PRICE_CREATION_EXCEPTION.getMessage()); + } + } + + public BigDecimal getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProductPrice productPrice = (ProductPrice) o; + return Objects.equals(value, productPrice.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/src/main/java/kitchenpos/product/domain/model/ProductSummary.java b/src/main/java/kitchenpos/product/domain/model/ProductSummary.java new file mode 100644 index 000000000..77a8cd9a6 --- /dev/null +++ b/src/main/java/kitchenpos/product/domain/model/ProductSummary.java @@ -0,0 +1,57 @@ +package kitchenpos.product.domain.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import java.util.Objects; +import java.util.UUID; + +@Entity +public class ProductSummary { + + @Id + @Column(name = "id") + private UUID id; + + private String name; + private long quantity; + + public ProductSummary(UUID productId, String name, long quantity) { + this.id = productId; + this.name = name; + this.quantity = quantity; + } + + protected ProductSummary() { + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public long getQuantity() { + return quantity; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProductSummary that = (ProductSummary) o; + return quantity == that.quantity && Objects.equals(id, that.id) && Objects.equals(name, + that.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, quantity); + } +} diff --git a/src/main/java/kitchenpos/products/domain/ProductRepository.java b/src/main/java/kitchenpos/product/domain/repository/ProductRepository.java similarity index 74% rename from src/main/java/kitchenpos/products/domain/ProductRepository.java rename to src/main/java/kitchenpos/product/domain/repository/ProductRepository.java index 3637e4232..b54094985 100644 --- a/src/main/java/kitchenpos/products/domain/ProductRepository.java +++ b/src/main/java/kitchenpos/product/domain/repository/ProductRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.products.domain; +package kitchenpos.product.domain.repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import kitchenpos.product.domain.model.Product; public interface ProductRepository { Product save(Product product); diff --git a/src/main/java/kitchenpos/product/domain/repository/ProductSummaryRepository.java b/src/main/java/kitchenpos/product/domain/repository/ProductSummaryRepository.java new file mode 100644 index 000000000..01fc992be --- /dev/null +++ b/src/main/java/kitchenpos/product/domain/repository/ProductSummaryRepository.java @@ -0,0 +1,9 @@ +package kitchenpos.product.domain.repository; + +import kitchenpos.product.domain.model.ProductSummary; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductSummaryRepository { + ProductSummary save(ProductSummary productSummary); +} diff --git a/src/main/java/kitchenpos/product/exception/ProductExceptionMessage.java b/src/main/java/kitchenpos/product/exception/ProductExceptionMessage.java new file mode 100644 index 000000000..22dbbd247 --- /dev/null +++ b/src/main/java/kitchenpos/product/exception/ProductExceptionMessage.java @@ -0,0 +1,18 @@ +package kitchenpos.product.exception; + +public enum ProductExceptionMessage { + + PRODUCT_NAME_CREATION_EXCEPTION("상품 이름을 채워주세요!"), + PRODUCT_NAME_VALIDATION_EXCEPTION("상품 이름에 비속어가 존재합니다. 비속어를 제외해주세요!"), + PRODUCT_PRICE_CREATION_EXCEPTION("상품 가격을 채워주세요!"); + + private final String message; + + ProductExceptionMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kitchenpos/products/domain/JpaProductRepository.java b/src/main/java/kitchenpos/product/infra/persistence/JpaProductRepository.java similarity index 54% rename from src/main/java/kitchenpos/products/domain/JpaProductRepository.java rename to src/main/java/kitchenpos/product/infra/persistence/JpaProductRepository.java index 90b069779..a519bd5b2 100644 --- a/src/main/java/kitchenpos/products/domain/JpaProductRepository.java +++ b/src/main/java/kitchenpos/product/infra/persistence/JpaProductRepository.java @@ -1,8 +1,9 @@ -package kitchenpos.products.domain; - -import org.springframework.data.jpa.repository.JpaRepository; +package kitchenpos.product.infra.persistence; import java.util.UUID; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import org.springframework.data.jpa.repository.JpaRepository; public interface JpaProductRepository extends ProductRepository, JpaRepository { } diff --git a/src/main/java/kitchenpos/product/infra/persistence/JpaProductSummaryRepository.java b/src/main/java/kitchenpos/product/infra/persistence/JpaProductSummaryRepository.java new file mode 100644 index 000000000..fec071612 --- /dev/null +++ b/src/main/java/kitchenpos/product/infra/persistence/JpaProductSummaryRepository.java @@ -0,0 +1,10 @@ +package kitchenpos.product.infra.persistence; + +import java.util.UUID; +import kitchenpos.product.domain.model.ProductSummary; +import kitchenpos.product.domain.repository.ProductSummaryRepository; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaProductSummaryRepository extends JpaRepository, ProductSummaryRepository { + +} diff --git a/src/main/java/kitchenpos/product/ui/ProductRestController.java b/src/main/java/kitchenpos/product/ui/ProductRestController.java new file mode 100644 index 000000000..c3f482b74 --- /dev/null +++ b/src/main/java/kitchenpos/product/ui/ProductRestController.java @@ -0,0 +1,58 @@ +package kitchenpos.product.ui; + +import java.net.URI; +import java.util.List; +import java.util.UUID; +import kitchenpos.product.application.ProductService; +import kitchenpos.product.application.dto.ChangeProductPriceServiceRq; +import kitchenpos.product.application.dto.CreateProductServiceRq; +import kitchenpos.product.application.dto.ProductServiceRs; +import kitchenpos.product.ui.dto.ChangeProductPriceRq; +import kitchenpos.product.ui.dto.CreateProductRq; +import kitchenpos.product.ui.dto.ProductRs; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/products") +@RestController +public class ProductRestController { + private final ProductService productService; + + public ProductRestController(final ProductService productService) { + this.productService = productService; + } + + @PostMapping + public ResponseEntity create(@RequestBody final CreateProductRq request) { + ProductServiceRs response = productService.create( + new CreateProductServiceRq(request.getName(), request.getPrice())); + return ResponseEntity.created(URI.create("/api/products/" + response.getId())) + .body(new ProductRs(response)); + } + + @PutMapping("/{productId}/price") + public ResponseEntity changePrice(@PathVariable("productId") final UUID productId, + @RequestBody final ChangeProductPriceRq request) { + ProductServiceRs response = productService.changePrice( + productId, + new ChangeProductPriceServiceRq(request.getPrice()) + ); + return ResponseEntity.ok(new ProductRs(response)); + } + + @GetMapping + public ResponseEntity> findAll() { + List response = productService.findAll(); + return ResponseEntity.ok( + response.stream() + .map(ProductRs::new) + .toList() + ); + } +} diff --git a/src/main/java/kitchenpos/product/ui/dto/ChangeProductPriceRq.java b/src/main/java/kitchenpos/product/ui/dto/ChangeProductPriceRq.java new file mode 100644 index 000000000..4ee3c34bf --- /dev/null +++ b/src/main/java/kitchenpos/product/ui/dto/ChangeProductPriceRq.java @@ -0,0 +1,18 @@ +package kitchenpos.product.ui.dto; + +import java.math.BigDecimal; + +public class ChangeProductPriceRq { + private BigDecimal price; + + public ChangeProductPriceRq(BigDecimal price) { + this.price = price; + } + + public ChangeProductPriceRq() { + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/product/ui/dto/CreateProductRq.java b/src/main/java/kitchenpos/product/ui/dto/CreateProductRq.java new file mode 100644 index 000000000..0b5e0b4f0 --- /dev/null +++ b/src/main/java/kitchenpos/product/ui/dto/CreateProductRq.java @@ -0,0 +1,24 @@ +package kitchenpos.product.ui.dto; + +import java.math.BigDecimal; + +public class CreateProductRq { + private String name; + private BigDecimal price; + + public CreateProductRq(String name, BigDecimal price) { + this.name = name; + this.price = price; + } + + public CreateProductRq() { + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/product/ui/dto/ProductRs.java b/src/main/java/kitchenpos/product/ui/dto/ProductRs.java new file mode 100644 index 000000000..bdf91191f --- /dev/null +++ b/src/main/java/kitchenpos/product/ui/dto/ProductRs.java @@ -0,0 +1,39 @@ +package kitchenpos.product.ui.dto; + +import java.math.BigDecimal; +import java.util.UUID; +import kitchenpos.product.application.dto.ProductServiceRs; +import kitchenpos.product.domain.model.Product; + +public class ProductRs { + private UUID id; + private String name; + private BigDecimal price; + + public ProductRs(Product product) { + this.id = product.getId(); + this.name = product.getInnerName(); + this.price = product.getInnerPrice(); + } + + public ProductRs(ProductServiceRs product) { + this.id = product.getId(); + this.name = product.getName(); + this.price = product.getPrice(); + } + + public ProductRs() { + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } +} diff --git a/src/main/java/kitchenpos/products/application/ProductService.java b/src/main/java/kitchenpos/products/application/ProductService.java deleted file mode 100644 index 20cf63996..000000000 --- a/src/main/java/kitchenpos/products/application/ProductService.java +++ /dev/null @@ -1,81 +0,0 @@ -package kitchenpos.products.application; - -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuProduct; -import kitchenpos.menus.domain.MenuRepository; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; -import kitchenpos.products.infra.PurgomalumClient; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.UUID; - -@Service -public class ProductService { - private final ProductRepository productRepository; - private final MenuRepository menuRepository; - private final PurgomalumClient purgomalumClient; - - public ProductService( - final ProductRepository productRepository, - final MenuRepository menuRepository, - final PurgomalumClient purgomalumClient - ) { - this.productRepository = productRepository; - this.menuRepository = menuRepository; - this.purgomalumClient = purgomalumClient; - } - - @Transactional - public Product create(final Product request) { - final BigDecimal price = request.getPrice(); - if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); - } - final String name = request.getName(); - if (Objects.isNull(name) || purgomalumClient.containsProfanity(name)) { - throw new IllegalArgumentException(); - } - final Product product = new Product(); - product.setId(UUID.randomUUID()); - product.setName(name); - product.setPrice(price); - return productRepository.save(product); - } - - @Transactional - public Product changePrice(final UUID productId, final Product request) { - final BigDecimal price = request.getPrice(); - if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); - } - final Product product = productRepository.findById(productId) - .orElseThrow(NoSuchElementException::new); - product.setPrice(price); - final List menus = menuRepository.findAllByProductId(productId); - for (final Menu menu : menus) { - BigDecimal sum = BigDecimal.ZERO; - for (final MenuProduct menuProduct : menu.getMenuProducts()) { - sum = sum.add( - menuProduct.getProduct() - .getPrice() - .multiply(BigDecimal.valueOf(menuProduct.getQuantity())) - ); - } - if (menu.getPrice().compareTo(sum) > 0) { - menu.setDisplayed(false); - } - } - return product; - } - - @Transactional(readOnly = true) - public List findAll() { - return productRepository.findAll(); - } -} diff --git a/src/main/java/kitchenpos/products/domain/Product.java b/src/main/java/kitchenpos/products/domain/Product.java deleted file mode 100644 index ee2a7dfa9..000000000 --- a/src/main/java/kitchenpos/products/domain/Product.java +++ /dev/null @@ -1,50 +0,0 @@ -package kitchenpos.products.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -import java.math.BigDecimal; -import java.util.UUID; - -@Table(name = "product") -@Entity -public class Product { - @Column(name = "id", columnDefinition = "binary(16)") - @Id - private UUID id; - - @Column(name = "name", nullable = false) - private String name; - - @Column(name = "price", nullable = false) - private BigDecimal price; - - public Product() { - } - - public UUID getId() { - return id; - } - - public void setId(final UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public BigDecimal getPrice() { - return price; - } - - public void setPrice(final BigDecimal price) { - this.price = price; - } -} diff --git a/src/main/java/kitchenpos/products/ui/ProductRestController.java b/src/main/java/kitchenpos/products/ui/ProductRestController.java deleted file mode 100644 index c71c795a4..000000000 --- a/src/main/java/kitchenpos/products/ui/ProductRestController.java +++ /dev/null @@ -1,43 +0,0 @@ -package kitchenpos.products.ui; - -import kitchenpos.products.application.ProductService; -import kitchenpos.products.domain.Product; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.net.URI; -import java.util.List; -import java.util.UUID; - -@RequestMapping("/api/products") -@RestController -public class ProductRestController { - private final ProductService productService; - - public ProductRestController(final ProductService productService) { - this.productService = productService; - } - - @PostMapping - public ResponseEntity create(@RequestBody final Product request) { - final Product response = productService.create(request); - return ResponseEntity.created(URI.create("/api/products/" + response.getId())) - .body(response); - } - - @PutMapping("/{productId}/price") - public ResponseEntity changePrice(@PathVariable final UUID productId, @RequestBody final Product request) { - return ResponseEntity.ok(productService.changePrice(productId, request)); - } - - @GetMapping - public ResponseEntity> findAll() { - return ResponseEntity.ok(productService.findAll()); - } -} diff --git a/src/main/java/kitchenpos/takeoutorders/empty.txt b/src/main/java/kitchenpos/takeoutorders/empty.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index da61c4529..347ad9039 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,8 @@ spring.datasource.password=password spring.datasource.url=jdbc:mysql://localhost:33306/kitchenpos spring.datasource.username=user -spring.flyway.enabled=true -spring.jpa.hibernate.ddl-auto=validate +spring.flyway.enabled=false +spring.jpa.hibernate.ddl-auto=create spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE diff --git a/src/main/resources/db/migration/V1__Initialize_project_tables.sql b/src/main/resources/db/migration/V1__Initialize_project_tables.sql index feb5d03b0..ad716b517 100644 --- a/src/main/resources/db/migration/V1__Initialize_project_tables.sql +++ b/src/main/resources/db/migration/V1__Initialize_project_tables.sql @@ -1,92 +1,92 @@ -create table menu -( - id binary(16) not null, - displayed bit not null, - name varchar(255) not null, - price decimal(19, 2) not null, - menu_group_id binary(16) not null, - primary key (id) -) engine = InnoDB; - -create table menu_group -( - id binary(16) not null, - name varchar(255) not null, - primary key (id) -) engine = InnoDB; - -create table menu_product -( - seq bigint not null auto_increment, - quantity bigint not null, - product_id binary(16) not null, - menu_id binary(16) not null, - primary key (seq) -) engine = InnoDB; - -create table order_line_item -( - seq bigint not null auto_increment, - quantity bigint not null, - menu_id binary(16) not null, - order_id binary(16) not null, - primary key (seq) -) engine = InnoDB; - -create table order_table -( - id binary(16) not null, - occupied bit not null, - name varchar(255) not null, - number_of_guests integer not null, - primary key (id) -) engine = InnoDB; - -create table orders -( - id binary(16) not null, - delivery_address varchar(255), - order_date_time datetime(6) not null, - status varchar(255) not null, - type varchar(255) not null, - order_table_id binary(16), - primary key (id) -) engine = InnoDB; - -create table product -( - id binary(16) not null, - name varchar(255) not null, - price decimal(19, 2) not null, - primary key (id) -) engine = InnoDB; - -alter table menu - add constraint fk_menu_to_menu_group - foreign key (menu_group_id) - references menu_group (id); - -alter table menu_product - add constraint fk_menu_product_to_product - foreign key (product_id) - references product (id); - -alter table menu_product - add constraint fk_menu_product_to_menu - foreign key (menu_id) - references menu (id); - -alter table order_line_item - add constraint fk_order_line_item_to_menu - foreign key (menu_id) - references menu (id); - -alter table order_line_item - add constraint fk_order_line_item_to_orders - foreign key (order_id) - references orders (id); - -alter table orders - add constraint fk_orders_to_order_table - foreign key (order_table_id) - references order_table (id); +-- create table menu +-- ( +-- id binary(16) not null, +-- displayed bit not null, +-- name varchar(255) not null, +-- price decimal(19, 2) not null, +-- menu_group_id binary(16) not null, +-- primary key (id) +-- ) engine = InnoDB; +-- +-- create table menu_group +-- ( +-- id binary(16) not null, +-- name varchar(255) not null, +-- primary key (id) +-- ) engine = InnoDB; +-- +-- create table menu_product +-- ( +-- seq bigint not null auto_increment, +-- quantity bigint not null, +-- product_id binary(16) not null, +-- menu_id binary(16) not null, +-- primary key (seq) +-- ) engine = InnoDB; +-- +-- create table order_line_item +-- ( +-- seq bigint not null auto_increment, +-- quantity bigint not null, +-- menu_id binary(16) not null, +-- order_id binary(16) not null, +-- primary key (seq) +-- ) engine = InnoDB; +-- +-- create table order_table +-- ( +-- id binary(16) not null, +-- occupied bit not null, +-- name varchar(255) not null, +-- number_of_guests integer not null, +-- primary key (id) +-- ) engine = InnoDB; +-- +-- create table orders +-- ( +-- id binary(16) not null, +-- delivery_address varchar(255), +-- order_date_time datetime(6) not null, +-- status varchar(255) not null, +-- type varchar(255) not null, +-- order_table_id binary(16), +-- primary key (id) +-- ) engine = InnoDB; +-- +-- create table product +-- ( +-- id binary(16) not null, +-- name varchar(255) not null, +-- price decimal(19, 2) not null, +-- primary key (id) +-- ) engine = InnoDB; +-- +-- alter table menu +-- add constraint fk_menu_to_menu_group +-- foreign key (menu_group_id) +-- references menu_group (id); +-- +-- alter table menu_product +-- add constraint fk_menu_product_to_product +-- foreign key (product_id) +-- references product (id); +-- +-- alter table menu_product +-- add constraint fk_menu_product_to_menu +-- foreign key (menu_id) +-- references menu (id); +-- +-- alter table order_line_item +-- add constraint fk_order_line_item_to_menu +-- foreign key (menu_id) +-- references menu (id); +-- +-- alter table order_line_item +-- add constraint fk_order_line_item_to_orders +-- foreign key (order_id) +-- references orders (id); +-- +-- alter table orders +-- add constraint fk_orders_to_order_table +-- foreign key (order_table_id) +-- references order_table (id); diff --git a/src/main/resources/db/migration/V2__Insert_default_data.sql b/src/main/resources/db/migration/V2__Insert_default_data.sql index 59ca0714f..cb38b9086 100644 --- a/src/main/resources/db/migration/V2__Insert_default_data.sql +++ b/src/main/resources/db/migration/V2__Insert_default_data.sql @@ -1,80 +1,110 @@ +# insert into product (id, name, price) -values (x'3b52824434f7406bbb7e690912f66b10', '후라이드', 16000); +# values (x'3b52824434f7406bbb7e690912f66b10', '후라이드', 16000); +# insert into product (id, name, price) -values (x'c5ee925c3dbb4941b825021446f24446', '양념치킨', 16000); +# values (x'c5ee925c3dbb4941b825021446f24446', '양념치킨', 16000); +# insert into product (id, name, price) -values (x'625c6fc4145d408f8dd533c16ba26064', '반반치킨', 16000); +# values (x'625c6fc4145d408f8dd533c16ba26064', '반반치킨', 16000); +# insert into product (id, name, price) -values (x'4721ee722ff3417fade3acd0a804605b', '통구이', 16000); +# values (x'4721ee722ff3417fade3acd0a804605b', '통구이', 16000); +# insert into product (id, name, price) -values (x'0ac16db71b024a87b9c1e7d8f226c48d', '간장치킨', 17000); +# values (x'0ac16db71b024a87b9c1e7d8f226c48d', '간장치킨', 17000); +# insert into product (id, name, price) -values (x'7de4b8affa0f4391aaa9c61ea9b40f83', '순살치킨', 17000); - +# values (x'7de4b8affa0f4391aaa9c61ea9b40f83', '순살치킨', 17000); +# +# insert into menu_group (id, name) +# values (x'f1860abc2ea1411bbd4abaa44f0d5580', '두마리메뉴'); +# insert into menu_group (id, name) -values (x'f1860abc2ea1411bbd4abaa44f0d5580', '두마리메뉴'); +# values (x'cbc75faefeb04bb18be2cb8ce5d8fded', '한마리메뉴'); +# insert into menu_group (id, name) -values (x'cbc75faefeb04bb18be2cb8ce5d8fded', '한마리메뉴'); +# values (x'5e9879b761124791a4cef22e94af8752', '순살파닭두마리메뉴'); +# insert into menu_group (id, name) -values (x'5e9879b761124791a4cef22e94af8752', '순살파닭두마리메뉴'); -insert into menu_group (id, name) -values (x'd9bc21accc104593b5064a40e0170e02', '신메뉴'); - -insert into menu (id, displayed, name, price, menu_group_id) -values (x'f59b1e1cb145440aaa6f6095a0e2d63b', true, '후라이드치킨', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# values (x'd9bc21accc104593b5064a40e0170e02', '신메뉴'); +# +# insert into menu (id, displayed, name, price, menu_group_id) +# values (x'f59b1e1cb145440aaa6f6095a0e2d63b', true, '후라이드치킨', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# insert into menu (id, displayed, name, price, menu_group_id) -values (x'e1254913860846aab23aa07c1dcbc648', true, '양념치킨', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# values (x'e1254913860846aab23aa07c1dcbc648', true, '양념치킨', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# insert into menu (id, displayed, name, price, menu_group_id) -values (x'191fa247b5f34b51b175e65db523f754', true, '반반치킨', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# values (x'191fa247b5f34b51b175e65db523f754', true, '반반치킨', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# insert into menu (id, displayed, name, price, menu_group_id) -values (x'33e558df7d934622b50efcc4282cd184', true, '통구이', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# values (x'33e558df7d934622b50efcc4282cd184', true, '통구이', 16000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# insert into menu (id, displayed, name, price, menu_group_id) -values (x'b9c670b04ef5409083496868df1c7d62', true, '간장치킨', 17000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# values (x'b9c670b04ef5409083496868df1c7d62', true, '간장치킨', 17000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# insert into menu (id, displayed, name, price, menu_group_id) -values (x'a64af6cac34d4cd882fe454abf512d1f', true, '순살치킨', 17000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); - -insert into menu_product (quantity, product_id, menu_id) -values (1, x'3b52824434f7406bbb7e690912f66b10', x'f59b1e1cb145440aaa6f6095a0e2d63b'); +# values (x'a64af6cac34d4cd882fe454abf512d1f', true, '순살치킨', 17000, x'cbc75faefeb04bb18be2cb8ce5d8fded'); +# +# insert into menu_product (quantity, product_id, menu_id) +# values (1, x'3b52824434f7406bbb7e690912f66b10', x'f59b1e1cb145440aaa6f6095a0e2d63b'); +# insert into menu_product (quantity, product_id, menu_id) -values (1, x'c5ee925c3dbb4941b825021446f24446', x'e1254913860846aab23aa07c1dcbc648'); +# values (1, x'c5ee925c3dbb4941b825021446f24446', x'e1254913860846aab23aa07c1dcbc648'); +# insert into menu_product (quantity, product_id, menu_id) -values (1, x'625c6fc4145d408f8dd533c16ba26064', x'191fa247b5f34b51b175e65db523f754'); +# values (1, x'625c6fc4145d408f8dd533c16ba26064', x'191fa247b5f34b51b175e65db523f754'); +# insert into menu_product (quantity, product_id, menu_id) -values (1, x'4721ee722ff3417fade3acd0a804605b', x'33e558df7d934622b50efcc4282cd184'); +# values (1, x'4721ee722ff3417fade3acd0a804605b', x'33e558df7d934622b50efcc4282cd184'); +# insert into menu_product (quantity, product_id, menu_id) -values (1, x'0ac16db71b024a87b9c1e7d8f226c48d', x'b9c670b04ef5409083496868df1c7d62'); +# values (1, x'0ac16db71b024a87b9c1e7d8f226c48d', x'b9c670b04ef5409083496868df1c7d62'); +# insert into menu_product (quantity, product_id, menu_id) -values (1, x'7de4b8affa0f4391aaa9c61ea9b40f83', x'a64af6cac34d4cd882fe454abf512d1f'); - +# values (1, x'7de4b8affa0f4391aaa9c61ea9b40f83', x'a64af6cac34d4cd882fe454abf512d1f'); +# +# insert into order_table (id, occupied, name, number_of_guests) +# values (x'8d71004329b6420e8452233f5a035520', false, '1번', 0); +# insert into order_table (id, occupied, name, number_of_guests) -values (x'8d71004329b6420e8452233f5a035520', false, '1번', 0); +# values (x'6ab59e8106eb441684e99faabc87c9ca', false, '2번', 0); +# insert into order_table (id, occupied, name, number_of_guests) -values (x'6ab59e8106eb441684e99faabc87c9ca', false, '2번', 0); +# values (x'ae92335ccd264626b7979e4ae8c4efbd', false, '3번', 0); +# insert into order_table (id, occupied, name, number_of_guests) -values (x'ae92335ccd264626b7979e4ae8c4efbd', false, '3번', 0); +# values (x'a9858d4b80d0428881f48f41596a23fb', false, '4번', 0); +# insert into order_table (id, occupied, name, number_of_guests) -values (x'a9858d4b80d0428881f48f41596a23fb', false, '4번', 0); +# values (x'3faec3ab5217405daaa2804f87697f84', false, '5번', 0); +# insert into order_table (id, occupied, name, number_of_guests) -values (x'3faec3ab5217405daaa2804f87697f84', false, '5번', 0); +# values (x'815b8395a2ad4e3589dc74c3b2191478', false, '6번', 0); +# insert into order_table (id, occupied, name, number_of_guests) -values (x'815b8395a2ad4e3589dc74c3b2191478', false, '6번', 0); +# values (x'7ce8b3a235454542ab9cb3d493bbd4fb', false, '7번', 0); +# insert into order_table (id, occupied, name, number_of_guests) -values (x'7ce8b3a235454542ab9cb3d493bbd4fb', false, '7번', 0); -insert into order_table (id, occupied, name, number_of_guests) -values (x'7bdb1ffde36e4e2b94e3d2c14d391ef3', false, '8번', 0); - -insert into orders (id, delivery_address, order_date_time, status, type, order_table_id) -values (x'69d78f383bff457cbb7226319c985fd8', '서울시 송파구 위례성대로 2', '2021-07-27', 'WAITING', 'DELIVERY', null); +# values (x'7bdb1ffde36e4e2b94e3d2c14d391ef3', false, '8번', 0); +# +# insert into orders (id, delivery_address, order_date_time, status, type, order_table_id) +# values (x'69d78f383bff457cbb7226319c985fd8', '서울시 송파구 위례성대로 2', '2021-07-27', 'WAITING', 'DELIVERY', null); +# insert into orders (id, delivery_address, order_date_time, status, type, order_table_id) -values (x'98da3d3859e04dacbbaeebf6560a43bd', null, '2021-07-27', 'COMPLETED', 'EAT_IN', - x'8d71004329b6420e8452233f5a035520'); +# values (x'98da3d3859e04dacbbaeebf6560a43bd', null, '2021-07-27', 'COMPLETED', 'EAT_IN', +# x'8d71004329b6420e8452233f5a035520'); +# insert into orders (id, delivery_address, order_date_time, status, type, order_table_id) -values (x'd7cc15b3e32c4bc8b440d3067b35522e', null, '2021-07-27', 'COMPLETED', 'EAT_IN', - x'8d71004329b6420e8452233f5a035520'); - -insert into order_line_item (quantity, menu_id, order_id) -values (1, x'f59b1e1cb145440aaa6f6095a0e2d63b', x'69d78f383bff457cbb7226319c985fd8'); +# values (x'd7cc15b3e32c4bc8b440d3067b35522e', null, '2021-07-27', 'COMPLETED', 'EAT_IN', +# x'8d71004329b6420e8452233f5a035520'); +# +# insert into order_line_item (quantity, menu_id, order_id) +# values (1, x'f59b1e1cb145440aaa6f6095a0e2d63b', x'69d78f383bff457cbb7226319c985fd8'); +# insert into order_line_item (quantity, menu_id, order_id) -values (1, x'f59b1e1cb145440aaa6f6095a0e2d63b', x'98da3d3859e04dacbbaeebf6560a43bd'); +# values (1, x'f59b1e1cb145440aaa6f6095a0e2d63b', x'98da3d3859e04dacbbaeebf6560a43bd'); +# insert into order_line_item (quantity, menu_id, order_id) -values (1, x'f59b1e1cb145440aaa6f6095a0e2d63b', x'd7cc15b3e32c4bc8b440d3067b35522e'); +# values (1, x'f59b1e1cb145440aaa6f6095a0e2d63b', x'd7cc15b3e32c4bc8b440d3067b35522e'); diff --git a/src/test/java/kitchenpos/Fixtures.java b/src/test/java/kitchenpos/Fixtures.java deleted file mode 100644 index 434768a52..000000000 --- a/src/test/java/kitchenpos/Fixtures.java +++ /dev/null @@ -1,131 +0,0 @@ -package kitchenpos; - -import kitchenpos.eatinorders.domain.Order; -import kitchenpos.eatinorders.domain.OrderLineItem; -import kitchenpos.eatinorders.domain.OrderStatus; -import kitchenpos.eatinorders.domain.OrderTable; -import kitchenpos.eatinorders.domain.OrderType; -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuGroup; -import kitchenpos.menus.domain.MenuProduct; -import kitchenpos.products.domain.Product; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Random; -import java.util.UUID; - -public class Fixtures { - public static final UUID INVALID_ID = new UUID(0L, 0L); - - public static Menu menu() { - return menu(19_000L, true, menuProduct()); - } - - public static Menu menu(final long price, final MenuProduct... menuProducts) { - return menu(price, false, menuProducts); - } - - public static Menu menu(final long price, final boolean displayed, final MenuProduct... menuProducts) { - final Menu menu = new Menu(); - menu.setId(UUID.randomUUID()); - menu.setName("후라이드+후라이드"); - menu.setPrice(BigDecimal.valueOf(price)); - menu.setMenuGroup(menuGroup()); - menu.setDisplayed(displayed); - menu.setMenuProducts(Arrays.asList(menuProducts)); - return menu; - } - - public static MenuGroup menuGroup() { - return menuGroup("두마리메뉴"); - } - - public static MenuGroup menuGroup(final String name) { - final MenuGroup menuGroup = new MenuGroup(); - menuGroup.setId(UUID.randomUUID()); - menuGroup.setName(name); - return menuGroup; - } - - public static MenuProduct menuProduct() { - final MenuProduct menuProduct = new MenuProduct(); - menuProduct.setSeq(new Random().nextLong()); - menuProduct.setProduct(product()); - menuProduct.setQuantity(2L); - return menuProduct; - } - - public static MenuProduct menuProduct(final Product product, final long quantity) { - final MenuProduct menuProduct = new MenuProduct(); - menuProduct.setSeq(new Random().nextLong()); - menuProduct.setProduct(product); - menuProduct.setQuantity(quantity); - return menuProduct; - } - - public static Order order(final OrderStatus status, final String deliveryAddress) { - final Order order = new Order(); - order.setId(UUID.randomUUID()); - order.setType(OrderType.DELIVERY); - order.setStatus(status); - order.setOrderDateTime(LocalDateTime.of(2020, 1, 1, 12, 0)); - order.setOrderLineItems(Arrays.asList(orderLineItem())); - order.setDeliveryAddress(deliveryAddress); - return order; - } - - public static Order order(final OrderStatus status) { - final Order order = new Order(); - order.setId(UUID.randomUUID()); - order.setType(OrderType.TAKEOUT); - order.setStatus(status); - order.setOrderDateTime(LocalDateTime.of(2020, 1, 1, 12, 0)); - order.setOrderLineItems(Arrays.asList(orderLineItem())); - return order; - } - - public static Order order(final OrderStatus status, final OrderTable orderTable) { - final Order order = new Order(); - order.setId(UUID.randomUUID()); - order.setType(OrderType.EAT_IN); - order.setStatus(status); - order.setOrderDateTime(LocalDateTime.of(2020, 1, 1, 12, 0)); - order.setOrderLineItems(Arrays.asList(orderLineItem())); - order.setOrderTable(orderTable); - return order; - } - - public static OrderLineItem orderLineItem() { - final OrderLineItem orderLineItem = new OrderLineItem(); - orderLineItem.setSeq(new Random().nextLong()); - orderLineItem.setMenu(menu()); - return orderLineItem; - } - - public static OrderTable orderTable() { - return orderTable(false, 0); - } - - public static OrderTable orderTable(final boolean occupied, final int numberOfGuests) { - final OrderTable orderTable = new OrderTable(); - orderTable.setId(UUID.randomUUID()); - orderTable.setName("1번"); - orderTable.setNumberOfGuests(numberOfGuests); - orderTable.setOccupied(occupied); - return orderTable; - } - - public static Product product() { - return product("후라이드", 16_000L); - } - - public static Product product(final String name, final long price) { - final Product product = new Product(); - product.setId(UUID.randomUUID()); - product.setName(name); - product.setPrice(BigDecimal.valueOf(price)); - return product; - } -} diff --git a/src/test/java/kitchenpos/TestFixtureFactory.java b/src/test/java/kitchenpos/TestFixtureFactory.java new file mode 100644 index 000000000..40004c6b9 --- /dev/null +++ b/src/test/java/kitchenpos/TestFixtureFactory.java @@ -0,0 +1,144 @@ +package kitchenpos; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuGroupNameCreationService; +import kitchenpos.menu.domain.model.MenuProduct; +import kitchenpos.order.common.model.Order; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.common.model.OrderStatus; +import kitchenpos.order.common.model.OrderType; +import kitchenpos.order.eatinorder.application.dto.CreateEatInOrderServiceRq; +import kitchenpos.order.eatinorder.application.dto.CreateEatInOrderServiceRq.OrderLineItemServiceDto; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.ui.dto.CreateEatInOrderRq; +import kitchenpos.order.eatinorder.ui.dto.CreateEatInOrderRq.OrderLineItemDto; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.model.ProductName; +import kitchenpos.product.domain.model.ProductNameCreationService; +import kitchenpos.product.domain.model.ProductPrice; + +public class TestFixtureFactory { + + public static MenuGroup createMenuGroup() { + return new MenuGroup( + new MenuGroupNameCreationService(new FakePurgomalumClient()) + .createName("한식") + ); + } + + public static Product createProduct(BigDecimal value) { + ProductName productName = createName("김치"); + ProductPrice productPrice = createPrice(value); + return new Product(productName, productPrice); + } + + private static ProductName createName(String value) { + ProductNameCreationService productNameCreationService = new ProductNameCreationService( + new FakePurgomalumClient()); + return productNameCreationService.createName(value); + } + + private static ProductPrice createPrice(BigDecimal price) { + return new ProductPrice(price); + } + + public static Menu createMenuWithProductAndGroup() { + MenuGroup menuGroup = createMenuGroup(); + Product product = createProduct(BigDecimal.valueOf(5000)); + MenuProduct menuProduct = new MenuProduct(1, product, product.getId()); + return new Menu("김치찌개", BigDecimal.valueOf(8000), true, List.of(menuProduct), menuGroup, + menuGroup.getId()); + } + + public static Menu createMenuWithProductAndGroup(boolean displayed) { + MenuGroup menuGroup = createMenuGroup(); + Product product = createProduct(BigDecimal.valueOf(5000)); + MenuProduct menuProduct = new MenuProduct(1, product, product.getId()); + return new Menu("김치찌개", BigDecimal.valueOf(8000), displayed, List.of(menuProduct), menuGroup, + menuGroup.getId()); + } + + public static Menu createMenu(MenuGroup menuGroup, Product product) { + MenuProduct menuProduct = new MenuProduct(1, product, product.getId()); + return new Menu("김치찌개", BigDecimal.valueOf(8000), true, List.of(menuProduct), menuGroup, + menuGroup.getId()); + } + + public static Menu createMenu(MenuGroup menuGroup, Product product, long quantity) { + MenuProduct menuProduct = new MenuProduct(quantity, product, product.getId()); + return new Menu("김치찌개", BigDecimal.valueOf(8000), true, List.of(menuProduct), menuGroup, + menuGroup.getId()); + } + + public static Menu createMenuWithProductAndGroup(String name, long price, Product product) { + MenuProduct menuProduct = new MenuProduct(1, product, product.getId()); + MenuGroup menuGroup = new MenuGroup("찌개"); + return new Menu(name, BigDecimal.valueOf(price), true, List.of(menuProduct), menuGroup, menuGroup.getId()); + } + + public static OrderTable createEmptyOrderTable() { + return new OrderTable("비어 있는 테이블", 0, false); + } + + public static OrderTable createUsingOrderTable() { + return new OrderTable("사용 중인 테이블", 4, true); + } + + public static Product createProduct(String name, long price) { + return new Product(createName(name), createPrice(BigDecimal.valueOf(price))); + } + + public static Order createOrderWithDeliveryType(OrderLineItem orderLineItem, OrderTable orderTable, + OrderStatus status) { + return new Order(OrderType.DELIVERY, status, LocalDateTime.now(), List.of(orderLineItem), + "주소", orderTable, orderTable.getId()); + } + + public static Order createOrderWithTakeOutType(OrderLineItem orderLineItem, OrderTable orderTable, + OrderStatus status) { + return new Order(OrderType.TAKEOUT, status, LocalDateTime.now(), List.of(orderLineItem), + "주소", orderTable, orderTable.getId()); + } + + public static Order createOrder(OrderLineItem orderLineItem, OrderTable orderTable, OrderType orderType, + OrderStatus orderStatus, String address) { + return new Order(orderType, orderStatus, LocalDateTime.now(), List.of(orderLineItem), + address, orderTable, orderTable.getId()); + } + + public static OrderLineItem createOrderLineItem(Menu menu) { + return new OrderLineItem(menu, 2, menu.getId(), BigDecimal.valueOf(8000)); + } + + public static EatInOrder createEatInOrderRequestWithEmptyTable(Menu menu, + EatInOrderFlow eatInOrderFlow) { + return new EatInOrder(LocalDateTime.now(), + List.of(new OrderLineItem(menu, 1, menu.getId(), BigDecimal.valueOf(8000))), eatInOrderFlow); + } + + public static CreateEatInOrderServiceRq createEatInOrderServiceRq(Menu menu, UUID orderTableId) { + return new CreateEatInOrderServiceRq( + List.of(new OrderLineItemServiceDto(menu.getId(), 1, BigDecimal.valueOf(8000))), orderTableId); + } + + public static CreateEatInOrderRq createEatInOrderRq(Menu menu, UUID orderTableId) { + return new CreateEatInOrderRq( + List.of(new OrderLineItemDto(menu.getId(), 1, BigDecimal.valueOf(8000))), orderTableId); + } + + public static CreateEatInOrderRq createEatInOrderRq(List menuIds, UUID orderTableId) { + return new CreateEatInOrderRq( + menuIds.stream() + .map(id -> new OrderLineItemDto(id, 2, BigDecimal.valueOf(8000))) + .toList(), + orderTableId); + } +} diff --git a/src/test/java/kitchenpos/common/infra/external/FakePurgomalumClient.java b/src/test/java/kitchenpos/common/infra/external/FakePurgomalumClient.java new file mode 100644 index 000000000..40402dc35 --- /dev/null +++ b/src/test/java/kitchenpos/common/infra/external/FakePurgomalumClient.java @@ -0,0 +1,17 @@ +package kitchenpos.common.infra.external; + +import kitchenpos.common.application.PurgomalumClient; + +public class FakePurgomalumClient implements PurgomalumClient { + + private boolean isProfanity = false; + + @Override + public boolean containsProfanity(String text) { + return isProfanity; + } + + public void setProfanity(boolean isProfanity) { + this.isProfanity = isProfanity; + } +} diff --git a/src/test/java/kitchenpos/eatinorders/application/FakeKitchenridersClient.java b/src/test/java/kitchenpos/eatinorders/application/FakeKitchenridersClient.java deleted file mode 100644 index 301e1377b..000000000 --- a/src/test/java/kitchenpos/eatinorders/application/FakeKitchenridersClient.java +++ /dev/null @@ -1,31 +0,0 @@ -package kitchenpos.eatinorders.application; - -import kitchenpos.deliveryorders.infra.KitchenridersClient; - -import java.math.BigDecimal; -import java.util.UUID; - -public class FakeKitchenridersClient implements KitchenridersClient { - private UUID orderId; - private BigDecimal amount; - private String deliveryAddress; - - @Override - public void requestDelivery(final UUID orderId, final BigDecimal amount, final String deliveryAddress) { - this.orderId = orderId; - this.amount = amount; - this.deliveryAddress = deliveryAddress; - } - - public UUID getOrderId() { - return orderId; - } - - public BigDecimal getAmount() { - return amount; - } - - public String getDeliveryAddress() { - return deliveryAddress; - } -} diff --git a/src/test/java/kitchenpos/eatinorders/application/InMemoryOrderRepository.java b/src/test/java/kitchenpos/eatinorders/application/InMemoryOrderRepository.java deleted file mode 100644 index 85f27e38d..000000000 --- a/src/test/java/kitchenpos/eatinorders/application/InMemoryOrderRepository.java +++ /dev/null @@ -1,40 +0,0 @@ -package kitchenpos.eatinorders.application; - -import kitchenpos.eatinorders.domain.Order; -import kitchenpos.eatinorders.domain.OrderRepository; -import kitchenpos.eatinorders.domain.OrderStatus; -import kitchenpos.eatinorders.domain.OrderTable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -public class InMemoryOrderRepository implements OrderRepository { - private final Map orders = new HashMap<>(); - - @Override - public Order save(final Order order) { - orders.put(order.getId(), order); - return order; - } - - @Override - public Optional findById(final UUID id) { - return Optional.ofNullable(orders.get(id)); - } - - @Override - public List findAll() { - return new ArrayList<>(orders.values()); - } - - @Override - public boolean existsByOrderTableAndStatusNot(final OrderTable orderTable, final OrderStatus status) { - return orders.values() - .stream() - .anyMatch(order -> order.getOrderTable().equals(orderTable) && order.getStatus() != status); - } -} diff --git a/src/test/java/kitchenpos/eatinorders/application/InMemoryOrderTableRepository.java b/src/test/java/kitchenpos/eatinorders/application/InMemoryOrderTableRepository.java deleted file mode 100644 index 663de6289..000000000 --- a/src/test/java/kitchenpos/eatinorders/application/InMemoryOrderTableRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package kitchenpos.eatinorders.application; - -import kitchenpos.eatinorders.domain.OrderTable; -import kitchenpos.eatinorders.domain.OrderTableRepository; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -public class InMemoryOrderTableRepository implements OrderTableRepository { - private final Map orderTables = new HashMap<>(); - - @Override - public OrderTable save(final OrderTable orderTable) { - orderTables.put(orderTable.getId(), orderTable); - return orderTable; - } - - @Override - public Optional findById(final UUID id) { - return Optional.ofNullable(orderTables.get(id)); - } - - @Override - public List findAll() { - return new ArrayList<>(orderTables.values()); - } -} diff --git a/src/test/java/kitchenpos/eatinorders/application/OrderServiceTest.java b/src/test/java/kitchenpos/eatinorders/application/OrderServiceTest.java deleted file mode 100644 index 8701fcaef..000000000 --- a/src/test/java/kitchenpos/eatinorders/application/OrderServiceTest.java +++ /dev/null @@ -1,396 +0,0 @@ -package kitchenpos.eatinorders.application; - -import kitchenpos.eatinorders.domain.Order; -import kitchenpos.eatinorders.domain.OrderLineItem; -import kitchenpos.eatinorders.domain.OrderRepository; -import kitchenpos.eatinorders.domain.OrderStatus; -import kitchenpos.eatinorders.domain.OrderTable; -import kitchenpos.eatinorders.domain.OrderTableRepository; -import kitchenpos.eatinorders.domain.OrderType; -import kitchenpos.menus.application.InMemoryMenuRepository; -import kitchenpos.menus.domain.MenuRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Random; -import java.util.UUID; - -import static kitchenpos.Fixtures.INVALID_ID; -import static kitchenpos.Fixtures.menu; -import static kitchenpos.Fixtures.menuProduct; -import static kitchenpos.Fixtures.order; -import static kitchenpos.Fixtures.orderTable; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -class OrderServiceTest { - private OrderRepository orderRepository; - private MenuRepository menuRepository; - private OrderTableRepository orderTableRepository; - private FakeKitchenridersClient kitchenridersClient; - private OrderService orderService; - - @BeforeEach - void setUp() { - orderRepository = new InMemoryOrderRepository(); - menuRepository = new InMemoryMenuRepository(); - orderTableRepository = new InMemoryOrderTableRepository(); - kitchenridersClient = new FakeKitchenridersClient(); - orderService = new OrderService(orderRepository, menuRepository, orderTableRepository, kitchenridersClient); - } - - @DisplayName("1개 이상의 등록된 메뉴로 배달 주문을 등록할 수 있다.") - @Test - void createDeliveryOrder() { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final Order expected = createOrderRequest( - OrderType.DELIVERY, "서울시 송파구 위례성대로 2", createOrderLineItemRequest(menuId, 19_000L, 3L) - ); - final Order actual = orderService.create(expected); - assertThat(actual).isNotNull(); - assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getType()).isEqualTo(expected.getType()), - () -> assertThat(actual.getStatus()).isEqualTo(OrderStatus.WAITING), - () -> assertThat(actual.getOrderDateTime()).isNotNull(), - () -> assertThat(actual.getOrderLineItems()).hasSize(1), - () -> assertThat(actual.getDeliveryAddress()).isEqualTo(expected.getDeliveryAddress()) - ); - } - - @DisplayName("1개 이상의 등록된 메뉴로 포장 주문을 등록할 수 있다.") - @Test - void createTakeoutOrder() { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final Order expected = createOrderRequest(OrderType.TAKEOUT, createOrderLineItemRequest(menuId, 19_000L, 3L)); - final Order actual = orderService.create(expected); - assertThat(actual).isNotNull(); - assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getType()).isEqualTo(expected.getType()), - () -> assertThat(actual.getStatus()).isEqualTo(OrderStatus.WAITING), - () -> assertThat(actual.getOrderDateTime()).isNotNull(), - () -> assertThat(actual.getOrderLineItems()).hasSize(1) - ); - } - - @DisplayName("1개 이상의 등록된 메뉴로 매장 주문을 등록할 수 있다.") - @Test - void createEatInOrder() { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final UUID orderTableId = orderTableRepository.save(orderTable(true, 4)).getId(); - final Order expected = createOrderRequest(OrderType.EAT_IN, orderTableId, createOrderLineItemRequest(menuId, 19_000L, 3L)); - final Order actual = orderService.create(expected); - assertThat(actual).isNotNull(); - assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getType()).isEqualTo(expected.getType()), - () -> assertThat(actual.getStatus()).isEqualTo(OrderStatus.WAITING), - () -> assertThat(actual.getOrderDateTime()).isNotNull(), - () -> assertThat(actual.getOrderLineItems()).hasSize(1), - () -> assertThat(actual.getOrderTable().getId()).isEqualTo(expected.getOrderTableId()) - ); - } - - @DisplayName("주문 유형이 올바르지 않으면 등록할 수 없다.") - @NullSource - @ParameterizedTest - void create(final OrderType type) { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final Order expected = createOrderRequest(type, createOrderLineItemRequest(menuId, 19_000L, 3L)); - assertThatThrownBy(() -> orderService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴가 없으면 등록할 수 없다.") - @MethodSource("orderLineItems") - @ParameterizedTest - void create(final List orderLineItems) { - final Order expected = createOrderRequest(OrderType.TAKEOUT, orderLineItems); - assertThatThrownBy(() -> orderService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - private static List orderLineItems() { - return Arrays.asList( - null, - Arguments.of(Collections.emptyList()), - Arguments.of(Arrays.asList(createOrderLineItemRequest(INVALID_ID, 19_000L, 3L))) - ); - } - - @DisplayName("매장 주문은 주문 항목의 수량이 0 미만일 수 있다.") - @ValueSource(longs = -1L) - @ParameterizedTest - void createEatInOrder(final long quantity) { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final UUID orderTableId = orderTableRepository.save(orderTable(true, 4)).getId(); - final Order expected = createOrderRequest( - OrderType.EAT_IN, orderTableId, createOrderLineItemRequest(menuId, 19_000L, quantity) - ); - assertDoesNotThrow(() -> orderService.create(expected)); - } - - @DisplayName("매장 주문을 제외한 주문의 경우 주문 항목의 수량은 0 이상이어야 한다.") - @ValueSource(longs = -1L) - @ParameterizedTest - void createWithoutEatInOrder(final long quantity) { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final Order expected = createOrderRequest( - OrderType.TAKEOUT, createOrderLineItemRequest(menuId, 19_000L, quantity) - ); - assertThatThrownBy(() -> orderService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("배달 주소가 올바르지 않으면 배달 주문을 등록할 수 없다.") - @NullAndEmptySource - @ParameterizedTest - void create(final String deliveryAddress) { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final Order expected = createOrderRequest( - OrderType.DELIVERY, deliveryAddress, createOrderLineItemRequest(menuId, 19_000L, 3L) - ); - assertThatThrownBy(() -> orderService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("빈 테이블에는 매장 주문을 등록할 수 없다.") - @Test - void createEmptyTableEatInOrder() { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final UUID orderTableId = orderTableRepository.save(orderTable(false, 0)).getId(); - final Order expected = createOrderRequest( - OrderType.EAT_IN, orderTableId, createOrderLineItemRequest(menuId, 19_000L, 3L) - ); - assertThatThrownBy(() -> orderService.create(expected)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("숨겨진 메뉴는 주문할 수 없다.") - @Test - void createNotDisplayedMenuOrder() { - final UUID menuId = menuRepository.save(menu(19_000L, false, menuProduct())).getId(); - final Order expected = createOrderRequest(OrderType.TAKEOUT, createOrderLineItemRequest(menuId, 19_000L, 3L)); - assertThatThrownBy(() -> orderService.create(expected)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("주문한 메뉴의 가격은 실제 메뉴 가격과 일치해야 한다.") - @Test - void createNotMatchedMenuPriceOrder() { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct())).getId(); - final Order expected = createOrderRequest(OrderType.TAKEOUT, createOrderLineItemRequest(menuId, 16_000L, 3L)); - assertThatThrownBy(() -> orderService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("주문을 접수한다.") - @Test - void accept() { - final UUID orderId = orderRepository.save(order(OrderStatus.WAITING, orderTable(true, 4))).getId(); - final Order actual = orderService.accept(orderId); - assertThat(actual.getStatus()).isEqualTo(OrderStatus.ACCEPTED); - } - - @DisplayName("접수 대기 중인 주문만 접수할 수 있다.") - @EnumSource(value = OrderStatus.class, names = "WAITING", mode = EnumSource.Mode.EXCLUDE) - @ParameterizedTest - void accept(final OrderStatus status) { - final UUID orderId = orderRepository.save(order(status, orderTable(true, 4))).getId(); - assertThatThrownBy(() -> orderService.accept(orderId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("배달 주문을 접수되면 배달 대행사를 호출한다.") - @Test - void acceptDeliveryOrder() { - final UUID orderId = orderRepository.save(order(OrderStatus.WAITING, "서울시 송파구 위례성대로 2")).getId(); - final Order actual = orderService.accept(orderId); - assertAll( - () -> assertThat(actual.getStatus()).isEqualTo(OrderStatus.ACCEPTED), - () -> assertThat(kitchenridersClient.getOrderId()).isEqualTo(orderId), - () -> assertThat(kitchenridersClient.getDeliveryAddress()).isEqualTo("서울시 송파구 위례성대로 2") - ); - } - - @DisplayName("주문을 서빙한다.") - @Test - void serve() { - final UUID orderId = orderRepository.save(order(OrderStatus.ACCEPTED)).getId(); - final Order actual = orderService.serve(orderId); - assertThat(actual.getStatus()).isEqualTo(OrderStatus.SERVED); - } - - @DisplayName("접수된 주문만 서빙할 수 있다.") - @EnumSource(value = OrderStatus.class, names = "ACCEPTED", mode = EnumSource.Mode.EXCLUDE) - @ParameterizedTest - void serve(final OrderStatus status) { - final UUID orderId = orderRepository.save(order(status)).getId(); - assertThatThrownBy(() -> orderService.serve(orderId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("주문을 배달한다.") - @Test - void startDelivery() { - final UUID orderId = orderRepository.save(order(OrderStatus.SERVED, "서울시 송파구 위례성대로 2")).getId(); - final Order actual = orderService.startDelivery(orderId); - assertThat(actual.getStatus()).isEqualTo(OrderStatus.DELIVERING); - } - - @DisplayName("배달 주문만 배달할 수 있다.") - @Test - void startDeliveryWithoutDeliveryOrder() { - final UUID orderId = orderRepository.save(order(OrderStatus.SERVED)).getId(); - assertThatThrownBy(() -> orderService.startDelivery(orderId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("서빙된 주문만 배달할 수 있다.") - @EnumSource(value = OrderStatus.class, names = "SERVED", mode = EnumSource.Mode.EXCLUDE) - @ParameterizedTest - void startDelivery(final OrderStatus status) { - final UUID orderId = orderRepository.save(order(status, "서울시 송파구 위례성대로 2")).getId(); - assertThatThrownBy(() -> orderService.startDelivery(orderId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("주문을 배달 완료한다.") - @Test - void completeDelivery() { - final UUID orderId = orderRepository.save(order(OrderStatus.DELIVERING, "서울시 송파구 위례성대로 2")).getId(); - final Order actual = orderService.completeDelivery(orderId); - assertThat(actual.getStatus()).isEqualTo(OrderStatus.DELIVERED); - } - - @DisplayName("배달 중인 주문만 배달 완료할 수 있다.") - @EnumSource(value = OrderStatus.class, names = "DELIVERING", mode = EnumSource.Mode.EXCLUDE) - @ParameterizedTest - void completeDelivery(final OrderStatus status) { - final UUID orderId = orderRepository.save(order(status, "서울시 송파구 위례성대로 2")).getId(); - assertThatThrownBy(() -> orderService.completeDelivery(orderId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("주문을 완료한다.") - @Test - void complete() { - final Order expected = orderRepository.save(order(OrderStatus.DELIVERED, "서울시 송파구 위례성대로 2")); - final Order actual = orderService.complete(expected.getId()); - assertThat(actual.getStatus()).isEqualTo(OrderStatus.COMPLETED); - } - - @DisplayName("배달 주문의 경우 배달 완료된 주문만 완료할 수 있다.") - @EnumSource(value = OrderStatus.class, names = "DELIVERED", mode = EnumSource.Mode.EXCLUDE) - @ParameterizedTest - void completeDeliveryOrder(final OrderStatus status) { - final UUID orderId = orderRepository.save(order(status, "서울시 송파구 위례성대로 2")).getId(); - assertThatThrownBy(() -> orderService.complete(orderId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("포장 및 매장 주문의 경우 서빙된 주문만 완료할 수 있다.") - @EnumSource(value = OrderStatus.class, names = "SERVED", mode = EnumSource.Mode.EXCLUDE) - @ParameterizedTest - void completeTakeoutAndEatInOrder(final OrderStatus status) { - final UUID orderId = orderRepository.save(order(status)).getId(); - assertThatThrownBy(() -> orderService.complete(orderId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("주문 테이블의 모든 매장 주문이 완료되면 빈 테이블로 설정한다.") - @Test - void completeEatInOrder() { - final OrderTable orderTable = orderTableRepository.save(orderTable(true, 4)); - final Order expected = orderRepository.save(order(OrderStatus.SERVED, orderTable)); - final Order actual = orderService.complete(expected.getId()); - assertAll( - () -> assertThat(actual.getStatus()).isEqualTo(OrderStatus.COMPLETED), - () -> assertThat(orderTableRepository.findById(orderTable.getId()).get().isOccupied()).isFalse(), - () -> assertThat(orderTableRepository.findById(orderTable.getId()).get().getNumberOfGuests()).isEqualTo(0) - ); - } - - @DisplayName("완료되지 않은 매장 주문이 있는 주문 테이블은 빈 테이블로 설정하지 않는다.") - @Test - void completeNotTable() { - final OrderTable orderTable = orderTableRepository.save(orderTable(true, 4)); - orderRepository.save(order(OrderStatus.ACCEPTED, orderTable)); - final Order expected = orderRepository.save(order(OrderStatus.SERVED, orderTable)); - final Order actual = orderService.complete(expected.getId()); - assertAll( - () -> assertThat(actual.getStatus()).isEqualTo(OrderStatus.COMPLETED), - () -> assertThat(orderTableRepository.findById(orderTable.getId()).get().isOccupied()).isTrue(), - () -> assertThat(orderTableRepository.findById(orderTable.getId()).get().getNumberOfGuests()).isEqualTo(4) - ); - } - - @DisplayName("주문의 목록을 조회할 수 있다.") - @Test - void findAll() { - final OrderTable orderTable = orderTableRepository.save(orderTable(true, 4)); - orderRepository.save(order(OrderStatus.SERVED, orderTable)); - orderRepository.save(order(OrderStatus.DELIVERED, "서울시 송파구 위례성대로 2")); - final List actual = orderService.findAll(); - assertThat(actual).hasSize(2); - } - - private Order createOrderRequest( - final OrderType type, - final String deliveryAddress, - final OrderLineItem... orderLineItems - ) { - final Order order = new Order(); - order.setType(type); - order.setDeliveryAddress(deliveryAddress); - order.setOrderLineItems(Arrays.asList(orderLineItems)); - return order; - } - - private Order createOrderRequest(final OrderType orderType, final OrderLineItem... orderLineItems) { - return createOrderRequest(orderType, Arrays.asList(orderLineItems)); - } - - private Order createOrderRequest(final OrderType orderType, final List orderLineItems) { - final Order order = new Order(); - order.setType(orderType); - order.setOrderLineItems(orderLineItems); - return order; - } - - private Order createOrderRequest( - final OrderType type, - final UUID orderTableId, - final OrderLineItem... orderLineItems - ) { - final Order order = new Order(); - order.setType(type); - order.setOrderTableId(orderTableId); - order.setOrderLineItems(Arrays.asList(orderLineItems)); - return order; - } - - private static OrderLineItem createOrderLineItemRequest(final UUID menuId, final long price, final long quantity) { - final OrderLineItem orderLineItem = new OrderLineItem(); - orderLineItem.setSeq(new Random().nextLong()); - orderLineItem.setMenuId(menuId); - orderLineItem.setPrice(BigDecimal.valueOf(price)); - orderLineItem.setQuantity(quantity); - return orderLineItem; - } -} diff --git a/src/test/java/kitchenpos/eatinorders/application/OrderTableServiceTest.java b/src/test/java/kitchenpos/eatinorders/application/OrderTableServiceTest.java deleted file mode 100644 index 01551a8d4..000000000 --- a/src/test/java/kitchenpos/eatinorders/application/OrderTableServiceTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package kitchenpos.eatinorders.application; - -import kitchenpos.eatinorders.domain.OrderRepository; -import kitchenpos.eatinorders.domain.OrderStatus; -import kitchenpos.eatinorders.domain.OrderTable; -import kitchenpos.eatinorders.domain.OrderTableRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.List; -import java.util.UUID; - -import static kitchenpos.Fixtures.order; -import static kitchenpos.Fixtures.orderTable; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - -class OrderTableServiceTest { - private OrderTableRepository orderTableRepository; - private OrderRepository orderRepository; - private OrderTableService orderTableService; - - @BeforeEach - void setUp() { - orderTableRepository = new InMemoryOrderTableRepository(); - orderRepository = new InMemoryOrderRepository(); - orderTableService = new OrderTableService(orderTableRepository, orderRepository); - } - - @DisplayName("주문 테이블을 등록할 수 있다.") - @Test - void create() { - final OrderTable expected = createOrderTableRequest("1번"); - final OrderTable actual = orderTableService.create(expected); - assertThat(actual).isNotNull(); - assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getName()).isEqualTo(expected.getName()), - () -> assertThat(actual.getNumberOfGuests()).isZero(), - () -> assertThat(actual.isOccupied()).isFalse() - ); - } - - @DisplayName("주문 테이블의 이름이 올바르지 않으면 등록할 수 없다.") - @NullAndEmptySource - @ParameterizedTest - void create(final String name) { - final OrderTable expected = createOrderTableRequest(name); - assertThatThrownBy(() -> orderTableService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("빈 테이블을 해지할 수 있다.") - @Test - void sit() { - final UUID orderTableId = orderTableRepository.save(orderTable(false, 0)).getId(); - final OrderTable actual = orderTableService.sit(orderTableId); - assertThat(actual.isOccupied()).isTrue(); - } - - @DisplayName("빈 테이블로 설정할 수 있다.") - @Test - void clear() { - final UUID orderTableId = orderTableRepository.save(orderTable(true, 4)).getId(); - final OrderTable actual = orderTableService.clear(orderTableId); - assertAll( - () -> assertThat(actual.getNumberOfGuests()).isZero(), - () -> assertThat(actual.isOccupied()).isFalse() - ); - } - - @DisplayName("완료되지 않은 주문이 있는 주문 테이블은 빈 테이블로 설정할 수 없다.") - @Test - void clearWithUncompletedOrders() { - final OrderTable orderTable = orderTableRepository.save(orderTable(true, 4)); - final UUID orderTableId = orderTable.getId(); - orderRepository.save(order(OrderStatus.ACCEPTED, orderTable)); - assertThatThrownBy(() -> orderTableService.clear(orderTableId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("방문한 손님 수를 변경할 수 있다.") - @Test - void changeNumberOfGuests() { - final UUID orderTableId = orderTableRepository.save(orderTable(true, 0)).getId(); - final OrderTable expected = changeNumberOfGuestsRequest(4); - final OrderTable actual = orderTableService.changeNumberOfGuests(orderTableId, expected); - assertThat(actual.getNumberOfGuests()).isEqualTo(4); - } - - @DisplayName("방문한 손님 수가 올바르지 않으면 변경할 수 없다.") - @ValueSource(ints = -1) - @ParameterizedTest - void changeNumberOfGuests(final int numberOfGuests) { - final UUID orderTableId = orderTableRepository.save(orderTable(true, 0)).getId(); - final OrderTable expected = changeNumberOfGuestsRequest(numberOfGuests); - assertThatThrownBy(() -> orderTableService.changeNumberOfGuests(orderTableId, expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("빈 테이블은 방문한 손님 수를 변경할 수 없다.") - @Test - void changeNumberOfGuestsInEmptyTable() { - final UUID orderTableId = orderTableRepository.save(orderTable(false, 0)).getId(); - final OrderTable expected = changeNumberOfGuestsRequest(4); - assertThatThrownBy(() -> orderTableService.changeNumberOfGuests(orderTableId, expected)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("주문 테이블의 목록을 조회할 수 있다.") - @Test - void findAll() { - orderTableRepository.save(orderTable()); - final List actual = orderTableService.findAll(); - assertThat(actual).hasSize(1); - } - - private OrderTable createOrderTableRequest(final String name) { - final OrderTable orderTable = new OrderTable(); - orderTable.setName(name); - return orderTable; - } - - private OrderTable changeNumberOfGuestsRequest(final int numberOfGuests) { - final OrderTable orderTable = new OrderTable(); - orderTable.setNumberOfGuests(numberOfGuests); - return orderTable; - } -} diff --git a/src/test/java/kitchenpos/menu/application/MenuGroupServiceTest.java b/src/test/java/kitchenpos/menu/application/MenuGroupServiceTest.java new file mode 100644 index 000000000..f00db979f --- /dev/null +++ b/src/test/java/kitchenpos/menu/application/MenuGroupServiceTest.java @@ -0,0 +1,84 @@ +package kitchenpos.menu.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import kitchenpos.menu.application.dto.CreateMenuGroupServiceRq; +import kitchenpos.menu.application.dto.MenuGroupServiceRs; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuGroupNameCreationService; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class MenuGroupServiceTest { + + private MenuGroupService menuGroupService; + private MenuGroupRepository menuGroupRepository; + + @BeforeEach + void setUp() { + menuGroupRepository = mock(MenuGroupRepository.class); + MenuGroupNameCreationService menuGroupNameCreationService = new MenuGroupNameCreationService( + new FakePurgomalumClient()); + menuGroupService = new MenuGroupService(menuGroupRepository, menuGroupNameCreationService); + } + + @Test + @DisplayName("메뉴 그룹을 생성한다") + void create_menuGroup() { + // given + CreateMenuGroupServiceRq request = new CreateMenuGroupServiceRq("한식"); + + when(menuGroupRepository.save(any(MenuGroup.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + // when + MenuGroupServiceRs result = menuGroupService.create(request); + + // then + assertThat(result.getId()).isNotNull(); + assertThat(result.getName()).isEqualTo("한식"); + verify(menuGroupRepository).save(any(MenuGroup.class)); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("메뉴 그룹 이름이 null이거나 비어있으면 예외가 발생한다") + void create_MenuGroup_fail(String name) { + // given + CreateMenuGroupServiceRq request = new CreateMenuGroupServiceRq(name); + + // when // then + assertThatThrownBy(() -> { + menuGroupService.create(request); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("메뉴 그룹 목록을 조회한다") + void findAll() { + // given + MenuGroup group1 = new MenuGroup("한식"); + MenuGroup group2 = new MenuGroup("중식"); + List expected = Arrays.asList(group1, group2); + when(menuGroupRepository.findAll()).thenReturn(expected); + + // when + List result = menuGroupService.findAll(); + + // then + assertThat(result).hasSize(2); + verify(menuGroupRepository).findAll(); + } +} diff --git a/src/test/java/kitchenpos/menu/application/MenuServiceTest.java b/src/test/java/kitchenpos/menu/application/MenuServiceTest.java new file mode 100644 index 000000000..949d29915 --- /dev/null +++ b/src/test/java/kitchenpos/menu/application/MenuServiceTest.java @@ -0,0 +1,261 @@ +package kitchenpos.menu.application; + +import static kitchenpos.TestFixtureFactory.createMenuWithProductAndGroup; +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import kitchenpos.common.application.PurgomalumClient; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import kitchenpos.menu.application.dto.ChangeMenuPriceServiceRq; +import kitchenpos.menu.application.dto.CreateMenuServiceRq; +import kitchenpos.menu.application.dto.CreateMenuServiceRq.MenuProductServiceRq; +import kitchenpos.menu.application.dto.MenuServiceRs; +import kitchenpos.menu.application.dto.SimpleMenuServiceRs; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuNameCreationService; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.domain.service.MarginValidator; +import kitchenpos.menu.domain.service.MenuProductValidator; +import kitchenpos.menu.infra.persistence.FakeMenuGroupRepository; +import kitchenpos.menu.infra.persistence.FakeMenuRepository; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import kitchenpos.product.infra.persistence.FakeProductRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class MenuServiceTest { + + private MenuService menuService; + private MenuRepository menuRepository; + private MenuGroupRepository menuGroupRepository; + private ProductRepository productRepository; + private PurgomalumClient purgomalumClient; + + @BeforeEach + void setUp() { + menuRepository = new FakeMenuRepository(new HashMap<>()); + menuGroupRepository = new FakeMenuGroupRepository(new HashMap<>()); + productRepository = new FakeProductRepository(new HashMap<>()); + purgomalumClient = new FakePurgomalumClient(); + MarginValidator marginValidator = new MarginValidator(menuRepository); + MenuProductValidator menuProductValidator = new MenuProductValidator(productRepository); + MenuNameCreationService menuNameCreationService = new MenuNameCreationService(purgomalumClient); + menuService = new MenuService(menuRepository, menuGroupRepository, productRepository, marginValidator, + menuProductValidator, menuNameCreationService); + } + + @Test + @DisplayName("메뉴를 생성한다") + void create_menu() { + // given + MenuGroup menuGroup = createMenuGroup(); + menuGroupRepository.save(menuGroup); + Product product = createProduct(BigDecimal.valueOf(5000)); + productRepository.save(product); + CreateMenuServiceRq request = createMenuServiceRequest("김치찌개", 12000, menuGroup.getId(), product.getId()); + + // when + MenuServiceRs result = menuService.create(request); + + // then + assertThat(result.getId()).isNotNull(); + assertThat(result.getName()).isEqualTo("김치찌개"); + assertThat(result.getPrice()).isEqualTo(BigDecimal.valueOf(12000)); + assertThat(result.getMenuGroupId()).isEqualTo(menuGroup.getId()); + assertThat(menuRepository.findAll().size()).isEqualTo(1); + } + + @Test + @DisplayName("메뉴 가격은 0원 미만이면 예외가 발생한다.") + void menu_price_exception() { + // given + MenuGroup menuGroup = createAndSaveMenuGroup(); + Product product = createAndSaveProduct(); + CreateMenuServiceRq request = createMenuServiceRequest("김치찌개", -1000, menuGroup.getId(), + product.getId()); + + // when // then + assertThatThrownBy(() -> { + menuService.create(request); + }).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("메뉴에 이름이 없으면 예외가 발생한다.") + void menu_name_exists_exception(String name) { + // given + MenuGroup menuGroup = createMenuGroup(); + menuGroupRepository.save(menuGroup); + Product product = createProduct(BigDecimal.valueOf(5000)); + productRepository.save(product); + CreateMenuServiceRq request = createMenuServiceRequest(name, 8000, menuGroup.getId(), + product.getId()); + + // when // then + assertThatThrownBy(() -> { + menuService.create(request); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("메뉴 이름에 비속어가 포함되면 예외가 발생한다") + void menu_name_profanity_exception() { + // given + MenuGroup menuGroup = createMenuGroup(); + menuGroupRepository.save(menuGroup); + Product product = createProduct(BigDecimal.valueOf(5000)); + productRepository.save(product); + CreateMenuServiceRq request = createMenuServiceRequest("fuck", 8000, menuGroup.getId(), + product.getId()); + + // 다운 캐스팅해서 강제로 메소드 호출 + FakePurgomalumClient fakePurgomalumClient = (FakePurgomalumClient) purgomalumClient; + fakePurgomalumClient.setProfanity(true); + + // when // then + assertThatThrownBy(() -> menuService.create(request)) + .isInstanceOf(IllegalArgumentException.class); + } + + /** + * 필요 없어진 테스트 + */ +// @Test +// @DisplayName("메뉴 그룹이 존재하지 않으면 예외가 발생한다") +// void menu_group_exception() { +// // given +// Product product = createProduct(BigDecimal.valueOf(5000)); +// MenuProduct menuProduct = new MenuProduct(1, product, product.getId()); +// CreateMenuServiceRq request = createMenuServiceRequest("김치찌개", 8000, UUID.randomUUID(), menuProduct.getProductId()); +// +// // when // then +// assertThatThrownBy(() -> { +// menuService.create(request); +// }).isInstanceOf(NoSuchElementException.class) +// .hasMessage("메뉴 그룹이 존재하지 않습니다!"); +// } + @Test + @DisplayName("메뉴의 판매 가격이 재료 가격의 총합 낮으면 예외가 발생한다.") + void create_menu_with_menuPrice_andTotalPrice_exception() { + // given + MenuGroup menuGroup = createMenuGroup(); + menuGroupRepository.save(menuGroup); + Product product = createProduct(BigDecimal.valueOf(10000)); + productRepository.save(product); + CreateMenuServiceRq request = createMenuServiceRequest("김치찌개", 8000, menuGroup.getId(), product.getId()); + + // when // then + assertThatThrownBy(() -> { + menuService.create(request); + }).isInstanceOf(IllegalStateException.class) + .hasMessage("마진이 남지 않습니다! 마진을 남기게 만들어주세요!"); + } + + @Test + @DisplayName("메뉴의 가격을 변경할 수 있다") + void change_price() { + // given + Menu menu = createMenuWithProductAndGroup(); + menuRepository.save(menu); + + ChangeMenuPriceServiceRq request = new ChangeMenuPriceServiceRq(BigDecimal.valueOf(12000)); + + // when + SimpleMenuServiceRs result = menuService.changePrice(menu.getId(), request); + + // then + assertThat(result.getPrice()).isEqualTo(BigDecimal.valueOf(12000)); + } + + @Test + @DisplayName("변경하려는 가격이 재료 가격의 총합보다 낮으면 예외가 발생한다") + void change_price_with_menuPrice_andTotalPrice_exception() { + // given + Menu menu = createMenuWithProductAndGroup(); + menuRepository.save(menu); + + ChangeMenuPriceServiceRq request = new ChangeMenuPriceServiceRq(BigDecimal.valueOf(4000)); + + // when // then + assertThatThrownBy(() -> { + menuService.changePrice(menu.getId(), request); + }).isInstanceOf(IllegalStateException.class) + .hasMessage("마진이 남지 않습니다! 마진을 남기게 만들어주세요!"); + } + + @Test + @DisplayName("메뉴를 표시 상태로 변경할 수 있다") + void display() { + // given + Menu menu = createMenuWithProductAndGroup(); + menuRepository.save(menu); + + // when + SimpleMenuServiceRs result = menuService.display(menu.getId()); + + // then + assertThat(result.isDisplayed()).isTrue(); + } + + @Test + @DisplayName("메뉴를 숨김 상태로 변경할 수 있다") + void hide() { + // given + Menu menu = createMenuWithProductAndGroup(); + menu.changeDisplay(); + menuRepository.save(menu); + + // when + SimpleMenuServiceRs result = menuService.hide(menu.getId()); + + // then + assertThat(result.isDisplayed()).isFalse(); + } + + + @Test + @DisplayName("전체 메뉴를 조회할 수 있다") + void find_allMenus() { + // given + List menus = List.of( + createMenuWithProductAndGroup(), createMenuWithProductAndGroup()); + for (Menu menu : menus) { + menuRepository.save(menu); + } + + // when + List result = menuService.findAll(); + + // then + assertThat(result).hasSize(2); + } + + private MenuGroup createMenuGroup() { + return new MenuGroup("한식"); + } + + private CreateMenuServiceRq createMenuServiceRequest(String name, int price, UUID menuGroupId, UUID productId) { + return new CreateMenuServiceRq(name, BigDecimal.valueOf(price), true, menuGroupId, + List.of(new MenuProductServiceRq(productId, 2))); + } + + private MenuGroup createAndSaveMenuGroup() { + return menuGroupRepository.save(createMenuGroup()); + } + + private Product createAndSaveProduct() { + return productRepository.save(createProduct("김치", 5000)); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/model/MenuGroupNameCreationServiceTest.java b/src/test/java/kitchenpos/menu/domain/model/MenuGroupNameCreationServiceTest.java new file mode 100644 index 000000000..9e99b9d05 --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/model/MenuGroupNameCreationServiceTest.java @@ -0,0 +1,35 @@ +package kitchenpos.menu.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import kitchenpos.common.application.PurgomalumClient; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MenuGroupNameCreationServiceTest { + + private MenuGroupNameCreationService menuGroupNameCreationService; + private PurgomalumClient purgomalumClient; + + @BeforeEach + void setUp() { + purgomalumClient = new FakePurgomalumClient(); + menuGroupNameCreationService = new MenuGroupNameCreationService(purgomalumClient); + } + + @Test + @DisplayName("이름에 비속어가 있으면 예외를 던집니다.") + void validate_name_exception() { + // given + String name = "비속어"; + FakePurgomalumClient fakePurgomalumClient = (FakePurgomalumClient) purgomalumClient; + fakePurgomalumClient.setProfanity(true); + + // when // then + assertThatThrownBy(() -> menuGroupNameCreationService.createName(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 카테고리 이름에 비속어가 존재합니다. 비속어를 제외해주세요!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/model/MenuGroupNameTest.java b/src/test/java/kitchenpos/menu/domain/model/MenuGroupNameTest.java new file mode 100644 index 000000000..a67cd4a8f --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/model/MenuGroupNameTest.java @@ -0,0 +1,20 @@ +package kitchenpos.menu.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class MenuGroupNameTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("메뉴 카테고리 이름은 비어있거나 null인 경우 예외를 던진다.") + void create_name_exception(String value) { + // when // then + assertThatThrownBy(() -> new MenuGroupName(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 카테고리 이름을 채워주세요!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/model/MenuNameCreationServiceTest.java b/src/test/java/kitchenpos/menu/domain/model/MenuNameCreationServiceTest.java new file mode 100644 index 000000000..908179d13 --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/model/MenuNameCreationServiceTest.java @@ -0,0 +1,35 @@ +package kitchenpos.menu.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import kitchenpos.common.application.PurgomalumClient; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MenuNameCreationServiceTest { + + private MenuNameCreationService menuNameCreationService; + private PurgomalumClient purgomalumClient; + + @BeforeEach + void setUp() { + purgomalumClient = new FakePurgomalumClient(); + menuNameCreationService = new MenuNameCreationService(purgomalumClient); + } + + @Test + @DisplayName("이름에 비속어가 있으면 예외를 던집니다.") + void validate_name_exception() { + // given + String name = "비속어"; + FakePurgomalumClient fakePurgomalumClient = (FakePurgomalumClient) purgomalumClient; + fakePurgomalumClient.setProfanity(true); + + // when // then + assertThatThrownBy(() -> menuNameCreationService.createName(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 이름에 비속어가 존재합니다. 비속어를 제외해주세요!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/model/MenuNameTest.java b/src/test/java/kitchenpos/menu/domain/model/MenuNameTest.java new file mode 100644 index 000000000..9a0e8e68e --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/model/MenuNameTest.java @@ -0,0 +1,20 @@ +package kitchenpos.menu.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class MenuNameTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("메뉴 이름이 비어있거나 null인 경우 예외를 던진다. ") + void create_menu_name_exception(String value) { + // when // then + assertThatThrownBy(() -> new MenuName(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 이름을 채워주세요!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/model/MenuPriceTest.java b/src/test/java/kitchenpos/menu/domain/model/MenuPriceTest.java new file mode 100644 index 000000000..f4c3961f5 --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/model/MenuPriceTest.java @@ -0,0 +1,28 @@ +package kitchenpos.menu.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class MenuPriceTest { + + private static Stream invalidMenuPriceProvider() { + return Stream.of( + null, + BigDecimal.valueOf(-1) + ); + } + + @DisplayName("가격은 비어있거나 null이면 예외가 발생한다") + @ParameterizedTest + @MethodSource("invalidMenuPriceProvider") + void create_price_exception(BigDecimal value) { + assertThatThrownBy(() -> new MenuPrice(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 가격을 채워주세요!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/model/MenuProductRsQuantityTest.java b/src/test/java/kitchenpos/menu/domain/model/MenuProductRsQuantityTest.java new file mode 100644 index 000000000..67760a07e --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/model/MenuProductRsQuantityTest.java @@ -0,0 +1,21 @@ +package kitchenpos.menu.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MenuProductRsQuantityTest { + + @Test + @DisplayName("메뉴 상품의 수량이 1 이상이 아니면 예외를 던진다.") + void create_menu_product_quantity_exception() { + // given + int quantity = 0; + + // when // then + assertThatThrownBy(() -> new MenuProductQuantity(quantity)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 상품의 수량은 0보다 커야 합니다!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/model/MenuTest.java b/src/test/java/kitchenpos/menu/domain/model/MenuTest.java new file mode 100644 index 000000000..d676661d1 --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/model/MenuTest.java @@ -0,0 +1,72 @@ +package kitchenpos.menu.domain.model; + +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import java.util.stream.Stream; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.model.ProductName; +import kitchenpos.product.domain.model.ProductNameCreationService; +import kitchenpos.product.domain.model.ProductPrice; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class MenuTest { + + private static Stream> invalidMenuProductProvider() { + return Stream.of( + null, + List.of() + ); + } + + @Test + @DisplayName("메뉴가 생성될 때, 메뉴 그룹이 포함되지 않으면 예외가 발생한다.") + void validate_menu_group_exists_exception() { + // given + ProductName productName = new ProductNameCreationService(new FakePurgomalumClient()).createName("배추"); + Product product = new Product(productName, new ProductPrice(BigDecimal.ONE)); + MenuProduct menuProduct = new MenuProduct( + product, + new MenuProductQuantity(1), + UUID.randomUUID() + ); + + // when // then + assertThatThrownBy(() -> new Menu( + new MenuName("김치"), + new MenuPrice(BigDecimal.ONE), + null, + false, + List.of(menuProduct), + UUID.randomUUID() + )).isInstanceOf(NoSuchElementException.class) + .hasMessage("메뉴 그룹이 존재하지 않습니다!"); + } + + @ParameterizedTest + @MethodSource("invalidMenuProductProvider") + @DisplayName("메뉴가 생성될 때, 메뉴 상품들이 포함되지 않으면 예외가 발생한다.") + void validate_menu_products_exists_exception(List menuProducts) { + // given + MenuGroup menuGroup = createMenuGroup(); + + // when // then + assertThatThrownBy(() -> new Menu( + new MenuName("김치"), + new MenuPrice(BigDecimal.ONE), + menuGroup, + false, + menuProducts, + menuGroup.getId() + )).isInstanceOf(NoSuchElementException.class) + .hasMessage("메뉴 상품이 존재하지 않습니다!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/repository/MenuQueryRepositoryTest.java b/src/test/java/kitchenpos/menu/domain/repository/MenuQueryRepositoryTest.java new file mode 100644 index 000000000..541f81dd6 --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/repository/MenuQueryRepositoryTest.java @@ -0,0 +1,103 @@ +package kitchenpos.menu.domain.repository; + +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static org.assertj.core.api.Assertions.assertThat; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.infra.persistence.QuerydslMenuQueryRepository; +import kitchenpos.product.domain.model.ProductSummary; +import kitchenpos.product.domain.repository.ProductSummaryRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +@DataJpaTest +@Import(QuerydslMenuQueryRepository.class) +class MenuQueryRepositoryTest { + + @Autowired + private MenuSummaryRepository menuSummaryRepository; + + @Autowired + private ProductSummaryRepository productSummaryRepository; + + @Autowired + private MenuGroupRepository menuGroupRepository; + + @Autowired + private MenuQueryRepository menuQueryRepository; + + @Test + @DisplayName("메뉴 요약 정보를 조회한다.") + void find_all() { + // given + MenuGroup menuGroup1 = createAndSaveMenuGroup(); + MenuGroup menuGroup2 = createAndSaveMenuGroup(); + + List firstProductSummaries = List.of(createAndSaveProductSummary()); + MenuSummary menuSummary1 = new MenuSummary(UUID.randomUUID(), "김치찌개1", BigDecimal.valueOf(8000), true, + menuGroup1.getId(), + menuGroup1.getName(), + firstProductSummaries); + menuSummaryRepository.save(menuSummary1); + + List secondProductSummaries = List.of(createAndSaveProductSummary()); + MenuSummary menuSummary2 = new MenuSummary(UUID.randomUUID(), "김치찌개2", BigDecimal.valueOf(8000), true, + menuGroup2.getId(), + menuGroup2.getName(), + secondProductSummaries); + menuSummaryRepository.save(menuSummary2); + + // when + List result = menuQueryRepository.findAll(); + + // then + assertThat(result) + .isNotNull() + .hasSize(2) + .satisfiesExactlyInAnyOrder( + menu -> { + assertThat(menu.getMenuName()).isEqualTo(menuSummary1.getMenuName()); + assertThat(menu.getPrice()).isEqualTo(menuSummary1.getPrice()); + assertThat(menu.getMenuGroupId()).isEqualTo(menuGroup1.getId()); + assertThat(menu.getProductSummaries()).hasSize(1); + assertThat(menu.getProductSummaries().getFirst().getName()).isEqualTo("김치"); + }, + menu -> { + assertThat(menu.getMenuName()).isEqualTo(menuSummary2.getMenuName()); + assertThat(menu.getPrice()).isEqualTo(menuSummary2.getPrice()); + assertThat(menu.getMenuGroupId()).isEqualTo(menuGroup2.getId()); + assertThat(menu.getProductSummaries()).hasSize(1); + assertThat(menu.getProductSummaries().getFirst().getName()).isEqualTo("김치"); + } + ); + } + + private ProductSummary createAndSaveProductSummary() { + ProductSummary productSummary = new ProductSummary(UUID.randomUUID(), "김치", 3); + productSummaryRepository.save(productSummary); + return productSummary; + } + + private MenuGroup createAndSaveMenuGroup() { + return menuGroupRepository.save(createMenuGroup()); + } + + @TestConfiguration + static class TestConfig { + @Bean + public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) { + return new JPAQueryFactory(entityManager); + } + } +} diff --git a/src/test/java/kitchenpos/menu/domain/service/MarginValidatorTest.java b/src/test/java/kitchenpos/menu/domain/service/MarginValidatorTest.java new file mode 100644 index 000000000..6be47b827 --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/service/MarginValidatorTest.java @@ -0,0 +1,68 @@ +package kitchenpos.menu.domain.service; + +import static kitchenpos.TestFixtureFactory.createMenu; +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.infra.persistence.FakeMenuRepository; +import kitchenpos.product.domain.model.Product; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MarginValidatorTest { + + private MenuRepository menuRepository; + + @BeforeEach + void setUp() { + menuRepository = new FakeMenuRepository(new HashMap<>()); + } + + @Test + @DisplayName("상품이 속한 메뉴들의 마진을 확인하고, 마진이 남지 않으면 게시를 하지 않는다.") + void check_margin_by_product() { + // given + Product product = createProduct(BigDecimal.valueOf(2000)); + Menu soup = createMenu(createMenuGroup(), product, 5); + Menu cake = createMenu(createMenuGroup(), product, 4); + + HashMap storage = new HashMap<>(); + storage.put(UUID.randomUUID(), soup); + storage.put(UUID.randomUUID(), cake); + menuRepository = new FakeMenuRepository(storage); + + // when + MarginValidator marginValidator = new MarginValidator(menuRepository); + marginValidator.checkMargin(product); + + // then + assertThat(soup.isDisplayed()).isEqualTo(false); + assertThat(cake.isDisplayed()).isEqualTo(true); + } + + @Test + @DisplayName("메뉴의 마진을 검증하고, 마진 여부를 알려준다.") + void check_margin_by_menu() { + Product product = createProduct(BigDecimal.valueOf(2000)); + Menu soup = createMenu(createMenuGroup(), product, 5); + + HashMap storage = new HashMap<>(); + storage.put(UUID.randomUUID(), soup); + menuRepository = new FakeMenuRepository(storage); + + // when + MarginValidator marginValidator = new MarginValidator(menuRepository); + boolean result = marginValidator.checkMargin(soup); + + // then + assertThat(soup.isDisplayed()).isEqualTo(false); + assertThat(result).isEqualTo(false); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/service/MenuProductRsValidatorTest.java b/src/test/java/kitchenpos/menu/domain/service/MenuProductRsValidatorTest.java new file mode 100644 index 000000000..d7d9518b1 --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/service/MenuProductRsValidatorTest.java @@ -0,0 +1,51 @@ +package kitchenpos.menu.domain.service; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import kitchenpos.menu.domain.model.MenuProduct; +import kitchenpos.menu.domain.model.MenuProductQuantity; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.model.ProductNameCreationService; +import kitchenpos.product.domain.model.ProductPrice; +import kitchenpos.product.domain.repository.ProductRepository; +import kitchenpos.product.infra.persistence.FakeProductRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MenuProductRsValidatorTest { + + private MenuProductValidator menuProductValidator; + private ProductRepository productRepository; + private ProductNameCreationService productNameCreationService; + + @BeforeEach + void setUp() { + productRepository = new FakeProductRepository(new HashMap<>()); + menuProductValidator = new MenuProductValidator(productRepository); + productNameCreationService = new ProductNameCreationService(new FakePurgomalumClient()); + } + + @Test + @DisplayName("메뉴 상품과 이에 포함된 상품이 존재하는지 확인하고, 없으면 예외를 던진다.") + void validate_menu_product_exception() { + // given + Product lettuce = new Product(productNameCreationService.createName("lettuce"), + new ProductPrice(BigDecimal.ONE)); + Product pepper = new Product(productNameCreationService.createName("pepper"), new ProductPrice(BigDecimal.ONE)); + + productRepository.save(lettuce); + + MenuProduct lettuces = new MenuProduct(lettuce, new MenuProductQuantity(1), lettuce.getId()); + MenuProduct peppers = new MenuProduct(pepper, new MenuProductQuantity(3), pepper.getId()); + + // when // then + assertThatThrownBy(() -> menuProductValidator.validateMenuProduct(List.of(lettuces, peppers))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 상품에 들어갈 상품 수와 실제 상품 수가 다릅니다!"); + } +} diff --git a/src/test/java/kitchenpos/menu/domain/service/MenuQueryModelSynchronizerTest.java b/src/test/java/kitchenpos/menu/domain/service/MenuQueryModelSynchronizerTest.java new file mode 100644 index 000000000..6ba4de7cf --- /dev/null +++ b/src/test/java/kitchenpos/menu/domain/service/MenuQueryModelSynchronizerTest.java @@ -0,0 +1,82 @@ +package kitchenpos.menu.domain.service; + +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.List; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuProduct; +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import kitchenpos.menu.domain.repository.MenuQueryRepository; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.domain.repository.MenuSummaryRepository; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import kitchenpos.product.domain.repository.ProductSummaryRepository; +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; + +@SpringBootTest +class MenuQueryModelSynchronizerTest { + + @Autowired + private MenuSummaryRepository menuSummaryRepository; + + @Autowired + private MenuRepository menuRepository; + + @Autowired + private MenuGroupRepository menuGroupRepository; + + @Autowired + private ProductRepository productRepository; + + @Autowired + private MenuQueryRepository menuQueryRepository; + + @Autowired + private ProductSummaryRepository productSummaryRepository; + + @Test + @DisplayName("이벤트를 받으면 메뉴 쿼리 모델을 업데이트 한다.") + void update_model() throws InterruptedException { + // given + MenuQueryModelSynchronizer menuQueryModelSynchronizer = new MenuQueryModelSynchronizer(menuSummaryRepository, + productSummaryRepository); + + // when + Menu menu = createAndSaveMenu(true); + + Thread.sleep(100); + + // then + List result = menuQueryRepository.findAll(); + + assertThat(result).hasSize(1); + assertThat(result.getFirst().getId()).isEqualTo(menu.getId()); + assertThat(result.getFirst().getMenuName()).isEqualTo("real 김치찌개"); + } + + private MenuGroup createAndSaveMenuGroup() { + return menuGroupRepository.save(createMenuGroup()); + } + + private Product createAndSaveProduct() { + return productRepository.save(createProduct("김치", 5000)); + } + + private Menu createAndSaveMenu(boolean displayed) { + MenuGroup menuGroup = createAndSaveMenuGroup(); + Product product = createAndSaveProduct(); + MenuProduct menuProduct = new MenuProduct(1, product, product.getId()); + Menu menu = new Menu("real 김치찌개", BigDecimal.valueOf(8000), displayed, List.of(menuProduct), menuGroup, + menuGroup.getId()); + return menuRepository.save(menu); + } +} diff --git a/src/test/java/kitchenpos/menu/infra/persistence/FakeMenuGroupRepository.java b/src/test/java/kitchenpos/menu/infra/persistence/FakeMenuGroupRepository.java new file mode 100644 index 000000000..0a3bc55b3 --- /dev/null +++ b/src/test/java/kitchenpos/menu/infra/persistence/FakeMenuGroupRepository.java @@ -0,0 +1,36 @@ +package kitchenpos.menu.infra.persistence; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.repository.MenuGroupRepository; + +public class FakeMenuGroupRepository implements MenuGroupRepository { + + private final Map storage; + + public FakeMenuGroupRepository(Map storage) { + this.storage = storage; + } + + @Override + public MenuGroup save(MenuGroup menuGroup) { + UUID id = UUID.randomUUID(); + menuGroup.setId(id); + storage.put(id, menuGroup); + return menuGroup; + } + + @Override + public List findAll() { + return new ArrayList<>(storage.values()); + } + + @Override + public Optional findById(UUID menuGroupId) { + return Optional.ofNullable(storage.get(menuGroupId)); + } +} diff --git a/src/test/java/kitchenpos/menu/infra/persistence/FakeMenuRepository.java b/src/test/java/kitchenpos/menu/infra/persistence/FakeMenuRepository.java new file mode 100644 index 000000000..56b6c4ace --- /dev/null +++ b/src/test/java/kitchenpos/menu/infra/persistence/FakeMenuRepository.java @@ -0,0 +1,51 @@ +package kitchenpos.menu.infra.persistence; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; + +public class FakeMenuRepository implements MenuRepository { + + private final Map storage; + + public FakeMenuRepository(Map storage) { + this.storage = storage; + } + + @Override + public Menu save(Menu menu) { + UUID id = UUID.randomUUID(); + menu.setId(id); + storage.put(id, menu); + return menu; + } + + @Override + public Optional findById(UUID menuId) { + return Optional.ofNullable(storage.get(menuId)); + } + + @Override + public List findAll() { + return new ArrayList<>(storage.values()); + } + + @Override + public List findAllByIdIn(List ids) { + return storage.values().stream() + .filter(menu -> ids.contains(menu.getId())) + .toList(); + } + + @Override + public List findAllByProductId(UUID productId) { + return storage.values().stream() + .filter(menu -> menu.getMenuProducts().stream() + .anyMatch(menuProduct -> menuProduct.getProductId().equals(productId))) + .toList(); + } +} diff --git a/src/test/java/kitchenpos/menu/ui/MenuGroupRestControllerTest.java b/src/test/java/kitchenpos/menu/ui/MenuGroupRestControllerTest.java new file mode 100644 index 000000000..a17368647 --- /dev/null +++ b/src/test/java/kitchenpos/menu/ui/MenuGroupRestControllerTest.java @@ -0,0 +1,67 @@ +package kitchenpos.menu.ui; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.ui.dto.CreateMenuGroupRq; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@SpringBootTest +@AutoConfigureMockMvc +class MenuGroupRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("메뉴 그룹을 생성한다.") + void create_success() throws Exception { + // given + CreateMenuGroupRq request = new CreateMenuGroupRq("한식"); + + // when + ResultActions result = mockMvc.perform(post("/api/menu-groups") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isCreated()) + .andExpect(header().exists("Location")) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.name").value("한식")); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("메뉴 그룹 이름이 비어있으면 400 상태코드를 반환한다") + void create_fail(String name) { + // given + assertThatThrownBy(() -> { + MenuGroup request = new MenuGroup(name); + // when + ResultActions result = mockMvc.perform(post("/api/menu-groups") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isBadRequest()); + }).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/kitchenpos/menu/ui/MenuRestControllerTest.java b/src/test/java/kitchenpos/menu/ui/MenuRestControllerTest.java new file mode 100644 index 000000000..dc9428844 --- /dev/null +++ b/src/test/java/kitchenpos/menu/ui/MenuRestControllerTest.java @@ -0,0 +1,234 @@ +package kitchenpos.menu.ui; + +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.math.BigDecimal; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.model.MenuProduct; +import kitchenpos.menu.domain.model.MenuSummary; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.domain.repository.MenuSummaryRepository; +import kitchenpos.menu.ui.dto.CreateMenuRq; +import kitchenpos.menu.ui.dto.CreateMenuRq.MenuProductRq; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.model.ProductSummary; +import kitchenpos.product.domain.repository.ProductRepository; +import kitchenpos.product.domain.repository.ProductSummaryRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class MenuRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private MenuRepository menuRepository; + + @Autowired + private MenuGroupRepository menuGroupRepository; + + @Autowired + private ProductRepository productRepository; + + @Autowired + private MenuSummaryRepository menuSummaryRepository; + + @Autowired + private ProductSummaryRepository productSummaryRepository; + + @Test + @DisplayName("메뉴를 생성한다") + void create_menu_success() throws Exception { + // given + MenuGroup menuGroup = createAndSaveMenuGroup(); + Product product = createAndSaveProduct(); + CreateMenuRq request = createMenuRequest("김치찌개", 12000, menuGroup.getId(), product.getId()); + + // when + ResultActions result = mockMvc.perform(post("/api/menus") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isCreated()) + .andExpect(header().exists("Location")) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.name").value("김치찌개")) + .andExpect(jsonPath("$.price").value(12000)) + .andExpect(jsonPath("$.menuGroupId").exists()) + .andExpect(jsonPath("$.menuProductRsList").isNotEmpty()); + } + + @Test + @DisplayName("메뉴 상품 목록이 비어있으면 400 상태코드를 반환한다") + void create_menuRequest_with_emptyProducts() throws Exception { + // given + MenuGroup menuGroup = createAndSaveMenuGroup(); + assertThatThrownBy(() -> { + Menu request = new Menu("김치찌개", BigDecimal.valueOf(8000), true, null, menuGroup, + menuGroup.getId()); + + // when + ResultActions result = mockMvc.perform(post("/api/menus") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isBadRequest()); + }).isInstanceOf(NoSuchElementException.class) + .hasMessage("메뉴 상품이 존재하지 않습니다!"); + } + + @Test + @DisplayName("메뉴의 가격을 변경한다") + void change_menuPrice() throws Exception { + // given + Menu menu = createAndSaveMenu(true); + menu.changePrice(BigDecimal.valueOf(8001)); + + // when + ResultActions result = mockMvc.perform(put("/api/menus/{menuId}/price", menu.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(menu))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(menu.getId().toString())) + .andExpect(jsonPath("$.price").value(8001)); + } + + @Test + @DisplayName("변경하려는 메뉴 가격이 음수이면 400 상태코드를 반환한다") + void change_menuPrice_with_negativePrice() throws Exception { + // given + Menu savedMenu = createAndSaveMenu(true); + assertThatThrownBy(() -> { + Menu request = new Menu(); + request.changePrice(BigDecimal.valueOf(-1)); + + // when + ResultActions result = mockMvc.perform(put("/api/menus/{menuId}/price", savedMenu.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isBadRequest()); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("메뉴 가격을 채워주세요!"); + } + + @Test + @DisplayName("메뉴를 표시 상태로 변경한다") + void display_menu() throws Exception { + // given + Menu savedMenu = createAndSaveMenu(false); + + // when + ResultActions result = mockMvc.perform(put("/api/menus/{menuId}/display", savedMenu.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(savedMenu.getId().toString())) + .andExpect(jsonPath("$.displayed").value(true)); + } + + @Test + @DisplayName("메뉴를 숨김 상태로 변경한다") + void hide_menu() throws Exception { + // given + Menu savedMenu = createAndSaveMenu(true); + + // when + ResultActions result = mockMvc.perform(put("/api/menus/{menuId}/hide", savedMenu.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(savedMenu.getId().toString())) + .andExpect(jsonPath("$.displayed").value(false)); + } + + @Test + @DisplayName("모든 메뉴를 조회한다.") + void find_allMenus() throws Exception { + // given + MenuGroup menuGroup1 = createAndSaveMenuGroup(); + MenuGroup menuGroup2 = createAndSaveMenuGroup(); + createAndSaveMenuSummary(menuGroup1); + createAndSaveMenuSummary(menuGroup2); + + // when + ResultActions result = mockMvc.perform(get("/api/menus")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)); + } + + private MenuGroup createAndSaveMenuGroup() { + return menuGroupRepository.save(createMenuGroup()); + } + + private Product createAndSaveProduct() { + return productRepository.save(createProduct("김치", 5000)); + } + + private Menu createAndSaveMenu(boolean displayed) { + MenuGroup menuGroup = createAndSaveMenuGroup(); + Product product = createAndSaveProduct(); + MenuProduct menuProduct = new MenuProduct(1, product, product.getId()); + Menu menu = new Menu("김치찌개", BigDecimal.valueOf(8000), displayed, List.of(menuProduct), menuGroup, + menuGroup.getId()); + return menuRepository.save(menu); + } + + private MenuSummary createAndSaveMenuSummary(MenuGroup menuGroup) { + ProductSummary productSummary = createAndSaveProductSummary(); + MenuSummary menuSummary = new MenuSummary(UUID.randomUUID(), "김치찌개", BigDecimal.valueOf(8000), true, + menuGroup.getId(), + menuGroup.getName(), + List.of(productSummary)); + menuSummaryRepository.save(menuSummary); + return menuSummary; + } + + private ProductSummary createAndSaveProductSummary() { + ProductSummary productSummary = new ProductSummary(UUID.randomUUID(), "김치", 3); + productSummaryRepository.save(productSummary); + return productSummary; + } + + private CreateMenuRq createMenuRequest(String name, int price, UUID menuGroupId, UUID productId) { + return new CreateMenuRq(name, BigDecimal.valueOf(price), true, menuGroupId, + List.of(new MenuProductRq(productId, 2))); + } +} diff --git a/src/test/java/kitchenpos/menus/application/InMemoryMenuGroupRepository.java b/src/test/java/kitchenpos/menus/application/InMemoryMenuGroupRepository.java deleted file mode 100644 index 85962e6dc..000000000 --- a/src/test/java/kitchenpos/menus/application/InMemoryMenuGroupRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package kitchenpos.menus.application; - -import kitchenpos.menus.domain.MenuGroup; -import kitchenpos.menus.domain.MenuGroupRepository; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -public class InMemoryMenuGroupRepository implements MenuGroupRepository { - private final Map menuGroups = new HashMap<>(); - - @Override - public MenuGroup save(final MenuGroup menuGroup) { - menuGroups.put(menuGroup.getId(), menuGroup); - return menuGroup; - } - - @Override - public Optional findById(final UUID id) { - return Optional.ofNullable(menuGroups.get(id)); - } - - @Override - public List findAll() { - return new ArrayList<>(menuGroups.values()); - } -} diff --git a/src/test/java/kitchenpos/menus/application/InMemoryMenuRepository.java b/src/test/java/kitchenpos/menus/application/InMemoryMenuRepository.java deleted file mode 100644 index ca30b679c..000000000 --- a/src/test/java/kitchenpos/menus/application/InMemoryMenuRepository.java +++ /dev/null @@ -1,47 +0,0 @@ -package kitchenpos.menus.application; - -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuRepository; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -public class InMemoryMenuRepository implements MenuRepository { - private final Map menus = new HashMap<>(); - - @Override - public Menu save(final Menu menu) { - menus.put(menu.getId(), menu); - return menu; - } - - @Override - public Optional findById(final UUID id) { - return Optional.ofNullable(menus.get(id)); - } - - @Override - public List findAll() { - return new ArrayList<>(menus.values()); - } - - @Override - public List findAllByIdIn(final List ids) { - return menus.values() - .stream() - .filter(menu -> ids.contains(menu.getId())) - .toList(); - } - - @Override - public List findAllByProductId(final UUID productId) { - return menus.values() - .stream() - .filter(menu -> menu.getMenuProducts().stream().anyMatch(menuProduct -> menuProduct.getProduct().getId().equals(productId))) - .toList(); - } -} diff --git a/src/test/java/kitchenpos/menus/application/MenuGroupServiceTest.java b/src/test/java/kitchenpos/menus/application/MenuGroupServiceTest.java deleted file mode 100644 index a5fbc71d1..000000000 --- a/src/test/java/kitchenpos/menus/application/MenuGroupServiceTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package kitchenpos.menus.application; - -import kitchenpos.menus.domain.MenuGroup; -import kitchenpos.menus.domain.MenuGroupRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; - -import java.util.List; - -import static kitchenpos.Fixtures.menuGroup; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - -class MenuGroupServiceTest { - private MenuGroupRepository menuGroupRepository; - private MenuGroupService menuGroupService; - - @BeforeEach - void setUp() { - menuGroupRepository = new InMemoryMenuGroupRepository(); - menuGroupService = new MenuGroupService(menuGroupRepository); - } - - @DisplayName("메뉴 그룹을 등록할 수 있다.") - @Test - void create() { - final MenuGroup expected = createMenuGroupRequest("두마리메뉴"); - final MenuGroup actual = menuGroupService.create(expected); - assertThat(actual).isNotNull(); - assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getName()).isEqualTo(expected.getName()) - ); - } - - @DisplayName("메뉴 그룹의 이름이 올바르지 않으면 등록할 수 없다.") - @NullAndEmptySource - @ParameterizedTest - void create(final String name) { - final MenuGroup expected = createMenuGroupRequest(name); - assertThatThrownBy(() -> menuGroupService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴 그룹의 목록을 조회할 수 있다.") - @Test - void findAll() { - menuGroupRepository.save(menuGroup("두마리메뉴")); - final List actual = menuGroupService.findAll(); - assertThat(actual).hasSize(1); - } - - private MenuGroup createMenuGroupRequest(final String name) { - final MenuGroup menuGroup = new MenuGroup(); - menuGroup.setName(name); - return menuGroup; - } -} diff --git a/src/test/java/kitchenpos/menus/application/MenuServiceTest.java b/src/test/java/kitchenpos/menus/application/MenuServiceTest.java deleted file mode 100644 index 277679118..000000000 --- a/src/test/java/kitchenpos/menus/application/MenuServiceTest.java +++ /dev/null @@ -1,270 +0,0 @@ -package kitchenpos.menus.application; - -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuGroupRepository; -import kitchenpos.menus.domain.MenuProduct; -import kitchenpos.menus.domain.MenuRepository; -import kitchenpos.products.application.FakePurgomalumClient; -import kitchenpos.products.application.InMemoryProductRepository; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; -import kitchenpos.products.infra.PurgomalumClient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.UUID; - -import static kitchenpos.Fixtures.INVALID_ID; -import static kitchenpos.Fixtures.menu; -import static kitchenpos.Fixtures.menuGroup; -import static kitchenpos.Fixtures.menuProduct; -import static kitchenpos.Fixtures.product; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - -class MenuServiceTest { - private MenuRepository menuRepository; - private MenuGroupRepository menuGroupRepository; - private ProductRepository productRepository; - private PurgomalumClient purgomalumClient; - private MenuService menuService; - private UUID menuGroupId; - private Product product; - - @BeforeEach - void setUp() { - menuRepository = new InMemoryMenuRepository(); - menuGroupRepository = new InMemoryMenuGroupRepository(); - productRepository = new InMemoryProductRepository(); - purgomalumClient = new FakePurgomalumClient(); - menuService = new MenuService(menuRepository, menuGroupRepository, productRepository, purgomalumClient); - menuGroupId = menuGroupRepository.save(menuGroup()).getId(); - product = productRepository.save(product("후라이드", 16_000L)); - } - - @DisplayName("1개 이상의 등록된 상품으로 메뉴를 등록할 수 있다.") - @Test - void create() { - final Menu expected = createMenuRequest( - "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) - ); - final Menu actual = menuService.create(expected); - assertThat(actual).isNotNull(); - assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getName()).isEqualTo(expected.getName()), - () -> assertThat(actual.getPrice()).isEqualTo(expected.getPrice()), - () -> assertThat(actual.getMenuGroup().getId()).isEqualTo(expected.getMenuGroupId()), - () -> assertThat(actual.isDisplayed()).isEqualTo(expected.isDisplayed()), - () -> assertThat(actual.getMenuProducts()).hasSize(1) - ); - } - - @DisplayName("상품이 없으면 등록할 수 없다.") - @MethodSource("menuProducts") - @ParameterizedTest - void create(final List menuProducts) { - final Menu expected = createMenuRequest("후라이드+후라이드", 19_000L, menuGroupId, true, menuProducts); - assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - private static List menuProducts() { - return Arrays.asList( - null, - Arguments.of(Collections.emptyList()), - Arguments.of(Arrays.asList(createMenuProductRequest(INVALID_ID, 2L))) - ); - } - - @DisplayName("메뉴에 속한 상품의 수량은 0개 이상이어야 한다.") - @Test - void createNegativeQuantity() { - final Menu expected = createMenuRequest( - "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), -1L) - ); - assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴의 가격이 올바르지 않으면 등록할 수 없다.") - @ValueSource(strings = "-1000") - @NullSource - @ParameterizedTest - void create(final BigDecimal price) { - final Menu expected = createMenuRequest( - "후라이드+후라이드", price, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) - ); - assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴에 속한 상품 금액의 합은 메뉴의 가격보다 크거나 같아야 한다.") - @Test - void createExpensiveMenu() { - final Menu expected = createMenuRequest( - "후라이드+후라이드", 33_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) - ); - assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴는 특정 메뉴 그룹에 속해야 한다.") - @NullSource - @ParameterizedTest - void create(final UUID menuGroupId) { - final Menu expected = createMenuRequest( - "후라이드+후라이드", 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) - ); - assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(NoSuchElementException.class); - } - - @DisplayName("메뉴의 이름이 올바르지 않으면 등록할 수 없다.") - @ValueSource(strings = {"비속어", "욕설이 포함된 이름"}) - @NullSource - @ParameterizedTest - void create(final String name) { - final Menu expected = createMenuRequest( - name, 19_000L, menuGroupId, true, createMenuProductRequest(product.getId(), 2L) - ); - assertThatThrownBy(() -> menuService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴의 가격을 변경할 수 있다.") - @Test - void changePrice() { - final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(product, 2L))).getId(); - final Menu expected = changePriceRequest(16_000L); - final Menu actual = menuService.changePrice(menuId, expected); - assertThat(actual.getPrice()).isEqualTo(expected.getPrice()); - } - - @DisplayName("메뉴의 가격이 올바르지 않으면 변경할 수 없다.") - @ValueSource(strings = "-1000") - @NullSource - @ParameterizedTest - void changePrice(final BigDecimal price) { - final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(product, 2L))).getId(); - final Menu expected = changePriceRequest(price); - assertThatThrownBy(() -> menuService.changePrice(menuId, expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴에 속한 상품 금액의 합은 메뉴의 가격보다 크거나 같아야 한다.") - @Test - void changePriceToExpensive() { - final UUID menuId = menuRepository.save(menu(19_000L, menuProduct(product, 2L))).getId(); - final Menu expected = changePriceRequest(33_000L); - assertThatThrownBy(() -> menuService.changePrice(menuId, expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("메뉴를 노출할 수 있다.") - @Test - void display() { - final UUID menuId = menuRepository.save(menu(19_000L, false, menuProduct(product, 2L))).getId(); - final Menu actual = menuService.display(menuId); - assertThat(actual.isDisplayed()).isTrue(); - } - - @DisplayName("메뉴의 가격이 메뉴에 속한 상품 금액의 합보다 높을 경우 메뉴를 노출할 수 없다.") - @Test - void displayExpensiveMenu() { - final UUID menuId = menuRepository.save(menu(33_000L, false, menuProduct(product, 2L))).getId(); - assertThatThrownBy(() -> menuService.display(menuId)) - .isInstanceOf(IllegalStateException.class); - } - - @DisplayName("메뉴를 숨길 수 있다.") - @Test - void hide() { - final UUID menuId = menuRepository.save(menu(19_000L, true, menuProduct(product, 2L))).getId(); - final Menu actual = menuService.hide(menuId); - assertThat(actual.isDisplayed()).isFalse(); - } - - @DisplayName("메뉴의 목록을 조회할 수 있다.") - @Test - void findAll() { - menuRepository.save(menu(19_000L, true, menuProduct(product, 2L))); - final List actual = menuService.findAll(); - assertThat(actual).hasSize(1); - } - - private Menu createMenuRequest( - final String name, - final long price, - final UUID menuGroupId, - final boolean displayed, - final MenuProduct... menuProducts - ) { - return createMenuRequest(name, BigDecimal.valueOf(price), menuGroupId, displayed, menuProducts); - } - - private Menu createMenuRequest( - final String name, - final BigDecimal price, - final UUID menuGroupId, - final boolean displayed, - final MenuProduct... menuProducts - ) { - return createMenuRequest(name, price, menuGroupId, displayed, Arrays.asList(menuProducts)); - } - - private Menu createMenuRequest( - final String name, - final long price, - final UUID menuGroupId, - final boolean displayed, - final List menuProducts - ) { - return createMenuRequest(name, BigDecimal.valueOf(price), menuGroupId, displayed, menuProducts); - } - - private Menu createMenuRequest( - final String name, - final BigDecimal price, - final UUID menuGroupId, - final boolean displayed, - final List menuProducts - ) { - final Menu menu = new Menu(); - menu.setName(name); - menu.setPrice(price); - menu.setMenuGroupId(menuGroupId); - menu.setDisplayed(displayed); - menu.setMenuProducts(menuProducts); - return menu; - } - - private static MenuProduct createMenuProductRequest(final UUID productId, final long quantity) { - final MenuProduct menuProduct = new MenuProduct(); - menuProduct.setProductId(productId); - menuProduct.setQuantity(quantity); - return menuProduct; - } - - private Menu changePriceRequest(final long price) { - return changePriceRequest(BigDecimal.valueOf(price)); - } - - private Menu changePriceRequest(final BigDecimal price) { - final Menu menu = new Menu(); - menu.setPrice(price); - return menu; - } -} diff --git a/src/test/java/kitchenpos/order/common/application/OrderServiceTest.java b/src/test/java/kitchenpos/order/common/application/OrderServiceTest.java new file mode 100644 index 000000000..1bd1ef60f --- /dev/null +++ b/src/test/java/kitchenpos/order/common/application/OrderServiceTest.java @@ -0,0 +1,243 @@ +package kitchenpos.order.common.application; + +import static kitchenpos.TestFixtureFactory.createEmptyOrderTable; +import static kitchenpos.TestFixtureFactory.createMenuWithProductAndGroup; +import static kitchenpos.TestFixtureFactory.createOrder; +import static kitchenpos.TestFixtureFactory.createOrderLineItem; +import static kitchenpos.TestFixtureFactory.createOrderWithDeliveryType; +import static kitchenpos.TestFixtureFactory.createOrderWithTakeOutType; +import static kitchenpos.TestFixtureFactory.createUsingOrderTable; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.order.common.model.Order; +import kitchenpos.order.common.model.OrderStatus; +import kitchenpos.order.common.model.OrderType; +import kitchenpos.order.common.repository.OrderRepository; +import kitchenpos.order.deliveryorder.infra.external.KitchenridersClient; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class OrderServiceTest { + + private OrderService orderService; + private OrderRepository orderRepository; + private MenuRepository menuRepository; + private OrderTableRepository orderTableRepository; + private KitchenridersClient kitchenridersClient; + + @BeforeEach + void setUp() { + orderRepository = mock(OrderRepository.class); + menuRepository = mock(MenuRepository.class); + orderTableRepository = mock(OrderTableRepository.class); + kitchenridersClient = mock(KitchenridersClient.class); + orderService = new OrderService(orderRepository, menuRepository, orderTableRepository, kitchenridersClient); + } + + private Order createOrderRequestWithOccupiedTable(OrderType type, OrderStatus orderStatus, Menu menu, + String address) { + OrderTable orderTable = createUsingOrderTable(); + return createOrder(createOrderLineItem(menu), orderTable, type, + orderStatus, address); + } + + private Order createOrderRequestWithEmptyTable(OrderType type, OrderStatus orderStatus, Menu menu, + String address) { + OrderTable orderTable = createEmptyOrderTable(); + return createOrder(createOrderLineItem(menu), orderTable, type, + orderStatus, address); + } + + @Nested + @DisplayName("공통 주문 관련") + class CommonOrder { + + @Test + @DisplayName("주문은 하나 이상의 주문 내역으로 생성할 수 있다") + void create_order() { + // given + Menu menu = createMenuWithProductAndGroup(); + Order request = createOrderRequestWithEmptyTable(OrderType.DELIVERY, OrderStatus.WAITING, menu, "서울"); + when(menuRepository.findAllByIdIn(anyList())).thenReturn(List.of(menu)); + when(menuRepository.findById(any())).thenReturn(Optional.of(menu)); + when(orderRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + + // when + Order result = orderService.create(request); + + // then + assertThat(result.getOrderLineItems()).hasSize(1); + } + + @Test + @DisplayName("게시되지 않은 메뉴 주문 시 예외가 발생한다.") + void menu_name_exception() { + // given + Menu menu = createMenuWithProductAndGroup(false); + when(menuRepository.findAllByIdIn(anyList())).thenReturn(List.of(menu)); + when(menuRepository.findById(any())).thenReturn(Optional.of(menu)); + + // when // then + assertThatThrownBy(() -> { + Order request = createOrderRequestWithEmptyTable(OrderType.DELIVERY, OrderStatus.WAITING, menu, "서울"); + orderService.create(request); + }).isInstanceOf(IllegalStateException.class) + .hasMessage("주문 내역의 메뉴가 게시되어 있지 않습니다!"); + } + } + + @Nested + @DisplayName("배달 주문") + class DeliveryOrderTest { + + @Test + @DisplayName("배달 주문 시 주소가 없으면 예외가 발생한다.") + void delivery_address_exception() { + // given + Menu menu = createMenuWithProductAndGroup(); + Order request = createOrderRequestWithEmptyTable(OrderType.DELIVERY, OrderStatus.SERVED, menu, null); + when(menuRepository.findAllByIdIn(anyList())).thenReturn(List.of(menu)); + when(menuRepository.findById(any(UUID.class))).thenReturn(Optional.of(menu)); + + // when // then + assertThatThrownBy(() -> orderService.create(request)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("배달 주문 접수 시 배달 요청을 한다") + void delivery_success() { + // given + OrderTable orderTable = createUsingOrderTable(); + Order order = createOrder(createOrderLineItem(createMenuWithProductAndGroup()), + orderTable, OrderType.DELIVERY, + OrderStatus.WAITING, "서울"); + when(orderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when + orderService.accept(order.getId()); + + // then + verify(kitchenridersClient).requestDelivery(any(), any(), any()); + } + + @ParameterizedTest + @EnumSource(value = OrderStatus.class, names = {"ACCEPTED", "SERVED", "DELIVERING", "DELIVERED", "COMPLETED"}) + @DisplayName("대기 중이 아닌 주문 시 예외가 발생한다.") + void accept_exception(OrderStatus status) { + // given + OrderTable orderTable = createUsingOrderTable(); + Order order = createOrderWithDeliveryType(createOrderLineItem(createMenuWithProductAndGroup()), + orderTable, status); + when(orderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when // then + assertThatThrownBy(() -> orderService.accept(order.getId())) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("대기 중인 주문만 접수할 수 있다") + void accept_success() { + // given + OrderTable orderTable = createUsingOrderTable(); + Order order = createOrderWithDeliveryType(createOrderLineItem(createMenuWithProductAndGroup()), + orderTable, OrderStatus.WAITING); + when(orderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when + Order result = orderService.accept(order.getId()); + + // then + assertThat(result.getStatus()).isEqualTo(OrderStatus.ACCEPTED); + } + + @Test + @DisplayName("배달 주문은 배달 상태와 배달 완료일 때 주문을 완료할 수 있다") + void complete_order() { + // given + OrderTable orderTable = createUsingOrderTable(); + Order order = createOrderWithDeliveryType(createOrderLineItem(createMenuWithProductAndGroup()), + orderTable, OrderStatus.DELIVERED); + when(orderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when + Order result = orderService.complete(order.getId()); + + // then + assertThat(result.getStatus()).isEqualTo(OrderStatus.COMPLETED); + } + } + + @Nested + @DisplayName("포장 주문") + class TakeOutOrderTest { + + @ParameterizedTest + @EnumSource(value = OrderStatus.class, names = {"ACCEPTED", "SERVED", "DELIVERING", "DELIVERED", "COMPLETED"}) + @DisplayName("대기 중이 아닌 주문 시 예외가 발생한다.") + void accept_exception(OrderStatus status) { + // given + OrderTable orderTable = createUsingOrderTable(); + Order order = createOrderWithTakeOutType(createOrderLineItem(createMenuWithProductAndGroup()), + orderTable, status); + when(orderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when // then + assertThatThrownBy(() -> orderService.accept(order.getId())) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("대기 중인 주문만 접수할 수 있다") + void accept_success() { + // given + OrderTable orderTable = createUsingOrderTable(); + Order order = createOrderWithTakeOutType(createOrderLineItem(createMenuWithProductAndGroup()), + orderTable, OrderStatus.WAITING); + when(orderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when + Order result = orderService.accept(order.getId()); + + // then + assertThat(result.getStatus()).isEqualTo(OrderStatus.ACCEPTED); + } + + @Test + @DisplayName("포장 주문은 서빙이 완료 되면 주문을 완료할 수 있다") + void complete_order() { + // given + OrderTable orderTable = createUsingOrderTable(); + Order order = createOrderWithTakeOutType(createOrderLineItem(createMenuWithProductAndGroup()), + orderTable, OrderStatus.SERVED); + when(orderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when + Order result = orderService.complete(order.getId()); + + // then + assertThat(result.getStatus()).isEqualTo(OrderStatus.COMPLETED); + } + + // 어떤 경우 든 통과함! + // 포장 주문 기능 추가 시 여기에 추가 + } +} diff --git a/src/test/java/kitchenpos/order/common/model/OrderLineItemTest.java b/src/test/java/kitchenpos/order/common/model/OrderLineItemTest.java new file mode 100644 index 000000000..7046c1cb0 --- /dev/null +++ b/src/test/java/kitchenpos/order/common/model/OrderLineItemTest.java @@ -0,0 +1,47 @@ +package kitchenpos.order.common.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import kitchenpos.menu.domain.model.Menu; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OrderLineItemTest { + + @Test + @DisplayName("주문 내역의 메뉴 수량은 0 이하면 예외를 던진다.") + void validate_order_line_item_quantity() { + // given + Menu menu = new Menu("김치", BigDecimal.ONE, true); + + // when // then + assertThatThrownBy(() -> new OrderLineItem(menu, 0, menu.getId(), BigDecimal.ONE)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("주문 내역의 메뉴 수량이 비어있습니다!"); + } + + @Test + @DisplayName("주문 내역의 메뉴가 게시 상태가 아니면 예외를 던진다.") + void validate_order_line_item_menu_display() { + // given + Menu menu = new Menu("김치", BigDecimal.ONE, false); + + // when // then + assertThatThrownBy(() -> new OrderLineItem(menu, 1, menu.getId(), BigDecimal.ONE)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("주문 내역의 메뉴가 게시되어 있지 않습니다!"); + } + + @Test + @DisplayName("주문 내역의 가격과 메뉴의 가격이 다르면 예외를 던진다.") + void validate_order_line_item_price() { + // given + Menu menu = new Menu("김치", BigDecimal.TEN, true); + + // when // then + assertThatThrownBy(() -> new OrderLineItem(menu, 1, menu.getId(), BigDecimal.ONE)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("주문 내역의 가격이 메뉴의 가격과 다릅니다!"); + } +} diff --git a/src/test/java/kitchenpos/order/common/model/OrderLineItemValidatorTest.java b/src/test/java/kitchenpos/order/common/model/OrderLineItemValidatorTest.java new file mode 100644 index 000000000..c4c425a82 --- /dev/null +++ b/src/test/java/kitchenpos/order/common/model/OrderLineItemValidatorTest.java @@ -0,0 +1,60 @@ +package kitchenpos.order.common.model; + +import static kitchenpos.TestFixtureFactory.createMenu; +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createProduct; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.infra.persistence.FakeMenuRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OrderLineItemValidatorTest { + + @Test + @DisplayName("주문 내역의 메뉴의 id 정보가 올바르지 않으면 예외를 던진다.") + void validate_another_id_exception() { + // given + MenuRepository menuRepository = new FakeMenuRepository(new HashMap<>()); + OrderLineItemValidator orderLineItemValidator = new OrderLineItemValidator(menuRepository); + + Menu firstMenu = createMenu(createMenuGroup(), createProduct(BigDecimal.valueOf(2000))); + Menu secondMenu = createMenu(createMenuGroup(), createProduct(BigDecimal.valueOf(3000))); + menuRepository.save(firstMenu); + menuRepository.save(secondMenu); + + OrderLineItem orderLineItem1 = new OrderLineItem(secondMenu, 2, secondMenu.getId(), BigDecimal.valueOf(8000)); + OrderLineItem orderLineItem2 = new OrderLineItem(secondMenu, 3, secondMenu.getId(), BigDecimal.valueOf(8000)); + + // when // then + Assertions.assertThatThrownBy(() -> orderLineItemValidator.validate(List.of(orderLineItem1, orderLineItem2))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("주문 내역의 메뉴 정보가 올바르지 않습니다."); + } + + @Test + @DisplayName("주문 내역의 중복 메뉴 존재 시 예외를 던진다.") + void validate_same_menu_exception() { + // given + MenuRepository menuRepository = new FakeMenuRepository(new HashMap<>()); + OrderLineItemValidator orderLineItemValidator = new OrderLineItemValidator(menuRepository); + + Menu firstMenu = createMenu(createMenuGroup(), createProduct(BigDecimal.valueOf(2000))); + Menu secondMenu = createMenu(createMenuGroup(), createProduct(BigDecimal.valueOf(3000))); + menuRepository.save(firstMenu); + menuRepository.save(secondMenu); + + OrderLineItem orderLineItem1 = new OrderLineItem(firstMenu, 2, secondMenu.getId(), BigDecimal.valueOf(8000)); + OrderLineItem orderLineItem2 = new OrderLineItem(secondMenu, 3, secondMenu.getId(), BigDecimal.valueOf(8000)); + + // when // then + Assertions.assertThatThrownBy(() -> orderLineItemValidator.validate(List.of(orderLineItem1, orderLineItem2))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("주문 내역의 메뉴 정보가 올바르지 않습니다."); + } +} diff --git a/src/test/java/kitchenpos/order/common/ui/OrderRestControllerTest.java b/src/test/java/kitchenpos/order/common/ui/OrderRestControllerTest.java new file mode 100644 index 000000000..00ff6ec7f --- /dev/null +++ b/src/test/java/kitchenpos/order/common/ui/OrderRestControllerTest.java @@ -0,0 +1,220 @@ +package kitchenpos.order.common.ui; + +import static kitchenpos.TestFixtureFactory.createEmptyOrderTable; +import static kitchenpos.TestFixtureFactory.createMenu; +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createOrderLineItem; +import static kitchenpos.TestFixtureFactory.createOrderWithDeliveryType; +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.order.common.model.Order; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.common.model.OrderStatus; +import kitchenpos.order.common.model.OrderType; +import kitchenpos.order.common.repository.OrderRepository; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class OrderRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private OrderRepository orderRepository; + @Autowired + private OrderTableRepository orderTableRepository; + @Autowired + private MenuRepository menuRepository; + @Autowired + private MenuGroupRepository menuGroupRepository; + @Autowired + private ProductRepository productRepository; + + /** + * 필요 없어진 테스트 + * + * @throws Exception + */ +// @Test +// @DisplayName("주문을 생성한다.") +// void create_success() throws Exception { +// // given +// Order request = createOrderRequestWithDeliveryType(); +// +// // when +// ResultActions result = mockMvc.perform(post("/api/orders") +// .contentType(MediaType.APPLICATION_JSON) +// .content(objectMapper.writeValueAsString(request))); +// +// // then +// result.andExpect(status().isCreated()) +// .andExpect(header().exists("Location")) +// .andExpect(jsonPath("$.id").exists()) +// .andExpect(jsonPath("$.orderLineItems").isArray()) +// .andExpect(jsonPath("$.status").value("WAITING")); +// } + @Test + @DisplayName("주문 상태가 주문 대기 중이라면 주문을 수락할 수 있다.") + void accept_success() throws Exception { + // given + Order savedOrder = createAndSaveOrderWithDeliveryType(); + + // when + ResultActions result = mockMvc.perform(put("/api/orders/{orderId}/accept", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.status").value("ACCEPTED")); + } + + @Test + @DisplayName("주문 상태가 접수 완료라면 서빙할 수 있다.") + void serve_success() throws Exception { + // given + Order savedOrder = createAndSaveOrderWithDeliveryType(); + savedOrder.setStatus(OrderStatus.ACCEPTED); + + // when + ResultActions result = mockMvc.perform(put("/api/orders/{orderId}/serve", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.status").value("SERVED")); + } + + @Test + @DisplayName("주문 상태가 서빙 완료라면 배달을 시작할 수 있다.") + void startDelivery_success() throws Exception { + // given + Order savedOrder = createAndSaveOrderWithDeliveryType(); + savedOrder.setStatus(OrderStatus.SERVED); + + // when + ResultActions result = mockMvc.perform(put("/api/orders/{orderId}/start-delivery", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.status").value("DELIVERING")); + } + + @Test + @DisplayName("주문 상태가 배달 중이라면 배달을 완료할 수 있다.") + void completeDelivery_success() throws Exception { + // given + Order savedOrder = createAndSaveOrderWithDeliveryType(); + savedOrder.setStatus(OrderStatus.DELIVERING); + + // when + ResultActions result = mockMvc.perform(put("/api/orders/{orderId}/complete-delivery", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.status").value("DELIVERED")); + } + + @ParameterizedTest + @CsvSource(value = { + "DELIVERY, DELIVERED, COMPLETED", + "TAKEOUT, SERVED, COMPLETED", + "EAT_IN, SERVED, COMPLETED", + }) + @DisplayName("주문 종류와 상태에 따라 주문을 완료할 수 있다.") + void complete_success(OrderType orderType, OrderStatus orderStatus, OrderStatus expected) throws Exception { + // given + Order savedOrder = createAndSaveOrderWithDeliveryType(); + savedOrder.setType(orderType); + savedOrder.setStatus(orderStatus); + + // when + ResultActions result = mockMvc.perform(put("/api/orders/{orderId}/complete", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.status").value(expected.toString())); + } + + @Test + @DisplayName("전체 주문을 조회한다.") + void findAll_success() throws Exception { + // given + createAndSaveOrderWithDeliveryType(); + createAndSaveOrderWithDeliveryType(); + + // when + ResultActions result = mockMvc.perform(get("/api/orders")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)); + } + + private Order createAndSaveOrderWithDeliveryType() { + MenuGroup menuGroup = createAndSaveMenuGroup(); + Product product = createAndSaveProduct(); + Menu menu = createAndSaveMenu(menuGroup, product); + + OrderLineItem orderLineItem = createOrderLineItem(menu); + OrderTable orderTable = createAndSaveOrderTable(); + + Order order = createOrderWithDeliveryType(orderLineItem, orderTable, OrderStatus.WAITING); + return orderRepository.save(order); + } + + private OrderTable createAndSaveOrderTable() { + OrderTable orderTable = createEmptyOrderTable(); + orderTableRepository.save(orderTable); + return orderTable; + } + + private Menu createAndSaveMenu(MenuGroup menuGroup, Product product) { + Menu menu = createMenu(menuGroup, product); + menuRepository.save(menu); + return menu; + } + + private Product createAndSaveProduct() { + Product product = createProduct("김치", 4000); + productRepository.save(product); + return product; + } + + private MenuGroup createAndSaveMenuGroup() { + MenuGroup menuGroup = createMenuGroup(); + menuGroupRepository.save(menuGroup); + return menuGroup; + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/application/OrderTableServiceTest.java b/src/test/java/kitchenpos/order/eatinorder/application/OrderTableServiceTest.java new file mode 100644 index 000000000..c7928df3a --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/application/OrderTableServiceTest.java @@ -0,0 +1,195 @@ +package kitchenpos.order.eatinorder.application; + +import static kitchenpos.TestFixtureFactory.createEatInOrderRequestWithEmptyTable; +import static kitchenpos.TestFixtureFactory.createEmptyOrderTable; +import static kitchenpos.TestFixtureFactory.createMenu; +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createProduct; +import static kitchenpos.TestFixtureFactory.createUsingOrderTable; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import kitchenpos.order.eatinorder.application.dto.CreateOrderTableServiceRq; +import kitchenpos.order.eatinorder.application.dto.OrderTableServiceRs; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import kitchenpos.order.eatinorder.domain.service.OrderTableOccupationManager; +import kitchenpos.order.eatinorder.infra.persistence.FakeEatInOrderRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class OrderTableServiceTest { + + private OrderTableService orderTableService; + private OrderTableRepository orderTableRepository; + private EatInOrderRepository eatInOrderRepository; + private OrderTableOccupationManager orderTableOccupationManager; + + @BeforeEach + void setUp() { + orderTableRepository = mock(OrderTableRepository.class); + eatInOrderRepository = new FakeEatInOrderRepository(new HashMap<>()); + orderTableOccupationManager = new OrderTableOccupationManager(eatInOrderRepository); + orderTableService = new OrderTableService(orderTableRepository, orderTableOccupationManager); + } + + @Test + @DisplayName("매장 테이블을 만들 수 있다") + void create() { + // given + CreateOrderTableServiceRq request = new CreateOrderTableServiceRq("1번 테이블"); + when(orderTableRepository.save(any(OrderTable.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + // when + OrderTableServiceRs result = orderTableService.create(request); + + // then + assertThat(result).isNotNull(); + assertThat(result.getName()).isEqualTo("1번 테이블"); + assertThat(result.getNumberOfGuests()).isZero(); + assertThat(result.isOccupied()).isFalse(); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("매장 테이블 이름이 없으면 예외가 발생한다.") + void orderTable_name_exception(String name) { + // given + CreateOrderTableServiceRq request = new CreateOrderTableServiceRq(name); + + // when // then + assertThatThrownBy(() -> { + orderTableService.create(request); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("전체 매장 테이블을 볼 수 있다") + void find_all() { + // given + List orderTables = List.of( + createEmptyOrderTable(), + createUsingOrderTable() + ); + when(orderTableRepository.findAll()).thenReturn(orderTables); + + // when + List result = orderTableService.findAll(); + + // then + assertThat(result).hasSize(2); + } + + @Test + @DisplayName("빈 테이블을 이용할 수 있다") + void sit() { + // given + OrderTable orderTable = createEmptyOrderTable(); + when(orderTableRepository.findById(any())).thenReturn(Optional.of(orderTable)); + + // when + OrderTableServiceRs result = orderTableService.sit(orderTable.getId()); + + // then + assertThat(result.isOccupied()).isTrue(); + } + + @Test + @DisplayName("모든 주문이 완료되면 테이블을 정리할 수 있다") + void clear() { + // given + OrderTable orderTable = createUsingOrderTable(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable( + createMenu(createMenuGroup(), createProduct(BigDecimal.valueOf(1000))), EatInOrderFlow.COMPLETED); + eatInOrder.occupyOrderTable(orderTable); + eatInOrderRepository.save(eatInOrder); + + when(orderTableRepository.findById(any())).thenReturn(Optional.of(orderTable)); + + // when + OrderTableServiceRs result = orderTableService.clear(orderTable.getId()); + + // then + assertThat(result.isOccupied()).isFalse(); + assertThat(result.getNumberOfGuests()).isZero(); + } + + @Test + @DisplayName("주문이 완료되지 않은 테이블을 정리할 시 예외가 발생한다.") + void orderTable_occupied_exception() { + // given + OrderTable orderTable = createUsingOrderTable(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable( + createMenu(createMenuGroup(), createProduct(BigDecimal.valueOf(1000))), EatInOrderFlow.SERVED); + eatInOrder.occupyOrderTable(orderTable); + eatInOrderRepository.save(eatInOrder); + + when(orderTableRepository.findById(any())).thenReturn(Optional.of(orderTable)); + + // when // then + assertThatThrownBy(() -> orderTableService.clear(orderTable.getId())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("주문이 완료되지 않은 매장 테이블은 정리할 수 없습니다."); + } + + @Test + @DisplayName("테이블에 손님이 있는 경우에만 앉아 있는 손님의 수를 변경할 수 있다") + void change_numberOfGuests() { + // given + OrderTable orderTable = createUsingOrderTable(); + orderTable.changeNumberOfGuests(4); + + when(orderTableRepository.findById(any())).thenReturn(Optional.of(orderTable)); + + // when + OrderTableServiceRs result = orderTableService.changeNumberOfGuests(orderTable.getId(), + orderTable); + + // then + assertThat(result.getNumberOfGuests()).isEqualTo(4); + } + + @Test + @DisplayName("빈 테이블의 손님 수 변경 시 예외가 발생한다.") + void change_numberOfGuests_not_occupied_exception() { + // given + OrderTable orderTable = createEmptyOrderTable(); + when(orderTableRepository.findById(any())).thenReturn(Optional.of(orderTable)); + + // when // then + assertThatThrownBy(() -> { + orderTable.changeNumberOfGuests(4); + orderTableService.changeNumberOfGuests(orderTable.getId(), orderTable); + }).isInstanceOf(IllegalStateException.class) + .hasMessage("주문 테이블이 비어있습니다!"); + } + + @Test + @DisplayName("손님 수를 음수로 변경하면 예외가 발생한다..") + void change_numberOfGuests_negative_number_exception() { + // given + OrderTable orderTable = createUsingOrderTable(); + when(orderTableRepository.findById(any())).thenReturn(Optional.of(orderTable)); + + // when // then + assertThatThrownBy(() -> { + orderTable.changeNumberOfGuests(-1); + orderTableService.changeNumberOfGuests(orderTable.getId(), orderTable); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessage("손님 수가 음수일 수 없습니다!"); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFactoryTest.java b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFactoryTest.java new file mode 100644 index 000000000..97c0e1537 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFactoryTest.java @@ -0,0 +1,49 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static kitchenpos.TestFixtureFactory.createMenu; +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.infra.persistence.FakeMenuRepository; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.common.model.OrderLineItemValidator; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import kitchenpos.order.eatinorder.infra.persistence.FakeOrderTableRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EatInOrderFactoryTest { + + @Test + @DisplayName("주문 내역과 매장 테이블 정보로 매장 주문을 생성한다.") + void create() { + // given + MenuRepository menuRepository = new FakeMenuRepository(new HashMap<>()); + OrderLineItemValidator orderLineItemValidator = new OrderLineItemValidator(menuRepository); + OrderTableRepository orderTableRepository = new FakeOrderTableRepository(new HashMap<>()); + EatInOrderFactory eatInOrderFactory = new EatInOrderFactory(orderLineItemValidator, orderTableRepository); + + Menu firstMenu = createMenu(createMenuGroup(), createProduct(BigDecimal.valueOf(2000))); + menuRepository.save(firstMenu); + + OrderTable orderTable = orderTableRepository.save(new OrderTable("1번 테이블", 3, true)); + UUID orderTableId = orderTable.getId(); + + OrderLineItem orderLineItem = new OrderLineItem(firstMenu, 2, firstMenu.getId(), BigDecimal.valueOf(8000)); + + // when + EatInOrder eatInOrder = eatInOrderFactory.create(List.of(orderLineItem), orderTableId); + + // then + assertThat(eatInOrder.getEatInOrderFlow()).isEqualTo(EatInOrderFlow.WAITING); + assertThat(eatInOrder.getOrderTableId()).isEqualTo(orderTableId); + assertThat(eatInOrder.getOrderLineItems()).isEqualTo(List.of(orderLineItem)); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFlowTest.java b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFlowTest.java new file mode 100644 index 000000000..151a1313c --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderFlowTest.java @@ -0,0 +1,48 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; + +class EatInOrderFlowTest { + + private static Stream provideOrderStatusTransitions() { + return Stream.of( + Arguments.of(EatInOrderFlow.WAITING, EatInOrderStatus.ACCEPTED, true), + Arguments.of(EatInOrderFlow.ACCEPTED, EatInOrderStatus.SERVED, true), + Arguments.of(EatInOrderFlow.SERVED, EatInOrderStatus.COMPLETED, true), + + Arguments.of(EatInOrderFlow.WAITING, EatInOrderStatus.SERVED, false), + Arguments.of(EatInOrderFlow.WAITING, EatInOrderStatus.COMPLETED, false), + Arguments.of(EatInOrderFlow.ACCEPTED, EatInOrderStatus.COMPLETED, false), + Arguments.of(EatInOrderFlow.SERVED, EatInOrderStatus.ACCEPTED, false) + ); + } + + @ParameterizedTest + @EnumSource(value = EatInOrderStatus.class, names = {"ACCEPTED", "SERVED", "COMPLETED"}) + @DisplayName("주문 상태에 해당하는 매장 주문 순서를 찾는다") + void find_eat_in_order_flow(EatInOrderStatus orderStatus) { + // when + EatInOrderFlow result = EatInOrderFlow.findByOrderStatus(orderStatus); + + // then + assertThat(result.name()).isEqualTo(orderStatus.name()); + } + + @ParameterizedTest + @MethodSource("provideOrderStatusTransitions") + @DisplayName("매장 주문 상태 변경이 올바른 흐름인지 검증한다") + void validate_eat_in_order_status(EatInOrderFlow currentFlow, EatInOrderStatus nextStatus, boolean expected) { + // when + boolean result = currentFlow.validateOrderStatus(nextStatus); + + // then + assertThat(result).isEqualTo(expected); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderMockTest.java b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderMockTest.java new file mode 100644 index 000000000..c0f6deadf --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderMockTest.java @@ -0,0 +1,65 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static kitchenpos.TestFixtureFactory.createEatInOrderRequestWithEmptyTable; +import static kitchenpos.TestFixtureFactory.createMenuWithProductAndGroup; +import static kitchenpos.TestFixtureFactory.createUsingOrderTable; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import kitchenpos.menu.domain.model.Menu; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.AbstractAggregateRoot; +import org.springframework.test.util.ReflectionTestUtils; + +@ExtendWith(MockitoExtension.class) +class EatInOrderMockTest { + + @Test + @DisplayName("매장 주문이 완료되었다면, 주문 테이블 점유 해제 이벤트를 발행한다.") + void publish() { + // given + Menu menu = createMenuWithProductAndGroup(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable(menu, EatInOrderFlow.SERVED); + OrderTable usingOrderTable = createUsingOrderTable(); + eatInOrder.occupyOrderTable(usingOrderTable); + + // when + eatInOrder.processOrderFlow(EatInOrderStatus.COMPLETED); + + // then + List events = getEvents(eatInOrder); + assertThat(events) + .hasSize(1) + .hasOnlyElementsOfType(ReleaseOrderTableEvent.class); + } + + @Test + @DisplayName("완료되지 않은 매장 주문은 주문 테이블 점유 해제 이벤트를 발행하지 않는다.") + void not_publish() { + // given + Menu menu = createMenuWithProductAndGroup(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable(menu, EatInOrderFlow.ACCEPTED); + OrderTable usingOrderTable = createUsingOrderTable(); + eatInOrder.occupyOrderTable(usingOrderTable); + + // when + eatInOrder.processOrderFlow(EatInOrderStatus.SERVED); + + // then + List events = getEvents(eatInOrder); + assertThat(events) + .hasSize(0) + .hasOnlyElementsOfType(ReleaseOrderTableEvent.class); + } + + private List getEvents(EatInOrder order) { + return (List) ReflectionTestUtils.getField( + order, + AbstractAggregateRoot.class, + "domainEvents" + ); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderTest.java b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderTest.java new file mode 100644 index 000000000..29135a81d --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/domain/model/EatInOrderTest.java @@ -0,0 +1,40 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static kitchenpos.TestFixtureFactory.createEatInOrderRequestWithEmptyTable; +import static kitchenpos.TestFixtureFactory.createMenuWithProductAndGroup; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import kitchenpos.menu.domain.model.Menu; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EatInOrderTest { + + @Test + @DisplayName("다음 매장 주문 순서를 요청하면 주문 순서를 검증하고 주문 순서를 변경한다.") + void process_order_flow() { + // given + Menu menu = createMenuWithProductAndGroup(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable(menu, EatInOrderFlow.WAITING); + + // when + eatInOrder.processOrderFlow(EatInOrderStatus.ACCEPTED); + + // then + assertThat(eatInOrder.getEatInOrderFlow().name()).isEqualTo(EatInOrderStatus.ACCEPTED.name()); + } + + @Test + @DisplayName("주문 순서가 맞지 않으면 예외를 던진다.") + void process_order_flow_exception() { + // given + Menu menu = createMenuWithProductAndGroup(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable(menu, EatInOrderFlow.ACCEPTED); + + // when // then + assertThatThrownBy(() -> eatInOrder.processOrderFlow(EatInOrderStatus.COMPLETED)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("잘못된 매장 주문 순서입니다. 주문 순서를 지켜주세요!"); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/domain/model/OrderTableNameTest.java b/src/test/java/kitchenpos/order/eatinorder/domain/model/OrderTableNameTest.java new file mode 100644 index 000000000..8f585a096 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/domain/model/OrderTableNameTest.java @@ -0,0 +1,21 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class OrderTableNameTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("주문 테이블의 이름이 비어있거나, null이면 예외를 던진다.") + void create_order_table_name_exception(String name) { + // when // then + assertThatThrownBy(() -> new OrderTableName(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("주문 테이블 이름을 채워주세요!"); + } + +} diff --git a/src/test/java/kitchenpos/order/eatinorder/domain/model/OrderTableTest.java b/src/test/java/kitchenpos/order/eatinorder/domain/model/OrderTableTest.java new file mode 100644 index 000000000..f1e2f2f41 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/domain/model/OrderTableTest.java @@ -0,0 +1,47 @@ +package kitchenpos.order.eatinorder.domain.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OrderTableTest { + + @Test + @DisplayName("주문 테이블이 비어있을 때, 테이블을 점유하는 손님 수를 변경하면 예외를 던진다.") + void change_number_of_guests_empty_order_table_exception() { + // given + OrderTable orderTable = new OrderTable(new OrderTableName("1번 테이블")); + + // when // then + assertThatThrownBy(() -> orderTable.changeNumberOfGuests(2)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("주문 테이블이 비어있습니다!"); + } + + @Test + @DisplayName("주문 테이블을 점유하는 손님 수를 음수로 변경하면 예외를 던진다.") + void change_number_of_guests_guest_number_exception() { + // given + OrderTable orderTable = new OrderTable(new OrderTableName("1번 테이블"), 1, true); + + // when // then + assertThatThrownBy(() -> orderTable.changeNumberOfGuests(-1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("손님 수가 음수일 수 없습니다!"); + } + + @Test + @DisplayName("주문 테이블을 손님이 점유하고 있을 때, 손님 수를 변경할 수 있다.") + void change_number_of_guests_success() { + // given + OrderTable orderTable = new OrderTable(new OrderTableName("1번 테이블"), 1, true); + + // when + orderTable.changeNumberOfGuests(3); + + // then + assertThat(orderTable.getNumberOfGuests()).isEqualTo(3); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/domain/service/OrderTableOccupationManagerTest.java b/src/test/java/kitchenpos/order/eatinorder/domain/service/OrderTableOccupationManagerTest.java new file mode 100644 index 000000000..89e35e4b1 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/domain/service/OrderTableOccupationManagerTest.java @@ -0,0 +1,75 @@ +package kitchenpos.order.eatinorder.domain.service; + +import static kitchenpos.TestFixtureFactory.createMenu; +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createOrderLineItem; +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.model.ReleaseOrderTableEvent; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; +import kitchenpos.order.eatinorder.infra.persistence.FakeEatInOrderRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OrderTableOccupationManagerTest { + + @Test + @DisplayName("매장 주문이 완료 되면 테이블의 점유 상태를 해지한다.") + void release() { + // given + EatInOrderRepository eatInOrderRepository = new FakeEatInOrderRepository(new HashMap<>()); + OrderTableOccupationManager manager = new OrderTableOccupationManager(eatInOrderRepository); + + OrderTable orderTable = new OrderTable("1번 테이블", 3, true); + EatInOrder eatInOrder = createEatInOrder(orderTable, EatInOrderFlow.COMPLETED); + ReleaseOrderTableEvent event = new ReleaseOrderTableEvent(orderTable); + + eatInOrderRepository.save(eatInOrder); + + // when + manager.release(event); + + // then + assertThat(orderTable.isOccupied()).isEqualTo(false); + assertThat(orderTable.getNumberOfGuests()).isEqualTo(0); + } + + @Test + @DisplayName("매장 주문이 완료 되지 않은 경우, 테이블 점유 상태를 해지하려 하면 예외가 발생한다.") + void release_exception() { + // given + EatInOrderRepository eatInOrderRepository = new FakeEatInOrderRepository(new HashMap<>()); + OrderTableOccupationManager manager = new OrderTableOccupationManager(eatInOrderRepository); + + OrderTable orderTable = new OrderTable("1번 테이블", 3, true); + EatInOrder eatInOrder = createEatInOrder(orderTable, EatInOrderFlow.SERVED); + ReleaseOrderTableEvent event = new ReleaseOrderTableEvent(orderTable); + + eatInOrderRepository.save(eatInOrder); + + // when // then + assertThatThrownBy(() -> manager.release(event)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("주문이 완료되지 않은 매장 테이블은 정리할 수 없습니다."); + } + + private EatInOrder createEatInOrder(OrderTable orderTable, EatInOrderFlow eatInOrderFlow) { + List orderLineItems = List.of(createOrderLineItem(createMenu(createMenuGroup(), createProduct( + BigDecimal.TWO), 3))); + EatInOrder eatInOrder = new EatInOrder(LocalDateTime.now(), + orderLineItems, + eatInOrderFlow); + eatInOrder.occupyOrderTable(orderTable); + return eatInOrder; + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/infra/persistence/FakeEatInOrderRepository.java b/src/test/java/kitchenpos/order/eatinorder/infra/persistence/FakeEatInOrderRepository.java new file mode 100644 index 000000000..258c6de2e --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/infra/persistence/FakeEatInOrderRepository.java @@ -0,0 +1,47 @@ +package kitchenpos.order.eatinorder.infra.persistence; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; + +public class FakeEatInOrderRepository implements EatInOrderRepository { + private final Map storage; + + public FakeEatInOrderRepository(Map storage) { + this.storage = storage; + } + + @Override + public Optional findById(UUID eatInOrderId) { + return Optional.of(storage.get(eatInOrderId)); + } + + @Override + public EatInOrder save(EatInOrder eatInOrder) { + UUID eatInOrderId = UUID.randomUUID(); + eatInOrder.setId(eatInOrderId); + storage.put(eatInOrderId, eatInOrder); + return eatInOrder; + } + + @Override + public boolean existsByOrderTableAndEatInOrderFlowNot(OrderTable orderTable, EatInOrderFlow eatInOrderFlow) { + Optional eatInOrder = storage.values().stream() + .filter(s -> s.getOrderTable() == orderTable) + .findFirst(); + return eatInOrder + .filter(flow -> flow.getEatInOrderFlow() != eatInOrderFlow) + .isPresent(); + } + + @Override + public List findAll() { + return new ArrayList<>(storage.values()); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/infra/persistence/FakeOrderTableRepository.java b/src/test/java/kitchenpos/order/eatinorder/infra/persistence/FakeOrderTableRepository.java new file mode 100644 index 000000000..eabd39915 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/infra/persistence/FakeOrderTableRepository.java @@ -0,0 +1,36 @@ +package kitchenpos.order.eatinorder.infra.persistence; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; + +public class FakeOrderTableRepository implements OrderTableRepository { + + private final Map storage; + + public FakeOrderTableRepository(Map storage) { + this.storage = storage; + } + + @Override + public OrderTable save(OrderTable orderTable) { + UUID id = UUID.randomUUID(); + orderTable.setId(id); + storage.put(id, orderTable); + return orderTable; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(storage.get(id)); + } + + @Override + public List findAll() { + return new ArrayList<>(storage.values()); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/service/EatInOrderServiceTest.java b/src/test/java/kitchenpos/order/eatinorder/service/EatInOrderServiceTest.java new file mode 100644 index 000000000..8304096d4 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/service/EatInOrderServiceTest.java @@ -0,0 +1,166 @@ +package kitchenpos.order.eatinorder.service; + +import static kitchenpos.TestFixtureFactory.createEatInOrderRequestWithEmptyTable; +import static kitchenpos.TestFixtureFactory.createEatInOrderServiceRq; +import static kitchenpos.TestFixtureFactory.createEmptyOrderTable; +import static kitchenpos.TestFixtureFactory.createMenuWithProductAndGroup; +import static kitchenpos.TestFixtureFactory.createUsingOrderTable; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Optional; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.order.common.model.OrderLineItemValidator; +import kitchenpos.order.eatinorder.application.EatInOrderService; +import kitchenpos.order.eatinorder.application.dto.CreateEatInOrderServiceRq; +import kitchenpos.order.eatinorder.application.dto.EatInOrderServiceRs; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFactory; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.model.ReleaseOrderTableEvent; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import kitchenpos.order.eatinorder.domain.service.OrderTableOccupationManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.AbstractAggregateRoot; +import org.springframework.test.util.ReflectionTestUtils; + +@ExtendWith(MockitoExtension.class) +class EatInOrderServiceTest { + + private EatInOrderService eatInOrderService; + private EatInOrderRepository eatInOrderRepository; + private MenuRepository menuRepository; + private OrderTableRepository orderTableRepository; + private OrderTableOccupationManager orderTableOccupationManager; + + @BeforeEach + void setUp() { + eatInOrderRepository = mock(EatInOrderRepository.class); + menuRepository = mock(MenuRepository.class); + orderTableRepository = mock(OrderTableRepository.class); + OrderLineItemValidator orderLineItemValidator = new OrderLineItemValidator(menuRepository); + EatInOrderFactory eatInOrderFactory = new EatInOrderFactory(orderLineItemValidator, orderTableRepository); + orderTableOccupationManager = new OrderTableOccupationManager(eatInOrderRepository); + eatInOrderService = new EatInOrderService(eatInOrderRepository, menuRepository, eatInOrderFactory); + } + + @Test + @DisplayName("이용 중이지 않은 테이블에서 주문 시 예외가 발생한다.") + void not_occupied_table_order_exception() { + // given + Menu menu = createMenuWithProductAndGroup(); + OrderTable orderTable = createEmptyOrderTable(); + + CreateEatInOrderServiceRq request = createEatInOrderServiceRq(menu, orderTable.getId()); + + when(menuRepository.findAllByIdIn(anyList())).thenReturn(List.of(menu)); + when(menuRepository.findById(any())).thenReturn(Optional.of(menu)); + when(orderTableRepository.findById(any())).thenReturn(Optional.of(orderTable)); + + // when // then + assertThatThrownBy(() -> eatInOrderService.create(request)) + .isInstanceOf(IllegalStateException.class); + } + + /** + * 이 테스트를 어떻게 할 지 고민해봐야 함! + */ + @Test + @DisplayName("매장 식사 주문 완료 시 다른 주문이 없으면 테이블을 비운다") + void clear_table_with_complete_status() { + // given + Menu menu = createMenuWithProductAndGroup(); + OrderTable orderTable = createUsingOrderTable(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable(menu, EatInOrderFlow.SERVED); + eatInOrder.occupyOrderTable(orderTable); + + when(eatInOrderRepository.findById(any())).thenReturn(Optional.of(eatInOrder)); + when(eatInOrderRepository.existsByOrderTableAndEatInOrderFlowNot(any(), any())).thenReturn(false); + + // when + EatInOrderServiceRs result = eatInOrderService.complete(eatInOrder.getId()); + orderTableOccupationManager.release(new ReleaseOrderTableEvent(orderTable)); + + // then + List events = getEvents(eatInOrder); + assertThat(events) + .hasSize(1) + .hasOnlyElementsOfType(ReleaseOrderTableEvent.class); + + assertThat(orderTable.isOccupied()).isFalse(); + assertThat(orderTable.getNumberOfGuests()).isZero(); + assertThat(result.getEatInOrderFlow()).isEqualTo(EatInOrderFlow.COMPLETED); + } + + private List getEvents(EatInOrder eatInOrder) { + return (List) ReflectionTestUtils.getField( + eatInOrder, + AbstractAggregateRoot.class, + "domainEvents" + ); + } + + @ParameterizedTest + @EnumSource(value = EatInOrderFlow.class, names = {"ACCEPTED", "SERVED", "COMPLETED"}) + @DisplayName("대기 중이 아닌 주문 시 예외가 발생한다.") + void accept_exception(EatInOrderFlow flow) { + // given + Menu menu = createMenuWithProductAndGroup(); + OrderTable orderTable = createUsingOrderTable(); + EatInOrder order = createEatInOrderRequestWithEmptyTable(menu, flow); + order.occupyOrderTable(orderTable); + when(eatInOrderRepository.findById(any())).thenReturn(Optional.of(order)); + + // when // then + assertThatThrownBy(() -> eatInOrderService.accept(order.getId())) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("대기 중인 주문만 접수할 수 있다") + void accept_success() { + // given + Menu menu = createMenuWithProductAndGroup(); + OrderTable orderTable = createUsingOrderTable(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable(menu, EatInOrderFlow.WAITING); + eatInOrder.occupyOrderTable(orderTable); + when(eatInOrderRepository.findById(any())).thenReturn(Optional.of(eatInOrder)); + + // when + EatInOrderServiceRs result = eatInOrderService.accept(eatInOrder.getId()); + + // then + assertThat(result.getEatInOrderFlow()).isEqualTo(EatInOrderFlow.ACCEPTED); + } + + @Test + @DisplayName("매장 주문은 서빙이 완료 되면 주문을 완료할 수 있다") + void complete_order() { + // given + Menu menu = createMenuWithProductAndGroup(); + OrderTable orderTable = createUsingOrderTable(); + EatInOrder eatInOrder = createEatInOrderRequestWithEmptyTable(menu, EatInOrderFlow.SERVED); + eatInOrder.occupyOrderTable(orderTable); + when(eatInOrderRepository.findById(any())).thenReturn(Optional.of(eatInOrder)); + + // when + EatInOrderServiceRs result = eatInOrderService.complete(eatInOrder.getId()); + + // then + assertThat(result.getEatInOrderFlow()).isEqualTo(EatInOrderFlow.COMPLETED); + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/ui/EatInOrderRestControllerTest.java b/src/test/java/kitchenpos/order/eatinorder/ui/EatInOrderRestControllerTest.java new file mode 100644 index 000000000..393318f32 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/ui/EatInOrderRestControllerTest.java @@ -0,0 +1,204 @@ +package kitchenpos.order.eatinorder.ui; + +import static kitchenpos.TestFixtureFactory.createEatInOrderRq; +import static kitchenpos.TestFixtureFactory.createMenu; +import static kitchenpos.TestFixtureFactory.createMenuGroup; +import static kitchenpos.TestFixtureFactory.createOrderLineItem; +import static kitchenpos.TestFixtureFactory.createProduct; +import static kitchenpos.TestFixtureFactory.createUsingOrderTable; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.model.MenuGroup; +import kitchenpos.menu.domain.repository.MenuGroupRepository; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.order.common.model.OrderLineItem; +import kitchenpos.order.eatinorder.domain.model.EatInOrder; +import kitchenpos.order.eatinorder.domain.model.EatInOrderFlow; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.EatInOrderRepository; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import kitchenpos.order.eatinorder.ui.dto.CreateEatInOrderRq; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class EatInOrderRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private EatInOrderRepository eatInOrderRepository; + @Autowired + private OrderTableRepository orderTableRepository; + @Autowired + private MenuRepository menuRepository; + @Autowired + private MenuGroupRepository menuGroupRepository; + @Autowired + private ProductRepository productRepository; + + @Test + @DisplayName("주문을 생성한다.") + void create_success() throws Exception { + // given + Menu menu1 = createAndSaveMenu(createAndSaveMenuGroup(), createAndSaveProduct()); + Menu menu2 = createAndSaveMenu(createAndSaveMenuGroup(), createAndSaveProduct()); + OrderTable orderTable = createAndSaveUsingOrderTable(); + + List menuIds = List.of(menu1.getId(), menu2.getId()); + CreateEatInOrderRq request = createEatInOrderRq(menuIds, orderTable.getId()); + + // when + ResultActions result = mockMvc.perform(post("/api/eatInOrder") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isCreated()) + .andExpect(header().exists("Location")) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.eatInOrderId").isNotEmpty()) + .andExpect(jsonPath("$.eatInOrderFlow").value(EatInOrderFlow.WAITING.toString())); + } + + @Test + @DisplayName("주문 상태가 주문 대기 중이라면 주문을 수락할 수 있다.") + void accept_success() throws Exception { + // given + EatInOrder savedOrder = createAndSaveEatInOrder(); + + // when + ResultActions result = mockMvc.perform(put("/api/eatInOrder/{eatInOrderId}/accept", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.eatInOrderId").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.eatInOrderFlow").value(EatInOrderFlow.ACCEPTED.toString())); + } + + @Test + @DisplayName("주문 상태가 접수 완료라면 서빙할 수 있다.") + void serve_success() throws Exception { + // given + EatInOrder savedOrder = createAndSaveEatInOrder(EatInOrderFlow.ACCEPTED); + + // when + ResultActions result = mockMvc.perform(put("/api/eatInOrder/{eatInOrderId}/serve", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.eatInOrderId").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.eatInOrderFlow").value(EatInOrderFlow.SERVED.toString())); + } + + @Test + @DisplayName("주문 종류와 상태에 따라 주문을 완료할 수 있다.") + void complete_success() throws Exception { + // given + EatInOrder savedOrder = createAndSaveEatInOrder(EatInOrderFlow.SERVED); + + // when + ResultActions result = mockMvc.perform(put("/api/eatInOrder/{eatInOrderId}/complete", savedOrder.getId())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.eatInOrderId").value(savedOrder.getId().toString())) + .andExpect(jsonPath("$.eatInOrderFlow").value(EatInOrderFlow.COMPLETED.toString())); + } + + @Test + @DisplayName("전체 주문을 조회한다.") + void findAll_success() throws Exception { + // given + createAndSaveEatInOrder(); + createAndSaveEatInOrder(); + + // when + ResultActions result = mockMvc.perform(get("/api/eatInOrder")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)); + } + + private EatInOrder createAndSaveEatInOrder() { + MenuGroup menuGroup = createAndSaveMenuGroup(); + Product product = createAndSaveProduct(); + Menu menu = createAndSaveMenu(menuGroup, product); + + OrderLineItem orderLineItem = createOrderLineItem(menu); + OrderTable orderTable = createAndSaveUsingOrderTable(); + + EatInOrder eatInOrder = createEatInOrder(orderLineItem, orderTable, EatInOrderFlow.WAITING); + return eatInOrderRepository.save(eatInOrder); + } + + private EatInOrder createAndSaveEatInOrder(EatInOrderFlow flow) { + MenuGroup menuGroup = createAndSaveMenuGroup(); + Product product = createAndSaveProduct(); + Menu menu = createAndSaveMenu(menuGroup, product); + + OrderLineItem orderLineItem = createOrderLineItem(menu); + OrderTable orderTable = createAndSaveUsingOrderTable(); + + EatInOrder eatInOrder = createEatInOrder(orderLineItem, orderTable, flow); + return eatInOrderRepository.save(eatInOrder); + } + + private EatInOrder createEatInOrder(OrderLineItem orderLineItem, OrderTable orderTable, EatInOrderFlow flow) { + EatInOrder eatInOrder = new EatInOrder(LocalDateTime.now(), List.of(orderLineItem), flow); + eatInOrder.occupyOrderTable(orderTable); + return eatInOrder; + } + + private OrderTable createAndSaveUsingOrderTable() { + OrderTable orderTable = createUsingOrderTable(); + orderTableRepository.save(orderTable); + return orderTable; + } + + private Menu createAndSaveMenu(MenuGroup menuGroup, Product product) { + Menu menu = createMenu(menuGroup, product); + menuRepository.save(menu); + return menu; + } + + private Product createAndSaveProduct() { + Product product = createProduct("김치", 4000); + productRepository.save(product); + return product; + } + + private MenuGroup createAndSaveMenuGroup() { + MenuGroup menuGroup = createMenuGroup(); + menuGroupRepository.save(menuGroup); + return menuGroup; + } +} diff --git a/src/test/java/kitchenpos/order/eatinorder/ui/OrderTableRestControllerTest.java b/src/test/java/kitchenpos/order/eatinorder/ui/OrderTableRestControllerTest.java new file mode 100644 index 000000000..a27c45273 --- /dev/null +++ b/src/test/java/kitchenpos/order/eatinorder/ui/OrderTableRestControllerTest.java @@ -0,0 +1,136 @@ +package kitchenpos.order.eatinorder.ui; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.order.eatinorder.domain.model.OrderTable; +import kitchenpos.order.eatinorder.domain.repository.OrderTableRepository; +import kitchenpos.order.eatinorder.ui.dto.CreateOrderTableRq; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class OrderTableRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private OrderTableRepository orderTableRepository; + + @Test + @DisplayName("주문 테이블을 생성한다") + void create_orderTable() throws Exception { + // given + CreateOrderTableRq request = new CreateOrderTableRq("1번 테이블"); + + // when + ResultActions perform = mockMvc.perform(post("/api/order-tables") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + perform.andExpect(status().isCreated()) + .andExpect(header().exists("Location")) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.numberOfGuests").value(0)) + .andExpect(jsonPath("$.occupied").value(false)); + } + + @Test + @DisplayName("주문 테이블을 착석 상태로 변경한다") + void sit_orderTable() throws Exception { + // given + OrderTable orderTable = createAndSaveOrderTable(4); + + // when + ResultActions perform = mockMvc.perform(put("/api/order-tables/{orderTableId}/sit", orderTable.getId())); + + // then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(orderTable.getId().toString())) + .andExpect(jsonPath("$.occupied").value(true)); + } + + @Test + @DisplayName("주문 테이블을 빈 상태로 변경한다") + void clear_orderTable() throws Exception { + // given + OrderTable orderTable = createAndSaveOrderTable(4); + orderTable.occupyTable(); + orderTableRepository.save(orderTable); + + // when + ResultActions perform = mockMvc.perform(put("/api/order-tables/{orderTableId}/clear", orderTable.getId())); + + // then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(orderTable.getId().toString())) + .andExpect(jsonPath("$.occupied").value(false)) + .andExpect(jsonPath("$.numberOfGuests").value(0)); + } + + @Test + @DisplayName("주문 테이블의 손님 수를 변경한다") + void change_numberOfGuests() throws Exception { + // given + OrderTable orderTable = createAndSaveOrderTable(4); + orderTable.occupyTable(); + orderTableRepository.save(orderTable); + + OrderTable request = createOrderTableRequest(6); + + // when + ResultActions perform = mockMvc.perform( + put("/api/order-tables/{orderTableId}/number-of-guests", orderTable.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(orderTable.getId().toString())) + .andExpect(jsonPath("$.numberOfGuests").value(6)); + } + + @Test + @DisplayName("전체 주문 테이블을 조회한다") + void find_allOrderTables() throws Exception { + // given + createAndSaveOrderTable(4); + createAndSaveOrderTable(6); + + // when + ResultActions perform = mockMvc.perform(get("/api/order-tables")); + + // then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)); + } + + private OrderTable createOrderTableRequest(int numberOfGuests) { + return new OrderTable("1번 테이블", numberOfGuests, false); + } + + private OrderTable createAndSaveOrderTable(int numberOfGuests) { + OrderTable orderTable = new OrderTable("1번 테이블", numberOfGuests, false); + return orderTableRepository.save(orderTable); + } +} diff --git a/src/test/java/kitchenpos/product/application/ProductServiceTest.java b/src/test/java/kitchenpos/product/application/ProductServiceTest.java new file mode 100644 index 000000000..4b4df7e1a --- /dev/null +++ b/src/test/java/kitchenpos/product/application/ProductServiceTest.java @@ -0,0 +1,137 @@ +package kitchenpos.product.application; + +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import kitchenpos.TestFixtureFactory; +import kitchenpos.common.application.PurgomalumClient; +import kitchenpos.menu.domain.model.Menu; +import kitchenpos.menu.domain.repository.MenuRepository; +import kitchenpos.menu.domain.service.MarginValidator; +import kitchenpos.product.application.dto.ChangeProductPriceServiceRq; +import kitchenpos.product.application.dto.CreateProductServiceRq; +import kitchenpos.product.application.dto.ProductServiceRs; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.model.ProductNameCreationService; +import kitchenpos.product.domain.repository.ProductRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ProductServiceTest { + + private ProductService productService; + private ProductRepository productRepository; + private MenuRepository menuRepository; + private PurgomalumClient purgomalumClient; + + @BeforeEach + void setUp() { + productRepository = mock(ProductRepository.class); + menuRepository = mock(MenuRepository.class); + purgomalumClient = mock(PurgomalumClient.class); + ProductNameCreationService productNameCreationService = new ProductNameCreationService(purgomalumClient); + MarginValidator marginValidator = new MarginValidator(menuRepository); + productService = new ProductService(productRepository, productNameCreationService, marginValidator); + } + + @Test + @DisplayName("상품을 등록할 수 있다") + void create() { + // given + CreateProductServiceRq request = new CreateProductServiceRq("김치", BigDecimal.valueOf(5000)); + when(purgomalumClient.containsProfanity(any())).thenReturn(false); + when(productRepository.save(any(Product.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + // when + ProductServiceRs result = productService.create(request); + + // then + assertThat(result.getId()).isNotNull(); + assertThat(result.getName()).isEqualTo("김치"); + assertThat(result.getPrice()).isEqualTo(BigDecimal.valueOf(5000)); + } + + @Test + @DisplayName("상품 가격은 0원 미만이면 예외가 발생한다.") + void product_price_exception() { + // given + CreateProductServiceRq request = new CreateProductServiceRq("김치", BigDecimal.valueOf(-1000)); + + // when // then + assertThatThrownBy(() -> { + productService.create(request); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 가격을 채워주세요!"); + } + + @Test + @DisplayName("상품 이름에 비속어를 넣으면 예외가 발생한다.") + void product_name_exception() { + // given + CreateProductServiceRq request = new CreateProductServiceRq("fuck", BigDecimal.valueOf(5000)); + when(purgomalumClient.containsProfanity("fuck")).thenReturn(true); + + // when // then + assertThatThrownBy(() -> productService.create(request)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("상품의 가격을 변경할 수 있다") + void change_price() { + // given + Product product = createProduct("김치", 5000); + ChangeProductPriceServiceRq request = new ChangeProductPriceServiceRq(BigDecimal.valueOf(6000)); + when(productRepository.findById(any())).thenReturn(Optional.of(product)); + when(menuRepository.findAllByProductId(any())).thenReturn(List.of()); + + // when + ProductServiceRs result = productService.changePrice(product.getId(), request); + + // then + assertThat(result.getPrice()).isEqualTo(BigDecimal.valueOf(6000)); + } + + @Test + @DisplayName("상품 가격이 변하면 메뉴의 판매 가격이 재료 가격의 총합보다 낮은 메뉴는 게시가 중단된다") + void change_price_exception() { + // given + Product product = createProduct("김치", 5000); + Menu menu = TestFixtureFactory.createMenuWithProductAndGroup("김치찌개", 7000, product); + ChangeProductPriceServiceRq request = new ChangeProductPriceServiceRq(new BigDecimal(8000)); + + when(productRepository.findById(any())).thenReturn(Optional.of(product)); + when(menuRepository.findAllByProductId(any())).thenReturn(List.of(menu)); + + // when + productService.changePrice(product.getId(), request); + + // then + assertThat(menu.isDisplayed()).isFalse(); + } + + @Test + @DisplayName("전체 상품을 조회할 수 있다") + void find_all() { + // given + List products = List.of( + createProduct("김치", 5000), + createProduct("된장", 3000) + ); + when(productRepository.findAll()).thenReturn(products); + + // when + List result = productService.findAll(); + + // then + assertThat(result).hasSize(2); + } +} diff --git a/src/test/java/kitchenpos/product/domain/model/ProductNameCreationServiceTest.java b/src/test/java/kitchenpos/product/domain/model/ProductNameCreationServiceTest.java new file mode 100644 index 000000000..1040fbd19 --- /dev/null +++ b/src/test/java/kitchenpos/product/domain/model/ProductNameCreationServiceTest.java @@ -0,0 +1,35 @@ +package kitchenpos.product.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import kitchenpos.common.application.PurgomalumClient; +import kitchenpos.common.infra.external.FakePurgomalumClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ProductNameCreationServiceTest { + + private ProductNameCreationService productNameCreationService; + private PurgomalumClient purgomalumClient; + + @BeforeEach + void setUp() { + purgomalumClient = new FakePurgomalumClient(); + productNameCreationService = new ProductNameCreationService(purgomalumClient); + } + + @Test + @DisplayName("이름에 비속어가 있으면 예외를 던집니다.") + void validate_name_exception() { + // given + String name = "비속어"; + FakePurgomalumClient fakePurgomalumClient = (FakePurgomalumClient) purgomalumClient; + fakePurgomalumClient.setProfanity(true); + + // when // then + assertThatThrownBy(() -> productNameCreationService.createName(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 이름에 비속어가 존재합니다. 비속어를 제외해주세요!"); + } +} diff --git a/src/test/java/kitchenpos/product/domain/model/ProductNameTest.java b/src/test/java/kitchenpos/product/domain/model/ProductNameTest.java new file mode 100644 index 000000000..69238f2c8 --- /dev/null +++ b/src/test/java/kitchenpos/product/domain/model/ProductNameTest.java @@ -0,0 +1,20 @@ +package kitchenpos.product.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class ProductNameTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("이름은 비어있거나, null이면 예외가 발생한다.") + void create_name_exception(String name) { + // when // then + assertThatThrownBy(() -> new ProductName(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 이름을 채워주세요!"); + } +} diff --git a/src/test/java/kitchenpos/product/domain/model/ProductPriceTest.java b/src/test/java/kitchenpos/product/domain/model/ProductPriceTest.java new file mode 100644 index 000000000..25c3da9c3 --- /dev/null +++ b/src/test/java/kitchenpos/product/domain/model/ProductPriceTest.java @@ -0,0 +1,28 @@ +package kitchenpos.product.domain.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class ProductPriceTest { + + private static Stream invalidProductPriceProvider() { + return Stream.of( + null, + BigDecimal.valueOf(-1) + ); + } + + @DisplayName("가격은 비어있거나 null이면 예외가 발생한다") + @ParameterizedTest + @MethodSource("invalidProductPriceProvider") + void create_price_exception(BigDecimal value) { + assertThatThrownBy(() -> new ProductPrice(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 가격을 채워주세요!"); + } +} diff --git a/src/test/java/kitchenpos/product/infra/persistence/FakeProductRepository.java b/src/test/java/kitchenpos/product/infra/persistence/FakeProductRepository.java new file mode 100644 index 000000000..06aa7523c --- /dev/null +++ b/src/test/java/kitchenpos/product/infra/persistence/FakeProductRepository.java @@ -0,0 +1,43 @@ +package kitchenpos.product.infra.persistence; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; + +public class FakeProductRepository implements ProductRepository { + + private final Map storage; + + public FakeProductRepository(Map storage) { + this.storage = storage; + } + + @Override + public List findAllByIdIn(List ids) { + return new ArrayList<>(storage.values().stream() + .filter(p -> ids.contains(p.getId())) + .toList()); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(storage.get(id)); + } + + @Override + public Product save(Product product) { + UUID id = UUID.randomUUID(); + product.setId(id); + storage.put(id, product); + return product; + } + + @Override + public List findAll() { + return new ArrayList<>(storage.values()); + } +} diff --git a/src/test/java/kitchenpos/product/ui/ProductRestControllerTest.java b/src/test/java/kitchenpos/product/ui/ProductRestControllerTest.java new file mode 100644 index 000000000..98c45c568 --- /dev/null +++ b/src/test/java/kitchenpos/product/ui/ProductRestControllerTest.java @@ -0,0 +1,99 @@ +package kitchenpos.product.ui; + +import static kitchenpos.TestFixtureFactory.createProduct; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.math.BigDecimal; +import kitchenpos.product.domain.model.Product; +import kitchenpos.product.domain.repository.ProductRepository; +import kitchenpos.product.ui.dto.ChangeProductPriceRq; +import kitchenpos.product.ui.dto.CreateProductRq; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class ProductRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private ProductRepository productRepository; + + @Test + @DisplayName("상품을 생성한다") + void create_product() throws Exception { + // given + CreateProductRq request = new CreateProductRq("김치", BigDecimal.valueOf(5000)); + + // when + ResultActions result = mockMvc.perform(post("/api/products") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isCreated()) + .andExpect(header().exists("Location")) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.name").value("김치")) + .andExpect(jsonPath("$.price").value(5000)); + } + + @Test + @DisplayName("상품의 가격을 변경한다") + void change_productPrice() throws Exception { + // given + Product product = createAndSaveProduct("김치", 5000); + ChangeProductPriceRq request = new ChangeProductPriceRq(BigDecimal.valueOf(6000)); + + // when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders.put("/api/products/{productId}/price", product.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(product.getId().toString())) + .andExpect(jsonPath("$.price").value(6000)); + } + + @Test + @DisplayName("상품 목록을 조회한다") + void find_allProducts() throws Exception { + // given + createAndSaveProduct("김치", 5000); + createAndSaveProduct("호박", 1000); + + // when + ResultActions result = mockMvc.perform(get("/api/products")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)); + } + + private Product createAndSaveProduct(String name, int price) { + Product product = createProduct(name, price); + return productRepository.save(product); + } +} diff --git a/src/test/java/kitchenpos/products/application/FakePurgomalumClient.java b/src/test/java/kitchenpos/products/application/FakePurgomalumClient.java deleted file mode 100644 index 3c4114798..000000000 --- a/src/test/java/kitchenpos/products/application/FakePurgomalumClient.java +++ /dev/null @@ -1,20 +0,0 @@ -package kitchenpos.products.application; - -import kitchenpos.products.infra.PurgomalumClient; - -import java.util.Arrays; -import java.util.List; - -public class FakePurgomalumClient implements PurgomalumClient { - private static final List profanities; - - static { - profanities = Arrays.asList("비속어", "욕설"); - } - - @Override - public boolean containsProfanity(final String text) { - return profanities.stream() - .anyMatch(profanity -> text.contains(profanity)); - } -} diff --git a/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java b/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java deleted file mode 100644 index b55c5ec5e..000000000 --- a/src/test/java/kitchenpos/products/application/InMemoryProductRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -package kitchenpos.products.application; - -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -public class InMemoryProductRepository implements ProductRepository { - private final Map products = new HashMap<>(); - - @Override - public Product save(final Product product) { - products.put(product.getId(), product); - return product; - } - - @Override - public Optional findById(final UUID id) { - return Optional.ofNullable(products.get(id)); - } - - @Override - public List findAll() { - return new ArrayList<>(products.values()); - } - - @Override - public List findAllByIdIn(final List ids) { - return products.values() - .stream() - .filter(product -> ids.contains(product.getId())) - .toList(); - } -} diff --git a/src/test/java/kitchenpos/products/application/ProductServiceTest.java b/src/test/java/kitchenpos/products/application/ProductServiceTest.java deleted file mode 100644 index 74a31073e..000000000 --- a/src/test/java/kitchenpos/products/application/ProductServiceTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package kitchenpos.products.application; - -import kitchenpos.menus.application.InMemoryMenuRepository; -import kitchenpos.menus.domain.Menu; -import kitchenpos.menus.domain.MenuRepository; -import kitchenpos.products.domain.Product; -import kitchenpos.products.domain.ProductRepository; -import kitchenpos.products.infra.PurgomalumClient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.math.BigDecimal; -import java.util.List; -import java.util.UUID; - -import static kitchenpos.Fixtures.menu; -import static kitchenpos.Fixtures.menuProduct; -import static kitchenpos.Fixtures.product; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - -class ProductServiceTest { - private ProductRepository productRepository; - private MenuRepository menuRepository; - private PurgomalumClient purgomalumClient; - private ProductService productService; - - @BeforeEach - void setUp() { - productRepository = new InMemoryProductRepository(); - menuRepository = new InMemoryMenuRepository(); - purgomalumClient = new FakePurgomalumClient(); - productService = new ProductService(productRepository, menuRepository, purgomalumClient); - } - - @DisplayName("상품을 등록할 수 있다.") - @Test - void create() { - final Product expected = createProductRequest("후라이드", 16_000L); - final Product actual = productService.create(expected); - assertThat(actual).isNotNull(); - assertAll( - () -> assertThat(actual.getId()).isNotNull(), - () -> assertThat(actual.getName()).isEqualTo(expected.getName()), - () -> assertThat(actual.getPrice()).isEqualTo(expected.getPrice()) - ); - } - - @DisplayName("상품의 가격이 올바르지 않으면 등록할 수 없다.") - @ValueSource(strings = "-1000") - @NullSource - @ParameterizedTest - void create(final BigDecimal price) { - final Product expected = createProductRequest("후라이드", price); - assertThatThrownBy(() -> productService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("상품의 이름이 올바르지 않으면 등록할 수 없다.") - @ValueSource(strings = {"비속어", "욕설이 포함된 이름"}) - @NullSource - @ParameterizedTest - void create(final String name) { - final Product expected = createProductRequest(name, 16_000L); - assertThatThrownBy(() -> productService.create(expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("상품의 가격을 변경할 수 있다.") - @Test - void changePrice() { - final UUID productId = productRepository.save(product("후라이드", 16_000L)).getId(); - final Product expected = changePriceRequest(15_000L); - final Product actual = productService.changePrice(productId, expected); - assertThat(actual.getPrice()).isEqualTo(expected.getPrice()); - } - - @DisplayName("상품의 가격이 올바르지 않으면 변경할 수 없다.") - @ValueSource(strings = "-1000") - @NullSource - @ParameterizedTest - void changePrice(final BigDecimal price) { - final UUID productId = productRepository.save(product("후라이드", 16_000L)).getId(); - final Product expected = changePriceRequest(price); - assertThatThrownBy(() -> productService.changePrice(productId, expected)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("상품의 가격이 변경될 때 메뉴의 가격이 메뉴에 속한 상품 금액의 합보다 크면 메뉴가 숨겨진다.") - @Test - void changePriceInMenu() { - final Product product = productRepository.save(product("후라이드", 16_000L)); - final Menu menu = menuRepository.save(menu(19_000L, true, menuProduct(product, 2L))); - productService.changePrice(product.getId(), changePriceRequest(8_000L)); - assertThat(menuRepository.findById(menu.getId()).get().isDisplayed()).isFalse(); - } - - @DisplayName("상품의 목록을 조회할 수 있다.") - @Test - void findAll() { - productRepository.save(product("후라이드", 16_000L)); - productRepository.save(product("양념치킨", 16_000L)); - final List actual = productService.findAll(); - assertThat(actual).hasSize(2); - } - - private Product createProductRequest(final String name, final long price) { - return createProductRequest(name, BigDecimal.valueOf(price)); - } - - private Product createProductRequest(final String name, final BigDecimal price) { - final Product product = new Product(); - product.setName(name); - product.setPrice(price); - return product; - } - - private Product changePriceRequest(final long price) { - return changePriceRequest(BigDecimal.valueOf(price)); - } - - private Product changePriceRequest(final BigDecimal price) { - final Product product = new Product(); - product.setPrice(price); - return product; - } -}