@@ -0,0 +1,172 @@
+package store.domain;
+
+import static store.constants.NumberConstants.FREEBIE_QUANTITY;
+
+import java.util.List;
+import store.dto.BuyGetQuantity;
+import store.dto.PromotionInfo;
+import store.io.InputView;
+
+public class PromotionManager {
+
+    private final List<PromotionInfo> promotionInfos;
+    private final StoreHouse storeHouse;
+    private final InputView inputView;
+
+    public PromotionManager(List<PromotionInfo> promotionInfos, StoreHouse storeHouse, InputView inputView) {
+        this.promotionInfos = promotionInfos;
+        this.storeHouse = storeHouse;
+        this.inputView = inputView;
+    }
+
+    public void setPromotionInfo() {
+        promotionInfos.forEach(promotionInfo -> {
+            Promotion.setQuantity(
+                    promotionInfo.getPromotionName(), promotionInfo.getBuyQuantity(), promotionInfo.getGetQuantity()
+            );
+            Promotion.setPromotionPeriods(
+                    promotionInfo.getPromotionName(), promotionInfo.getStartDateTime(), promotionInfo.getEndDateTime()
+            );
+        });
+    }
+
+    public Receipt applyPromotion(Product product, int purchaseQuantity) {
+        Receipt receipt = new Receipt();
+        if (!isValidPromotionApplicable(product, purchaseQuantity)) {
+            processRegularPricePayment(product, purchaseQuantity);
+            return receipt;
+        }
+        return getReceiptWhenPromotionPayment(product, purchaseQuantity, receipt);
+    }
+
+    public boolean isValidPromotionApplicable(Product product, int purchaseQuantity) {
+        return validPromotionPeriod(product.getPromotionName()) &&
+                canApplyPromotion(product, purchaseQuantity) &&
+                validPromotionProductStock(product);
+    }
+
+    private boolean validPromotionPeriod(Promotion promotionName) {
+        return Promotion.isPromotionValid(promotionName);
+    }
+
+    private boolean canApplyPromotion(Product product, int purchaseQuantity) {
+        BuyGetQuantity buyAndGetQuantity = getBuyAndGetQuantity(product.getPromotionName());
+        int buyQuantity = buyAndGetQuantity.getBuyQuantity();
+        return purchaseQuantity >= buyQuantity;
+    }
+
+    private boolean validPromotionProductStock(Product product) {
+        BuyGetQuantity buyAndGetQuantity = getBuyAndGetQuantity(product.getPromotionName());
+        int buyQuantity = buyAndGetQuantity.getBuyQuantity();
+        return buyQuantity <= product.getQuantity();
+    }
+
+    private void processRegularPricePayment(Product product, int purchaseQuantity) {
+
+        Product generalProduct = findProductByPromotionName(product, Promotion.NULL);
+        storeHouse.buy(generalProduct, purchaseQuantity);
+    }
+
+    private Product findProductByPromotionName(Product product, Promotion promotionName) {
+        List<Product> products = storeHouse.findProductByName(product.getName());
+
+        return products.stream()
+                .filter(prdt -> !prdt.getPromotionName().equals(promotionName))
+                .findFirst()
+                .orElse(null);
+    }
+
+    private Receipt getReceiptWhenPromotionPayment(Product product, int purchaseQuantity, Receipt receipt) {
+        if (processFullPromotionPayment(receipt, product, purchaseQuantity)) {
+            return receipt;
+        }
+        processPartialPromotionPayment(receipt, product, purchaseQuantity);
+        return receipt;
+    }
+
+    private boolean processFullPromotionPayment(Receipt receipt, Product product, int purchaseQuantity) {
+
+        if (purchaseQuantity <= product.getQuantity()) {
+            return canAddOneFreebie(receipt, product, purchaseQuantity);
+        }
+        return false;
+    }
+
+    private boolean canAddOneFreebie(Receipt receipt, Product product, int purchaseQuantity) {
+        BuyGetQuantity buyAndGetQuantity = getBuyAndGetQuantity(product.getPromotionName());
+        int buyQuantity = buyAndGetQuantity.getBuyQuantity();
+        int getQuantity = buyAndGetQuantity.getGetQuantity();
+        int totalPurchaseQuantity = purchaseQuantity;
+        if (purchaseQuantity % (buyQuantity + getQuantity) == buyQuantity) {
+            totalPurchaseQuantity = addOneFreebie(receipt, product, purchaseQuantity);
+        }
+        receipt.addFreebieProduct(product, totalPurchaseQuantity / (buyQuantity + getQuantity));
+        return true;
+    }
+
+    private int addOneFreebie(Receipt receipt, Product product, int purchaseQuantity) {
+        Choice freebieAdditionChoice = inputView.readFreebieAdditionChoice(product.getName());
+        int totalPurchaseQuantity = purchaseQuantity;
+        if (freebieAdditionChoice.equals(Choice.Y)) {
+            totalPurchaseQuantity += FREEBIE_QUANTITY;
+            addFreebieFromRegularProduct(receipt, product, purchaseQuantity);
+        }
+        storeHouse.buy(product, totalPurchaseQuantity);
+        return totalPurchaseQuantity;
+    }
+
+    private void addFreebieFromRegularProduct(Receipt receipt, Product product, int purchaseQuantity) {
+        BuyGetQuantity buyAndGetQuantity = getBuyAndGetQuantity(product.getPromotionName());
+        int buyQuantity = buyAndGetQuantity.getBuyQuantity();
+        int getQuantity = buyAndGetQuantity.getGetQuantity();
+        int remainder = purchaseQuantity % (buyQuantity + getQuantity);
+        if (purchaseQuantity == (product.getQuantity() - remainder)) {
+            processRegularPricePayment(product, FREEBIE_QUANTITY);
+            receipt.addFreebieProduct(product, FREEBIE_QUANTITY);
+        }
+    }
+
+    private void processPartialPromotionPayment(Receipt receipt, Product product, int purchaseQuantity) {
+        BuyGetQuantity buyAndGetQuantity = getBuyAndGetQuantity(product.getPromotionName());
+        int buyQuantity = buyAndGetQuantity.getBuyQuantity();
+        int getQuantity = buyAndGetQuantity.getGetQuantity();
+
+        int promotionAppliedQuantity = getPromotionAppliedQuantity(product);
+        processRegularPricePayment(product, purchaseQuantity, promotionAppliedQuantity);
+        storeHouse.buy(product, promotionAppliedQuantity);
+        receipt.addFreebieProduct(product, promotionAppliedQuantity / (buyQuantity + getQuantity));
+    }
+
+    private int getPromotionAppliedQuantity(Product product) {
+        BuyGetQuantity buyAndGetQuantity = getBuyAndGetQuantity(product.getPromotionName());
+        int bundle = buyAndGetQuantity.getBuyQuantity() + buyAndGetQuantity.getGetQuantity();
+        int promotionStock = product.getQuantity();
+        int count = promotionStock / bundle;
+
+        return count * (buyAndGetQuantity.getBuyQuantity() + buyAndGetQuantity.getGetQuantity());
+    }
+
+    private void processRegularPricePayment(Product product, int purchaseQuantity, int promotionAppliedQuantity) {
+        int regularPricePaymentQuantity = purchaseQuantity - promotionAppliedQuantity;
+        Choice regularPricePaymentChoice = getRegularPriceApplicationChoice(product,
+                regularPricePaymentQuantity);
+        if (regularPricePaymentChoice.equals(
+                Choice.Y)) {
+            Product regularProduct = findProductByPromotionName(product, Promotion.NULL);
+            storeHouse.buy(regularProduct, regularPricePaymentQuantity);
+        }
+    }
+
+    private Choice getRegularPriceApplicationChoice(Product product, int purchaseQuantity) {
+        return inputView.readRegularPricePaymentChoice(product.getName(), purchaseQuantity);
+    }
+
+
+    public static BuyGetQuantity getBuyAndGetQuantity(Promotion promotionName) {
+        return BuyGetQuantity.of(promotionName.getBuyQuantity(), promotionName.getGetQuantity());
+    }
+
+    public List<PromotionInfo> getPromotionInfos() {
+        return promotionInfos;
+    }
+}

리뷰: 도메인클래스의 주요책임은 '도메인을 잘 표현해내는 것'이라고 생각합니다. 해당 도메인 클래스는 많은 비즈니스 로직을 포함하고 있으므로, 단일책임원칙이 위배된 것 같습니다! 서비스계층을 도입하거나 책임을 분리해보는건 어떨까요?