@@ -0,0 +1,125 @@
+package store.service;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import java.util.ArrayList;
+import java.util.List;
+import store.model.Item;
+import store.model.Product;
+import store.model.Products;
+import store.model.PromotionApplyStatus;
+import store.util.constants.ServiceConstants;
+
+public class StoreService {
+
+    public StoreService() {
+    }
+
+    public PromotionApplyStatus checkPromotionState(Products products, Item item) {
+        Product product = products.findProductByNameAndPromotionIsNotNull(item.getName());
+        if (product == null || !product.inStock() || !product.isApplyPromotion(DateTimes.now())) {
+            return PromotionApplyStatus.NO_PROMOTION;
+        }
+        if (product.canGetForFreeByPromotion(item.getQuantity())) {
+            return PromotionApplyStatus.NEED_GET;
+        }
+        if (!product.canBuy(item.getQuantity()) || !product.canBuy(item.getQuantity() + product.getPromotionGet())) {
+            return PromotionApplyStatus.NOT_ENOUGH_STOCK;
+        }
+        return PromotionApplyStatus.NO_PROMOTION;
+    }
+
+    public String getPromotionMessage(Products products, Item item, PromotionApplyStatus promotionApplyStatus) {
+        Product product = products.findProductByNameAndPromotionIsNotNull(item.getName());
+        if (promotionApplyStatus == PromotionApplyStatus.NOT_ENOUGH_STOCK) {
+            int applyPromotion = product.getMaxCanApplyPromotion(item.getQuantity());
+            int notApplyPromotionCount = item.getQuantity() - applyPromotion;
+            return String.format(promotionApplyStatus.getMessage(), item.getName(), notApplyPromotionCount);
+        }
+        if (promotionApplyStatus == PromotionApplyStatus.NEED_GET) {
+            return String.format(promotionApplyStatus.getMessage(), item.getName());
+        }
+        return promotionApplyStatus.getMessage();
+    }
+
+    public void processQuantityByPromotion(Item item, PromotionApplyStatus promotionApplyStatus, String input,
+                                           Products products) {
+        if (promotionApplyStatus == PromotionApplyStatus.NEED_GET && input.equals(ServiceConstants.YES)) {
+            addGetToItem(item, products);
+            return;
+        }
+        if (promotionApplyStatus == PromotionApplyStatus.NOT_ENOUGH_STOCK && input.equals(ServiceConstants.NO)) {
+            takeOffFormItem(item, products);
+        }
+    }
+
+    private void addGetToItem(Item item, Products products) {
+        Product product = products.findProductByNameAndPromotionIsNotNull(item.getName());
+        int get = product.getPromotionGet();
+        item.addQuantity(get);
+    }
+
+    private void takeOffFormItem(Item item, Products products) {
+        Product product = products.findProductByNameAndPromotionIsNotNull(item.getName());
+        int applyPromotion = product.getMaxCanApplyPromotion(item.getQuantity());
+        int notApplyPromotionCount = item.getQuantity() - applyPromotion;
+        item.takeOffQuantity(notApplyPromotionCount);
+    }
+
+    public List<Item> getItemsForFreeByPromotion(Products products, List<Item> items) {
+        List<Item> freeItems = new ArrayList<>();
+        for (Item item : items) {
+            int getForFree = getGetCountByPromotion(products, item);
+            if (getForFree == 0) {
+                continue;
+            }
+            freeItems.add(new Item(item.getName(), getForFree));
+        }
+        return freeItems;
+    }
+
+    private int getGetCountByPromotion(Products products, Item item) {
+        Product product = products.findProductByNameAndPromotionIsNotNull(item.getName());
+        if (product == null || !product.isApplyPromotion(DateTimes.now())) {
+            return 0;
+        }
+        return product.getMaxCanGetForFreeByPromotion(item.getQuantity());
+    }
+
+    public void processStock(Products products, List<Item> items) {
+        for (Item item : items) {
+            int quantity = processPromotionProduct(products, item);
+            processNotPromotionProduct(products, item, quantity);
+        }
+    }
+
+    private int processPromotionProduct(Products products, Item item) {
+        Product product = products.findProductByNameAndPromotionIsNotNull(item.getName());
+        int quantity = item.getQuantity();
+        if (product != null) {
+            quantity = decreaseProductQuantity(product, quantity);
+        }
+        return quantity;
+    }
+
+    private void processNotPromotionProduct(Products products, Item item, int quantity) {
+        Product product = products.findProductByNameAndPromotionIsNull(item.getName());
+        if (quantity == 0 || product == null) {
+            return;
+        }
+        product.decreaseQuantity(quantity);
+    }
+
+    private int decreaseProductQuantity(Product product, int quantity) {
+        if (product.canBuy(quantity)) {
+            product.decreaseQuantity(quantity);
+            return 0;
+        }
+        int remainingQuantity = quantity - product.getQuantity();
+        product.decreaseQuantity(product.getQuantity());
+        return remainingQuantity;
+    }
+
+    public boolean isContinue(String input) {
+        return input.equals(ServiceConstants.YES);
+    }
+}

리뷰: 원래는 StoreController에서 담당하는 비즈니스 로직을 전부 StoreService로 분리하려 하였습니다. 가장 중심이 되는 product와 관련된 로직을 전부 가져가도록 하였는데 이 과정에서 product가 promotion과 item을 모두 사용하는 로직이 많이 포함된 것 같습니다. item 생성과 관련된 로직과 receipt 생성과 관련된 로직은 확실히 구분 지을 수 있다 생각하여 각각의 Service로 분리하였지만, 타임어택 이슈로 StoreService에 대한 고민은 깊게 해보지 못하였습니다. 시간이 부족하여 큰 틀만 가지고 그 안의 세세한 부분까지는 고민하지 못했던 영향이 큰 것 같습니다