diff --git a/README.md b/README.md index c31ca54c..90d2dcd7 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# java-chicken-2019 \ No newline at end of file +# java-chicken-2020-tdd + +## 기능 요구사항 +1. pos 프로그램을 통해 주문하기, 계산하기 기능을 실행할 수 있어야 한다. +2. 테이블을 선택해 메뉴를 주문할 수 있어야 한다. + 1. 테이블을 구현한다. + 2. 메뉴를 구현한다. + 3. 번호에 맞게 테이블, 메뉴를 선택할 수 있어야 한다. + 4. 주문 수량을 정할 수 있어야 한다. + 1. 메뉴 당 주문 수량은 99개 이하여야 한다. +3. 주문이 남아있는 테이블은 콘솔에 표시되어야 한다. +4. 테이블 별 주문 내역을 통해 주문 내역을 계산해야 한다. + 1. 주문 내역을 표시해야 한다. + 2. 주문 내역에 따른 총 가격을 계산해야 한다. + 3. 총 가격을 할인 정책에 따라 계산 후 최종 가격을 표시해야 한다. + 1. 지불 방식 별 할인 정책을 세울 수 있어야 한다. + 2. 주문 내역에 따라 할인 정책을 세울 수 있어야 한다. + 4. 결제가 불가능할 경우 예외처리가 필요하다. diff --git a/build.gradle b/build.gradle index 5aa86890..b946ffea 100644 --- a/build.gradle +++ b/build.gradle @@ -11,4 +11,8 @@ repositories { dependencies { testCompile('org.junit.jupiter:junit-jupiter:5.5.2') testCompile('org.assertj:assertj-core:3.14.0') +} + +test { + useJUnitPlatform() } \ No newline at end of file diff --git a/src/main/java/Application.java b/src/main/java/Application.java index ea0d34fd..4f244a06 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,21 +1,28 @@ -import domain.Menu; -import domain.MenuRepository; -import domain.Table; -import domain.TableRepository; +import java.util.Scanner; + +import controller.ChickenController; +import controller.PageController; +import domain.menu.MenuRepository; +import domain.table.TableRepository; +import service.MenuService; +import service.PaymentService; +import service.TableOrderService; import view.InputView; import view.OutputView; -import java.util.List; - public class Application { - // TODO 구현 진행 - public static void main(String[] args) { - final List tables = TableRepository.tables(); - OutputView.printTables(tables); + public static void main(String[] args) { + MenuRepository menuRepository = new MenuRepository(); + TableRepository tableRepository = new TableRepository(); + InputView inputView = new InputView(new Scanner(System.in)); - final int tableNumber = InputView.inputTableNumber(); + ChickenController chickenController = new ChickenController(inputView, + new OutputView(), new MenuService(menuRepository), + new PaymentService(tableRepository), new TableOrderService(tableRepository, menuRepository)); - final List menus = MenuRepository.menus(); - OutputView.printMenus(menus); - } + PageController pageController = new PageController(inputView, chickenController); + while (true) { + pageController.run(); + } + } } diff --git a/src/main/java/controller/ChickenController.java b/src/main/java/controller/ChickenController.java new file mode 100644 index 00000000..1dc81342 --- /dev/null +++ b/src/main/java/controller/ChickenController.java @@ -0,0 +1,48 @@ +package controller; + +import service.MenuService; +import service.PaymentService; +import service.TableOrderService; +import service.dto.PayRequest; +import service.dto.TableOrderRequest; +import view.InputView; +import view.OutputView; + +public class ChickenController { + private final InputView inputView; + private final OutputView outputView; + private final MenuService menuService; + private final PaymentService paymentService; + private final TableOrderService tableOrderService; + + public ChickenController(InputView inputView, OutputView outputView, MenuService menuService, + PaymentService paymentService, TableOrderService tableOrderService) { + this.inputView = inputView; + this.outputView = outputView; + this.menuService = menuService; + this.paymentService = paymentService; + this.tableOrderService = tableOrderService; + } + + public void order() { + outputView.printTables(tableOrderService.findAllTables()); + int tableNumber = inputView.inputTableNumber(); + + outputView.printMenus(menuService.findAllMenus()); + int menuNumber = inputView.inputMenuNumber(); + + int amount = inputView.inputAmount(); + + tableOrderService.addOrder(new TableOrderRequest(tableNumber, menuNumber, amount)); + } + + public void pay() { + outputView.printTables(tableOrderService.findAllTables()); + int tableNumber = inputView.inputTableNumber(); + + outputView.printOrder(tableOrderService.findTableOrder(tableNumber)); + int paymentNumber = inputView.inputPaymentNumber(); + + outputView.printPaymentAmount(paymentService.payOrder(new PayRequest(tableNumber, paymentNumber))); + } +} diff --git a/src/main/java/controller/Command.java b/src/main/java/controller/Command.java new file mode 100644 index 00000000..750041f2 --- /dev/null +++ b/src/main/java/controller/Command.java @@ -0,0 +1,29 @@ +package controller; + +import java.util.Arrays; +import java.util.function.Consumer; + +public enum Command { + ORDER(1, ChickenController::order), + PAY(2, ChickenController::pay), + EXIT(3, chickenController -> System.exit(0)); + + private final int number; + private final Consumer consumer; + + Command(int number, Consumer consumer) { + this.number = number; + this.consumer = consumer; + } + + public static Command findByNumber(int number) { + return Arrays.stream(values()) + .filter(command -> command.number == number) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("값에 해당하는 명령이 없습니다. number : " + number)); + } + + public void execute(ChickenController chickenController) { + consumer.accept(chickenController); + } +} diff --git a/src/main/java/controller/PageController.java b/src/main/java/controller/PageController.java new file mode 100644 index 00000000..12955aa2 --- /dev/null +++ b/src/main/java/controller/PageController.java @@ -0,0 +1,22 @@ +package controller; + +import view.InputView; + +public class PageController { + private final InputView inputView; + private final ChickenController chickenController; + + public PageController(InputView inputView, ChickenController chickenController) { + this.inputView = inputView; + this.chickenController = chickenController; + } + + public void run() { + try { + Command command = Command.findByNumber(inputView.inputCommandNumber()); + command.execute(chickenController); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/domain/Category.java b/src/main/java/domain/Category.java deleted file mode 100644 index 2e2c07ae..00000000 --- a/src/main/java/domain/Category.java +++ /dev/null @@ -1,17 +0,0 @@ -package domain; - -public enum Category { - CHICKEN("치킨"), - BEVERAGE("음료"); - - private final String name; - - Category(final String name) { - this.name = name; - } - - @Override - public String toString() { - return "[" + name + "]"; - } -} diff --git a/src/main/java/domain/Menu.java b/src/main/java/domain/Menu.java deleted file mode 100644 index 9f5a078e..00000000 --- a/src/main/java/domain/Menu.java +++ /dev/null @@ -1,20 +0,0 @@ -package domain; - -public class Menu { - private final int number; - private final String name; - private final Category category; - private final int price; - - public Menu(final int number, final String name, final Category category, final int price) { - this.number = number; - this.name = name; - this.category = category; - this.price = price; - } - - @Override - public String toString() { - return category + " " + number + " - " + name + " : " + price + "원"; - } -} diff --git a/src/main/java/domain/MenuRepository.java b/src/main/java/domain/MenuRepository.java deleted file mode 100644 index fd3fe537..00000000 --- a/src/main/java/domain/MenuRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package domain; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class MenuRepository { - private static final List menus = new ArrayList<>(); - - static { - menus.add(new Menu(1, "후라이드", Category.CHICKEN, 16_000)); - menus.add(new Menu(2, "양념치킨", Category.CHICKEN, 16_000)); - menus.add(new Menu(3, "반반치킨", Category.CHICKEN, 16_000)); - menus.add(new Menu(4, "통구이", Category.CHICKEN, 16_000)); - menus.add(new Menu(5, "간장치킨", Category.CHICKEN, 17_000)); - menus.add(new Menu(6, "순살치킨", Category.CHICKEN, 17_000)); - menus.add(new Menu(21, "콜라", Category.BEVERAGE, 1_000)); - menus.add(new Menu(22, "사이다", Category.BEVERAGE, 1_000)); - } - - public static List menus() { - return Collections.unmodifiableList(menus); - } -} diff --git a/src/main/java/domain/Payment.java b/src/main/java/domain/Payment.java new file mode 100644 index 00000000..f0569eb2 --- /dev/null +++ b/src/main/java/domain/Payment.java @@ -0,0 +1,31 @@ +package domain; + +import java.util.Arrays; + +public enum Payment { + CARD(1, price -> price), + CASH(2, price -> (int)(0.95 * price)); + + private final int number; + private final PricingDiscountStrategy pricingDiscountStrategy; + + Payment(int number, PricingDiscountStrategy pricingDiscountStrategy) { + this.number = number; + this.pricingDiscountStrategy = pricingDiscountStrategy; + } + + public static Payment findByNumber(int number) { + return Arrays.stream(values()) + .filter(payment -> payment.number == number) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당하는 결제 수단이 존재하지 않습니다. number : " + number)); + } + + public int calculatePay(int price) { + return pricingDiscountStrategy.calculate(price); + } + + private interface PricingDiscountStrategy { + int calculate(int price); + } +} diff --git a/src/main/java/domain/Table.java b/src/main/java/domain/Table.java deleted file mode 100644 index 500c517e..00000000 --- a/src/main/java/domain/Table.java +++ /dev/null @@ -1,14 +0,0 @@ -package domain; - -public class Table { - private final int number; - - public Table(final int number) { - this.number = number; - } - - @Override - public String toString() { - return Integer.toString(number); - } -} diff --git a/src/main/java/domain/TableRepository.java b/src/main/java/domain/TableRepository.java deleted file mode 100644 index c9c791e2..00000000 --- a/src/main/java/domain/TableRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package domain; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class TableRepository { - private static final List
tables = new ArrayList<>(); - - static { - tables.add(new Table(1)); - tables.add(new Table(2)); - tables.add(new Table(3)); - tables.add(new Table(5)); - tables.add(new Table(6)); - tables.add(new Table(8)); - } - - public static List
tables() { - return Collections.unmodifiableList(tables); - } -} diff --git a/src/main/java/domain/menu/Category.java b/src/main/java/domain/menu/Category.java new file mode 100644 index 00000000..2376b44c --- /dev/null +++ b/src/main/java/domain/menu/Category.java @@ -0,0 +1,21 @@ +package domain.menu; + +public enum Category { + CHICKEN("치킨"), + BEVERAGE("음료"); + + private final String name; + + Category(final String name) { + this.name = name; + } + + @Override + public String toString() { + return "[" + name + "]"; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/domain/menu/Menu.java b/src/main/java/domain/menu/Menu.java new file mode 100644 index 00000000..2a96d49b --- /dev/null +++ b/src/main/java/domain/menu/Menu.java @@ -0,0 +1,50 @@ +package domain.menu; + +import domain.order.Amount; + +public class Menu { + private final int number; + private final String name; + private final Category category; + private final int price; + + public Menu(final int number, final String name, final Category category, final int price) { + this.number = number; + this.name = name; + this.category = category; + this.price = price; + } + + public boolean isSameNumber(int other) { + return number == other; + } + + public boolean isSameCategory(Category other) { + return category == other; + } + + public int calculateMultiplePrice(Amount amount) { + return amount.multiply(price); + } + + public int getNumber() { + return number; + } + + public String getName() { + return name; + } + + public String getCategoryName() { + return category.getName(); + } + + public int getPrice() { + return price; + } + + @Override + public String toString() { + return category + " " + number + " - " + name + " : " + price + "원"; + } +} diff --git a/src/main/java/domain/menu/MenuRepository.java b/src/main/java/domain/menu/MenuRepository.java new file mode 100644 index 00000000..c3d73b5e --- /dev/null +++ b/src/main/java/domain/menu/MenuRepository.java @@ -0,0 +1,35 @@ +package domain.menu; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class MenuRepository { + private static final List menus = new ArrayList<>(); + + static { + menus.add(new Menu(1, "후라이드", Category.CHICKEN, 16_000)); + menus.add(new Menu(2, "양념치킨", Category.CHICKEN, 16_000)); + menus.add(new Menu(3, "반반치킨", Category.CHICKEN, 16_000)); + menus.add(new Menu(4, "통구이", Category.CHICKEN, 16_000)); + menus.add(new Menu(5, "간장치킨", Category.CHICKEN, 17_000)); + menus.add(new Menu(6, "순살치킨", Category.CHICKEN, 17_000)); + menus.add(new Menu(21, "콜라", Category.BEVERAGE, 1_000)); + menus.add(new Menu(22, "사이다", Category.BEVERAGE, 1_000)); + } + + public static List menus() { + return Collections.unmodifiableList(menus); + } + + public Optional findByNumber(int number) { + return menus.stream() + .filter(menu -> menu.isSameNumber(number)) + .findAny(); + } + + public List findAll() { + return Collections.unmodifiableList(menus); + } +} diff --git a/src/main/java/domain/order/Amount.java b/src/main/java/domain/order/Amount.java new file mode 100644 index 00000000..dba8af06 --- /dev/null +++ b/src/main/java/domain/order/Amount.java @@ -0,0 +1,63 @@ +package domain.order; + +import java.util.HashMap; +import java.util.Map; + +public class Amount { + private static final int MAX = 99; + private static final int MIN = 1; + + private final int amount; + + private Amount(int amount) { + validate(amount); + this.amount = amount; + } + + private static void validate(int amount) { + if (amount < MIN) { + throw new IllegalArgumentException("메뉴 주문은 " + MIN + "개보다 적을 수 없습니다. amount : " + amount); + } + if (amount > MAX) { + throw new IllegalArgumentException("메뉴 주문은 " + MAX + "개보다 많을 수 없습니다. amount : " + amount); + } + } + + public static Amount of(int amount) { + validate(amount); + return AmountCache.CACHE.get(amount); + } + + public static Amount sum(Amount first, Amount second) { + return Amount.of(first.amount + second.amount); + } + + public Amount add(int other) { + return Amount.of(amount + other); + } + + public int calculateSum(int other) { + return other + amount; + } + + public int multiply(int price) { + return amount * price; + } + + public int getAmount() { + return amount; + } + + private static class AmountCache { + private static Map CACHE = new HashMap<>(); + + static { + for (int i = MIN; i <= MAX; i++) { + CACHE.put(i, new Amount(i)); + } + } + + private AmountCache() { + } + } +} diff --git a/src/main/java/domain/order/Order.java b/src/main/java/domain/order/Order.java new file mode 100644 index 00000000..ed1eae65 --- /dev/null +++ b/src/main/java/domain/order/Order.java @@ -0,0 +1,58 @@ +package domain.order; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import domain.menu.Category; +import domain.menu.Menu; + +public class Order { + private final Map menuOrders; + + public Order(Map menuOrders) { + Objects.requireNonNull(menuOrders, "값이 존재하지 않습니다."); + this.menuOrders = menuOrders; + } + + public static Order empty() { + return new Order(new HashMap<>()); + } + + public int calculatePrice() { + return menuOrders.entrySet().stream() + .mapToInt(entry -> entry.getKey().calculateMultiplePrice(entry.getValue())) + .sum(); + } + + public void addMenu(Menu menu, Amount amount) { + menuOrders.merge(menu, amount, Amount::sum); + } + + public boolean isEmpty() { + return menuOrders.isEmpty(); + } + + public void clear() { + menuOrders.clear(); + } + + public int divideCategoryAmountByUnit(Category category, int unit) { + if (unit < 0) { + throw new IllegalArgumentException("unit의 단위는 0보다 커야 합니다. unit : " + unit); + } + return countAmountByCategory(category) / unit; + } + + private int countAmountByCategory(Category category) { + return menuOrders.keySet().stream() + .filter(menu -> menu.isSameCategory(category)) + .map(menuOrders::get) + .reduce(0, (subTotal, amount) -> amount.calculateSum(subTotal), Integer::sum); + } + + public Map getMenuOrders() { + return Collections.unmodifiableMap(menuOrders); + } +} diff --git a/src/main/java/domain/table/OrderAmountDiscount.java b/src/main/java/domain/table/OrderAmountDiscount.java new file mode 100644 index 00000000..a640664e --- /dev/null +++ b/src/main/java/domain/table/OrderAmountDiscount.java @@ -0,0 +1,25 @@ +package domain.table; + +import domain.menu.Category; +import domain.order.Order; + +public enum OrderAmountDiscount implements OrderDiscountStrategy { + TEN_CHICKEN_DISCOUNT(order -> (order.calculatePrice() - + order.divideCategoryAmountByUnit(Category.CHICKEN, Constants.TEN_CHICKEN) * Constants.TEN_CHICKEN_DISCOUNT)); + + private final OrderDiscountStrategy orderDiscountStrategy; + + OrderAmountDiscount(OrderDiscountStrategy orderDiscountStrategy) { + this.orderDiscountStrategy = orderDiscountStrategy; + } + + @Override + public int calculate(Order order) { + return orderDiscountStrategy.calculate(order); + } + + private static class Constants { + private static final int TEN_CHICKEN_DISCOUNT = 10_000; + private static final int TEN_CHICKEN = 10; + } +} diff --git a/src/main/java/domain/table/OrderDiscountStrategy.java b/src/main/java/domain/table/OrderDiscountStrategy.java new file mode 100644 index 00000000..cb12506a --- /dev/null +++ b/src/main/java/domain/table/OrderDiscountStrategy.java @@ -0,0 +1,7 @@ +package domain.table; + +import domain.order.Order; + +public interface OrderDiscountStrategy { + int calculate(Order order); +} diff --git a/src/main/java/domain/table/Table.java b/src/main/java/domain/table/Table.java new file mode 100644 index 00000000..e901852f --- /dev/null +++ b/src/main/java/domain/table/Table.java @@ -0,0 +1,58 @@ +package domain.table; + +import domain.menu.Menu; +import domain.order.Amount; +import domain.order.Order; + +public class Table { + private final int number; + private final Order order = Order.empty(); + + public Table(final int number) { + this.number = number; + } + + public boolean isSameNumber(int other) { + return number == other; + } + + public boolean isOrderEmpty() { + return order.isEmpty(); + } + + public void addMenu(Menu menu, Amount amount) { + order.addMenu(menu, amount); + } + + public void clearOrder() { + order.clear(); + } + + public int calculatePrice() { + return order.calculatePrice(); + } + + public int calculatePrice(OrderDiscountStrategy orderDiscountStrategy) { + return orderDiscountStrategy.calculate(order); + } + + public Order getOrderIfExist() { + if (isOrderEmpty()) { + throw new IllegalStateException("주문이 비어있습니다."); + } + return getOrder(); + } + + public int getNumber() { + return number; + } + + public Order getOrder() { + return order; + } + + @Override + public String toString() { + return Integer.toString(number); + } +} diff --git a/src/main/java/domain/table/TableRepository.java b/src/main/java/domain/table/TableRepository.java new file mode 100644 index 00000000..07472769 --- /dev/null +++ b/src/main/java/domain/table/TableRepository.java @@ -0,0 +1,33 @@ +package domain.table; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class TableRepository { + private static final List
tables = new ArrayList<>(); + + static { + tables.add(new Table(1)); + tables.add(new Table(2)); + tables.add(new Table(3)); + tables.add(new Table(5)); + tables.add(new Table(6)); + tables.add(new Table(8)); + } + + public static List
tables() { + return Collections.unmodifiableList(tables); + } + + public List
findAll() { + return Collections.unmodifiableList(tables); + } + + public Optional
findByNumber(int number) { + return tables.stream() + .filter(table -> table.isSameNumber(number)) + .findAny(); + } +} diff --git a/src/main/java/service/MenuService.java b/src/main/java/service/MenuService.java new file mode 100644 index 00000000..84cf6cc4 --- /dev/null +++ b/src/main/java/service/MenuService.java @@ -0,0 +1,18 @@ +package service; + +import java.util.List; + +import domain.menu.MenuRepository; +import service.dto.MenuResponse; + +public class MenuService { + private final MenuRepository menuRepository; + + public MenuService(MenuRepository menuRepository) { + this.menuRepository = menuRepository; + } + + public List findAllMenus() { + return MenuResponse.listOf(menuRepository.findAll()); + } +} diff --git a/src/main/java/service/PaymentService.java b/src/main/java/service/PaymentService.java new file mode 100644 index 00000000..099dd353 --- /dev/null +++ b/src/main/java/service/PaymentService.java @@ -0,0 +1,30 @@ +package service; + +import domain.Payment; +import domain.table.OrderAmountDiscount; +import domain.table.Table; +import domain.table.TableRepository; +import service.dto.PayRequest; +import service.dto.PayResponse; + +public class PaymentService { + private final TableRepository tableRepository; + + public PaymentService(TableRepository tableRepository) { + this.tableRepository = tableRepository; + } + + public PayResponse payOrder(PayRequest payRequest) { + Table table = findTableByNumber(payRequest.getTableNumber()); + Payment payment = Payment.findByNumber(payRequest.getPaymentNumber()); + PayResponse payResponse = new PayResponse( + payment.calculatePay(table.calculatePrice(OrderAmountDiscount.TEN_CHICKEN_DISCOUNT))); + table.clearOrder(); + return payResponse; + } + + private Table findTableByNumber(int number) { + return tableRepository.findByNumber(number) + .orElseThrow(() -> new IllegalArgumentException("해당하는 번호의 테이블이 없습니다. number : " + number)); + } +} diff --git a/src/main/java/service/TableOrderService.java b/src/main/java/service/TableOrderService.java new file mode 100644 index 00000000..e4455b10 --- /dev/null +++ b/src/main/java/service/TableOrderService.java @@ -0,0 +1,47 @@ +package service; + +import java.util.List; + +import domain.menu.Menu; +import domain.menu.MenuRepository; +import domain.order.Amount; +import domain.table.Table; +import domain.table.TableRepository; +import service.dto.MenuOrderResponse; +import service.dto.TableOrderRequest; +import service.dto.TableOrderResponse; +import service.dto.TableResponse; + +public class TableOrderService { + private final MenuRepository menuRepository; + private final TableRepository tableRepository; + + public TableOrderService(TableRepository tableRepository, MenuRepository menuRepository) { + this.menuRepository = menuRepository; + this.tableRepository = tableRepository; + } + + public TableOrderResponse findTableOrder(int number) { + return new TableOrderResponse(number, MenuOrderResponse.listOf(findTableByNumber(number).getOrderIfExist())); + } + + public List findAllTables() { + return TableResponse.listOf(tableRepository.findAll()); + } + + public void addOrder(TableOrderRequest tableOrderRequest) { + Table table = findTableByNumber(tableOrderRequest.getTableNumber()); + table.addMenu(findMenuByNumber(tableOrderRequest.getMenuNumber()), + Amount.of(tableOrderRequest.getMenuAmount())); + } + + private Menu findMenuByNumber(int number) { + return menuRepository.findByNumber(number) + .orElseThrow(() -> new IllegalArgumentException("해당하는 번호의 메뉴가 없습니다. number : " + number)); + } + + private Table findTableByNumber(int number) { + return tableRepository.findByNumber(number) + .orElseThrow(() -> new IllegalArgumentException("해당하는 번호의 테이블이 없습니다. number :" + number)); + } +} diff --git a/src/main/java/service/dto/MenuOrderResponse.java b/src/main/java/service/dto/MenuOrderResponse.java new file mode 100644 index 00000000..c7e914a4 --- /dev/null +++ b/src/main/java/service/dto/MenuOrderResponse.java @@ -0,0 +1,38 @@ +package service.dto; + +import java.util.List; +import java.util.stream.Collectors; + +import domain.order.Order; + +public class MenuOrderResponse { + private String menuName; + private int amount; + private int price; + + private MenuOrderResponse(String menuName, int amount, int price) { + this.menuName = menuName; + this.amount = amount; + this.price = price; + } + + public static List listOf(Order order) { + return order.getMenuOrders().entrySet() + .stream() + .map(entry -> new MenuOrderResponse(entry.getKey().getName(), entry.getValue().getAmount(), + entry.getKey().getPrice())) + .collect(Collectors.toList()); + } + + public String getMenuName() { + return menuName; + } + + public int getAmount() { + return amount; + } + + public int getPrice() { + return price; + } +} diff --git a/src/main/java/service/dto/MenuResponse.java b/src/main/java/service/dto/MenuResponse.java new file mode 100644 index 00000000..f9838513 --- /dev/null +++ b/src/main/java/service/dto/MenuResponse.java @@ -0,0 +1,46 @@ +package service.dto; + +import java.util.List; +import java.util.stream.Collectors; + +import domain.menu.Menu; + +public class MenuResponse { + private String categoryName; + private int number; + private String name; + private int price; + + private MenuResponse(String categoryName, int number, String name, int price) { + this.categoryName = categoryName; + this.number = number; + this.name = name; + this.price = price; + } + + public static List listOf(List menus) { + return menus.stream() + .map(MenuResponse::of) + .collect(Collectors.toList()); + } + + private static MenuResponse of(Menu menu) { + return new MenuResponse(menu.getCategoryName(), menu.getNumber(), menu.getName(), menu.getPrice()); + } + + public String getCategoryName() { + return categoryName; + } + + public int getNumber() { + return number; + } + + public String getName() { + return name; + } + + public int getPrice() { + return price; + } +} diff --git a/src/main/java/service/dto/PayRequest.java b/src/main/java/service/dto/PayRequest.java new file mode 100644 index 00000000..0bafeaa0 --- /dev/null +++ b/src/main/java/service/dto/PayRequest.java @@ -0,0 +1,19 @@ +package service.dto; + +public class PayRequest { + private int tableNumber; + private int paymentNumber; + + public PayRequest(int tableNumber, int paymentNumber) { + this.tableNumber = tableNumber; + this.paymentNumber = paymentNumber; + } + + public int getTableNumber() { + return tableNumber; + } + + public int getPaymentNumber() { + return paymentNumber; + } +} diff --git a/src/main/java/service/dto/PayResponse.java b/src/main/java/service/dto/PayResponse.java new file mode 100644 index 00000000..94a4f35c --- /dev/null +++ b/src/main/java/service/dto/PayResponse.java @@ -0,0 +1,13 @@ +package service.dto; + +public class PayResponse { + private int paymentAmount; + + public PayResponse(int paymentAmount) { + this.paymentAmount = paymentAmount; + } + + public int getPaymentAmount() { + return paymentAmount; + } +} diff --git a/src/main/java/service/dto/TableOrderRequest.java b/src/main/java/service/dto/TableOrderRequest.java new file mode 100644 index 00000000..d1524918 --- /dev/null +++ b/src/main/java/service/dto/TableOrderRequest.java @@ -0,0 +1,25 @@ +package service.dto; + +public class TableOrderRequest { + private int tableNumber; + private int menuNumber; + private int menuAmount; + + public TableOrderRequest(int tableNumber, int menuNumber, int menuAmount) { + this.tableNumber = tableNumber; + this.menuNumber = menuNumber; + this.menuAmount = menuAmount; + } + + public int getTableNumber() { + return tableNumber; + } + + public int getMenuNumber() { + return menuNumber; + } + + public int getMenuAmount() { + return menuAmount; + } +} diff --git a/src/main/java/service/dto/TableOrderResponse.java b/src/main/java/service/dto/TableOrderResponse.java new file mode 100644 index 00000000..075e96ff --- /dev/null +++ b/src/main/java/service/dto/TableOrderResponse.java @@ -0,0 +1,21 @@ +package service.dto; + +import java.util.List; + +public class TableOrderResponse { + private int tableNumber; + private List menuOrderResponses; + + public TableOrderResponse(int tableNumber, List menuOrderResponses) { + this.tableNumber = tableNumber; + this.menuOrderResponses = menuOrderResponses; + } + + public int getTableNumber() { + return tableNumber; + } + + public List getMenuOrderResponses() { + return menuOrderResponses; + } +} diff --git a/src/main/java/service/dto/TableResponse.java b/src/main/java/service/dto/TableResponse.java new file mode 100644 index 00000000..e353e10d --- /dev/null +++ b/src/main/java/service/dto/TableResponse.java @@ -0,0 +1,34 @@ +package service.dto; + +import java.util.List; +import java.util.stream.Collectors; + +import domain.table.Table; + +public class TableResponse { + private int number; + private boolean isOrdering; + + private TableResponse(int number, boolean isOrdering) { + this.number = number; + this.isOrdering = isOrdering; + } + + public static TableResponse of(Table table) { + return new TableResponse(table.getNumber(), !table.getOrder().isEmpty()); + } + + public static List listOf(List
tables) { + return tables.stream() + .map(TableResponse::of) + .collect(Collectors.toList()); + } + + public int getNumber() { + return number; + } + + public boolean isOrdering() { + return isOrdering; + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index d5db24e2..769bd0d7 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -3,10 +3,37 @@ import java.util.Scanner; public class InputView { - private static final Scanner scanner = new Scanner(System.in); + private final Scanner scanner; - public static int inputTableNumber() { + public InputView(Scanner scanner) { + this.scanner = scanner; + } + + public int inputTableNumber() { System.out.println("## 주문할 테이블을 선택하세요."); return scanner.nextInt(); } + + public int inputMenuNumber() { + System.out.println("## 등록할 메뉴를 선택하세요."); + return scanner.nextInt(); + } + + public int inputAmount() { + System.out.println("## 메뉴의 수량을 입력하세요."); + return scanner.nextInt(); + } + + public int inputPaymentNumber() { + System.out.println("## 신용 카드는 1번, 현금은 2번"); + return scanner.nextInt(); + } + + public int inputCommandNumber() { + System.out.println("## 메인 화면"); + System.out.println("1 - 주문 등록"); + System.out.println("2 - 결제하기"); + System.out.println("3 - 프로그램 종료"); + return scanner.nextInt(); + } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index edb25cc4..8cdf847a 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,40 +1,77 @@ package view; -import domain.Menu; -import domain.Table; - import java.util.List; +import service.dto.MenuOrderResponse; +import service.dto.MenuResponse; +import service.dto.PayResponse; +import service.dto.TableOrderResponse; +import service.dto.TableResponse; + public class OutputView { - private static final String TOP_LINE = "┌ ─ ┐"; - private static final String TABLE_FORMAT = "| %s |"; - private static final String BOTTOM_LINE = "└ ─ ┘"; - - public static void printTables(final List
tables) { - System.out.println("## 테이블 목록"); - final int size = tables.size(); - printLine(TOP_LINE, size); - printTableNumbers(tables); - printLine(BOTTOM_LINE, size); - } - - public static void printMenus(final List menus) { - for (final Menu menu : menus) { - System.out.println(menu); - } - } - - private static void printLine(final String line, final int count) { - for (int index = 0; index < count; index++) { - System.out.print(line); - } - System.out.println(); - } - - private static void printTableNumbers(final List
tables) { - for (final Table table : tables) { - System.out.printf(TABLE_FORMAT, table); - } - System.out.println(); - } + private static final String TOP_LINE = "┌ ─ ┐"; + private static final String TABLE_FORMAT = "| %s |"; + private static final String BOTTOM_LINE = "└ ─ ┘"; + private static final String BOTTOM_LINE_ORDERED = "└ ₩ ┘"; + + public void printTables(final List tables) { + System.out.println("## 테이블 목록"); + final int size = tables.size(); + printLine(size); + printTableNumbers(tables); + printBottomLine(tables); + } + + public void printMenus(final List menus) { + for (final MenuResponse menu : menus) { + System.out.println( + "[" + menu.getCategoryName() + "]" + " " + menu.getNumber() + " - " + menu.getName() + " : " + + menu.getPrice() + "원"); + } + } + + private void printLine(final int count) { + for (int index = 0; index < count; index++) { + System.out.print(OutputView.TOP_LINE); + } + System.out.println(); + } + + private void printBottomLine(List tableResponses) { + for (TableResponse tableResponse : tableResponses) { + printBottom(tableResponse); + } + System.out.println(); + } + + private void printBottom(TableResponse tableResponse) { + if (tableResponse.isOrdering()) { + System.out.print(BOTTOM_LINE_ORDERED); + return; + } + System.out.print(BOTTOM_LINE); + } + + private void printTableNumbers(final List tables) { + for (final TableResponse table : tables) { + System.out.printf(TABLE_FORMAT, table.getNumber()); + } + System.out.println(); + } + + public void printOrder(TableOrderResponse menuOrderResponses) { + System.out.println("## 주문 내역"); + System.out.println("메뉴 수량 금액"); + for (MenuOrderResponse menuOrderResponse : menuOrderResponses.getMenuOrderResponses()) { + System.out.println(menuOrderResponse.getMenuName() + " " + menuOrderResponse.getAmount() + " " + + menuOrderResponse.getPrice()); + } + System.out.println(); + System.out.printf("## %d번 테이블의 결제를 진행합니다.\n", menuOrderResponses.getTableNumber()); + } + + public void printPaymentAmount(PayResponse payResponse) { + System.out.println("## 최종 결제할 금액"); + System.out.printf("%d원\n", payResponse.getPaymentAmount()); + } } diff --git a/src/test/java/domain/PaymentTest.java b/src/test/java/domain/PaymentTest.java new file mode 100644 index 00000000..dfd89148 --- /dev/null +++ b/src/test/java/domain/PaymentTest.java @@ -0,0 +1,20 @@ +package domain; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PaymentTest { + @Test + @DisplayName("결제 수단에 따른 할인액을 계산") + void calculatePay() { + assertThat(Payment.CASH.calculatePay(10_000)).isEqualTo(9_500); + } + + @Test + @DisplayName("숫자에 해당하는 결제 방식을 불러오는 지 확인") + void findByNumber() { + assertThat(Payment.findByNumber(1)).isEqualTo(Payment.CARD); + } +} \ No newline at end of file diff --git a/src/test/java/domain/menu/MenuRepositoryTest.java b/src/test/java/domain/menu/MenuRepositoryTest.java new file mode 100644 index 00000000..6f311662 --- /dev/null +++ b/src/test/java/domain/menu/MenuRepositoryTest.java @@ -0,0 +1,22 @@ +package domain.menu; + +import static org.assertj.core.api.Assertions.*; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MenuRepositoryTest { + @Test + @DisplayName("생성 테스트") + void constructor() { + Assertions.assertThat(new MenuRepository()).isNotNull(); + } + + @Test + @DisplayName("등록된 메뉴를 number에 따라 찾을 수 있는 지 확인한다.") + void findByNumber() { + MenuRepository menuRepository = new MenuRepository(); + assertThat(menuRepository.findByNumber(1).get()).isEqualTo(MenuRepository.menus().get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/domain/menu/MenuTest.java b/src/test/java/domain/menu/MenuTest.java new file mode 100644 index 00000000..e41d9541 --- /dev/null +++ b/src/test/java/domain/menu/MenuTest.java @@ -0,0 +1,31 @@ +package domain.menu; + +import static org.assertj.core.api.Assertions.*; + +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 domain.order.Amount; + +class MenuTest { + @Test + @DisplayName("같은 number를 가지는 지 확인") + void isSameNumber() { + assertThat(new Menu(1, "맛치킨", Category.CHICKEN, 10_000).isSameNumber(1)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"CHICKEN, true", "BEVERAGE, false"}) + void isSameCategory(Category category, boolean expected) { + assertThat(new Menu(1, "치킨일까", category, 10_000).isSameCategory(Category.CHICKEN)).isEqualTo(expected); + } + + @Test + @DisplayName("주문 수량에 맞게 가격을 계산하는 기능") + void calculateMultiplePrice() { + assertThat(new Menu(1, "치킨", Category.CHICKEN, 12_000).calculateMultiplePrice(Amount.of(10))) + .isEqualTo(120_000); + } +} \ No newline at end of file diff --git a/src/test/java/domain/order/AmountTest.java b/src/test/java/domain/order/AmountTest.java new file mode 100644 index 00000000..c00d6393 --- /dev/null +++ b/src/test/java/domain/order/AmountTest.java @@ -0,0 +1,55 @@ +package domain.order; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class AmountTest { + @Test + @DisplayName("생성 테스트") + void of() { + assertThat(Amount.of(1)).isNotNull(); + } + + @Test + @DisplayName("같은 값이면 같은 객체인지 확인") + void ofEqualObject() { + assertThat(Amount.of(1)).isEqualTo(Amount.of(1)); + } + + @ParameterizedTest + @DisplayName("생성 테스트 - 범위를 벗어날 시 예외 처리") + @ValueSource(ints = {0, 100}) + void ofNegativeException(int amount) { + assertThatThrownBy(() -> Amount.of(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("메뉴 주문은"); + } + + @Test + @DisplayName("주문 수량을 더하는 기능") + void add() { + assertThat(Amount.of(1).add(10)).isEqualTo(Amount.of(11)); + } + + @Test + @DisplayName("주어진 값에 양을 곱하는 기능") + void multiply() { + assertThat(Amount.of(10).multiply(100)).isEqualTo(1_000); + } + + @Test + @DisplayName("서로 다른 Amount를 더하는 기능") + void sum() { + assertThat(Amount.sum(Amount.of(10), Amount.of(5))).isEqualTo(Amount.of(15)); + } + + @Test + @DisplayName("서른 다른 Amount를 더했을 때 값을 나타내는 기능") + void calculateSum() { + assertThat(Amount.of(99).calculateSum(10)).isEqualTo(109); + } +} diff --git a/src/test/java/domain/order/OrderTest.java b/src/test/java/domain/order/OrderTest.java new file mode 100644 index 00000000..0d5bd7c6 --- /dev/null +++ b/src/test/java/domain/order/OrderTest.java @@ -0,0 +1,65 @@ +package domain.order; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import domain.menu.Category; +import domain.menu.Menu; + +public class OrderTest { + private Map menuOrders = new HashMap<>(); + + @BeforeEach + void setUp() { + menuOrders.put(new Menu(1, "가", Category.CHICKEN, 10_000), Amount.of(10)); + menuOrders.put(new Menu(2, "나", Category.CHICKEN, 15_000), Amount.of(5)); + } + + @Test + @DisplayName("생성 테스트") + void constructor() { + assertThat(new Order(menuOrders)).isNotNull(); + } + + @Test + @DisplayName("빈 주문 내역을 반환") + void empty() { + assertThat(Order.empty().isEmpty()).isTrue(); + } + + @Test + @DisplayName("주문 내역의 총 가격을 계산") + void calculateOrderPrice() { + Order order = new Order(menuOrders); + assertThat(order.calculatePrice()).isEqualTo(175_000); + } + + @Test + @DisplayName("새로운 주문 내역을 추가") + void add() { + Order order = new Order(menuOrders); + order.addMenu(new Menu(3, "다", Category.BEVERAGE, 2_000), Amount.of(2)); + assertThat(order.getMenuOrders()).hasSize(3); + } + + @Test + @DisplayName("주문 내역이 비어있는 지 확인") + void isEmpty() { + assertThat(new Order(Collections.emptyMap()).isEmpty()).isTrue(); + } + + @Test + @DisplayName("주문 내역 초기화") + void clear() { + Order order = new Order(menuOrders); + order.clear(); + assertThat(order.isEmpty()).isTrue(); + } +} diff --git a/src/test/java/domain/table/OrderAmountDiscountTest.java b/src/test/java/domain/table/OrderAmountDiscountTest.java new file mode 100644 index 00000000..4d3ea5c2 --- /dev/null +++ b/src/test/java/domain/table/OrderAmountDiscountTest.java @@ -0,0 +1,20 @@ +package domain.table; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import domain.menu.Category; +import domain.menu.Menu; +import domain.order.Amount; + +class OrderAmountDiscountTest { + @Test + @DisplayName("치킨 10개당 10000원 할인이 제대로 되는지 확인") + void calculate() { + Table table = new Table(1); + table.addMenu(new Menu(1, "치킨", Category.CHICKEN, 15_000), Amount.of(10)); + assertThat(table.calculatePrice(OrderAmountDiscount.TEN_CHICKEN_DISCOUNT)).isEqualTo(140_000); + } +} \ No newline at end of file diff --git a/src/test/java/domain/table/TableRepositoryTest.java b/src/test/java/domain/table/TableRepositoryTest.java new file mode 100644 index 00000000..eb993c69 --- /dev/null +++ b/src/test/java/domain/table/TableRepositoryTest.java @@ -0,0 +1,15 @@ +package domain.table; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class TableRepositoryTest { + @Test + @DisplayName("등록된 테이블을 number에 따라 찾을 수 있는 지 확인한다.") + void findByNumber() { + TableRepository tableRepository = new TableRepository(); + assertThat(tableRepository.findByNumber(1).get()).isEqualTo(TableRepository.tables().get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/domain/table/TableTest.java b/src/test/java/domain/table/TableTest.java new file mode 100644 index 00000000..77f9c99f --- /dev/null +++ b/src/test/java/domain/table/TableTest.java @@ -0,0 +1,77 @@ +package domain.table; + +import static org.assertj.core.api.Assertions.*; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import domain.menu.Category; +import domain.menu.Menu; +import domain.menu.MenuRepository; +import domain.order.Amount; + +class TableTest { + @Test + @DisplayName("테이블이 주어진 번호에 해당하는 지 테스트") + void isSameNumber() { + Assertions.assertThat(new Table(1).isSameNumber(1)).isTrue(); + } + + @Test + @DisplayName("테이블의 주문이 비어있는 지 확인") + void isOrderEmpty() { + Table table = new Table(1); + table.isOrderEmpty(); + } + + @Test + @DisplayName("주문 정보를 테이블에 입력할 수 있는 지 확인") + void addMenu() { + Table table = new Table(1); + table.addMenu(MenuRepository.menus().get(0), Amount.of(10)); + assertThat(table.isOrderEmpty()).isFalse(); + } + + @Test + @DisplayName("주문 정보를 초기화하는 지 확인") + void clearOrder() { + Table table = new Table(1); + table.addMenu(MenuRepository.menus().get(0), Amount.of(10)); + table.clearOrder(); + assertThat(table.isOrderEmpty()).isTrue(); + } + + @Test + @DisplayName("테이블의 주문 내역을 계산") + void calculateDiscountPrice() { + Table table = new Table(1); + table.addMenu(new Menu(1, "가", Category.CHICKEN, 10_000), Amount.of(10)); + assertThat(table.calculatePrice()).isEqualTo(100_000); + } + + @Test + @DisplayName("테이블의 주문 내역을 할인 정책에 따라 계산") + void calculateDiscountPriceWithDiscount() { + Table table = new Table(1); + table.addMenu(new Menu(1, "가", Category.CHICKEN, 10_000), Amount.of(10)); + assertThat(table.calculatePrice(new TestOrderDiscountStrategy())).isEqualTo(50_000); + } + + @Test + @DisplayName("테이블의 주문 내역이 있는 경우 Order를 반환한다.") + void getOrderIfExist() { + Table table = new Table(1); + table.addMenu(new Menu(1, "가", Category.CHICKEN, 10_000), Amount.of(10)); + assertThat(table.getOrderIfExist()).isNotNull(); + } + + @Test + @DisplayName("테이블의 주문 내역이 없는 경우 예외를 발생시킨다.") + void getOrderIfExistException() { + Table table = new Table(1); + assertThatThrownBy(table::getOrderIfExist) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("비어"); + } +} \ No newline at end of file diff --git a/src/test/java/domain/table/TestOrderDiscountStrategy.java b/src/test/java/domain/table/TestOrderDiscountStrategy.java new file mode 100644 index 00000000..c6abeb24 --- /dev/null +++ b/src/test/java/domain/table/TestOrderDiscountStrategy.java @@ -0,0 +1,10 @@ +package domain.table; + +import domain.order.Order; + +public class TestOrderDiscountStrategy implements OrderDiscountStrategy { + @Override + public int calculate(Order order) { + return (int)(order.calculatePrice() * 0.5); + } +} diff --git a/src/test/java/empty.txt b/src/test/java/empty.txt deleted file mode 100644 index e69de29b..00000000