@@ -0,0 +1,143 @@
+package store.controller;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import java.util.List;
+import java.util.function.Supplier;
+import store.constant.CommonMessage;
+import store.constant.CommonValue;
+import store.constant.SignMessage;
+import store.domain.GeneralProduct;
+import store.domain.PromotionProduct;
+import store.domain.Receipt;
+import store.domain.Storage;
+import store.exception.ConvenienceStoreException;
+import store.service.ReceiptService;
+import store.service.StorageService;
+import store.utils.Calculator;
+import store.utils.Compare;
+import store.utils.Parser;
+import store.view.input.InputView;
+import store.view.output.OutputView;
+
+public class ConvenienceStoreController {
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final Storage storage;
+
+    public ConvenienceStoreController(InputView inputView, OutputView outputView, StorageService storageService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.storage = storageService.initializeStorage();
+    }
+
+    public void operate() {
+        String retryFlag;
+        do {
+            outputView.writeStorageStatus(storage);
+            Receipt receipt = new Receipt();
+            processPurchase(userPurchaseProduct(), receipt);
+            outputView.writeReceipt(receipt, userMembership());
+            retryFlag = userRetry();
+        } while (retryFlag.equalsIgnoreCase(CommonMessage.YES.getCommonMessage()));
+        inputView.closeConsole();
+    }
+
+    private void processPurchase(List<String> purchaseProduct, Receipt receipt) {
+        for (String detail : purchaseProduct) {
+            List<String> item = List.of(detail.split(SignMessage.HYPHEN.getSign()));
+            PromotionProduct product = storage.findPromotionProduct(item.get(CommonValue.ZERO.getValue()));
+            if (product != null && product.getPromotion().isActive(DateTimes.now())) {
+                processPromotionProduct(product, Parser.parse(item.get(CommonValue.ONE.getValue())), receipt);
+                continue;
+            }
+            processGeneralPurchase(item, receipt);
+        }
+    }
+
+    private void processGeneralPurchase(List<String> item, Receipt receipt) {
+        ReceiptService.purchaseGeneralProduct(storage, item.get(CommonValue.ZERO.getValue()),
+                Integer.parseInt(item.get(CommonValue.ONE.getValue())), receipt);
+    }
+
+    private void processPromotionProduct(PromotionProduct promotionProduct, int itemQuantity, Receipt receipt) {
+        int stock = Calculator.calculateRemainStock(promotionProduct, itemQuantity);
+        int purchase = Calculator.calculateUserRemain(promotionProduct, itemQuantity);
+        boolean freeTag = userOneMoreFree(purchase, stock, promotionProduct, itemQuantity, receipt);
+        boolean supplementTag = supplementStock(itemQuantity, stock, promotionProduct, receipt);
+        useStock(freeTag, supplementTag, promotionProduct, itemQuantity, receipt);
+    }
+
+    private boolean userOneMoreFree(int leftBuy, int leftStock, PromotionProduct product, int count, Receipt receipt) {
+        if (Compare.checkFreeOneMore(leftBuy, leftStock, product)) {
+            storage.subtractPromotionProduct(product, count);
+            if (checkUserAnswer(product).equals(CommonMessage.YES.getCommonMessage())) {
+                storage.subtractPromotionProduct(product, product.getPromotionGetGet());
+                return ReceiptService.oneMoreReceipt(receipt, count, leftBuy, product);
+            }
+            return ReceiptService.noOneMoreReceipt(receipt, count, leftBuy, product);
+        }
+        return false;
+    }
+
+    private boolean supplementStock(int quantity, int stock, PromotionProduct product, Receipt receipt) {
+        if (Compare.checkSupplementStock(stock)) {
+            int noPromotion = Calculator.calculateNoPromotion(product);
+            int beforePromotionQuantity = product.getQuantity();
+            storage.subtractPromotionProduct(product, noPromotion);
+            String userAnswer = checkUserNoPromotion(product, Calculator.minus(quantity, noPromotion));
+            return suppleGeneralProduct(product, userAnswer, Calculator.minus(quantity, noPromotion), receipt,
+                    beforePromotionQuantity);
+        }
+        return false;
+    }
+
+    private boolean suppleGeneralProduct(PromotionProduct product, String answer, int quantity, Receipt receipt,
+                                         int prevQuantity) {
+        GeneralProduct generalProduct = storage.findGeneralProduct(product.getName());
+        int currentQuantity = product.getQuantity();
+        if (answer.equals(CommonMessage.YES.getCommonMessage())) {
+            storage.subtractGeneralProduct(generalProduct, Calculator.minus(quantity, currentQuantity));
+            storage.subtractPromotionProduct(product, currentQuantity);
+            return ReceiptService.noDiscountPurchase(receipt, quantity, prevQuantity, currentQuantity, product);
+        }
+        return ReceiptService.noOneMoreReceipt(receipt, prevQuantity, currentQuantity, product);
+    }
+
+    private void useStock(boolean freeTag, boolean suppleTag, PromotionProduct product, int quantity, Receipt receipt) {
+        if (!freeTag && !suppleTag) {
+            storage.subtractPromotionProduct(product, quantity);
+            ReceiptService.useStock(product, quantity, receipt);
+        }
+    }
+
+    private String userRetry() {
+        return handleUserInput(inputView::readRetry);
+    }
+
+    private String userMembership() {
+        return handleUserInput(inputView::readMembership);
+    }
+
+    private List<String> userPurchaseProduct() {
+        return handleUserInput(() -> storage.validateStorageStatus(inputView.readItems()));
+    }
+
+    private String checkUserAnswer(PromotionProduct promotionProduct) {
+        return handleUserInput(() -> inputView.readOneMoreFree(promotionProduct.getName()));
+    }
+
+    private String checkUserNoPromotion(PromotionProduct promotionProduct, int quantity) {
+        return handleUserInput(() -> inputView.readNoDiscountAnswer(promotionProduct.getName(), quantity));
+    }
+
+    private <T> T handleUserInput(Supplier<T> inputSupplier) {
+        while (true) {
+            try {
+                return inputSupplier.get();
+            } catch (ConvenienceStoreException convenienceStoreException) {
+                outputView.displayErrorMessage(convenienceStoreException);
+            }
+        }
+    }
+
+}

리뷰: 말씀해주신 내용을 확인해보았습니다!

제가 알고 있는 요구사항은, `[콜라-2]`, `[에너지바-1]`을 구매한 경우 1개 무료 증정을 받고, 멤버십을 적용한다면 에너지바 1개에 대해서만 멤버십 할인이 적용되는 것입니다. 
마찬가지로, `[콜라-2]`개를 구매하고 무료 증정을 받지 않는다면, 콜라 2개에 대해서 멤버십 할인이 적용되는 것으로 이해하고 있습니다.

제 로직은 위와 같은 방식으로 동작하고 있습니다. 
혹시 다른 요구사항이 있었다면 설명해 주실 수 있으신가요?

예를 들어, `[콜라-2]`, `[에너지바-1]`를 구매한 상황에서 콜라 1개를 무료 증정 받는 경우, 그리고 멤버십 할인이 적용되는 경우를 살펴보겠습니다.

`ReceiptItem`의 `getQuantity` 메서드에서 반환하는 값은 사용자가 무료로 증정받은 상품의 개수를 의미합니다.
`OutputView`의 `writeReceipt` 메서드에서는 `writeUserTotalReceipt()` 메서드를 마지막 라인에서 호출하며, 이 메서드에서 `writeShowTotalMembershipDiscountAmount()`를 통해 멤버십 할인 부분이 출력됩니다.
`Receipt` 클래스의 `validateMembership` 메서드를 보면, 사용자의 대답이 `Y`일 경우 `getMembershipDiscount()`를 호출하게 되어 있습니다.
`getMembershipDiscount` 메서드에서는 `noTotalPromotionAmount()`를 호출하는데, 이 메서드에서는 `receiptItem`의 `증정 개수가 0`일 경우에만 프로모션이 적용되지 않은 상품으로 판단하고, 이에 따라 멤버십 할인을 적용하기 위해 총 가격을 계산합니다.
따라서,` [콜라-2]`,` [에너지바-1]`을 구매하고, 멤버십 할인을 받을 경우, 에너지바 1개에 대해서만 멤버십 할인이 적용되는 로직입니다.
동일하게 `[콜라-2]`를 구매하고, 무료 증정 1개를 받지 않는다고 선택했을 경우, 멤버십 할인을 받을 때 콜라 2개 전체에 대해 멤버십 할인이 적용됩니다.
추가적으로 개선할 부분이 있다면 피드백 주시면 감사하겠습니다! 😊