diff --git a/src/main/java/christmas/Application.java b/src/main/java/christmas/Application.java index b9ba6a2..5b9d4f3 100644 --- a/src/main/java/christmas/Application.java +++ b/src/main/java/christmas/Application.java @@ -1,7 +1,10 @@ package christmas; +import christmas.config.AppConfig; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + AppConfig app = new AppConfig(); + app.run(); } -} +} \ No newline at end of file diff --git a/src/main/java/christmas/config/AppConfig.java b/src/main/java/christmas/config/AppConfig.java new file mode 100644 index 0000000..5b7d22f --- /dev/null +++ b/src/main/java/christmas/config/AppConfig.java @@ -0,0 +1,18 @@ +package christmas.config; + +import christmas.controller.EventController; +import christmas.view.InputView; +import christmas.view.OutputView; + +public class AppConfig { + private final EventController eventController; + + public AppConfig() { + this.eventController = new EventController(new InputView(), new OutputView()); + } + + public void run() { + eventController.init(); + eventController.run(); + } +} diff --git a/src/main/java/christmas/config/MenuConfig.java b/src/main/java/christmas/config/MenuConfig.java new file mode 100644 index 0000000..c85ef34 --- /dev/null +++ b/src/main/java/christmas/config/MenuConfig.java @@ -0,0 +1,21 @@ +package christmas.config; + +import christmas.model.domain.Menu; +import christmas.repository.MenuRepository; + +public class MenuConfig { + public static void initMenus() { + MenuRepository.addMenu(new Menu("양송이수프", 6_000, "애피타이저")); + MenuRepository.addMenu(new Menu("타파스", 5_500, "애피타이저")); + MenuRepository.addMenu(new Menu("시저샐러드", 8_000, "애피타이저")); + MenuRepository.addMenu(new Menu("티본스테이크", 55_000, "메인")); + MenuRepository.addMenu(new Menu("바비큐립", 54_000, "메인")); + MenuRepository.addMenu(new Menu("해산물파스타", 35_000, "메인")); + MenuRepository.addMenu(new Menu("크리스마스파스타", 25_000, "메인")); + MenuRepository.addMenu(new Menu("초코케이크", 15_000, "디저트")); + MenuRepository.addMenu(new Menu("아이스크림", 5_000, "디저트")); + MenuRepository.addMenu(new Menu("제로콜라", 3_000, "음료")); + MenuRepository.addMenu(new Menu("레드와인", 60_000, "음료")); + MenuRepository.addMenu(new Menu("샴페인", 25_000, "음료")); + } +} diff --git a/src/main/java/christmas/controller/EventController.java b/src/main/java/christmas/controller/EventController.java new file mode 100644 index 0000000..faf60f4 --- /dev/null +++ b/src/main/java/christmas/controller/EventController.java @@ -0,0 +1,116 @@ +package christmas.controller; + +import christmas.config.MenuConfig; +import christmas.model.domain.*; +import christmas.repository.MenuRepository; +import christmas.service.DiscountCalculator; +import christmas.view.InputView; +import christmas.view.OutputView; + +import java.time.LocalDate; + +public class EventController { + + private static final String GIFT_CHAMPAGNE = "샴페인 1개"; + private static final String NO_GIFT = "없음"; + private static final String ORDER_DELIMITER = ","; + private static final String DETAIL_DELIMITER = "-"; + private static final int GIFT_DISCOUNT_AMOUNT = 25000; + private static final int EVENT_YEAR = 2023; + private static final int EVENT_MONTH = 12; + + private final InputView inputView; + private final OutputView outputView; + private final DiscountCalculator discountCalculator; + + public EventController(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + this.discountCalculator = new DiscountCalculator(); + } + + public void init() { + MenuConfig.initMenus(); + } + + public void run() { + while (true) { + try { + int visitDay = readValidVisitDate(); + LocalDate visitDate = LocalDate.of(EVENT_YEAR, EVENT_MONTH, visitDay); + + Order order = createOrderFromInput(); + processOrder(order, visitDay, visitDate); + break; + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + } + } + } + + private int readValidVisitDate() { + while (true) { + try { + return inputView.readVisitDate(); + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + } + } + } + + private Order createOrderFromInput() { + Order order = new Order(); + String[] orderDetails = readValidOrderDetails(); + for (String detail : orderDetails) { + String[] parts = detail.split(DETAIL_DELIMITER); + Menu menu = MenuRepository.findByName(parts[0].trim()); + int quantity = Integer.parseInt(parts[1].trim()); + order.addMenu(menu, quantity); + } + return order; + } + + private String[] readValidOrderDetails() { + while (true) { + try { + return inputView.readOrderDetails().split(ORDER_DELIMITER); + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + } + } + } + + private void processOrder(Order order, int visitDay, LocalDate visitDate) { + int totalPrice = order.calculateTotalPrice(); + + int dailyDiscount = discountCalculator.calculateDailyDiscount(visitDay); + int weekdayDiscount = discountCalculator.calculateWeekdayDiscount(order, visitDate); + int weekendDiscount = discountCalculator.calculateWeekendDiscount(order, visitDate); + int specialDayDiscount = discountCalculator.calculateSpecialDayDiscount(visitDate); + + boolean eligibleForGift = discountCalculator.isEligibleForGift(totalPrice); + String gift = getGiftBasedOnEligibility(eligibleForGift); + + int totalDiscount = calculateTotalDiscount(dailyDiscount, weekdayDiscount, weekendDiscount, specialDayDiscount, eligibleForGift); + int finalPrice = totalPrice - (dailyDiscount + weekdayDiscount + weekendDiscount + specialDayDiscount); + + String badge = Badge.getBadgeByBenefit(totalDiscount).getName(); + + outputView.printOrderSummary(order, totalPrice, totalDiscount, finalPrice, gift, dailyDiscount, weekdayDiscount, weekendDiscount, specialDayDiscount, badge); + } + + private String getGiftBasedOnEligibility(boolean eligibleForGift) { + if (eligibleForGift) { + return GIFT_CHAMPAGNE; + } + return NO_GIFT; + } + + private int calculateTotalDiscount(int dailyDiscount, int weekdayDiscount, int weekendDiscount, int specialDayDiscount, boolean eligibleForGift) { + int giftDiscount = 0; + if (eligibleForGift) { + giftDiscount = GIFT_DISCOUNT_AMOUNT; + } + return dailyDiscount + weekdayDiscount + weekendDiscount + specialDayDiscount + giftDiscount; + } +} \ No newline at end of file diff --git a/src/main/java/christmas/model/domain/Badge.java b/src/main/java/christmas/model/domain/Badge.java new file mode 100644 index 0000000..cda2c6c --- /dev/null +++ b/src/main/java/christmas/model/domain/Badge.java @@ -0,0 +1,28 @@ +package christmas.model.domain; +import java.util.Arrays; + +public enum Badge { + SANTA("산타", 20000), + TREE("트리", 10000), + STAR("별", 5000), + NONE("없음", 0); + + private final String name; + private final int minBenefit; + + Badge(String name, int minBenefit) { + this.name = name; + this.minBenefit = minBenefit; + } + + public String getName() { + return name; + } + + public static Badge getBadgeByBenefit(int totalBenefit) { + return Arrays.stream(values()) + .filter(badge -> totalBenefit >= badge.minBenefit) + .findFirst() + .orElse(NONE); + } +} diff --git a/src/main/java/christmas/model/domain/Menu.java b/src/main/java/christmas/model/domain/Menu.java new file mode 100644 index 0000000..5bc071d --- /dev/null +++ b/src/main/java/christmas/model/domain/Menu.java @@ -0,0 +1,25 @@ +package christmas.model.domain; + +public class Menu { + private final String name; + private final int price; + private final String category; + + public Menu(String name, int price, String category) { + this.name = name; + this.price = price; + this.category = category; + } + + public String getName() { + return name; + } + + public int getPrice() { + return price; + } + + public String getCategory() { + return category; + } +} \ No newline at end of file diff --git a/src/main/java/christmas/model/domain/Order.java b/src/main/java/christmas/model/domain/Order.java new file mode 100644 index 0000000..68b8063 --- /dev/null +++ b/src/main/java/christmas/model/domain/Order.java @@ -0,0 +1,22 @@ +package christmas.model.domain; + +import java.util.HashMap; +import java.util.Map; + +public class Order { + private final Map orderDetails = new HashMap<>(); + + public void addMenu(Menu menu, int quantity) { + orderDetails.put(menu, orderDetails.getOrDefault(menu, 0) + quantity); + } + + public Map getOrderDetails() { + return orderDetails; + } + + public int calculateTotalPrice() { + return orderDetails.entrySet().stream() + .mapToInt(entry -> entry.getKey().getPrice() * entry.getValue()) + .sum(); + } +} diff --git a/src/main/java/christmas/repository/MenuRepository.java b/src/main/java/christmas/repository/MenuRepository.java new file mode 100644 index 0000000..b683a5c --- /dev/null +++ b/src/main/java/christmas/repository/MenuRepository.java @@ -0,0 +1,17 @@ +package christmas.repository; + +import christmas.model.domain.Menu; +import java.util.HashMap; +import java.util.Map; + +public class MenuRepository { + private static final Map menus = new HashMap<>(); + + public static void addMenu(Menu menu) { + menus.put(menu.getName(), menu); + } + + public static Menu findByName(String name) { + return menus.get(name); + } +} \ No newline at end of file diff --git a/src/main/java/christmas/service/DiscountCalculator.java b/src/main/java/christmas/service/DiscountCalculator.java new file mode 100644 index 0000000..93e00db --- /dev/null +++ b/src/main/java/christmas/service/DiscountCalculator.java @@ -0,0 +1,77 @@ +package christmas.service; + +import christmas.model.domain.Order; + +import java.time.DayOfWeek; +import java.time.LocalDate; + +public class DiscountCalculator { + + private static final int DAILY_DISCOUNT_START = 1000; + private static final int DAILY_DISCOUNT_INCREMENT = 100; + private static final int CATEGORY_DISCOUNT_AMOUNT = 2023; + private static final int SPECIAL_DAY_DISCOUNT = 1000; + private static final int GIFT_ELIGIBILITY_THRESHOLD = 120000; + private static final String MAIN_CATEGORY = "메인"; + private static final String DESSERT_CATEGORY = "디저트"; + private static final int CHRISTMAS_DAY = 25; + + public int calculateDailyDiscount(int day) { + if (isInvalidDiscountDay(day)) { + return 0; + } + return DAILY_DISCOUNT_START + ((day - 1) * DAILY_DISCOUNT_INCREMENT); + } + + public int calculateWeekdayDiscount(Order order, LocalDate visitDate) { + if (isWeekday(visitDate.getDayOfWeek())) { + return calculateCategoryDiscount(order, DESSERT_CATEGORY); + } + return 0; + } + + public int calculateWeekendDiscount(Order order, LocalDate visitDate) { + if (isWeekend(visitDate.getDayOfWeek())) { + return calculateCategoryDiscount(order, MAIN_CATEGORY); + } + return 0; + } + + public int calculateSpecialDayDiscount(LocalDate visitDate) { + if (isSpecialDay(visitDate)) { + return SPECIAL_DAY_DISCOUNT; + } + return 0; + } + + public boolean isEligibleForGift(int totalPrice) { + return totalPrice >= GIFT_ELIGIBILITY_THRESHOLD; + } + + private boolean isInvalidDiscountDay(int day) { + return day < 1 || day > 25; + } + + private int calculateCategoryDiscount(Order order, String category) { + return order.getOrderDetails().entrySet().stream() + .filter(entry -> entry.getKey().getCategory().equals(category)) + .mapToInt(entry -> CATEGORY_DISCOUNT_AMOUNT * entry.getValue()) + .sum(); + } + + private boolean isWeekday(DayOfWeek dayOfWeek) { + return dayOfWeek == DayOfWeek.MONDAY || + dayOfWeek == DayOfWeek.TUESDAY || + dayOfWeek == DayOfWeek.WEDNESDAY || + dayOfWeek == DayOfWeek.THURSDAY || + dayOfWeek == DayOfWeek.SUNDAY; + } + + private boolean isWeekend(DayOfWeek dayOfWeek) { + return dayOfWeek == DayOfWeek.FRIDAY || dayOfWeek == DayOfWeek.SATURDAY; + } + + private boolean isSpecialDay(LocalDate date) { + return date.getDayOfWeek() == DayOfWeek.SUNDAY || date.getDayOfMonth() == CHRISTMAS_DAY; + } +} \ No newline at end of file diff --git a/src/main/java/christmas/view/InputView.java b/src/main/java/christmas/view/InputView.java new file mode 100644 index 0000000..2a6cbfc --- /dev/null +++ b/src/main/java/christmas/view/InputView.java @@ -0,0 +1,98 @@ +package christmas.view; + +import camp.nextstep.edu.missionutils.Console; +import christmas.repository.MenuRepository; + +import java.util.HashSet; +import java.util.Set; + +public class InputView { + + private static final String VISIT_DATE_PROMPT = "12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)"; + private static final String ORDER_DETAILS_PROMPT = "주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)"; + private static final String ERROR_INVALID_DATE = "[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요."; + private static final String ERROR_INVALID_ORDER = "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."; + private static final String ORDER_DELIMITER = ","; + private static final String DETAIL_DELIMITER = "-"; + + public int readVisitDate() { + System.out.println(VISIT_DATE_PROMPT); + String input = Console.readLine(); + return parseVisitDate(input); + } + + private int parseVisitDate(String input) { + try { + int date = Integer.parseInt(input); + validateDate(date); + return date; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ERROR_INVALID_DATE); + } + } + + private void validateDate(int date) { + if (date < 1 || date > 31) { + throw new IllegalArgumentException(ERROR_INVALID_DATE); + } + } + + public String readOrderDetails() { + System.out.println(ORDER_DETAILS_PROMPT); + String input = Console.readLine(); + validateOrderInput(input); + return input; + } + + private void validateOrderInput(String input) { + validateEmptyInput(input); + + Set uniqueMenus = new HashSet<>(); + String[] orders = input.split(ORDER_DELIMITER); + + for (String order : orders) { + validateOrder(order, uniqueMenus); + } + } + + private void validateEmptyInput(String input) { + if (input == null || input.trim().isEmpty()) { + throw new IllegalArgumentException(ERROR_INVALID_ORDER); + } + } + + private void validateOrder(String order, Set uniqueMenus) { + String[] details = order.split(DETAIL_DELIMITER); + validateOrderFormat(details); + String menuName = details[0].trim(); + validateMenuName(menuName, uniqueMenus); + String quantityString = details[1].trim(); + validateQuantity(quantityString); + } + + private void validateOrderFormat(String[] details) { + if (details.length != 2) { + throw new IllegalArgumentException(ERROR_INVALID_ORDER); + } + } + + private void validateMenuName(String menuName, Set uniqueMenus) { + if (MenuRepository.findByName(menuName) == null) { + throw new IllegalArgumentException(ERROR_INVALID_ORDER); + } + if (!uniqueMenus.add(menuName)) { + throw new IllegalArgumentException(ERROR_INVALID_ORDER); + } + } + + private void validateQuantity(String quantityString) { + try { + int quantity = Integer.parseInt(quantityString); + if (quantity < 1) { + throw new IllegalArgumentException(ERROR_INVALID_ORDER); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ERROR_INVALID_ORDER); + } + } +} \ No newline at end of file diff --git a/src/main/java/christmas/view/OutputView.java b/src/main/java/christmas/view/OutputView.java new file mode 100644 index 0000000..2222f18 --- /dev/null +++ b/src/main/java/christmas/view/OutputView.java @@ -0,0 +1,125 @@ +package christmas.view; + +import christmas.model.domain.Order; + +import java.text.NumberFormat; +import java.util.Locale; + +public class OutputView { + + private static final String ORDER_MENU_HEADER = "<주문 메뉴>"; + private static final String TOTAL_PRICE_HEADER = "\n<할인 전 총주문 금액>"; + private static final String GIFT_HEADER = "\n<증정 메뉴>"; + private static final String DISCOUNT_DETAILS_HEADER = "\n<혜택 내역>"; + private static final String TOTAL_BENEFITS_HEADER = "\n<총혜택 금액>"; + private static final String FINAL_PRICE_HEADER = "\n<할인 후 예상 결제 금액>"; + private static final String BADGE_HEADER = "\n<12월 이벤트 배지>"; + private static final String WON_SUFFIX = "원"; + private static final String NO_DISCOUNT_MESSAGE = "없음"; + private static final String DAILY_DISCOUNT_LABEL = "크리스마스 디데이 할인: "; + private static final String WEEKDAY_DISCOUNT_LABEL = "평일 할인: "; + private static final String WEEKEND_DISCOUNT_LABEL = "주말 할인: "; + private static final String SPECIAL_DISCOUNT_LABEL = "특별 할인: "; + private static final String GIFT_EVENT_LABEL = "증정 이벤트: "; + private static final String ORDER_ITEM_FORMAT = "%s %d개"; + private static final String NO_GIFT = "없음"; + private static final String NEGATIVE_PREFIX = "-"; + private static final int GIFT_DISCOUNT_AMOUNT = 25000; + + public void printOrderSummary(Order order, int totalPrice, int totalDiscount, int finalPrice, String gift, int dailyDiscount, int weekdayDiscount, int weekendDiscount, int specialDayDiscount, String badge) { + printOrderDetails(order); + printPriceDetails(totalPrice); + printGiftDetails(gift); + printDiscountDetails(dailyDiscount, weekdayDiscount, weekendDiscount, specialDayDiscount, gift); + printTotalBenefits(totalDiscount); + printFinalPrice(finalPrice); + printBadge(badge); + } + + private void printOrderDetails(Order order) { + System.out.println(ORDER_MENU_HEADER); + order.getOrderDetails().forEach((menu, quantity) -> { + System.out.println(String.format(ORDER_ITEM_FORMAT, menu.getName(), quantity)); + }); + } + + private void printPriceDetails(int totalPrice) { + System.out.println(TOTAL_PRICE_HEADER); + System.out.println(formatCurrency(totalPrice) + WON_SUFFIX); + } + + private void printGiftDetails(String gift) { + System.out.println(GIFT_HEADER); + System.out.println(gift); + } + + private void printDiscountDetails(int dailyDiscount, int weekdayDiscount, int weekendDiscount, int specialDayDiscount, String gift) { + System.out.println(DISCOUNT_DETAILS_HEADER); + if (dailyDiscount == 0 && weekdayDiscount == 0 && weekendDiscount == 0 && specialDayDiscount == 0 && NO_GIFT.equals(gift)) { + System.out.println(NO_DISCOUNT_MESSAGE); + return; + } + printDailyDiscount(dailyDiscount); + printWeekdayDiscount(weekdayDiscount); + printWeekendDiscount(weekendDiscount); + printSpecialDayDiscount(specialDayDiscount); + printGiftEventDiscount(gift); + } + + private void printDailyDiscount(int dailyDiscount) { + if (dailyDiscount > 0) { + System.out.println(DAILY_DISCOUNT_LABEL + formatDiscount(dailyDiscount) + WON_SUFFIX); + } + } + + private void printWeekdayDiscount(int weekdayDiscount) { + if (weekdayDiscount > 0) { + System.out.println(WEEKDAY_DISCOUNT_LABEL + formatDiscount(weekdayDiscount) + WON_SUFFIX); + } + } + + private void printWeekendDiscount(int weekendDiscount) { + if (weekendDiscount > 0) { + System.out.println(WEEKEND_DISCOUNT_LABEL + formatDiscount(weekendDiscount) + WON_SUFFIX); + } + } + + private void printSpecialDayDiscount(int specialDayDiscount) { + if (specialDayDiscount > 0) { + System.out.println(SPECIAL_DISCOUNT_LABEL + formatDiscount(specialDayDiscount) + WON_SUFFIX); + } + } + + private void printGiftEventDiscount(String gift) { + if (!NO_GIFT.equals(gift)) { + System.out.println(GIFT_EVENT_LABEL + formatDiscount(GIFT_DISCOUNT_AMOUNT) + WON_SUFFIX); + } + } + + private void printTotalBenefits(int totalDiscount) { + System.out.println(TOTAL_BENEFITS_HEADER); + System.out.println(formatDiscount(totalDiscount) + WON_SUFFIX); + } + + private void printFinalPrice(int finalPrice) { + System.out.println(FINAL_PRICE_HEADER); + System.out.println(formatCurrency(finalPrice) + WON_SUFFIX); + } + + private void printBadge(String badge) { + System.out.println(BADGE_HEADER); + System.out.println(badge); + } + + private String formatDiscount(int amount) { + return NEGATIVE_PREFIX + formatCurrency(amount); + } + + private String formatCurrency(int amount) { + return NumberFormat.getInstance(Locale.KOREA).format(amount); + } + + public void printError(String message) { + System.out.println(message); + } +} \ No newline at end of file