diff --git a/docs/README.md b/docs/README.md index e69de29b..ebe44352 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,56 @@ +# ๐Ÿ’ช ํ”„๋กœ์ ํŠธ ๊ฐœ์š” + +# ๐Ÿ“ ๊ตฌํ˜„ ๊ธฐ๋Šฅ ๋ชฉ๋ก + +### ์‹๋‹น ๋ฐฉ๋ฌธ ๋‚ ์งœ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ + +- [x] `์•ˆ๋…•ํ•˜์„ธ์š”! ์šฐํ…Œ์ฝ” ์‹๋‹น 12์›” ์ด๋ฒคํŠธ ํ”Œ๋ž˜๋„ˆ์ž…๋‹ˆ๋‹ค.`๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] `12์›” ์ค‘ ์‹๋‹น ์˜ˆ์ƒ ๋ฐฉ๋ฌธ ๋‚ ์งœ๋Š” ์–ธ์ œ์ธ๊ฐ€์š”? (์ˆซ์ž๋งŒ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”!)`๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] ์‹๋‹น ์˜ˆ์ƒ ๋ฐฉ๋ฌธ ๋‚ ์งœ๋ฅผ ์ž…๋ ฅํ•œ๋‹ค. + - [x] 1 ์ด์ƒ 31 ์ดํ•˜์˜ ์ˆซ์ž ์ž…๋ ฅ์ž„์„ ๊ฒ€์ฆํ•œ๋‹ค. + +### ์ฃผ๋ฌธํ•  ๋ฉ”๋‰ด์™€ ๊ฐœ์ˆ˜๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ + +- [x] `์ฃผ๋ฌธํ•˜์‹ค ๋ฉ”๋‰ด๋ฅผ ๋ฉ”๋‰ด์™€ ๊ฐœ์ˆ˜๋ฅผ ์•Œ๋ ค ์ฃผ์„ธ์š”. (e.g. ํ•ด์‚ฐ๋ฌผํŒŒ์Šคํƒ€-2,๋ ˆ๋“œ์™€์ธ-1,์ดˆ์ฝ”์ผ€์ดํฌ-1)`๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] `ํƒ€ํŒŒ์Šค-1,์ œ๋กœ์ฝœ๋ผ-1`์™€ ๊ฐ™์ด ๋ฉ”๋‰ด์™€ ๊ฐœ์ˆ˜๋ฅผ ์ž…๋ ฅํ•œ๋‹ค. + - [x] ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์œผ๋กœ ์ž…๋ ฅ๋˜์—ˆ์Œ์„ ๊ฒ€์ฆํ•œ๋‹ค. + - [x] ์Œ๋ฃŒ๋งŒ ์ฃผ๋ฌธ๋œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹˜์„ ๊ฒ€์ฆํ•œ๋‹ค. + - [x] ์ค‘๋ณต๋˜๋Š” ๋ฉ”๋‰ด๊ฐ€ ์ž…๋ ฅ๋˜์ง€ ์•Š์•˜์Œ์„ ๊ฒ€์ฆํ•œ๋‹ค. + - [x] 20๊ฐœ ์ดํ•˜์˜ ๋ฉ”๋‰ด์ž„์„ ๊ฒ€์ฆํ•œ๋‹ค. + +### ์ด๋ฒคํŠธ๋ฅผ ์ ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ + +- [ ] ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์ด 10,000์› ์ด์ƒ์ธ ๊ฒฝ์šฐ์—๋งŒ ์ด๋ฒคํŠธ๋ฅผ ์ ์šฉํ•œ๋‹ค. +- [x] ํฌ๋ฆฌ์Šค๋งˆ์Šค ๋””๋ฐ์ด ํ• ์ธ ์ด๋ฒคํŠธ๋ฅผ ์ ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค. + - [x] ์กฐ๊ฑด์€ ์ด๋ฒคํŠธ ๊ธฐ๊ฐ„์ด `2023.12.1 ~ 2023.12.25`์ธ ๊ฒฝ์šฐ์ด๋‹ค. + - [x] ํ• ์ธ ๊ธˆ์•ก์€ 1000์›์œผ๋กœ ์‹œ์ž‘ํ•˜์—ฌ 100์› ์”ฉ ์ฆ๊ฐ€ํ•œ๋‹ค. +- [x] ํ‰์ผ ํ• ์ธ ์ด๋ฒคํŠธ๋ฅผ ์ ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค. + - [x] ์กฐ๊ฑด์€ ์ผ์š”์ผ ~ ๋ชฉ์š”์ผ์ธ ๊ฒฝ์šฐ์ด๋‹ค. + - [x] ๋””์ €ํŠธ ๋ฉ”๋‰ด ํ•˜๋‚˜ ๋‹น 2,023์› ํ• ์ธํ•œ๋‹ค. +- [x] ์ฃผ๋ง ํ• ์ธ ์ด๋ฒคํŠธ๋ฅผ ์ ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค. + - [x] ์กฐ๊ฑด์€ ๊ธˆ์š”์ผ, ํ† ์š”์ผ์ธ ๊ฒฝ์šฐ์ด๋‹ค. + - [x] ๋ฉ”์ธ ๋ฉ”๋‰ด ํ•˜๋‚˜ ๋‹น 2,023์› ํ• ์ธํ•œ๋‹ค. +- [x] ํŠน๋ณ„ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. + - [x] ์กฐ๊ฑด์€ ๋‹ฌ๋ ฅ์— ๋ณ„์ด ํ‘œ์‹œ๋œ ๋‚ ์ธ ๊ฒฝ์šฐ์ด๋‹ค. + - [x] ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์—์„œ 1000์›์„ ํ• ์ธํ•œ๋‹ค. +- [x] ์ฆ์ •์ด๋ฒคํŠธ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. + - [x] ์กฐ๊ฑด์€ ํ• ์ธ ์ „ ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์ด 12๋งŒ์› ์ด์ƒ์ธ ๊ฒฝ์šฐ์ด๋‹ค. + - [x] ์ƒดํŽ˜์ธ 1๊ฐœ๋ฅผ ์ฆ์ •ํ•œ๋‹ค. + +### ์ฃผ๋ฌธ ๋ฉ”๋‰ด์™€ ํ• ์ธ ์ „ ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ์ถœ๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ + +- [x] `12์›” 26์ผ์— ์šฐํ…Œ์ฝ” ์‹๋‹น์—์„œ ๋ฐ›์„ ์ด๋ฒคํŠธ ํ˜œํƒ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ!`๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] `<์ฃผ๋ฌธ ๋ฉ”๋‰ด>`๋ฅผ ์ถœ๋ ฅํ•˜๊ณ , ์ฃผ๋ฌธํ•œ ๋ฉ”๋‰ด์™€ ๊ฐœ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] `<ํ• ์ธ ์ „ ์ด์ฃผ๋ฌธ ๊ธˆ์•ก>`๋ฅผ ์ถœ๋ ฅํ•˜๊ณ , ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ์ถœ๋ ฅํ•œ๋‹ค. + +### ์ด๋ฒคํŠธ ์ ์šฉ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ + +- [x] ์ฆ์ • ๋ฉ”๋‰ด์˜ ์ด๋ฆ„๊ณผ ๊ฐœ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] ์ด๋ฒคํŠธ ๋ณ„ ํ˜œํƒ ๋‚ด์—ญ์„ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] ์ด ํ˜œํƒ ๊ธˆ์•ก์„ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] ํ• ์ธ ํ›„ ์˜ˆ์ƒ ๊ฒฐ์ œ ๊ธˆ์•ก์„ ์ถœ๋ ฅํ•œ๋‹ค. + +### ๋ฐฐ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ + +- [x] ์ด ํ• ์ธ ๊ธˆ์•ก์œผ๋กœ ๋ฐฐ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. +- [x] ๋ฐฐ์ง€์˜ ์ด๋ฆ„์„ ์ถœ๋ ฅํ•œ๋‹ค. diff --git a/src/main/java/christmas/Application.java b/src/main/java/christmas/Application.java index b9ba6a2d..a6d296b3 100644 --- a/src/main/java/christmas/Application.java +++ b/src/main/java/christmas/Application.java @@ -1,7 +1,17 @@ package christmas; +import camp.nextstep.edu.missionutils.Console; +import christmas.controller.PromotionManager; +import christmas.view.InputView; +import christmas.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„ + PromotionManager promotionManager = new PromotionManager( + new InputView(), + new OutputView() + ); + promotionManager.run(); + Console.close(); } } diff --git a/src/main/java/christmas/controller/PromotionManager.java b/src/main/java/christmas/controller/PromotionManager.java new file mode 100644 index 00000000..a9c6134d --- /dev/null +++ b/src/main/java/christmas/controller/PromotionManager.java @@ -0,0 +1,112 @@ +package christmas.controller; + +import christmas.controller.dto.PromotionResult; +import christmas.controller.dto.PromotionsResult; +import christmas.domain.Badge; +import christmas.domain.Benefit; +import christmas.domain.Discount; +import christmas.domain.Orders; +import christmas.domain.constants.Promotion; +import christmas.view.InputView; +import christmas.view.OutputView; +import christmas.view.console.ConsoleWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +public class PromotionManager { + private final InputView inputView; + private final OutputView outputView; + + public PromotionManager(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + this.outputView.start(); + } + + public void run() { + int day = retry(inputView::readDay); + Orders orders = retry(inputView::readOrders); + int amount = printBeforePromotion(orders); + Optional result = applyPromotion(day, orders); + printAfterPromotion(amount, result); + } + + private int printBeforePromotion(Orders orders) { + outputView.printMenu(orders); + int amount = orders.calculateTotalPrice(); + outputView.printPrice(amount); + return amount; + } + + private void printAfterPromotion(int amount, Optional result) { + outputView.printGiftAndBenefit(result); + int benefit = calculateTotalBenefit(result); + outputView.printBenefitAmount(benefit); + outputView.printFinalPrice(calculateFinalPrice(amount, result)); + outputView.printBadge(calculateBadge(benefit)); + } + + private Badge calculateBadge(int benefit) { + return Badge.from(benefit); + } + + private int calculateFinalPrice(int amount, Optional result) { + if (result.isEmpty()) { + return amount; + } + int discount = (int) result.get().promotionResults() + .stream() + .map(PromotionResult::benefit) + .filter(element -> element instanceof Discount) + .mapToInt(element -> element.calculate()) + .sum(); + return amount - discount; + } + + private int calculateTotalBenefit(Optional result) { + if (result.isEmpty()) { + return 0; + } + return result.get().promotionResults() + .stream() + .mapToInt(element -> element.benefit().calculate()) + .sum(); + } + + private Optional applyPromotion(int day, Orders orders) { + if (orders.calculateTotalPrice() > 10000) { + return Optional.of( + getPromotionResult(day, orders) + ); + } + return Optional.empty(); + } + + private PromotionsResult getPromotionResult(int day, Orders orders) { + List promotionResults = new ArrayList<>(); + for (Promotion promotion : Promotion.values()) { + Optional benefit = promotion.getInstance().apply(day, orders); + if (benefit.isPresent()) { + promotionResults.add( + new PromotionResult( + promotion, + benefit.get() + ) + ); + } + } + return new PromotionsResult(promotionResults); + } + + private static T retry(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + ConsoleWriter.printlnMessage(e.getMessage()); + } + } + } +} diff --git a/src/main/java/christmas/controller/dto/PromotionResult.java b/src/main/java/christmas/controller/dto/PromotionResult.java new file mode 100644 index 00000000..99859fb5 --- /dev/null +++ b/src/main/java/christmas/controller/dto/PromotionResult.java @@ -0,0 +1,10 @@ +package christmas.controller.dto; + +import christmas.domain.Benefit; +import christmas.domain.constants.Promotion; + +public record PromotionResult( + Promotion promotion, + Benefit benefit +) { +} diff --git a/src/main/java/christmas/controller/dto/PromotionsResult.java b/src/main/java/christmas/controller/dto/PromotionsResult.java new file mode 100644 index 00000000..8a0cdc10 --- /dev/null +++ b/src/main/java/christmas/controller/dto/PromotionsResult.java @@ -0,0 +1,8 @@ +package christmas.controller.dto; + +import java.util.List; + +public record PromotionsResult( + List promotionResults +) { +} diff --git a/src/main/java/christmas/domain/Badge.java b/src/main/java/christmas/domain/Badge.java new file mode 100644 index 00000000..3744275d --- /dev/null +++ b/src/main/java/christmas/domain/Badge.java @@ -0,0 +1,29 @@ +package christmas.domain; + +import java.util.Arrays; +import java.util.Comparator; + +public enum Badge { + STAR("๋ณ„", 5000), + TREE("ํŠธ๋ฆฌ", 10000), + SANTA("์‚ฐํƒ€", 20000); + + private final String name; + private final int threshold; + + Badge(String name, int threshold) { + this.name = name; + this.threshold = threshold; + } + + public static Badge from(int price) { + return Arrays.stream(Badge.values()) + .filter(b -> price >= b.threshold) + .max(Comparator.comparingInt(b -> b.threshold)) + .orElse(null); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/christmas/domain/Benefit.java b/src/main/java/christmas/domain/Benefit.java new file mode 100644 index 00000000..2a5acf68 --- /dev/null +++ b/src/main/java/christmas/domain/Benefit.java @@ -0,0 +1,5 @@ +package christmas.domain; + +public interface Benefit { + int calculate(); +} diff --git a/src/main/java/christmas/domain/Discount.java b/src/main/java/christmas/domain/Discount.java new file mode 100644 index 00000000..18f63e20 --- /dev/null +++ b/src/main/java/christmas/domain/Discount.java @@ -0,0 +1,15 @@ +package christmas.domain; + +public class Discount implements Benefit { + private final int discount; + + public Discount(int discount) { + this.discount = discount; + + } + + @Override + public int calculate() { + return discount; + } +} diff --git a/src/main/java/christmas/domain/Gift.java b/src/main/java/christmas/domain/Gift.java new file mode 100644 index 00000000..48402f7b --- /dev/null +++ b/src/main/java/christmas/domain/Gift.java @@ -0,0 +1,26 @@ +package christmas.domain; + +import christmas.domain.constants.Menu; + +public class Gift implements Benefit { + private Menu menu; + private int count; + + public Gift(Menu menu, int count) { + this.menu = menu; + this.count = count; + } + + @Override + public int calculate() { + return menu.getPrice() * count; + } + + public Menu getMenu() { + return menu; + } + + public int getCount() { + return count; + } +} diff --git a/src/main/java/christmas/domain/Order.java b/src/main/java/christmas/domain/Order.java new file mode 100644 index 00000000..11279729 --- /dev/null +++ b/src/main/java/christmas/domain/Order.java @@ -0,0 +1,30 @@ +package christmas.domain; + +import christmas.domain.constants.Menu; +import christmas.domain.constants.MenuCategory; + +public class Order { + private Menu menu; + private int count; + + public Order(Menu menu, int count) { + this.menu = menu; + this.count = count; + } + + public int calculatePrice() { + return menu.getPrice() * count; + } + + public boolean isCategory(MenuCategory category) { + return menu.getCategory().equals(category); + } + + public Menu getMenu() { + return menu; + } + + public int getCount() { + return count; + } +} diff --git a/src/main/java/christmas/domain/Orders.java b/src/main/java/christmas/domain/Orders.java new file mode 100644 index 00000000..2e9ff41a --- /dev/null +++ b/src/main/java/christmas/domain/Orders.java @@ -0,0 +1,78 @@ +package christmas.domain; + +import christmas.domain.constants.MenuCategory; +import christmas.global.exception.CustomException; +import christmas.global.exception.ErrorMessage; +import java.util.Collections; +import java.util.List; + +public class Orders { + private List orders; + + public Orders(List orders) { + Validator.validate(orders); + this.orders = orders; + } + + public int getCountOfCategory(MenuCategory category) { + int count = 0; + for (Order order : orders) { + if (order.isCategory(category)) { + count += order.getCount(); + } + } + return count; + } + + public int calculateTotalPrice() { + return orders.stream() + .mapToInt(order -> order.calculatePrice()) + .sum(); + } + + public List getOrders() { + return Collections.unmodifiableList(orders); + } + + private static class Validator { + private static void validate(List orders) { + validateDuplicatedItem(orders); + validateAllDrinkMenu(orders); + validateMenuCount(orders); + } + + private static int validateMenuCount(List orders) { + return (int) orders.stream() + .mapToInt(order -> order.getCount()) + .count(); + } + + private static void validateAllDrinkMenu(List orders) { + if (isAllDrinkMenu(orders)) { + throw CustomException.from(ErrorMessage.INVALID_ORDER_ERROR); + } + } + + private static boolean isAllDrinkMenu(List orders) { + return orders.stream() + .allMatch(order -> order.isCategory(MenuCategory.DRINK)); + } + + private static void validateDuplicatedItem(List items) { + if (hasDuplicatedItem(items)) { + throw CustomException.from(ErrorMessage.INVALID_ORDER_ERROR); + } + } + + private static boolean hasDuplicatedItem(List items) { + return items.size() != calculateUniqueItemsCount(items); + } + + private static int calculateUniqueItemsCount(List items) { + return (int) items.stream() + .map(Order::getMenu) + .distinct() + .count(); + } + } +} diff --git a/src/main/java/christmas/domain/constants/Menu.java b/src/main/java/christmas/domain/constants/Menu.java new file mode 100644 index 00000000..dc67abae --- /dev/null +++ b/src/main/java/christmas/domain/constants/Menu.java @@ -0,0 +1,52 @@ +package christmas.domain.constants; + +import christmas.global.exception.CustomException; +import christmas.global.exception.ErrorMessage; +import java.util.Arrays; + +public enum Menu { + YANGSONGI_SOUP(6000, "์–‘์†ก์ด์ˆ˜ํ”„", MenuCategory.APPETIZER), + TAPAS(5500, "ํƒ€ํŒŒ์Šค", MenuCategory.APPETIZER), + CAESAR_SALAD(8000, "์‹œ์ €์ƒ๋Ÿฌ๋“œ", MenuCategory.APPETIZER), + + T_BONE_STEAK(55000, "ํ‹ฐ๋ณธ์Šคํ…Œ์ดํฌ", MenuCategory.MAIN), + BARBECUE_RIBS(54000, "๋ฐ”๋น„ํ๋ฆฝ", MenuCategory.MAIN), + SEAFOOD_PASTA(35000, "ํ•ด์‚ฐ๋ฌผํŒŒ์Šคํƒ€", MenuCategory.MAIN), + CHRISTMAS_PASTA(25000, "ํฌ๋ฆฌ์Šค๋งˆ์ŠคํŒŒ์Šคํƒ€", MenuCategory.MAIN), + + CHOCOLATE_CAKE(15000, "์ดˆ์ฝ”์ผ€์ดํฌ", MenuCategory.DESSERT), + ICECREAM(5000, "์•„์ด์Šคํฌ๋ฆผ", MenuCategory.DESSERT), + + ZERO_COLA(3000, "์ œ๋กœ์ฝœ๋ผ", MenuCategory.DRINK), + RED_WINE(60000, "๋ ˆ๋“œ์™€์ธ", MenuCategory.DRINK), + CHAMPAGNE(25000, "์ƒดํŽ˜์ธ", MenuCategory.DRINK); + + private int price; + private String name; + private MenuCategory category; + + Menu(int price, String name, MenuCategory category) { + this.price = price; + this.name = name; + this.category = category; + } + + public static Menu from(String menu) { + return Arrays.stream(Menu.values()) + .filter(element -> element.name.equals(menu)) + .findFirst() + .orElseThrow(() -> CustomException.from(ErrorMessage.INVALID_ORDER_ERROR)); + } + + public int getPrice() { + return price; + } + + public String getName() { + return name; + } + + public MenuCategory getCategory() { + return category; + } +} diff --git a/src/main/java/christmas/domain/constants/MenuCategory.java b/src/main/java/christmas/domain/constants/MenuCategory.java new file mode 100644 index 00000000..efd725ec --- /dev/null +++ b/src/main/java/christmas/domain/constants/MenuCategory.java @@ -0,0 +1,18 @@ +package christmas.domain.constants; + +public enum MenuCategory { + APPETIZER("์• ํ”ผํƒ€์ด์ €"), + MAIN("๋ฉ”์ธ"), + DESSERT("๋””์ €ํŠธ"), + DRINK("์Œ๋ฃŒ"); + + private String category; + + MenuCategory(String category) { + this.category = category; + } + + public String getCategory() { + return category; + } +} diff --git a/src/main/java/christmas/domain/constants/Promotion.java b/src/main/java/christmas/domain/constants/Promotion.java new file mode 100644 index 00000000..f1c71f15 --- /dev/null +++ b/src/main/java/christmas/domain/constants/Promotion.java @@ -0,0 +1,32 @@ +package christmas.domain.constants; + +import christmas.service.ChristmasPromotion; +import christmas.service.GiftPromotion; +import christmas.service.PromotionService; +import christmas.service.SpecialPromotion; +import christmas.service.WeekdayPromotion; +import christmas.service.WeekendPromotion; + +public enum Promotion { + CHRISTMAS(new ChristmasPromotion(), "ํฌ๋ฆฌ์Šค๋งˆ์Šค ๋””๋ฐ์ด ํ• ์ธ"), + WEEKDAY(new WeekdayPromotion(), "ํ‰์ผ ํ• ์ธ"), + WEEKEND(new WeekendPromotion(), "์ฃผ๋ง ํ• ์ธ"), + SPECIAL(new SpecialPromotion(), "ํŠน๋ณ„ ํ• ์ธ"), + GIFT(new GiftPromotion(), "์ฆ์ • ์ด๋ฒคํŠธ"); + + private final PromotionService promotionService; + private final String name; + + Promotion(PromotionService promotionService, String name) { + this.promotionService = promotionService; + this.name = name; + } + + public PromotionService getInstance() { + return promotionService; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/christmas/domain/constants/WeekInfo.java b/src/main/java/christmas/domain/constants/WeekInfo.java new file mode 100644 index 00000000..65e6d827 --- /dev/null +++ b/src/main/java/christmas/domain/constants/WeekInfo.java @@ -0,0 +1,31 @@ +package christmas.domain.constants; + +import static java.time.DayOfWeek.FRIDAY; +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.DayOfWeek.THURSDAY; +import static java.time.DayOfWeek.TUESDAY; +import static java.time.DayOfWeek.WEDNESDAY; + +import java.time.DayOfWeek; +import java.util.Arrays; +import java.util.List; + +public enum WeekInfo { + WEEKDAY(List.of(SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY)), + WEEKEND(List.of(FRIDAY, SATURDAY)); + + private final List days; + + WeekInfo(List days) { + this.days = days; + } + + public static WeekInfo from(DayOfWeek dayOfWeek) { + return Arrays.stream(WeekInfo.values()) + .filter(element -> element.days.contains(dayOfWeek)) + .findFirst() + .orElseThrow(() -> new IllegalStateException()); + } +} diff --git a/src/main/java/christmas/global/exception/CustomException.java b/src/main/java/christmas/global/exception/CustomException.java new file mode 100644 index 00000000..fa65bb24 --- /dev/null +++ b/src/main/java/christmas/global/exception/CustomException.java @@ -0,0 +1,13 @@ +package christmas.global.exception; + +public class CustomException extends IllegalArgumentException { + private static final String PREFIX = "[ERROR] "; + + private CustomException(ErrorMessage errorMessage) { + super(PREFIX + errorMessage.getMessage()); + } + + public static CustomException from(ErrorMessage errorMessage) { + return new CustomException(errorMessage); + } +} diff --git a/src/main/java/christmas/global/exception/ErrorMessage.java b/src/main/java/christmas/global/exception/ErrorMessage.java new file mode 100644 index 00000000..d80f2df0 --- /dev/null +++ b/src/main/java/christmas/global/exception/ErrorMessage.java @@ -0,0 +1,17 @@ +package christmas.global.exception; + +public enum ErrorMessage { + BLANK_INPUT_ERROR("๋นˆ ๋ฌธ์ž์—ด์ด ์ž…๋ ฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), + INVALID_DAY_ERROR("์œ ํšจํ•˜์ง€ ์•Š์€ ๋‚ ์งœ์ž…๋‹ˆ๋‹ค. ๋‹ค์‹œ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."), + INVALID_ORDER_ERROR("์œ ํšจํ•˜์ง€ ์•Š์€ ์ฃผ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‹ค์‹œ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."); + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/christmas/service/ChristmasPromotion.java b/src/main/java/christmas/service/ChristmasPromotion.java new file mode 100644 index 00000000..ac206221 --- /dev/null +++ b/src/main/java/christmas/service/ChristmasPromotion.java @@ -0,0 +1,34 @@ +package christmas.service; + +import christmas.domain.Benefit; +import christmas.domain.Discount; +import christmas.domain.Orders; +import java.util.Optional; + +public class ChristmasPromotion implements PromotionService { + private static final int START = 1; + private static final int END = 25; + private static final int INITIAL_DISCOUNT = 1000; + private static final int INTERVAL = 100; + + @Override + public Optional apply(int day, Orders orders) { + if (isQualified(day)) { + return Optional.of( + new Discount(calculate(day, orders)) + ); + } + return Optional.empty(); + } + + public boolean isQualified(int day) { + return day >= START && day <= END; + } + + /** + * ํ• ์ธ ์˜ˆ์ • ๊ธˆ์•ก์ด ์ฃผ๋ฌธ ๊ธˆ์•ก๋ณด๋‹ค ๋งŽ์€ ๊ฒฝ์šฐ, ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ๋ฐ˜ํ™˜ + */ + private int calculate(int day, Orders orders) { + return INITIAL_DISCOUNT + (day - 1) * INTERVAL; + } +} diff --git a/src/main/java/christmas/service/GiftPromotion.java b/src/main/java/christmas/service/GiftPromotion.java new file mode 100644 index 00000000..34feba91 --- /dev/null +++ b/src/main/java/christmas/service/GiftPromotion.java @@ -0,0 +1,27 @@ +package christmas.service; + +import christmas.domain.Benefit; +import christmas.domain.Gift; +import christmas.domain.Orders; +import christmas.domain.constants.Menu; +import java.util.Optional; + +public class GiftPromotion implements PromotionService { + private static final int THRESHOLD = 120000; + private static Menu menu = Menu.CHAMPAGNE; + private static int count = 1; + + @Override + public Optional apply(int day, Orders orders) { + if (isQualified(orders)) { + return Optional.of( + new Gift(menu, count) + ); + } + return Optional.empty(); + } + + private boolean isQualified(Orders orders) { + return orders.calculateTotalPrice() >= THRESHOLD; + } +} diff --git a/src/main/java/christmas/service/PromotionService.java b/src/main/java/christmas/service/PromotionService.java new file mode 100644 index 00000000..faf57f7b --- /dev/null +++ b/src/main/java/christmas/service/PromotionService.java @@ -0,0 +1,9 @@ +package christmas.service; + +import christmas.domain.Benefit; +import christmas.domain.Orders; +import java.util.Optional; + +public interface PromotionService { + Optional apply(int day, Orders orders); +} diff --git a/src/main/java/christmas/service/SpecialPromotion.java b/src/main/java/christmas/service/SpecialPromotion.java new file mode 100644 index 00000000..8948c92b --- /dev/null +++ b/src/main/java/christmas/service/SpecialPromotion.java @@ -0,0 +1,31 @@ +package christmas.service; + +import christmas.domain.Benefit; +import christmas.domain.Discount; +import christmas.domain.Orders; +import java.util.Arrays; +import java.util.Optional; + +public class SpecialPromotion implements PromotionService { + private static final int[] SPECIAL_DAYS = { + 3, 10, 17, 24, 25, 31 + }; + private static final int DISCOUNT = 1000; + + @Override + public Optional apply(int day, Orders orders) { + if (isQualified(day)) { + return Optional.of( + new Discount(DISCOUNT) + ); + } + return Optional.empty(); + } + + private boolean isQualified(int day) { + return Arrays.stream(SPECIAL_DAYS) + .filter(special -> special == day) + .findAny() + .isPresent(); + } +} diff --git a/src/main/java/christmas/service/WeekdayPromotion.java b/src/main/java/christmas/service/WeekdayPromotion.java new file mode 100644 index 00000000..660d2ffa --- /dev/null +++ b/src/main/java/christmas/service/WeekdayPromotion.java @@ -0,0 +1,38 @@ +package christmas.service; + +import static christmas.domain.constants.WeekInfo.WEEKDAY; + +import christmas.domain.Benefit; +import christmas.domain.Discount; +import christmas.domain.Orders; +import christmas.domain.constants.MenuCategory; +import christmas.domain.constants.WeekInfo; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.Optional; + +public class WeekdayPromotion implements PromotionService { + private static final int YEAR = 2023; + private static final int MONTH = 12; + private static final int DISCOUNT_PER_MENU = 2023; + + @Override + public Optional apply(int day, Orders orders) { + if (isQualified(day)) { + return Optional.of( + new Discount(calculateDiscount(orders)) + ); + } + return Optional.empty(); + } + + private boolean isQualified(int day) { + DayOfWeek dayOfWeek = LocalDate.of(YEAR, MONTH, day).getDayOfWeek(); + return WeekInfo.from(dayOfWeek).equals(WEEKDAY); + } + + private int calculateDiscount(Orders orders) { + int dessertMenu = orders.getCountOfCategory(MenuCategory.DESSERT); + return dessertMenu * DISCOUNT_PER_MENU; + } +} diff --git a/src/main/java/christmas/service/WeekendPromotion.java b/src/main/java/christmas/service/WeekendPromotion.java new file mode 100644 index 00000000..4a7881cd --- /dev/null +++ b/src/main/java/christmas/service/WeekendPromotion.java @@ -0,0 +1,38 @@ +package christmas.service; + +import static christmas.domain.constants.WeekInfo.WEEKEND; + +import christmas.domain.Benefit; +import christmas.domain.Discount; +import christmas.domain.Orders; +import christmas.domain.constants.MenuCategory; +import christmas.domain.constants.WeekInfo; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.Optional; + +public class WeekendPromotion implements PromotionService { + private static final int YEAR = 2023; + private static final int MONTH = 12; + private static final int DISCOUNT_PER_MENU = 2023; + + @Override + public Optional apply(int day, Orders orders) { + if (isQualified(day)) { + return Optional.of( + new Discount(calculateDiscount(orders)) + ); + } + return Optional.empty(); + } + + private boolean isQualified(int day) { + DayOfWeek dayOfWeek = LocalDate.of(YEAR, MONTH, day).getDayOfWeek(); + return WeekInfo.from(dayOfWeek).equals(WEEKEND); + } + + private int calculateDiscount(Orders orders) { + int mainMenu = orders.getCountOfCategory(MenuCategory.MAIN); + return mainMenu * DISCOUNT_PER_MENU; + } +} diff --git a/src/main/java/christmas/view/InputView.java b/src/main/java/christmas/view/InputView.java new file mode 100644 index 00000000..560eaa7d --- /dev/null +++ b/src/main/java/christmas/view/InputView.java @@ -0,0 +1,87 @@ +package christmas.view; + +import christmas.domain.Order; +import christmas.domain.Orders; +import christmas.domain.constants.Menu; +import christmas.global.exception.CustomException; +import christmas.global.exception.ErrorMessage; +import christmas.view.console.ConsoleReader; +import christmas.view.console.ConsoleWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InputView { + private static final int START_DAY = 1; + private static final int END_DAY = 31; + + private static final String ORDER_SEPARATOR = ","; + private static final String ORDER_INFO_SEPARATOR = "-"; + + + public int readDay() { + ConsoleWriter.printlnMessage("12์›” ์ค‘ ์‹๋‹น ์˜ˆ์ƒ ๋ฐฉ๋ฌธ ๋‚ ์งœ๋Š” ์–ธ์ œ์ธ๊ฐ€์š”? (์ˆซ์ž๋งŒ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”!)"); + return Validator.validateDay(ConsoleReader.enterMessage()); + } + + public Orders readOrders() { + ConsoleWriter.printlnMessage("์ฃผ๋ฌธํ•˜์‹ค ๋ฉ”๋‰ด๋ฅผ ๋ฉ”๋‰ด์™€ ๊ฐœ์ˆ˜๋ฅผ ์•Œ๋ ค ์ฃผ์„ธ์š”. (e.g. ํ•ด์‚ฐ๋ฌผํŒŒ์Šคํƒ€-2,๋ ˆ๋“œ์™€์ธ-1,์ดˆ์ฝ”์ผ€์ดํฌ-1)"); + return new Orders( + Validator.validateOrders(ConsoleReader.enterMessage()) + ); + } + + private static class Validator { + public static int validateDay(String message) { + int number = validateNumber(message, ErrorMessage.INVALID_DAY_ERROR); + validateRange(number, START_DAY, END_DAY); + return number; + } + + public static int validateNumber(String message, ErrorMessage errorMessage) { + if (isNotNumber(message)) { + throw CustomException.from(errorMessage); + } + return Integer.parseInt(message); + } + + private static boolean isNotNumber(String str) { + return !str.matches("\\d+"); + } + + public static void validateRange(int number, int start, int end) { + if (isInvalidRange(number, start, end)) { + throw CustomException.from(ErrorMessage.INVALID_DAY_ERROR); + } + } + + private static boolean isInvalidRange(int number, int start, int end) { + return number < start || number > end; + } + + public static List validateOrders(String message) { + List orders = parseStringToList(message, ORDER_SEPARATOR); + List orderList = new ArrayList<>(); + for (String order : orders) { + List orderInfo = parseStringToList(order, ORDER_INFO_SEPARATOR); + if (orderInfo.size() != 2) { + throw CustomException.from(ErrorMessage.INVALID_ORDER_ERROR); + } + orderList.add(new Order( + Menu.from(orderInfo.get(0)), + validateNumber(orderInfo.get(1), ErrorMessage.INVALID_ORDER_ERROR) + ) + ); + } + return orderList; + } + + private static List parseStringToList(String message, String separator) { + return Arrays.stream(split(message, separator)).toList(); + } + + private static String[] split(String message, String separator) { + return message.split(separator, -1); + } + } +} diff --git a/src/main/java/christmas/view/OutputView.java b/src/main/java/christmas/view/OutputView.java new file mode 100644 index 00000000..556b9bb6 --- /dev/null +++ b/src/main/java/christmas/view/OutputView.java @@ -0,0 +1,97 @@ +package christmas.view; + +import christmas.controller.dto.PromotionResult; +import christmas.controller.dto.PromotionsResult; +import christmas.domain.Badge; +import christmas.domain.Benefit; +import christmas.domain.Gift; +import christmas.domain.Order; +import christmas.domain.Orders; +import christmas.view.console.ConsoleWriter; +import java.util.Optional; + +public class OutputView { + private static final String ORDER_MENU = "%s %s๊ฐœ"; + private static final String PRICE = "%,d์›"; + private static final String DISCOUNT = "-%,d์›"; + private static final String NONE = "์—†์Œ"; + private static final String PROMOTION_RESULT = "%s: -%,d์›"; + + public void start() { + ConsoleWriter.printlnMessage("์•ˆ๋…•ํ•˜์„ธ์š”! ์šฐํ…Œ์ฝ” ์‹๋‹น 12์›” ์ด๋ฒคํŠธ ํ”Œ๋ž˜๋„ˆ์ž…๋‹ˆ๋‹ค."); + } + + public void printMenu(Orders orders) { + ConsoleWriter.printlnMessage("12์›” 26์ผ์— ์šฐํ…Œ์ฝ” ์‹๋‹น์—์„œ ๋ฐ›์„ ์ด๋ฒคํŠธ ํ˜œํƒ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ!"); + + ConsoleWriter.printlnMessage("<์ฃผ๋ฌธ ๋ฉ”๋‰ด>"); + for (Order order : orders.getOrders()) { + ConsoleWriter.printlnFormat(ORDER_MENU, order.getMenu().getName(), order.getCount()); + } + ConsoleWriter.println(); + } + + public void printPrice(int amount) { + ConsoleWriter.printlnMessage("<ํ• ์ธ ์ „ ์ด์ฃผ๋ฌธ ๊ธˆ์•ก>"); + ConsoleWriter.printlnFormat(PRICE, amount); + ConsoleWriter.println(); + } + + public void printGiftAndBenefit(Optional result) { + printGiftMenu(result); + ConsoleWriter.println(); + printBenefit(result); + ConsoleWriter.println(); + } + + private void printGiftMenu(Optional result) { + ConsoleWriter.printlnMessage("<์ฆ์ • ๋ฉ”๋‰ด>"); + if (result.isEmpty()) { + ConsoleWriter.printlnMessage(NONE); + return; + } + for (PromotionResult promotionResult : result.get().promotionResults()) { + Benefit benefit = promotionResult.benefit(); + if (benefit instanceof Gift) { + Gift gift = (Gift) benefit; + ConsoleWriter.printlnFormat(ORDER_MENU, gift.getMenu().getName(), gift.getCount()); + } + } + } + + private void printBenefit(Optional result) { + ConsoleWriter.printlnMessage("<ํ˜œํƒ ๋‚ด์—ญ>"); + if (result.isEmpty()) { + ConsoleWriter.printlnMessage(NONE); + return; + } + for (PromotionResult promotionResult : result.get().promotionResults()) { + ConsoleWriter.printlnFormat( + PROMOTION_RESULT, + promotionResult.promotion().getName(), + promotionResult.benefit().calculate() + ); + } + } + + public void printBenefitAmount(int amount) { + ConsoleWriter.printlnMessage("<์ดํ˜œํƒ ๊ธˆ์•ก>"); + ConsoleWriter.printlnFormat(DISCOUNT, amount); + ConsoleWriter.println(); + } + + public void printFinalPrice(int finalPrice) { + ConsoleWriter.printlnMessage("<ํ• ์ธ ํ›„ ์˜ˆ์ƒ ๊ฒฐ์ œ ๊ธˆ์•ก>"); + ConsoleWriter.printlnFormat(PRICE, finalPrice); + ConsoleWriter.println(); + } + + public void printBadge(Badge badge) { + ConsoleWriter.printlnMessage("<12์›” ์ด๋ฒคํŠธ ๋ฐฐ์ง€>"); + if (badge == null) { + ConsoleWriter.printlnMessage(NONE); + return; + } + ConsoleWriter.printlnMessage(badge.getName()); + } +} diff --git a/src/main/java/christmas/view/console/ConsoleReader.java b/src/main/java/christmas/view/console/ConsoleReader.java new file mode 100644 index 00000000..fb3dc4b3 --- /dev/null +++ b/src/main/java/christmas/view/console/ConsoleReader.java @@ -0,0 +1,24 @@ +package christmas.view.console; + +import camp.nextstep.edu.missionutils.Console; +import christmas.global.exception.CustomException; +import christmas.global.exception.ErrorMessage; + +public final class ConsoleReader { + public static String enterMessage() { + return Validator.validate(Console.readLine()); + } + + private static class Validator { + public static String validate(String message) { + validateBlankInput(message); + return message; + } + + private static void validateBlankInput(String message) { + if (message.isBlank()) { + throw CustomException.from(ErrorMessage.BLANK_INPUT_ERROR); + } + } + } +} diff --git a/src/main/java/christmas/view/console/ConsoleWriter.java b/src/main/java/christmas/view/console/ConsoleWriter.java new file mode 100644 index 00000000..7d7beb85 --- /dev/null +++ b/src/main/java/christmas/view/console/ConsoleWriter.java @@ -0,0 +1,15 @@ +package christmas.view.console; + +public final class ConsoleWriter { + public static void printlnMessage(String message) { + System.out.println(message); + } + + public static void printlnFormat(String message, Object... args) { + printlnMessage(String.format(message, args)); + } + + public static void println() { + System.out.println(); + } +}