From f13e6e2ca6d9674dbd7822eaa35dd04e3bdb94e5 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Mon, 3 Mar 2025 18:13:01 +0900 Subject: [PATCH 01/27] =?UTF-8?q?docs(README.md)=20:=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index d54f268..f5bf5d1 100644 --- a/README.md +++ b/README.md @@ -1 +1,14 @@ # java-convenience-store-precourse + +# 기능 목록 +1. 확장 가능한 상품 enum +2. 프로모션 class +3. 편의점 class : 재고(상품, 프로모션 연관), 편의점 이름 +4. 구매 service : 재고, 프로모션, 영수증 모델 연관 +5. 영수증 class : 멤버십 할인, 계산 +6. 입력 view +7. 출력 view +8. validator +9. 편의점 controller +10. 시스템 메세지 enum +11. 에러 메세지 enum \ No newline at end of file From 6d7e27ba9cab8beaca78d0de1e63f7edff390ca6 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Mon, 3 Mar 2025 18:28:22 +0900 Subject: [PATCH 02/27] =?UTF-8?q?chore(directory)=20:=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/store/controller/StoreController.java | 4 ++++ src/main/java/store/model/Product.java | 4 ++++ src/main/java/store/model/Promotion.java | 4 ++++ src/main/java/store/model/Receipt.java | 4 ++++ src/main/java/store/model/Store.java | 4 ++++ src/main/java/store/service/PurchaseService.java | 4 ++++ src/main/java/store/utility/ErrorMessage.java | 4 ++++ src/main/java/store/utility/SystemMessage.java | 4 ++++ src/main/java/store/utility/Validator.java | 4 ++++ src/main/java/store/view/InputView.java | 4 ++++ src/main/java/store/view/OutputView.java | 4 ++++ 11 files changed, 44 insertions(+) create mode 100644 src/main/java/store/controller/StoreController.java create mode 100644 src/main/java/store/model/Product.java create mode 100644 src/main/java/store/model/Promotion.java create mode 100644 src/main/java/store/model/Receipt.java create mode 100644 src/main/java/store/model/Store.java create mode 100644 src/main/java/store/service/PurchaseService.java create mode 100644 src/main/java/store/utility/ErrorMessage.java create mode 100644 src/main/java/store/utility/SystemMessage.java create mode 100644 src/main/java/store/utility/Validator.java create mode 100644 src/main/java/store/view/InputView.java create mode 100644 src/main/java/store/view/OutputView.java diff --git a/src/main/java/store/controller/StoreController.java b/src/main/java/store/controller/StoreController.java new file mode 100644 index 0000000..627ae00 --- /dev/null +++ b/src/main/java/store/controller/StoreController.java @@ -0,0 +1,4 @@ +package store.controller; + +public class StoreController { +} diff --git a/src/main/java/store/model/Product.java b/src/main/java/store/model/Product.java new file mode 100644 index 0000000..a79612a --- /dev/null +++ b/src/main/java/store/model/Product.java @@ -0,0 +1,4 @@ +package store.model; + +public enum Product { +} diff --git a/src/main/java/store/model/Promotion.java b/src/main/java/store/model/Promotion.java new file mode 100644 index 0000000..0f77c90 --- /dev/null +++ b/src/main/java/store/model/Promotion.java @@ -0,0 +1,4 @@ +package store.model; + +public class Promotion { +} diff --git a/src/main/java/store/model/Receipt.java b/src/main/java/store/model/Receipt.java new file mode 100644 index 0000000..d6d18aa --- /dev/null +++ b/src/main/java/store/model/Receipt.java @@ -0,0 +1,4 @@ +package store.model; + +public class Receipt { +} \ No newline at end of file diff --git a/src/main/java/store/model/Store.java b/src/main/java/store/model/Store.java new file mode 100644 index 0000000..1ffc0d9 --- /dev/null +++ b/src/main/java/store/model/Store.java @@ -0,0 +1,4 @@ +package store.model; + +public class Store { +} diff --git a/src/main/java/store/service/PurchaseService.java b/src/main/java/store/service/PurchaseService.java new file mode 100644 index 0000000..fb4c764 --- /dev/null +++ b/src/main/java/store/service/PurchaseService.java @@ -0,0 +1,4 @@ +package store.service; + +public class PurchaseService { +} diff --git a/src/main/java/store/utility/ErrorMessage.java b/src/main/java/store/utility/ErrorMessage.java new file mode 100644 index 0000000..a5efdfb --- /dev/null +++ b/src/main/java/store/utility/ErrorMessage.java @@ -0,0 +1,4 @@ +package store.utility; + +public enum ErrorMessage { +} diff --git a/src/main/java/store/utility/SystemMessage.java b/src/main/java/store/utility/SystemMessage.java new file mode 100644 index 0000000..66d5702 --- /dev/null +++ b/src/main/java/store/utility/SystemMessage.java @@ -0,0 +1,4 @@ +package store.utility; + +public enum SystemMessage { +} diff --git a/src/main/java/store/utility/Validator.java b/src/main/java/store/utility/Validator.java new file mode 100644 index 0000000..fa74d26 --- /dev/null +++ b/src/main/java/store/utility/Validator.java @@ -0,0 +1,4 @@ +package store.utility; + +public class Validator { +} diff --git a/src/main/java/store/view/InputView.java b/src/main/java/store/view/InputView.java new file mode 100644 index 0000000..3f182f6 --- /dev/null +++ b/src/main/java/store/view/InputView.java @@ -0,0 +1,4 @@ +package store.view; + +public class InputView { +} diff --git a/src/main/java/store/view/OutputView.java b/src/main/java/store/view/OutputView.java new file mode 100644 index 0000000..0c4d0c3 --- /dev/null +++ b/src/main/java/store/view/OutputView.java @@ -0,0 +1,4 @@ +package store.view; + +public class OutputView { +} From 53b621a9a9cb0a0a92dd972ec88d85edcbd8ea69 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Mon, 3 Mar 2025 20:48:20 +0900 Subject: [PATCH 03/27] fix(directory) : ProductInitializeService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resource directory를 읽어 초기설정하는 서비스 추가 --- README.md | 3 ++- src/main/java/store/service/ProductInitializeService.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/main/java/store/service/ProductInitializeService.java diff --git a/README.md b/README.md index f5bf5d1..4ec778f 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,5 @@ 8. validator 9. 편의점 controller 10. 시스템 메세지 enum -11. 에러 메세지 enum \ No newline at end of file +11. 에러 메세지 enum +12. resource를 읽고 상품 등록하는 service \ No newline at end of file diff --git a/src/main/java/store/service/ProductInitializeService.java b/src/main/java/store/service/ProductInitializeService.java new file mode 100644 index 0000000..f0bb96f --- /dev/null +++ b/src/main/java/store/service/ProductInitializeService.java @@ -0,0 +1,4 @@ +package store.service; + +public class ProductInitializeService { +} From bb1cfc66424bae089e130d084d712788d200015f Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Thu, 6 Mar 2025 00:04:25 +0900 Subject: [PATCH 04/27] =?UTF-8?q?feat(model)=20:=20Promotion=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능목록 1번 --- src/main/java/store/model/Promotion.java | 148 ++++++++++++++++++ .../java/store/model/PromotionRepository.java | 35 +++++ 2 files changed, 183 insertions(+) create mode 100644 src/main/java/store/model/PromotionRepository.java diff --git a/src/main/java/store/model/Promotion.java b/src/main/java/store/model/Promotion.java index 0f77c90..51e2827 100644 --- a/src/main/java/store/model/Promotion.java +++ b/src/main/java/store/model/Promotion.java @@ -1,4 +1,152 @@ package store.model; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.function.Function; +import java.util.stream.IntStream; +import store.utility.ErrorMessage; +import store.utility.Parser; +import store.utility.Validator; + public class Promotion { + private static final int MOST_GET_NUM = 5, LEAST_GET_NUM = 1; + private static final String errorHeader = "Promotion : "; + private static final Rule currentRule = Rule.BUYNGET1; + private final String name; + private final int buy; + private final int get; + private final LocalDate start_date; + private final LocalDate end_date; + + private Promotion(Builder builder) { + this.name = builder.name; + this.buy = builder.buy; + this.get = builder.get; + this.start_date = builder.start_date; + this.end_date = builder.end_date; + } + + public String getName() { + return name; + } + + public int getBuy() { + return buy; + } + + public int getGet() { + return get; + } + + public boolean isDatePromotionPeriod(LocalDate date) { + return start_date.isBefore(date)&&end_date.isAfter(date); + } + + public void isPromotionPeriodsOverlap(Promotion promotion){ + if(this.isDatePromotionPeriod(promotion.start_date) || + this.isDatePromotionPeriod(promotion.end_date)) { + throw new IllegalStateException(ErrorMessage.PROMOTION_DATE_OVERLAP.getMessage()); + } + } + + public static void isMatchingRule(int getNumber){ + if(!currentRule.matches(getNumber)) { + throw new IllegalArgumentException + (String.format(ErrorMessage.RESOURCE_RANGE_WRONG.getMessage(), getNumber, currentRule)); + } + } + + private enum Rule{ + BUYNGET1(1, 1), + BUYNGETN(LEAST_GET_NUM, MOST_GET_NUM); + + private final int startGetNumber; + private final int endGetNumber; + + Rule(int startGetNumber, int endGetNumber){ + this.startGetNumber = startGetNumber; + this.endGetNumber = endGetNumber; + } + + public boolean matches(int getNumber) { + return this.startGetNumber<=getNumber && getNumber<=this.endGetNumber; + } + }; + + public static class Builder{ + private String name; + private int buy; + private int get; + private LocalDate start_date; + private LocalDate end_date; + + public static Function, Builder> createBuilderByColumns(List columns){ + return (List values)->{ + Builder builder = new Builder(); + IntStream.range(0, columns.size()) + .forEach(i -> builder.returnBuilderByName(columns.get(i)).apply(values.get(i))); + return builder; + }; + } + + Function returnBuilderByName(String name){ + return switch (name) { + case "name" -> this::setName; + case "buy" -> this::setBuy; + case "get" -> this::setGet; + case "start_date" -> this::setStartDate; + case "end_date" -> this::setEndDate; + default -> throw new IllegalArgumentException( + errorHeader + String.format(ErrorMessage.RESOURCE_COLUMN_WRONG.getMessage(), name)); + }; + } + + Builder setName(String input){ + this.name = input; + return this; + } + + Builder setBuy(String input){ + try{ + this.buy = Parser.NumberParse(input); + return this; + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + e.getMessage()); + } + } + + Builder setGet(String input){ + try{ + int get = Parser.NumberParse(input); + isMatchingRule(get); + this.get = get; + return this; + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + e.getMessage()); + } + } + + Builder setStartDate(String input){ + try{ + this.start_date = Parser.DateParse(input); + return this; + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + e.getMessage()); + } + } + + Builder setEndDate(String input) { + try { + this.end_date = Parser.DateParse(input); + return this; + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + ErrorMessage.INPUT_NOT_DATE.getMessage()); + } + } + + public Promotion build() { + return new Promotion(this); + } + } } diff --git a/src/main/java/store/model/PromotionRepository.java b/src/main/java/store/model/PromotionRepository.java new file mode 100644 index 0000000..93728a9 --- /dev/null +++ b/src/main/java/store/model/PromotionRepository.java @@ -0,0 +1,35 @@ +package store.model; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class PromotionRepository { + private static final Map> promotionRepository = new HashMap<>(); + private static final String errorHeader = "PromotionRepository : "; + + public static void addPromotion(String key, Promotion promotion) { + try{ + List newEntry = promotionRepository.get(key); + newEntry.forEach(entry -> entry.isPromotionPeriodsOverlap(promotion)); + newEntry.add(promotion); + promotionRepository.replace(key, newEntry); + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + e.getMessage()); + } + } + + public static Optional isPromotion(String key, LocalDate date){ + List registeredPromotions = promotionRepository.get(key); + if(registeredPromotions == null) return Optional.empty(); + return registeredPromotions.stream() + .filter(promotion -> promotion.isDatePromotionPeriod(date)).findFirst(); + } + + //확장을 위해 미리 형태만 잡아둠 + public void removeOldestPromotion(){} + + public void removeAllFinishedPromotions(){} +} From 2ed549a8ff69bad06eb63b75a9bc33b3061e105d Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Thu, 3 Apr 2025 23:58:13 +0900 Subject: [PATCH 05/27] =?UTF-8?q?fix(README.md)=20:=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4ec778f..6fffb1f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# java-convenience-store-precourse +# java-convenience-stock-precourse # 기능 목록 -1. 확장 가능한 상품 enum -2. 프로모션 class -3. 편의점 class : 재고(상품, 프로모션 연관), 편의점 이름 -4. 구매 service : 재고, 프로모션, 영수증 모델 연관 -5. 영수증 class : 멤버십 할인, 계산 -6. 입력 view -7. 출력 view -8. validator -9. 편의점 controller -10. 시스템 메세지 enum -11. 에러 메세지 enum -12. resource를 읽고 상품 등록하는 service \ No newline at end of file + +1. 확장, 축소 가능한 상품, 프로모션, 재고 +2. 영수증 : 구매 내역 저장 및 계산 +3. 유저 관리 : 유저(멤버십, 영수증 인스턴스 관리), 유저 생성 및 식별 코드 생성 +4. 편의점 초기 설정 기능 : resource 읽고 초기 세팅 +5. 구매 처리 : 재고, 프로모션, 영수증 모델 연관 +6. 입력 view : 테스트 목적으로 inputProvider를 inject +7. 출력 view +8. 편의점 전체 로직 controller +9. 시스템 메세지 enum +10. 에러 메세지 enum +11. 시스템 상수 : 편의점 이름, Y/N문자 설정 \ No newline at end of file From cff31baf0d0194e6c12b83550cc2f8963305ce7c Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Thu, 3 Apr 2025 23:59:29 +0900 Subject: [PATCH 06/27] =?UTF-8?q?fix(model)=20:=20Promotion=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 디렉토리 이동 및 로직 수정 --- .../java/store/model/PromotionRepository.java | 35 ---------- .../store/model/{ => domain}/Promotion.java | 26 ++++---- .../model/repository/PromotionRepository.java | 65 +++++++++++++++++++ 3 files changed, 77 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/store/model/PromotionRepository.java rename src/main/java/store/model/{ => domain}/Promotion.java (87%) create mode 100644 src/main/java/store/model/repository/PromotionRepository.java diff --git a/src/main/java/store/model/PromotionRepository.java b/src/main/java/store/model/PromotionRepository.java deleted file mode 100644 index 93728a9..0000000 --- a/src/main/java/store/model/PromotionRepository.java +++ /dev/null @@ -1,35 +0,0 @@ -package store.model; - -import java.time.LocalDate; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -public class PromotionRepository { - private static final Map> promotionRepository = new HashMap<>(); - private static final String errorHeader = "PromotionRepository : "; - - public static void addPromotion(String key, Promotion promotion) { - try{ - List newEntry = promotionRepository.get(key); - newEntry.forEach(entry -> entry.isPromotionPeriodsOverlap(promotion)); - newEntry.add(promotion); - promotionRepository.replace(key, newEntry); - } catch (Exception e) { - throw new IllegalArgumentException(errorHeader + e.getMessage()); - } - } - - public static Optional isPromotion(String key, LocalDate date){ - List registeredPromotions = promotionRepository.get(key); - if(registeredPromotions == null) return Optional.empty(); - return registeredPromotions.stream() - .filter(promotion -> promotion.isDatePromotionPeriod(date)).findFirst(); - } - - //확장을 위해 미리 형태만 잡아둠 - public void removeOldestPromotion(){} - - public void removeAllFinishedPromotions(){} -} diff --git a/src/main/java/store/model/Promotion.java b/src/main/java/store/model/domain/Promotion.java similarity index 87% rename from src/main/java/store/model/Promotion.java rename to src/main/java/store/model/domain/Promotion.java index 51e2827..951b261 100644 --- a/src/main/java/store/model/Promotion.java +++ b/src/main/java/store/model/domain/Promotion.java @@ -1,13 +1,12 @@ -package store.model; +package store.model.domain; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; +import java.time.LocalDateTime; import java.util.List; import java.util.function.Function; import java.util.stream.IntStream; import store.utility.ErrorMessage; import store.utility.Parser; -import store.utility.Validator; public class Promotion { private static final int MOST_GET_NUM = 5, LEAST_GET_NUM = 1; @@ -16,8 +15,8 @@ public class Promotion { private final String name; private final int buy; private final int get; - private final LocalDate start_date; - private final LocalDate end_date; + private final LocalDateTime start_date; + private final LocalDateTime end_date; private Promotion(Builder builder) { this.name = builder.name; @@ -39,7 +38,7 @@ public int getGet() { return get; } - public boolean isDatePromotionPeriod(LocalDate date) { + public boolean isDatePromotionPeriod(LocalDateTime date) { return start_date.isBefore(date)&&end_date.isAfter(date); } @@ -61,16 +60,15 @@ private enum Rule{ BUYNGET1(1, 1), BUYNGETN(LEAST_GET_NUM, MOST_GET_NUM); - private final int startGetNumber; - private final int endGetNumber; + private final int minGet, maxGet; Rule(int startGetNumber, int endGetNumber){ - this.startGetNumber = startGetNumber; - this.endGetNumber = endGetNumber; + this.minGet = startGetNumber; + this.maxGet = endGetNumber; } public boolean matches(int getNumber) { - return this.startGetNumber<=getNumber && getNumber<=this.endGetNumber; + return this.minGet <=getNumber && getNumber<=this.maxGet; } }; @@ -78,8 +76,8 @@ public static class Builder{ private String name; private int buy; private int get; - private LocalDate start_date; - private LocalDate end_date; + private LocalDateTime start_date; + private LocalDateTime end_date; public static Function, Builder> createBuilderByColumns(List columns){ return (List values)->{ @@ -141,7 +139,7 @@ Builder setEndDate(String input) { this.end_date = Parser.DateParse(input); return this; } catch (Exception e) { - throw new IllegalArgumentException(errorHeader + ErrorMessage.INPUT_NOT_DATE.getMessage()); + throw new IllegalArgumentException(errorHeader + String.format(ErrorMessage.INPUT_NOT_FORMAT.getMessage(),"날짜")); } } diff --git a/src/main/java/store/model/repository/PromotionRepository.java b/src/main/java/store/model/repository/PromotionRepository.java new file mode 100644 index 0000000..b51a0f6 --- /dev/null +++ b/src/main/java/store/model/repository/PromotionRepository.java @@ -0,0 +1,65 @@ +package store.model.repository; + +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import store.model.domain.Promotion; +import store.model.repository.interfaces.RepositoryProvider; +import store.utility.Pair; + +public class PromotionRepository implements RepositoryProvider { + private final Map> promotionRepository; + private static final String errorHeader = "PromotionRepository : "; + + public PromotionRepository(){ + promotionRepository = new HashMap<>(); + } + + @Override + public void addItem(String key, Object item) { + try{ + Promotion promotion = (Promotion) item; + List newEntry = promotionRepository.get(key); + if(newEntry!=null){ + //validation process + newEntry.forEach(entry -> entry.isPromotionPeriodsOverlap(promotion)); + newEntry.add(promotion); + promotionRepository.replace(key, newEntry); + + }else{ + newEntry= List.of(promotion); + promotionRepository.put(key, newEntry); + } + + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + e.getMessage()); + } + } + + @Override + public Pair isItem(String name){ + return itemInDateRangeExists(name, DateTimes.now()); + } + + //Boolean defines whether there actually is promotion + public Pair itemInDateRangeExists (String key, LocalDateTime date){ + List registeredPromotions = promotionRepository.get(key); + if(registeredPromotions.isEmpty()) return new Pair<>(false,null); + Optional foundPromotion = registeredPromotions.stream() + .filter(promotion -> promotion.isDatePromotionPeriod(date)).findFirst(); + return new Pair<>(true, foundPromotion.orElse(null)); + } + + //확장을 위해 미리 형태만 잡아둠 + public void removeOldestPromotion(){} + + public void removeAllFinishedPromotions(){} + + @Override + public String toString(){ + return promotionRepository.values().toString(); + } +} From 74a7d103064c05d068bf7cbcd579fc9511893db6 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:04:31 +0900 Subject: [PATCH 07/27] =?UTF-8?q?feat(model)=20:=20Product=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능목록 1번 --- src/main/java/store/model/Product.java | 4 -- src/main/java/store/model/domain/Product.java | 54 +++++++++++++++++++ .../model/repository/ProductRepository.java | 33 ++++++++++++ .../service/ProductInitializeService.java | 4 -- 4 files changed, 87 insertions(+), 8 deletions(-) delete mode 100644 src/main/java/store/model/Product.java create mode 100644 src/main/java/store/model/domain/Product.java create mode 100644 src/main/java/store/model/repository/ProductRepository.java delete mode 100644 src/main/java/store/service/ProductInitializeService.java diff --git a/src/main/java/store/model/Product.java b/src/main/java/store/model/Product.java deleted file mode 100644 index a79612a..0000000 --- a/src/main/java/store/model/Product.java +++ /dev/null @@ -1,4 +0,0 @@ -package store.model; - -public enum Product { -} diff --git a/src/main/java/store/model/domain/Product.java b/src/main/java/store/model/domain/Product.java new file mode 100644 index 0000000..92848ec --- /dev/null +++ b/src/main/java/store/model/domain/Product.java @@ -0,0 +1,54 @@ +package store.model.domain; + +import java.lang.reflect.Field; +import java.util.Comparator; +import java.util.List; +import store.utility.ErrorMessage; +import store.utility.Parser; + +public class Product implements Comparable { + String name; + int price; + + //확장성을 위해 이리 복잡하게 설계했습니다. 현재 모델의 필드명의 순서를 바꾸던지 추가해도 + //해당 팩토리 패턴을 그대로 사용할 수 있습니다. + public static Product createProduct(List generatedFields){ + Product product = new Product(); + Field[] fields = Product.class.getDeclaredFields(); + + if (generatedFields == null || generatedFields.size() != fields.length) { + throw new IllegalArgumentException(String.format( + ErrorMessage.RESOURCE_LENGTH_WRONG.getMessage(),fields.length)); + } + + for (int i = 0; i < fields.length; i++) { + fields[i].setAccessible(true); // Allow private field modification + try { + Object value = Parser.castValue(fields[i].getType(), generatedFields.get(i)); + fields[i].set(product, value); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to set field value", e); + } + } + return product; + } + + public String getName() { + return name; + } + + public int getPrice() { + return price; + } + + @Override + public String toString() { + String processedPrice = String.format("%,d", price); + return name + ' ' + processedPrice + "원"; + } + + @Override + public int compareTo(Product other) { + return this.name.compareTo(other.name); // Sorts alphabetically + } +} diff --git a/src/main/java/store/model/repository/ProductRepository.java b/src/main/java/store/model/repository/ProductRepository.java new file mode 100644 index 0000000..b9129dc --- /dev/null +++ b/src/main/java/store/model/repository/ProductRepository.java @@ -0,0 +1,33 @@ +package store.model.repository; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import store.model.domain.Product; +import store.model.repository.interfaces.RepositoryProvider; + +public class ProductRepository implements RepositoryProvider { + private final Map productRepository; + private static final String errorHeader = "ProductRepository : "; + + public ProductRepository(){ + productRepository = new HashMap<>(); + } + + @Override + public void addItem(String key, Object item) { + try{ + Product product = (Product) item; + productRepository.putIfAbsent(key, product); + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + e.getMessage()); + } + } + + @Override + public Product isItem(List generatedFields){ + Product product = Product.createProduct(generatedFields); + addItem(generatedFields.getFirst(), product); + return product; + } +} diff --git a/src/main/java/store/service/ProductInitializeService.java b/src/main/java/store/service/ProductInitializeService.java deleted file mode 100644 index f0bb96f..0000000 --- a/src/main/java/store/service/ProductInitializeService.java +++ /dev/null @@ -1,4 +0,0 @@ -package store.service; - -public class ProductInitializeService { -} From 7449facb38eae390f72a333c8cfd4a22d40ac7ce Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:05:44 +0900 Subject: [PATCH 08/27] =?UTF-8?q?feat(model)=20:=20Stock=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능목록 1번 --- src/main/java/store/model/Store.java | 4 - src/main/java/store/model/domain/Stock.java | 94 +++++++++++++++++++ .../model/repository/StockRepository.java | 73 ++++++++++++++ 3 files changed, 167 insertions(+), 4 deletions(-) delete mode 100644 src/main/java/store/model/Store.java create mode 100644 src/main/java/store/model/domain/Stock.java create mode 100644 src/main/java/store/model/repository/StockRepository.java diff --git a/src/main/java/store/model/Store.java b/src/main/java/store/model/Store.java deleted file mode 100644 index 1ffc0d9..0000000 --- a/src/main/java/store/model/Store.java +++ /dev/null @@ -1,4 +0,0 @@ -package store.model; - -public class Store { -} diff --git a/src/main/java/store/model/domain/Stock.java b/src/main/java/store/model/domain/Stock.java new file mode 100644 index 0000000..d276c02 --- /dev/null +++ b/src/main/java/store/model/domain/Stock.java @@ -0,0 +1,94 @@ +package store.model.domain; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import store.utility.ErrorMessage; +import store.utility.Parser; + +public class Stock implements Comparable{ + private Product product; + private int quantity; + private Promotion promotion; + + Stock(Product product, Object quantity){ + this.product = product; + this.quantity = (int) quantity; + } + + public Stock setPromotion(Promotion promotion){ + this.promotion = promotion; + return this; + } + + public Stock removePromotion(){ + promotion = null; + return this; + } + + public static Stock createStore(Product product, List storeValues){ + List fields = new java.util.ArrayList<>(List.of(Stock.class.getDeclaredFields())); + fields.removeIf(field -> field.getName().equals("product") || field.getName().equals("promotion")); + if (storeValues == null || storeValues.size() != fields.size()) { + throw new IllegalArgumentException(String.format( + ErrorMessage.RESOURCE_LENGTH_WRONG.getMessage(),fields.size())); + } + return new Stock(product, Parser.castValue(fields.getFirst().getType(), storeValues.getFirst())); + } + + public static Stock createStore(Product product, Promotion promotion, int quantity){ + return new Stock(product, quantity).setPromotion(promotion); + } + + public void deltaQuantity(int delta) { + quantity+=delta; + } + + public void removeQuantity(){ + quantity=0; + } + + public String getProductName() { + return product.getName(); + } + + public Product getProduct(){ + return product; + } + + public Promotion getPromotion() { + return promotion; + } + + public int getTotalPrice(){ + return product.getPrice() * quantity; + } + + public int getQuantity() { + return quantity; + } + + public boolean isEmpty(){ + return product==null&&promotion==null&&quantity==0; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(product.toString()).append(" "); + if(quantity==0){ + builder.append("재고 없음"); + return builder.toString(); + } + builder.append(quantity).append("개").append(" "); + if(promotion != null) { + builder.append(promotion.getName()); + } + return builder.toString(); + } + + @Override + public int compareTo(Stock other) { + return this.product.compareTo(other.product); // Sorts alphabetically + } +} diff --git a/src/main/java/store/model/repository/StockRepository.java b/src/main/java/store/model/repository/StockRepository.java new file mode 100644 index 0000000..5943b75 --- /dev/null +++ b/src/main/java/store/model/repository/StockRepository.java @@ -0,0 +1,73 @@ +package store.model.repository; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; +import jdk.jfr.Description; +import store.model.domain.Stock; +import store.model.repository.interfaces.RepositoryProvider; +import store.utility.Pair; + +public class StockRepository implements RepositoryProvider { + private final Map> stockRepository; + + public StockRepository(){ + stockRepository = new HashMap<>(); + } + + @Override + public void addItem(Object item){ + Stock stock = (Stock)item; + String productName = stock.getProductName(); + Pair repositoryValue = stockRepository.get(productName); + if(stockRepository.replace(productName, changedStockField(repositoryValue, stock))==null){ + stockRepository.put(productName, changedStockField(repositoryValue, stock)); + } + } + + @Override + @Description("First value : Promotion stock\nSecond Value : Normal Stock") + public Pair isItem(String name){ + return stockRepository.get(name); + } + + public List allEntriesToString() { + return stockRepository.values().stream().flatMap(StockRepository::streamList).toList(); + } + + public static Stream streamList(Pair inputPair){ + return Stream.of(inputPair.getFirst(), inputPair.getSecond()) + .filter(Objects::nonNull) + .map(Object::toString); + } + + + //------------------------------------------------------------------------------------------------// + // private methods from here + + private Pair changedStockField(Pair repositoryValue, Stock newItem){ + if(repositoryValue == null){ repositoryValue = new Pair<>(null, null);} + Stock promotionStock = repositoryValue.getFirst(), + normalStock = repositoryValue.getSecond(); + + if(newItem.getPromotion()!=null){ + repositoryValue.setFirst(processedStock(promotionStock, newItem)); + if(normalStock==null) { + newItem = Stock.createStore(newItem.getProduct(), null, 0); + } + } + repositoryValue.setSecond(processedStock(normalStock, newItem)); + + return repositoryValue; + } + + private Stock processedStock(Stock originalStock, Stock newStock){ + if(originalStock == null) originalStock = newStock; + else originalStock.deltaQuantity(newStock.getQuantity()); + return originalStock; + } + + +} From b832ccd7a65273a91b35e3bc54d58f0254d3aa7f Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:07:03 +0900 Subject: [PATCH 09/27] fix(model) : RepositoryProvider(Interface) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit repository 관리, 테스트를 위해 interface 상속으로 구조 변경 --- .../repository/interfaces/RepositoryProvider.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/store/model/repository/interfaces/RepositoryProvider.java diff --git a/src/main/java/store/model/repository/interfaces/RepositoryProvider.java b/src/main/java/store/model/repository/interfaces/RepositoryProvider.java new file mode 100644 index 0000000..8a4e721 --- /dev/null +++ b/src/main/java/store/model/repository/interfaces/RepositoryProvider.java @@ -0,0 +1,10 @@ +package store.model.repository.interfaces; + +import java.util.List; + +public interface RepositoryProvider { + default void addItem(String key, Object item){}; + default void addItem(Object item){}; + default Object isItem(String name){return null;} + default Object isItem(List generatedFields){return null;} +} From 5df430aab40bb107ce6cfe0ffaebdd6f80542b03 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:08:19 +0900 Subject: [PATCH 10/27] fix(directory, service) : ProductInitializeService -> StoreInitializeService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 상품 생성만 담당하는 서비스를 전체 편의점 생성으로 변경 기능목록 4번 --- .../store/service/StoreInitializeService.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/main/java/store/service/StoreInitializeService.java diff --git a/src/main/java/store/service/StoreInitializeService.java b/src/main/java/store/service/StoreInitializeService.java new file mode 100644 index 0000000..a25f65a --- /dev/null +++ b/src/main/java/store/service/StoreInitializeService.java @@ -0,0 +1,115 @@ +package store.service; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import store.model.domain.Product; +import store.model.domain.Stock; +import store.model.domain.Promotion; +import store.model.domain.Promotion.Builder; +import store.service.RepositoryService.AddItemType; +import store.service.RepositoryService.isItemType; +import store.utility.ErrorMessage; +import store.utility.FileReadTool; +import store.utility.Pair; +import store.utility.Parser; +import java.util.function.Function; + +public class StoreInitializeService { + private final RepositoryService repositoryService; + private List> table; + + public StoreInitializeService(RepositoryService repositoryService){ + this.repositoryService = repositoryService; + } + + public void setPromotions() { + List rawTable = FileReadTool.readFile("promotions.md"); + table = new ArrayList<>(Parser.stringListParse(",", rawTable)); + + Function, Builder> builderRowInjector = Promotion.Builder.createBuilderByColumns(table.getFirst()); + table.removeFirst(); + table.forEach(row -> addPromotion(builderRowInjector.apply(row))); + } + + public void setProductsAndStocks() { + List rawTable = FileReadTool.readFile("products.md"); + table = new ArrayList<>(Parser.stringListParse(",", rawTable)); + + List productFieldIndex = getProductFieldMatchingIndexList(table.getFirst()); + if (productFieldIndex.isEmpty()) { + throw new IllegalArgumentException(ErrorMessage.RESOURCE_COLUMN_WRONG.getMessage()); + } + Integer promotionIndex = getPromotionIndex(table.getFirst()); + table.stream().skip(1).forEach(row -> rowToStock(row, productFieldIndex, promotionIndex)); + } + + //------------------------------------------------------------------------------------------------// + // private methods from here + + private void addPromotion(Builder promotionBuilder) { + Promotion promotion = promotionBuilder.build(); + repositoryService.addItem(AddItemType.PROMOTION, promotion.getName(), promotion); + } + + private void rowToStock(List row, List productFieldIndex, int promotionIndex) { + Product product = extractProduct(row, productFieldIndex); + List storeValues = extractStoreValues(row, productFieldIndex, promotionIndex); + Stock stock = Stock.createStore(product, storeValues); + if(promotionIndex>-1){ + stock.setPromotion(extractPromotion(row, promotionIndex)); + } + repositoryService.addItem(stock); + } + + private Product extractProduct(List row, List productFieldIndex) { + // need to reformat this code if dataset length > 10 + List productValues = new ArrayList<>(Collections.nCopies(productFieldIndex.size(), null)); + for (int i = 0; i < row.size(); i++) { + if (productFieldIndex.contains(i)) { + productValues.set(productFieldIndex.indexOf(i), row.get(i)); + } + } + return (Product) repositoryService.isItem(productValues); + } + + private Promotion extractPromotion(List row, int promotionIndex) { + String value = row.get(promotionIndex); + if(Objects.equals(value, "null")) return null; + Pair promotion = (Pair) repositoryService.isItem(isItemType.PROMOTION, value); + if (!promotion.getFirst()) { + throw new NoSuchElementException( + String.format(ErrorMessage.RESOURCE_NO_SUCH_INSTANCE.getMessage(), value) + ); + } + return promotion.getSecond(); + + } + + private List extractStoreValues(List row, List productFieldIndex, int promotionIndex) { + List storeValues = new ArrayList<>(); + for (int i = 0; i < row.size(); i++) { + if (!productFieldIndex.contains(i) && promotionIndex!=i) { + storeValues.add(row.get(i)); + } + } + return storeValues; + } + + private List getProductFieldMatchingIndexList(List columnLabels) { + Class product = Product.class; + Field[] fields = product.getDeclaredFields(); + return Arrays.stream(fields) + .map(field -> columnLabels.indexOf(field.getName())) + .filter(index -> index >= 0) + .toList(); + } + + private Integer getPromotionIndex(List columnLabels) { + return columnLabels.indexOf("promotion"); + } +} From 5dcca0aa354e1c8098d624ce7aacb7f158ab1dae Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:10:40 +0900 Subject: [PATCH 11/27] feat(utility) : FileReadTool, Parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FileReadTool : Resource에서 값을 String으로 값을 읽어옴 - Parser : 적절한 타입 변환 담당 기능목록 4번 --- src/main/java/store/utility/FileReadTool.java | 28 ++++++++++++++ src/main/java/store/utility/Parser.java | 38 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/java/store/utility/FileReadTool.java create mode 100644 src/main/java/store/utility/Parser.java diff --git a/src/main/java/store/utility/FileReadTool.java b/src/main/java/store/utility/FileReadTool.java new file mode 100644 index 0000000..7a4ac73 --- /dev/null +++ b/src/main/java/store/utility/FileReadTool.java @@ -0,0 +1,28 @@ +package store.utility; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public final class FileReadTool { + private static final String errorHeader = "FileReadTool : "; + public static List readFile(String fileName){ + List lines = new ArrayList<>(); + try (InputStream inputStream = FileReadTool.class.getClassLoader().getResourceAsStream(fileName); + BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(inputStream)))) { + + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } catch (IOException | NullPointerException e) { + throw new RuntimeException("Error reading file: " + fileName, e); + } + return lines; + } +} diff --git a/src/main/java/store/utility/Parser.java b/src/main/java/store/utility/Parser.java new file mode 100644 index 0000000..6091e3b --- /dev/null +++ b/src/main/java/store/utility/Parser.java @@ -0,0 +1,38 @@ +package store.utility; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +public final class Parser { + public static List> stringListParse(String regex, List inputList){ + return inputList.stream().map(row-> List.of(row.split(regex))).toList(); + } + + public static List stringParse(String regex, String inputLine){ + return List.of(inputLine.split(regex)); + } + + public static int NumberParse(String inputString){ + try{ + return Integer.parseInt(inputString); + } catch (Exception e) { + throw new IllegalArgumentException(ErrorMessage.INPUT_NOT_FORMAT.getMessage()); + } + } + + public static LocalDateTime DateParse(String inputString){ + try{ + return LocalDate.parse(inputString, DateTimeFormatter.ofPattern("uuuu-MM-dd")).atStartOfDay(); + } catch (Exception e) { + throw new IllegalArgumentException(String.format(ErrorMessage.INPUT_NOT_FORMAT.getMessage(),inputString+" : 날짜")); + } + } + + public static Object castValue(Class type, String value){ + if(type == int.class) return NumberParse(value); + //double, boolean 검사해야하면 추후 여기에 코드 추가 + return value; + } +} From b378c068201e57fea6a75ddc3e9b4ecb6cdb7f24 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:11:56 +0900 Subject: [PATCH 12/27] =?UTF-8?q?feat(model)=20:=20User=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User, UserRepository, UserService 추후 확장성을 고려해 설계 기능목록 3번 --- src/main/java/store/model/domain/User.java | 74 +++++++++++++++++++ .../model/repository/UserRepository.java | 34 +++++++++ src/main/java/store/service/UserService.java | 29 ++++++++ 3 files changed, 137 insertions(+) create mode 100644 src/main/java/store/model/domain/User.java create mode 100644 src/main/java/store/model/repository/UserRepository.java create mode 100644 src/main/java/store/service/UserService.java diff --git a/src/main/java/store/model/domain/User.java b/src/main/java/store/model/domain/User.java new file mode 100644 index 0000000..5547bbe --- /dev/null +++ b/src/main/java/store/model/domain/User.java @@ -0,0 +1,74 @@ +package store.model.domain; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import store.model.domain.Membership.Type; +import store.utility.ErrorMessage; +import store.utility.Pair; + +public class User { + Receipt receipt; + Membership membership; + boolean useMembership; + boolean receiptAvailable; + + public User(){ + membership = Type.NORMAL.create(); + receipt = Receipt.makeReceipt(); + receiptAvailable = false; + useMembership = false; + } + + public void useMembership(boolean membership) { + this.useMembership = membership; + } + + public void calculateReceipt(){ + receipt.setTotalStockPrice(); + receipt.setTotalPromotionPrice(); + + int membershipDiscount = 0; + if(useMembership) { + membershipDiscount = membership.discount(receipt.getTotalStockPriceWithoutPromotion()); + } + receipt.setTotalMembershipPrice(membershipDiscount); + receipt.setFinalPayPrice(); + + receiptAvailable = true; + } + + public String getReceiptText(){ + if(receiptAvailable){ + return receipt.toString(); + } + throw new IllegalStateException( + String.format(ErrorMessage.ACCESS_BEFORE_INITIALIZATION.getMessage(), + "required receipt's values", "getReceiptText")); + } + + public void resetReceipt(){ + receipt = null; + receipt = Receipt.makeReceipt(); + } + + public ReceiptProxy accessReceipt(){ + return new ReceiptProxy(receipt); + } + + public static class ReceiptProxy { + private final Receipt receipt; + private ReceiptProxy(Receipt receipt) { + this.receipt = receipt; + } + + public void addPurchasedStock(Stock stock){ + receipt.addPurchasedStock(stock); + } + + public void addPromotionStock(Stock stock){ + receipt.addPromotionStock(stock); + } + } +} diff --git a/src/main/java/store/model/repository/UserRepository.java b/src/main/java/store/model/repository/UserRepository.java new file mode 100644 index 0000000..ae43a51 --- /dev/null +++ b/src/main/java/store/model/repository/UserRepository.java @@ -0,0 +1,34 @@ +package store.model.repository; + +import java.util.HashMap; +import java.util.Map; +import store.model.domain.User; +import store.model.repository.interfaces.RepositoryProvider; + +public class UserRepository implements RepositoryProvider { + private final Map userRepository; + private static final String errorHeader = "UserRepository : "; + + public UserRepository(){ + userRepository = new HashMap<>(); + } + + @Override + public void addItem(String key, Object item) { + try{ + User user = (User) item; + userRepository.putIfAbsent(key, user); + } catch (Exception e) { + throw new IllegalArgumentException(errorHeader + e.getMessage()); + } + } + + @Override + public User isItem(String userKey){ + return userRepository.get(userKey); + } + + public void removeItem(String userKey){ + userRepository.remove(userKey); + } +} diff --git a/src/main/java/store/service/UserService.java b/src/main/java/store/service/UserService.java new file mode 100644 index 0000000..62ee883 --- /dev/null +++ b/src/main/java/store/service/UserService.java @@ -0,0 +1,29 @@ +package store.service; + +import java.util.UUID; +import store.model.domain.User; +import store.model.repository.UserRepository; + +public class UserService { + UserRepository userRepository; + + public UserService(UserRepository userRepository){ + this.userRepository = userRepository; + } + + public String createUser(){ + User user = new User(); + String userKey = String.valueOf(UUID.randomUUID()); + userRepository.addItem(userKey, user); + return userKey; + } + + public User retrieveUser(String userKey){ + return userRepository.isItem(userKey); + } + + public void removeUser(String userKey){ + userRepository.removeItem(userKey); + } + +} From ae573071b9d88a10af4a5635284339e6fb4b78f9 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:14:13 +0900 Subject: [PATCH 13/27] =?UTF-8?q?feat(utility)=20:=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EA=B0=92=20=EA=B4=80=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?enum=EA=B3=BC=20global=20var?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SystemConstantVariable SystemMessage ErrorMessage 5dcca0aa354e1c8098d624ce7aacb7f158ab1dae 커밋으로 인한 Validator 제거 기능목록 9, 10번 --- src/main/java/store/utility/ErrorMessage.java | 20 +++++++++++++++++ .../store/utility/SystemConstantVariable.java | 14 ++++++++++++ .../java/store/utility/SystemMessage.java | 22 +++++++++++++++++++ src/main/java/store/utility/Validator.java | 4 ---- 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 src/main/java/store/utility/SystemConstantVariable.java delete mode 100644 src/main/java/store/utility/Validator.java diff --git a/src/main/java/store/utility/ErrorMessage.java b/src/main/java/store/utility/ErrorMessage.java index a5efdfb..f214c8c 100644 --- a/src/main/java/store/utility/ErrorMessage.java +++ b/src/main/java/store/utility/ErrorMessage.java @@ -1,4 +1,24 @@ package store.utility; public enum ErrorMessage { + INPUT_NOT_FORMAT("%s가 아닌 다른 형식의 문자열이 입력되었습니다."), + RESOURCE_COLUMN_WRONG("모델의 필드명과 리소스 내의 컬럼명 %s이 일치하지 않습니다."), + RESOURCE_RANGE_WRONG("리소스 항목 %d은 정해진 규칙 %s의 범위를 초과합니다."), + RESOURCE_LENGTH_WRONG("리소스 항목 리스트 길이가 모델의 필드 길이 %d와 일치하지 않습니다."), + RESOURCE_NO_SUCH_INSTANCE("%s에 해당하는 명칭의 인스턴스가 존재하지 않습니다."), + PROMOTION_DATE_OVERLAP("같은 명칭의 두 프로모션의 날짜가 겹칩니다."), + PARAMETER_NOT_ACCEPTABLE("입력된 %s가 함수의 요구조건 %s에 어긋납니다."), + ACCESS_BEFORE_INITIALIZATION("변수 %s가 초기화 전 함수 %s를 호출했습니다."), + PURCHASE_NOT_ABLE("입력한 상품 개수 %d개는 재고를 초과해 구매할 수 없습니다."),; + + public static final String errorHeader = "[ERROR] "; + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } } diff --git a/src/main/java/store/utility/SystemConstantVariable.java b/src/main/java/store/utility/SystemConstantVariable.java new file mode 100644 index 0000000..acdd09f --- /dev/null +++ b/src/main/java/store/utility/SystemConstantVariable.java @@ -0,0 +1,14 @@ +package store.utility; + +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDateTime; +import java.util.List; + +public final class SystemConstantVariable { + public static final String STORE_NAME = "W"; + //public static final LocalDateTime TODAY = DateTimes.now(); + public static final String INPUT_CONFIRM_STRING = "Y", INPUT_REJECT_STRING ="N"; + public static final List INPUT_EXAMPLES = List.of("사이다", "1", "감자칩", "2"); + + +} diff --git a/src/main/java/store/utility/SystemMessage.java b/src/main/java/store/utility/SystemMessage.java index 66d5702..8a9d5c5 100644 --- a/src/main/java/store/utility/SystemMessage.java +++ b/src/main/java/store/utility/SystemMessage.java @@ -1,4 +1,26 @@ package store.utility; +import static store.utility.SystemConstantVariable.INPUT_CONFIRM_STRING; +import static store.utility.SystemConstantVariable.INPUT_REJECT_STRING; + public enum SystemMessage { + GREETING("안녕하세요. W편의점입니다.\n" + + "현재 보유하고 있는 상품입니다."), + PURCHASE_GUIDE("구매하실 상품명과 수량을 입력해 주세요. (예: %s)"), + NO_PROMOTION_GUIDE("현재 %s %d개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까? (" + + INPUT_CONFIRM_STRING +"/"+ INPUT_REJECT_STRING +")"), + YES_PROMOTION_GUIDE("현재 %s은(는) %d개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (" + + INPUT_CONFIRM_STRING +"/"+ INPUT_REJECT_STRING +")"), + MEMBERSHIP_GUIDE("멤버십 할인을 받으시겠습니까? (" + INPUT_CONFIRM_STRING +"/"+ INPUT_REJECT_STRING +")"), + DISMISSAL("감사합니다. 구매하고 싶은 다른 상품이 있나요? (" + INPUT_CONFIRM_STRING +"/"+ INPUT_REJECT_STRING +")"); + + private final String message; + + SystemMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } } diff --git a/src/main/java/store/utility/Validator.java b/src/main/java/store/utility/Validator.java deleted file mode 100644 index fa74d26..0000000 --- a/src/main/java/store/utility/Validator.java +++ /dev/null @@ -1,4 +0,0 @@ -package store.utility; - -public class Validator { -} From d44b24f58140ff4215e13f3f6b30da99ae75a8e9 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:17:38 +0900 Subject: [PATCH 14/27] =?UTF-8?q?feat(view)=20:=20InputView=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InputProvider Interface 설계 - DefaultInputProvider : 기본 실행용 Provider - InputView : InputProvider 받음 기능목록 6번 --- src/main/java/store/view/InputView.java | 48 ++++++++++++++++++- .../view/provider/DefaultInputProvider.java | 27 +++++++++++ .../provider/interfaces/InputProvider.java | 6 +++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/main/java/store/view/provider/DefaultInputProvider.java create mode 100644 src/main/java/store/view/provider/interfaces/InputProvider.java diff --git a/src/main/java/store/view/InputView.java b/src/main/java/store/view/InputView.java index 3f182f6..70286cb 100644 --- a/src/main/java/store/view/InputView.java +++ b/src/main/java/store/view/InputView.java @@ -1,4 +1,50 @@ package store.view; -public class InputView { +import static store.utility.SystemConstantVariable.INPUT_EXAMPLES; + +import camp.nextstep.edu.missionutils.Console; +import store.utility.InputFormatter; +import store.utility.SystemMessage; +import store.view.provider.DefaultInputProvider; +import store.view.provider.interfaces.InputProvider; + +public class InputView{ + InputProvider inputProvider; + + public InputView(){ inputProvider = new DefaultInputProvider(); } + public InputView(InputProvider inputProvider){ this.inputProvider = inputProvider;} + + public static InputView createByProvider(InputProvider inputProvider){ + return new InputView(inputProvider); + } + + public String readPurchaseItems(){ + System.out.printf(SystemMessage.PURCHASE_GUIDE.getMessage() + "%n", InputFormatter.stockFormat(INPUT_EXAMPLES)); + return inputProvider.readNormal(); + } + + public String readIfRegularPricePurchase(String productName, int stockAmount){ + System.out.printf(SystemMessage.NO_PROMOTION_GUIDE.getMessage() + "%n", productName, stockAmount); + return inputProvider.readYorN(); + } + + public String readIfAdditionalPurchase(String productName, int stockAmount){ + System.out.printf(SystemMessage.YES_PROMOTION_GUIDE.getMessage() + "%n", productName, stockAmount); + return inputProvider.readYorN(); + } + + public String readIfMembershipDiscount(){ + System.out.println(SystemMessage.MEMBERSHIP_GUIDE.getMessage()); + return inputProvider.readYorN(); + } + + public String readDismissal(){ + System.out.println(SystemMessage.DISMISSAL.getMessage()); + return inputProvider.readYorN(); + } + + //for debugging +// public String myIdentity(){ +// return inputProvider.toString(); +// } } diff --git a/src/main/java/store/view/provider/DefaultInputProvider.java b/src/main/java/store/view/provider/DefaultInputProvider.java new file mode 100644 index 0000000..1dd6c97 --- /dev/null +++ b/src/main/java/store/view/provider/DefaultInputProvider.java @@ -0,0 +1,27 @@ +package store.view.provider; + +import static store.utility.SystemConstantVariable.INPUT_EXAMPLES; + +import camp.nextstep.edu.missionutils.Console; +import store.utility.InputFormatter; +import store.utility.SystemMessage; +import store.view.provider.interfaces.InputProvider; + +public class DefaultInputProvider implements InputProvider { + @Override + public String readNormal(){ + return Console.readLine(); + } + + @Override + public String readYorN(){ + String input = Console.readLine(); + return InputFormatter.isConfirmRejectFormat(input); + } + + //for debugging + @Override + public String toString(){ + return "DefaultInput"; + } +} diff --git a/src/main/java/store/view/provider/interfaces/InputProvider.java b/src/main/java/store/view/provider/interfaces/InputProvider.java new file mode 100644 index 0000000..355df36 --- /dev/null +++ b/src/main/java/store/view/provider/interfaces/InputProvider.java @@ -0,0 +1,6 @@ +package store.view.provider.interfaces; + +public interface InputProvider { + String readNormal(); + String readYorN(); +} From c70919b1b1868a5d41cdded6209b3c3791b7b9cc Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:19:40 +0900 Subject: [PATCH 15/27] =?UTF-8?q?feat(model)=20:=20Membership,=20Receipt?= =?UTF-8?q?=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능목록 2번 --- src/main/java/store/model/Receipt.java | 4 - .../java/store/model/domain/Membership.java | 30 +++++ src/main/java/store/model/domain/Receipt.java | 111 ++++++++++++++++++ 3 files changed, 141 insertions(+), 4 deletions(-) delete mode 100644 src/main/java/store/model/Receipt.java create mode 100644 src/main/java/store/model/domain/Membership.java create mode 100644 src/main/java/store/model/domain/Receipt.java diff --git a/src/main/java/store/model/Receipt.java b/src/main/java/store/model/Receipt.java deleted file mode 100644 index d6d18aa..0000000 --- a/src/main/java/store/model/Receipt.java +++ /dev/null @@ -1,4 +0,0 @@ -package store.model; - -public class Receipt { -} \ No newline at end of file diff --git a/src/main/java/store/model/domain/Membership.java b/src/main/java/store/model/domain/Membership.java new file mode 100644 index 0000000..818c572 --- /dev/null +++ b/src/main/java/store/model/domain/Membership.java @@ -0,0 +1,30 @@ +package store.model.domain; + +public class Membership { + private Type type; + + Membership(Type type){ + this.type = type; + } + + public int discount(int price){ + int result = (int) Math.round(price * type.DISCOUNT_PERCENTAGE); + return Math.min(result, type.DISCOUNT_LIMIT); + } + + public enum Type{ + NORMAL(0.3, 8000),; + + Type(double discount_percentage,int discount_limit){ + this.DISCOUNT_PERCENTAGE = discount_percentage; + this.DISCOUNT_LIMIT = discount_limit; + } + + final double DISCOUNT_PERCENTAGE; + final int DISCOUNT_LIMIT; + + public Membership create(){ + return new Membership(this); + } + } +} diff --git a/src/main/java/store/model/domain/Receipt.java b/src/main/java/store/model/domain/Receipt.java new file mode 100644 index 0000000..12b0942 --- /dev/null +++ b/src/main/java/store/model/domain/Receipt.java @@ -0,0 +1,111 @@ +package store.model.domain; + +import java.util.TreeMap; +import store.utility.ErrorMessage; +import store.utility.SystemConstantVariable; + +public class Receipt { + private static final String HEADLINE = "===========%s 편의점=============\n" + + "상품명 수량 금액"; + private static final String MIDLINE = "===========증 정=============\n"; + private static final String ENDLINE = "==============================\n"; + + // The Integer in the Map is for total price + // TreeMap는 comparable 또는 comparator를 설정 시 alphabetical 정렬로 저장됨. + private final TreeMap purchasedStocks; + private final TreeMap promotionStocks; + private int totalStockPrice; + private int totalStockQuantity; + private int totalPromotionPrice; + private int totalMembershipPrice; + private int finalPayPrice; + + private Receipt() { + this.purchasedStocks = new TreeMap<>(); + this.promotionStocks = new TreeMap<>(); + } + + // Public method to get the single instance + public static Receipt makeReceipt() { + return new Receipt(); + } + + public void addPurchasedStock(Stock stock){ + purchasedStocks.put(stock, stock.getTotalPrice()); + } + + public void addPromotionStock(Stock stock){ + promotionStocks.put(stock, stock.getTotalPrice()); + } + + public void setTotalStockPrice(){ + totalStockPrice = purchasedStocks.values().stream().reduce(0, Integer::sum); + totalStockQuantity = purchasedStocks.descendingKeySet().stream().mapToInt(Stock::getQuantity).sum(); + } + + public void setTotalPromotionPrice() { + totalPromotionPrice = promotionStocks.values().stream().reduce(0, Integer::sum); + } + + public void setTotalMembershipPrice(int price) { + totalMembershipPrice = price; + } + + public void setFinalPayPrice(){ + finalPayPrice = totalStockPrice - totalPromotionPrice - totalMembershipPrice; + } + + public int getTotalStockPriceWithoutPromotion(){ + int result = totalStockPrice - totalPromotionPrice; + if(result<=0) { + throw new IllegalStateException(String.format(ErrorMessage.ACCESS_BEFORE_INITIALIZATION.getMessage(), "totalStockPrice", "getTotalStockPriceWithoutPromotion")); + } + return result; + } + + @Override + public String toString() { + StringBuilder receiptBuilder = new StringBuilder(); + + generateTopReceipt(receiptBuilder); + generateMidReceipt(receiptBuilder); + generateEndReceipt(receiptBuilder); + + return receiptBuilder.toString(); + } + + private void generateTopReceipt(StringBuilder receiptBuilder){ + receiptBuilder.append(String.format(HEADLINE, SystemConstantVariable.STORE_NAME)); + purchasedStocks.descendingKeySet() + .forEach(stock -> + receiptBuilder.append(stock.getProductName()) + .append(" ") + .append(stock.getQuantity()) + .append(" ") + .append(String.format("%,d", stock.getTotalPrice())) + .append("\n") + ); + } + + private void generateMidReceipt(StringBuilder receiptBuilder){ + receiptBuilder.append(MIDLINE); + promotionStocks.descendingKeySet().forEach(stock-> + receiptBuilder.append(stock.getProductName()) + .append(" ") + .append(stock.getQuantity()) + .append("\n") + ); + } + + private void generateEndReceipt(StringBuilder receiptBuilder){ + receiptBuilder.append(ENDLINE) + .append("총구매액").append(" ").append(totalStockQuantity).append(" ").append(String.format("%,d", totalStockPrice)) + .append("\n") + .append("행사할인").append(" ").append(String.format("-%,d", totalPromotionPrice)) + .append("\n") + .append("멤버십할인").append(" ").append(String.format("-%,d", totalMembershipPrice)) + .append("\n") + .append("내실돈").append(" ").append(String.format("%,d", finalPayPrice)) + .append("\n"); + } +} \ No newline at end of file From 9ecfe262724ec5ae77f5fae0af0a8dd21ef2ccce Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:21:22 +0900 Subject: [PATCH 16/27] feat(utility) : InputFormatter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5dcca0aa354e1c8098d624ce7aacb7f158ab1dae 커밋과 연관, 형식에 따라 값 분해 및 조립 --- .../java/store/utility/InputFormatter.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/store/utility/InputFormatter.java diff --git a/src/main/java/store/utility/InputFormatter.java b/src/main/java/store/utility/InputFormatter.java new file mode 100644 index 0000000..b8fa194 --- /dev/null +++ b/src/main/java/store/utility/InputFormatter.java @@ -0,0 +1,59 @@ +package store.utility; + +import static store.utility.SystemConstantVariable.INPUT_CONFIRM_STRING; +import static store.utility.SystemConstantVariable.INPUT_REJECT_STRING; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class InputFormatter { + private static final String OUTER_BRACKET = "["; + private static final String INNER_BRACKET = "]"; + private static final String CONNECTOR = "-"; + // Regex of FORMAT rejects 0 or negative value of input's quantity place. + private static final Pattern FULL_FORMAT = Pattern.compile("\\[([^\\]-]+)-([1-9]\\d*)\\](,\\[([^\\]-]+)-([1-9]\\d*)\\])*"); + private static final Pattern PART_FORMAT = Pattern.compile("\\[([^\\]-]+)-([1-9]\\d*)\\]"); + + public static String[] isStockFormat(String inputLine){ + String[] result = patternExtractor(inputLine); + if(result.length>0 && !Objects.equals(result[0], inputLine)) return result; + throw new IllegalArgumentException( + String.format(ErrorMessage.INPUT_NOT_FORMAT.getMessage(), + OUTER_BRACKET+"문자"+CONNECTOR+"양의 정수"+INNER_BRACKET)); + } + + public static String stockFormat(List inputItem){ + if(inputItem.size()%2!=0) { + throw new IllegalArgumentException( + String.format(ErrorMessage.PARAMETER_NOT_ACCEPTABLE.getMessage(), "리스트", "짝수")); + } + StringBuilder resultFormat = new StringBuilder(); + for(int i=0; i matches = new ArrayList<>(); + while (matcher.find()) { + matches.add(matcher.group(1)); + matches.add(matcher.group(2));// Extract full match + } + return matches.toArray(new String[0]); + } + +} From ae85d760ef747379f7eff8a92d235ec34effcab0 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:22:50 +0900 Subject: [PATCH 17/27] feat(service) : RepositoryService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리포지토리 상호작용을 담당하는 service --- .../java/store/service/RepositoryService.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/java/store/service/RepositoryService.java diff --git a/src/main/java/store/service/RepositoryService.java b/src/main/java/store/service/RepositoryService.java new file mode 100644 index 0000000..54fa7bb --- /dev/null +++ b/src/main/java/store/service/RepositoryService.java @@ -0,0 +1,63 @@ +package store.service; + +import java.util.List; +import store.model.repository.ProductRepository; +import store.model.repository.PromotionRepository; +import store.model.repository.interfaces.RepositoryProvider; +import store.model.repository.StockRepository; + +public class RepositoryService { + private final RepositoryProvider promotionRepository; + private final RepositoryProvider productRepository; + private final RepositoryProvider stockRepository; + + public RepositoryService(PromotionRepository promotionRepository, ProductRepository productRepository, StockRepository stockRepository){ + this.promotionRepository = promotionRepository; + this.productRepository = productRepository; + this.stockRepository = stockRepository; + } + + public enum AddItemType { + PROMOTION, + PRODUCT, + @Deprecated + STOCK; + } + + public enum isItemType{ + PROMOTION, + @Deprecated + PRODUCT, + STOCK; + } + + void addItem(AddItemType type, String key, Object item){ + if(type == AddItemType.PROMOTION) promotionRepository.addItem(key, item); + if(type == AddItemType.PRODUCT) productRepository.addItem(key, item); + //if(type == Type.STOCK) : 현재는 사용되지 않으므로 주석처리함 + }; + + void addItem(Object item){ + stockRepository.addItem(item); + }; + + Object isItem(isItemType type, String name){ + if(type == isItemType.PROMOTION) return promotionRepository.isItem(name); + if(type == isItemType.STOCK) return stockRepository.isItem(name); + //if(type == Type.PRODUCT) : 현재는 사용되지 않으므로 주석처리함 + return null; + } + + Object isItem(List generatedFields){ + return productRepository.isItem(generatedFields); + } + + public List getCurrentStockStringList(){ + StockRepository repository = (StockRepository) stockRepository; + return repository.allEntriesToString(); + } + + public String getPromotionRepository() { + return promotionRepository.toString(); + } +} From b796256d3bc787530066ca1325907779b11b8576 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 00:24:28 +0900 Subject: [PATCH 18/27] feat(service) : PurchaseService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자의 구매 행동을 총괄하는 서비스 - Pair : c++의 Pair와 동일, 자바로만 구현해둔 것 - CheckPromotionDTO : 구매에 따른 재고 변경을 위한 dto --- .../store/model/dto/CheckPromotionDTO.java | 64 +++++++++++ .../java/store/service/PurchaseService.java | 104 ++++++++++++++++++ src/main/java/store/utility/Pair.java | 25 +++++ 3 files changed, 193 insertions(+) create mode 100644 src/main/java/store/model/dto/CheckPromotionDTO.java create mode 100644 src/main/java/store/utility/Pair.java diff --git a/src/main/java/store/model/dto/CheckPromotionDTO.java b/src/main/java/store/model/dto/CheckPromotionDTO.java new file mode 100644 index 0000000..8cbff5d --- /dev/null +++ b/src/main/java/store/model/dto/CheckPromotionDTO.java @@ -0,0 +1,64 @@ +package store.model.dto; + +import store.model.domain.Product; +import store.model.domain.Promotion; +import store.model.domain.Stock; +import store.utility.Pair; + +public class CheckPromotionDTO { + boolean isPurchasable; + boolean isRelatedStock; + Product product; + Promotion promotion; + int infoQuantity; + int normalQuantity; + int promotionQuantity; + boolean isAdditionSuggest; + + + public enum Type{ + NORMAL, + PROMOTION + } + + public CheckPromotionDTO(boolean isPurchasable, boolean isRelatedStock, boolean isAdditionSuggest, + Stock baseStock, int infoQuantity, int normalQuantity, int promotionQuantity){ + this.isPurchasable = isPurchasable; + this.isRelatedStock = isRelatedStock; + this.isAdditionSuggest = isAdditionSuggest; + this.product = baseStock.getProduct(); + this.promotion = baseStock.getPromotion(); + this.infoQuantity = infoQuantity; + this.normalQuantity = normalQuantity; + this.promotionQuantity = promotionQuantity; + } + + public boolean isPurchasable(){ return isPurchasable; } + + public boolean isRelatedStock() { + return isRelatedStock; + } + + public boolean isAdditionSuggest() { + return isAdditionSuggest; + } + + public Product getProduct() { + return product; + } + + public Promotion getPromotion() { return promotion; } + + public int getInfoQuantity() { + return infoQuantity; + } + + public int getNormalQuantity() { return normalQuantity; } + + public int getPromotionQuantity() { return promotionQuantity; } + + public void changeQuantity(Type type, int deltaQuantity) { + if(type==Type.NORMAL) normalQuantity += deltaQuantity; + if(type==Type.PROMOTION) promotionQuantity += deltaQuantity; + } +} diff --git a/src/main/java/store/service/PurchaseService.java b/src/main/java/store/service/PurchaseService.java index fb4c764..fa067b9 100644 --- a/src/main/java/store/service/PurchaseService.java +++ b/src/main/java/store/service/PurchaseService.java @@ -1,4 +1,108 @@ package store.service; +import camp.nextstep.edu.missionutils.DateTimes; +import store.model.domain.User; +import store.model.dto.CheckPromotionDTO; +import store.model.domain.Promotion; +import store.model.domain.Stock; +import store.service.RepositoryService.isItemType; +import store.utility.ErrorMessage; +import store.utility.Pair; + public class PurchaseService { + UserService userService; + RepositoryService repositoryService; + + public PurchaseService(UserService userService, RepositoryService repositoryService){ + this.userService = userService; + this.repositoryService = repositoryService; + } + + public void confirmPurchaseToReceipt(String userKey, Stock promotionStock, Stock normalStock){ + User user = userService.retrieveUser(userKey); + user.accessReceipt().addPurchasedStock(normalStock); + user.accessReceipt().addPromotionStock(promotionStock); + } + + public void useMembership(String userKey, boolean isMembership){ + userService.retrieveUser(userKey).useMembership(isMembership); + } + + //이건 조건을 다 뺴지 않는 이상 줄이기가 안되드라구요...쩔수없이 이거대로 하기로 + public CheckPromotionDTO checkPromotionForCustomerInput(String userKey, String productName, int customerQuantity){ + Pair stocks = (Pair) repositoryService.isItem(isItemType.STOCK, productName); + if(stocks == null) throw new IllegalArgumentException(String.format(ErrorMessage.RESOURCE_NO_SUCH_INSTANCE.getMessage(), productName)); + + Stock basePromoStock = stocks.getFirst(); + Stock baseNormStock = stocks.getSecond(); + Promotion promotion; + + if(basePromoStock == null) { promotion = null; } + else { promotion = basePromoStock.getPromotion(); } + + int normalQuantity = baseNormStock.getQuantity(); + + // Case 1: No promotion -> Check only normal stock + if (promotion == null || !promotion.isDatePromotionPeriod(DateTimes.now())) { + return new CheckPromotionDTO(normalQuantity >= customerQuantity, false, false, baseNormStock, 0, customerQuantity, 0); + } + + // Case 2: Promotion applies + int totalMinusPromotion = promotion.getBuy() + promotion.getGet(); + int promotionQuantity = basePromoStock.getQuantity(); + + // Case 2-1: Not enough promo stock → Check normal stock + if (promotionQuantity < totalMinusPromotion) { + return new CheckPromotionDTO(normalQuantity >= customerQuantity, true, false, basePromoStock, customerQuantity, customerQuantity, 0); + } + + // Case 2-2: Customer bought fewer than promo stock → Handle promo suggestion + if (customerQuantity < promotionQuantity) { + int infoQuantity = customerQuantity % totalMinusPromotion; + if (infoQuantity == totalMinusPromotion - 1) { + return new CheckPromotionDTO(true, true, true, basePromoStock, 1, 0, customerQuantity); + } + if (infoQuantity != 0) { + return new CheckPromotionDTO(normalQuantity >= infoQuantity, true, false, basePromoStock, infoQuantity, infoQuantity, customerQuantity - infoQuantity); + } + } + + // Case 2-3: Promotion stock isn't enough for full purchase → Partial promo, rest normal + if (promotionQuantity != totalMinusPromotion && totalMinusPromotion != customerQuantity) { + int infoQuantity = customerQuantity - promotionQuantity + promotionQuantity % totalMinusPromotion; + return new CheckPromotionDTO( + normalQuantity >= infoQuantity && promotionQuantity > customerQuantity - infoQuantity, + true, + false, + basePromoStock, + infoQuantity, + infoQuantity, + customerQuantity - infoQuantity + ); + } + + // Case 2-4: Default fallback when above conditions don't match + return new CheckPromotionDTO( + normalQuantity + promotionQuantity >= customerQuantity, + true, + false, + basePromoStock, + 0, + Math.min(customerQuantity, normalQuantity), + Math.min(customerQuantity, promotionQuantity) + ); + } + + public String getReceipt(String userKey){ + User user = userService.retrieveUser(userKey); + user.calculateReceipt(); + return user.getReceiptText(); + } + + public void resetReceipt(String userKey){ + User user = userService.retrieveUser(userKey); + user.resetReceipt(); + } + + } diff --git a/src/main/java/store/utility/Pair.java b/src/main/java/store/utility/Pair.java new file mode 100644 index 0000000..7311c1a --- /dev/null +++ b/src/main/java/store/utility/Pair.java @@ -0,0 +1,25 @@ +package store.utility; + +public class Pair { + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public void setFirst(K key){this.key = key;} + public void setSecond(V value){this.value = value;} + public K getFirst() { return key; } + public V getSecond() { return value; } + + public boolean isEmpty(){ + return key==null&&value==null; + } + + @Override + public String toString() { + return key + " - " + value; + } +} \ No newline at end of file From e60d1120933b1bfdd5e9465369a5e41177c33711 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:26:03 +0900 Subject: [PATCH 19/27] fix(service) : PurchaseService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 구매 시 재고차감 로직이 없길래 수정 --- .../model/repository/StockRepository.java | 18 +++++++++++++----- .../java/store/service/PurchaseService.java | 4 ++-- .../java/store/service/RepositoryService.java | 6 ++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/store/model/repository/StockRepository.java b/src/main/java/store/model/repository/StockRepository.java index 5943b75..6d31199 100644 --- a/src/main/java/store/model/repository/StockRepository.java +++ b/src/main/java/store/model/repository/StockRepository.java @@ -17,20 +17,28 @@ public StockRepository(){ stockRepository = new HashMap<>(); } + @Override + @Description("First value : Promotion stock\nSecond Value : Normal Stock") + public Pair isItem(String name){ + return stockRepository.get(name); + } + @Override public void addItem(Object item){ Stock stock = (Stock)item; String productName = stock.getProductName(); - Pair repositoryValue = stockRepository.get(productName); + Pair repositoryValue = isItem(productName); if(stockRepository.replace(productName, changedStockField(repositoryValue, stock))==null){ stockRepository.put(productName, changedStockField(repositoryValue, stock)); } } - @Override - @Description("First value : Promotion stock\nSecond Value : Normal Stock") - public Pair isItem(String name){ - return stockRepository.get(name); + public void deductItem(String productName, int promotionStockDelta,int normalStockDelta){ + Pair repositoryValue = isItem(productName); + Stock first = repositoryValue.getFirst(); + Stock second = repositoryValue.getSecond(); + if(first!=null) first.deltaQuantity(-1 * promotionStockDelta); + if(second!=null) second.deltaQuantity(-1*normalStockDelta); } public List allEntriesToString() { diff --git a/src/main/java/store/service/PurchaseService.java b/src/main/java/store/service/PurchaseService.java index fa067b9..69f691f 100644 --- a/src/main/java/store/service/PurchaseService.java +++ b/src/main/java/store/service/PurchaseService.java @@ -18,9 +18,9 @@ public PurchaseService(UserService userService, RepositoryService repositoryServ this.repositoryService = repositoryService; } - public void confirmPurchaseToReceipt(String userKey, Stock promotionStock, Stock normalStock){ + public void confirmPurchaseToReceipt(String userKey, Stock promotionStock, Stock totalStock){ User user = userService.retrieveUser(userKey); - user.accessReceipt().addPurchasedStock(normalStock); + user.accessReceipt().addPurchasedStock(totalStock); user.accessReceipt().addPromotionStock(promotionStock); } diff --git a/src/main/java/store/service/RepositoryService.java b/src/main/java/store/service/RepositoryService.java index 54fa7bb..fa5c803 100644 --- a/src/main/java/store/service/RepositoryService.java +++ b/src/main/java/store/service/RepositoryService.java @@ -1,6 +1,7 @@ package store.service; import java.util.List; +import store.model.domain.Stock; import store.model.repository.ProductRepository; import store.model.repository.PromotionRepository; import store.model.repository.interfaces.RepositoryProvider; @@ -57,6 +58,11 @@ public List getCurrentStockStringList(){ return repository.allEntriesToString(); } + public void deductStock(String productName, int promotionStockDelta,int normalStockDelta){ + StockRepository repository = (StockRepository) stockRepository; + repository.deductItem(productName, promotionStockDelta, normalStockDelta); + } + public String getPromotionRepository() { return promotionRepository.toString(); } From 007d26afa014be02c58124d4a53e2e4cf245a049 Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:27:34 +0900 Subject: [PATCH 20/27] feat(controller) : StoreController --- .../store/controller/StoreController.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/main/java/store/controller/StoreController.java b/src/main/java/store/controller/StoreController.java index 627ae00..cb0afab 100644 --- a/src/main/java/store/controller/StoreController.java +++ b/src/main/java/store/controller/StoreController.java @@ -1,4 +1,122 @@ package store.controller; +import java.util.List; +import java.util.Objects; +import store.model.domain.Stock; +import store.model.dto.CheckPromotionDTO; +import store.model.dto.CheckPromotionDTO.Type; +import store.service.PurchaseService; +import store.service.RepositoryService; +import store.service.StoreInitializeService; +import store.service.UserService; +import store.utility.ErrorMessage; +import store.utility.InputFormatter; +import store.utility.Parser; +import store.view.InputView; +import store.view.OutputView; + public class StoreController { + InputView inputView; + OutputView outputView; + StoreInitializeService storeInitializeService; + PurchaseService purchaseService; + RepositoryService repositoryService; + UserService userService; + + public StoreController(InputView inputView, OutputView outputView, + StoreInitializeService storeInitializeService, + PurchaseService purchaseService, + RepositoryService repositoryService, UserService userService){ + this.inputView = inputView; + this.outputView = outputView; + this.storeInitializeService = storeInitializeService; + this.purchaseService = purchaseService; + this.repositoryService = repositoryService; + this.userService = userService; + } + + public String initializing(){ + try{ + storeInitializeService.setPromotions(); + storeInitializeService.setProductsAndStocks(); + return userService.createUser(); + }catch (Exception e){ + outputView.printError(ErrorMessage.errorHeader + e.getMessage()); + throw e; + } + } + + public void startPurchase(){ + outputView.printGreeting(); + List currentStock = repositoryService.getCurrentStockStringList(); + outputView.printCurrentStock(currentStock); + } + + public boolean purchasing(String userKey){ + try{ + String inputLine = inputView.readPurchaseItems(); + String[] splittedInput = InputFormatter.isStockFormat(inputLine); + purchaseEachItem(userKey, splittedInput); + outputView.printError("purchasing item executed"); + return false; + }catch (Exception e){ + outputView.printError(ErrorMessage.errorHeader + e.getMessage()); + return true; + } + } + + public int endPurchase(String userKey){ + try{ + String inputLine = inputView.readIfMembershipDiscount(); + purchaseService.useMembership(userKey, Objects.equals(inputLine, "Y")); + String receipt = purchaseService.getReceipt(userKey); + outputView.printReceipt(receipt); + purchaseService.resetReceipt(userKey); + inputLine = inputView.readDismissal(); + if(Objects.equals(inputLine, "N")) return -1; + return 0; + }catch (Exception e){ + outputView.printError(ErrorMessage.errorHeader + e.getMessage()); + return 1; + } + } + + public void disconnectUser(String userKey){ + userService.removeUser(userKey); + } + + + + + //------------------------------------------------------------------------------------------------// + // private methods from here + + //정가 질문에 N 답할 시 정가 상품만 구매하지 않는 것으로 이해하고 코드 짰습니다. + private CheckPromotionDTO readAccordingToCheckPromotionDTO(CheckPromotionDTO result, int customerQuantity) { + if (!result.isPurchasable()) throw new IllegalArgumentException(String.format(ErrorMessage.PURCHASE_NOT_ABLE.getMessage(), customerQuantity)); + String productName = result.getProduct().getName(); + if (result.isRelatedStock() && result.isAdditionSuggest()) { + String input = inputView.readIfAdditionalPurchase(productName, result.getInfoQuantity()); + if(Objects.equals(input, "N")){ result.changeQuantity(Type.PROMOTION,-1*result.getInfoQuantity());} + } + if (result.isRelatedStock() && result.getInfoQuantity() != 0) { + String input = inputView.readIfRegularPricePurchase(productName, result.getInfoQuantity()); + if(Objects.equals(input, "N")){ result.changeQuantity(Type.NORMAL,-1*result.getInfoQuantity()); } + } + + repositoryService.deductStock(productName, result.getPromotionQuantity(),result.getNormalQuantity()); + return result; + } + + private void purchaseEachItem(String userKey, String[] splittedInput){ + for(int i=0; i Date: Fri, 4 Apr 2025 01:28:06 +0900 Subject: [PATCH 21/27] feat(view) : OutputView --- src/main/java/store/view/OutputView.java | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/java/store/view/OutputView.java b/src/main/java/store/view/OutputView.java index 0c4d0c3..9670bbe 100644 --- a/src/main/java/store/view/OutputView.java +++ b/src/main/java/store/view/OutputView.java @@ -1,4 +1,31 @@ package store.view; +import java.util.List; +import store.utility.SystemConstantVariable; +import store.utility.SystemMessage; + public class OutputView { + private static final String BULLETIN = "- "; + + public void printGreeting(){ + System.out.printf(SystemMessage.GREETING.getMessage() + "%n", SystemConstantVariable.STORE_NAME); + lineBreak(); + } + public void printCurrentStock(List currentStock){ + currentStock.forEach(stockLine -> System.out.println(BULLETIN+stockLine)); + lineBreak(); + } + + public void printReceipt(String receipt){ + System.out.printf(receipt); + lineBreak(); + } + + public void printError(String error){ + System.out.println(error); + } + + public void lineBreak(){ + System.out.println("\n"); + } } From c59eca90bdd6bac8618b85426b34a9a68ad339db Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:28:52 +0900 Subject: [PATCH 22/27] feat(test) : TestInputProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InputView에 test시 inject할 provider --- .../view/provider/TestInputProvider.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/java/store/view/provider/TestInputProvider.java diff --git a/src/main/java/store/view/provider/TestInputProvider.java b/src/main/java/store/view/provider/TestInputProvider.java new file mode 100644 index 0000000..1052fd0 --- /dev/null +++ b/src/main/java/store/view/provider/TestInputProvider.java @@ -0,0 +1,34 @@ +package store.view.provider; + +import java.text.Normalizer; +import store.view.provider.interfaces.InputProvider; + +public class TestInputProvider implements InputProvider { + String purchaseLine; + String[] YorN; + int index; + + public TestInputProvider(String purchaseLine, String[] YorN){ + this.purchaseLine = Normalizer.normalize(purchaseLine, Normalizer.Form.NFC); + this.YorN = YorN; + index = 0; + } + + @Override + public String readNormal(){ + return purchaseLine; + } + + @Override + public String readYorN(){ + if(index == YorN.length) index = 0; + String target = YorN[index++]; + return target; + } + + //for debugging + @Override + public String toString(){ + return "MockInput"; + } +} From 25e302f72d052a3ba7d8e8acbc83ca8e1176f61d Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:29:52 +0900 Subject: [PATCH 23/27] feat(main) : Application MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테스트를 고려한 testRun 메서드 breakpoint 설정 가능 --- src/main/java/store/Application.java | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/main/java/store/Application.java b/src/main/java/store/Application.java index ec4afd8..6d96b75 100644 --- a/src/main/java/store/Application.java +++ b/src/main/java/store/Application.java @@ -1,7 +1,71 @@ package store; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import store.controller.StoreController; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8)); + AppConfig appConfig = new AppConfig(); + new Application().run(appConfig); + } + + public void run(AppConfig appConfig){ + boolean repeatInput = true; + boolean repeatProgram = true; + StoreController storeController = appConfig.getStoreController(); + String accessingUserID = storeController.initializing(); + + while(repeatProgram){ + storeController.startPurchase(); + while(repeatInput){ + repeatInput = storeController.purchasing(accessingUserID); + } + + repeatInput = true; + + while(repeatInput){ + int result = storeController.endPurchase(accessingUserID); + if(result < 1) repeatInput = false; + if(result < 0) repeatProgram = false; + } + repeatInput = true; + } + storeController.disconnectUser(accessingUserID); + } + + public void testRun(AppConfig appConfig, Breakpoint breakpoint){ + boolean repeatInput = true; + boolean repeatProgram = true; + StoreController storeController = appConfig.getStoreController(); + String accessingUserID = storeController.initializing(); + + while(repeatProgram){ + storeController.startPurchase(); + if(breakpoint==Breakpoint.PRODUCT_GUIDE) break; + while(repeatInput){ + repeatInput = storeController.purchasing(accessingUserID); + if(breakpoint==Breakpoint.FIRST_PURCHASE_ATTEMPT) break; + } + + if(breakpoint==Breakpoint.FIRST_PURCHASE_ATTEMPT) break; + repeatInput = true; + + while(repeatInput){ + int result = storeController.endPurchase(accessingUserID); + if(result < 1) repeatInput = false; + if(result < 0) repeatProgram = false; + } + repeatInput = true; + } + storeController.disconnectUser(accessingUserID); + } + + public enum Breakpoint{ + PRODUCT_GUIDE, + FIRST_PURCHASE_ATTEMPT, + NONE } } From f961174203ede35cbfee640bfac2db0e0fb3220a Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:31:14 +0900 Subject: [PATCH 24/27] feat(main) : AppConfig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPT선생도 배웠다한 MockSetting 기능이 있읍니다. 테스트에 관련된 객체를 생성해 주입할수 있게 만듦. --- src/main/java/store/AppConfig.java | 119 +++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/main/java/store/AppConfig.java diff --git a/src/main/java/store/AppConfig.java b/src/main/java/store/AppConfig.java new file mode 100644 index 0000000..16b74d5 --- /dev/null +++ b/src/main/java/store/AppConfig.java @@ -0,0 +1,119 @@ +package store; + +import java.util.function.Consumer; +import store.controller.StoreController; +import store.model.repository.ProductRepository; +import store.model.repository.PromotionRepository; +import store.model.repository.StockRepository; +import store.model.repository.UserRepository; +import store.service.PurchaseService; +import store.service.RepositoryService; +import store.service.StoreInitializeService; +import store.service.UserService; +import store.utility.ErrorMessage; +import store.view.InputView; +import store.view.OutputView; + +public class AppConfig { + private ProductRepository productRepository; + private PromotionRepository promotionRepository; + private StockRepository stockRepository; + private UserRepository userRepository; + + private RepositoryService repositoryService; + private UserService userService; + private StoreInitializeService storeInitializeService; + private PurchaseService purchaseService; + + private InputView inputView; + private OutputView outputView; + + private StoreController storeController; + + public MockSetting mock; + + public AppConfig(){ + setRepository(); + setService(); + setView(); + setContoller(); + + mock = new MockSetting(this); + } + + public StoreController getStoreController(){ + return storeController; + } + + public static class MockSetting { + public SingleVariable InputView; + public SingleVariable ProductRepository; + public SingleVariable PromotionRepository; + public SingleVariable StockRepository; + + public MockSetting(AppConfig parent){ + this.InputView = new SingleVariable<>(v -> parent.inputView = v, parent::setContoller); + this.ProductRepository = new SingleVariable<>(v -> parent.productRepository = v, ()->{parent.setService(); parent.setContoller();}); + this.StockRepository = new SingleVariable<>(v -> parent.stockRepository = v, ()->{parent.setService(); parent.setContoller();}); + this.PromotionRepository = new SingleVariable<>(v -> parent.promotionRepository = v, ()->{parent.setService(); parent.setContoller();}); + } + + static class SingleVariable implements Injectable { + private Consumer setter; + private Refresh func; + public SingleVariable(Consumer setter, Refresh func){ + this.setter = setter; + this.func = func; + } + @Override + public void inject(Object mockedInstance){ + try{ + setter.accept((T) mockedInstance); + func.refresh(); + } catch (Exception e){ + throw new IllegalStateException(ErrorMessage.errorHeader+ + String.format(ErrorMessage.PARAMETER_NOT_ACCEPTABLE.getMessage(), + "입력된 타입의 mockInstance",setter.getClass().getName())); + } + } + } + } + + @FunctionalInterface + interface Injectable { + void inject(Object mockedInstance); + } + + @FunctionalInterface + interface Refresh{ + void refresh(); + } + + private void setRepository(){ + productRepository = new ProductRepository(); + promotionRepository = new PromotionRepository(); + stockRepository = new StockRepository(); + userRepository = new UserRepository(); + } + + private void setService(){ + repositoryService = new RepositoryService(promotionRepository, productRepository, stockRepository); + userService = new UserService(userRepository); + storeInitializeService = new StoreInitializeService(repositoryService); + purchaseService = new PurchaseService(userService, repositoryService); + } + + private void setView(){ + inputView = new InputView(); + outputView = new OutputView(); + } + + private void setContoller(){ + storeController = new StoreController(inputView, outputView, storeInitializeService, purchaseService, repositoryService, userService); + } + + //for debugging +// public void myInputView(){ +// System.out.println("Injected InputView: " + inputView.myIdentity()); +// } +} From 38fb9d0faee5d40deb850330cb11b3552b75288b Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:32:03 +0900 Subject: [PATCH 25/27] fix(main) : ApplicationTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 설계한 프로그램이 기존 테스트 코드와는 호환되지 않아 약간 수정함 --- src/test/java/store/ApplicationTest.java | 35 +++++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/test/java/store/ApplicationTest.java b/src/test/java/store/ApplicationTest.java index 7eac229..73a8069 100644 --- a/src/test/java/store/ApplicationTest.java +++ b/src/test/java/store/ApplicationTest.java @@ -4,16 +4,24 @@ import org.junit.jupiter.api.Test; import java.time.LocalDate; +import store.Application.Breakpoint; +import store.controller.StoreController; +import store.view.InputView; +import store.view.provider.TestInputProvider; import static camp.nextstep.edu.missionutils.test.Assertions.assertNowTest; import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; import static org.assertj.core.api.Assertions.assertThat; class ApplicationTest extends NsTest { + AppConfig appConfig = new AppConfig(); @Test void 파일에_있는_상품_목록_출력() { - assertSimpleTest(() -> { - run("[물-1]", "N", "N"); + assertNowTest(() -> { + String[] YorN = new String[]{"N","N"}; + InputView mockInputView = InputView.createByProvider(new TestInputProvider("[물-1]", YorN)); + appConfig.mock.InputView.inject(mockInputView); + runTest(Breakpoint.PRODUCT_GUIDE); assertThat(output()).contains( "- 콜라 1,000원 10개 탄산2+1", "- 콜라 1,000원 10개", @@ -34,13 +42,16 @@ class ApplicationTest extends NsTest { "- 컵라면 1,700원 1개 MD추천상품", "- 컵라면 1,700원 10개" ); - }); + },LocalDate.of(2024, 11, 2).atStartOfDay()); } @Test void 여러_개의_일반_상품_구매() { assertSimpleTest(() -> { - run("[비타민워터-3],[물-2],[정식도시락-2]", "N", "N"); + String[] YorN = new String[]{"N","N"}; + InputView mockInputView = InputView.createByProvider(new TestInputProvider("[비타민워터-3],[물-2],[정식도시락-2]", YorN)); + appConfig.mock.InputView.inject(mockInputView); + runTest(Breakpoint.NONE); assertThat(output().replaceAll("\\s", "")).contains("내실돈18,300"); }); } @@ -48,7 +59,10 @@ class ApplicationTest extends NsTest { @Test void 기간에_해당하지_않는_프로모션_적용() { assertNowTest(() -> { - run("[감자칩-2]", "N", "N"); + String[] YorN = new String[]{"N","N"}; + InputView mockInputView = InputView.createByProvider(new TestInputProvider("[감자칩-2]", YorN)); + appConfig.mock.InputView.inject(mockInputView); + runTest(Breakpoint.NONE); assertThat(output().replaceAll("\\s", "")).contains("내실돈3,000"); }, LocalDate.of(2024, 2, 1).atStartOfDay()); } @@ -56,8 +70,11 @@ class ApplicationTest extends NsTest { @Test void 예외_테스트() { assertSimpleTest(() -> { - runException("[컵라면-12]", "N", "N"); - assertThat(output()).contains("[ERROR] 재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요."); + String[] YorN = new String[]{"N","N"}; + InputView mockInputView = InputView.createByProvider(new TestInputProvider("[컵라면-12]", YorN)); + appConfig.mock.InputView.inject(mockInputView); + runTest(Breakpoint.FIRST_PURCHASE_ATTEMPT); + assertThat(output()).contains("[ERROR] 입력한 상품 개수 12개는 재고를 초과해 구매할 수 없습니다."); }); } @@ -65,4 +82,8 @@ class ApplicationTest extends NsTest { public void runMain() { Application.main(new String[]{}); } + + private void runTest(Breakpoint breakpoint){ + new Application().testRun(appConfig, breakpoint); + } } From 483b9eb23f174f532a070cdd76cbd6e8fef1f3ea Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:38:04 +0900 Subject: [PATCH 26/27] chore(main) : removed unused imports --- src/main/java/store/AppConfig.java | 4 ++-- src/main/java/store/controller/StoreController.java | 2 +- src/main/java/store/model/domain/Product.java | 1 - src/main/java/store/model/domain/Promotion.java | 3 +-- src/main/java/store/model/domain/Stock.java | 1 - src/main/java/store/model/domain/User.java | 5 ----- src/main/java/store/model/dto/CheckPromotionDTO.java | 1 - src/main/java/store/service/PurchaseService.java | 2 +- src/main/java/store/service/RepositoryService.java | 12 ++++++------ src/main/java/store/utility/FileReadTool.java | 1 - .../java/store/utility/SystemConstantVariable.java | 2 -- src/main/java/store/view/InputView.java | 2 -- .../store/view/provider/DefaultInputProvider.java | 3 --- 13 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/main/java/store/AppConfig.java b/src/main/java/store/AppConfig.java index 16b74d5..9b73c5e 100644 --- a/src/main/java/store/AppConfig.java +++ b/src/main/java/store/AppConfig.java @@ -59,8 +59,8 @@ public MockSetting(AppConfig parent){ } static class SingleVariable implements Injectable { - private Consumer setter; - private Refresh func; + private final Consumer setter; + private final Refresh func; public SingleVariable(Consumer setter, Refresh func){ this.setter = setter; this.func = func; diff --git a/src/main/java/store/controller/StoreController.java b/src/main/java/store/controller/StoreController.java index cb0afab..b3482b5 100644 --- a/src/main/java/store/controller/StoreController.java +++ b/src/main/java/store/controller/StoreController.java @@ -111,7 +111,7 @@ private CheckPromotionDTO readAccordingToCheckPromotionDTO(CheckPromotionDTO res private void purchaseEachItem(String userKey, String[] splittedInput){ for(int i=0; i stocks = (Pair) repositoryService.isItem(isItemType.STOCK, productName); if(stocks == null) throw new IllegalArgumentException(String.format(ErrorMessage.RESOURCE_NO_SUCH_INSTANCE.getMessage(), productName)); diff --git a/src/main/java/store/service/RepositoryService.java b/src/main/java/store/service/RepositoryService.java index fa5c803..aea060f 100644 --- a/src/main/java/store/service/RepositoryService.java +++ b/src/main/java/store/service/RepositoryService.java @@ -1,7 +1,6 @@ package store.service; import java.util.List; -import store.model.domain.Stock; import store.model.repository.ProductRepository; import store.model.repository.PromotionRepository; import store.model.repository.interfaces.RepositoryProvider; @@ -36,11 +35,11 @@ void addItem(AddItemType type, String key, Object item){ if(type == AddItemType.PROMOTION) promotionRepository.addItem(key, item); if(type == AddItemType.PRODUCT) productRepository.addItem(key, item); //if(type == Type.STOCK) : 현재는 사용되지 않으므로 주석처리함 - }; + } void addItem(Object item){ stockRepository.addItem(item); - }; + } Object isItem(isItemType type, String name){ if(type == isItemType.PROMOTION) return promotionRepository.isItem(name); @@ -63,7 +62,8 @@ public void deductStock(String productName, int promotionStockDelta,int normalSt repository.deductItem(productName, promotionStockDelta, normalStockDelta); } - public String getPromotionRepository() { - return promotionRepository.toString(); - } + //디버깅용으로 만들어뒀던 메서드 +// public String getPromotionRepository() { +// return promotionRepository.toString(); +// } } diff --git a/src/main/java/store/utility/FileReadTool.java b/src/main/java/store/utility/FileReadTool.java index 7a4ac73..12fa0f8 100644 --- a/src/main/java/store/utility/FileReadTool.java +++ b/src/main/java/store/utility/FileReadTool.java @@ -1,7 +1,6 @@ package store.utility; import java.io.BufferedReader; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; diff --git a/src/main/java/store/utility/SystemConstantVariable.java b/src/main/java/store/utility/SystemConstantVariable.java index acdd09f..14bd4ea 100644 --- a/src/main/java/store/utility/SystemConstantVariable.java +++ b/src/main/java/store/utility/SystemConstantVariable.java @@ -1,7 +1,5 @@ package store.utility; -import camp.nextstep.edu.missionutils.DateTimes; -import java.time.LocalDateTime; import java.util.List; public final class SystemConstantVariable { diff --git a/src/main/java/store/view/InputView.java b/src/main/java/store/view/InputView.java index 70286cb..f28268e 100644 --- a/src/main/java/store/view/InputView.java +++ b/src/main/java/store/view/InputView.java @@ -1,8 +1,6 @@ package store.view; import static store.utility.SystemConstantVariable.INPUT_EXAMPLES; - -import camp.nextstep.edu.missionutils.Console; import store.utility.InputFormatter; import store.utility.SystemMessage; import store.view.provider.DefaultInputProvider; diff --git a/src/main/java/store/view/provider/DefaultInputProvider.java b/src/main/java/store/view/provider/DefaultInputProvider.java index 1dd6c97..6aa309d 100644 --- a/src/main/java/store/view/provider/DefaultInputProvider.java +++ b/src/main/java/store/view/provider/DefaultInputProvider.java @@ -1,10 +1,7 @@ package store.view.provider; -import static store.utility.SystemConstantVariable.INPUT_EXAMPLES; - import camp.nextstep.edu.missionutils.Console; import store.utility.InputFormatter; -import store.utility.SystemMessage; import store.view.provider.interfaces.InputProvider; public class DefaultInputProvider implements InputProvider { From da0fde5dd443ae49ba6b1f406bd72dcdad0bda8e Mon Sep 17 00:00:00 2001 From: yeunsuh Date: Fri, 4 Apr 2025 01:47:01 +0900 Subject: [PATCH 27/27] fix(controller) : removed debugging message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전에 디버깅용으로 넣어뒀던 println메세지를 삭제안했어서 했읍니다. --- src/main/java/store/controller/StoreController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/store/controller/StoreController.java b/src/main/java/store/controller/StoreController.java index b3482b5..ae30236 100644 --- a/src/main/java/store/controller/StoreController.java +++ b/src/main/java/store/controller/StoreController.java @@ -57,7 +57,6 @@ public boolean purchasing(String userKey){ String inputLine = inputView.readPurchaseItems(); String[] splittedInput = InputFormatter.isStockFormat(inputLine); purchaseEachItem(userKey, splittedInput); - outputView.printError("purchasing item executed"); return false; }catch (Exception e){ outputView.printError(ErrorMessage.errorHeader + e.getMessage());