From 6ff9be03173eb1470448db98d93da829ed1ab7b5 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Sun, 23 Feb 2025 01:25:17 +0900 Subject: [PATCH 1/6] =?UTF-8?q?docs(README):=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=A6=AC=EB=93=9C=EB=AF=B8=EC=97=90=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/README.md b/README.md index d54f268..eb6ebf6 100644 --- a/README.md +++ b/README.md @@ -1 +1,224 @@ # java-convenience-store-precourse +## πŸš€ κΈ°λŠ₯ μš”κ΅¬ 사항 + +ꡬ맀자의 할인 ν˜œνƒκ³Ό 재고 상황을 κ³ λ €ν•˜μ—¬ μ΅œμ’… 결제 κΈˆμ•‘μ„ κ³„μ‚°ν•˜κ³  μ•ˆλ‚΄ν•˜λŠ” 결제 μ‹œμŠ€ν…œμ„ κ΅¬ν˜„ν•œλ‹€. + +- μ‚¬μš©μžκ°€ μž…λ ₯ν•œ μƒν’ˆμ˜ 가격과 μˆ˜λŸ‰μ„ 기반으둜 μ΅œμ’… 결제 κΈˆμ•‘μ„ κ³„μ‚°ν•œλ‹€. + - 총ꡬ맀앑은 μƒν’ˆλ³„ 가격과 μˆ˜λŸ‰μ„ κ³±ν•˜μ—¬ κ³„μ‚°ν•˜λ©°, ν”„λ‘œλͺ¨μ…˜ 및 멀버십 할인 정책을 λ°˜μ˜ν•˜μ—¬ μ΅œμ’… 결제 κΈˆμ•‘μ„ μ‚°μΆœν•œλ‹€. +- ꡬ맀 λ‚΄μ—­κ³Ό μ‚°μΆœν•œ κΈˆμ•‘ 정보λ₯Ό 영수증으둜 좜λ ₯ν•œλ‹€. +- 영수증 좜λ ₯ ν›„ μΆ”κ°€ ꡬ맀λ₯Ό μ§„ν–‰ν• μ§€ λ˜λŠ” μ’…λ£Œν• μ§€λ₯Ό 선택할 수 μžˆλ‹€. +- μ‚¬μš©μžκ°€ 잘λͺ»λœ 값을 μž…λ ₯ν•  경우 `IllegalArgumentException`λ₯Ό λ°œμƒμ‹œν‚€κ³ , "[ERROR]"둜 μ‹œμž‘ν•˜λŠ” μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ ν›„ κ·Έ λΆ€λΆ„λΆ€ν„° μž…λ ₯을 λ‹€μ‹œ λ°›λŠ”λ‹€. + - `Exception`이 μ•„λ‹ŒΒ `IllegalArgumentException`,Β `IllegalStateException`Β λ“±κ³Ό 같은 λͺ…ν™•ν•œ μœ ν˜•μ„ μ²˜λ¦¬ν•œλ‹€. + +### 재고 관리 + +- 각 μƒν’ˆμ˜ 재고 μˆ˜λŸ‰μ„ κ³ λ €ν•˜μ—¬ 결제 κ°€λŠ₯ μ—¬λΆ€λ₯Ό ν™•μΈν•œλ‹€. +- 고객이 μƒν’ˆμ„ ꡬ맀할 λ•Œλ§ˆλ‹€, 결제된 μˆ˜λŸ‰λ§ŒνΌ ν•΄λ‹Ή μƒν’ˆμ˜ μž¬κ³ μ—μ„œ μ°¨κ°ν•˜μ—¬ μˆ˜λŸ‰μ„ κ΄€λ¦¬ν•œλ‹€. +- 재고λ₯Ό μ°¨κ°ν•¨μœΌλ‘œμ¨ μ‹œμŠ€ν…œμ€ μ΅œμ‹  재고 μƒνƒœλ₯Ό μœ μ§€ν•˜λ©°, λ‹€μŒ 고객이 ꡬ맀할 λ•Œ μ •ν™•ν•œ 재고 정보λ₯Ό μ œκ³΅ν•œλ‹€. + +### ν”„λ‘œλͺ¨μ…˜ 할인 + +- 였늘 λ‚ μ§œκ°€ ν”„λ‘œλͺ¨μ…˜ κΈ°κ°„ 내에 ν¬ν•¨λœ κ²½μš°μ—λ§Œ 할인을 μ μš©ν•œλ‹€. +- ν”„λ‘œλͺ¨μ…˜μ€ N개 ꡬ맀 μ‹œ 1개 무료 증정(Buy N Get 1 Free)의 ν˜•νƒœλ‘œ μ§„ν–‰λœλ‹€. +- 1+1 λ˜λŠ” 2+1 ν”„λ‘œλͺ¨μ…˜μ΄ 각각 μ§€μ •λœ μƒν’ˆμ— 적용되며, 동일 μƒν’ˆμ— μ—¬λŸ¬ ν”„λ‘œλͺ¨μ…˜μ΄ μ μš©λ˜μ§€ μ•ŠλŠ”λ‹€. +- ν”„λ‘œλͺ¨μ…˜ ν˜œνƒμ€ ν”„λ‘œλͺ¨μ…˜ 재고 λ‚΄μ—μ„œλ§Œ μ μš©ν•  수 μžˆλ‹€. +- ν”„λ‘œλͺ¨μ…˜ κΈ°κ°„ 쀑이라면 ν”„λ‘œλͺ¨μ…˜ 재고λ₯Ό μš°μ„ μ μœΌλ‘œ μ°¨κ°ν•˜λ©°, ν”„λ‘œλͺ¨μ…˜ μž¬κ³ κ°€ λΆ€μ‘±ν•  κ²½μš°μ—λŠ” 일반 재고λ₯Ό μ‚¬μš©ν•œλ‹€. +- ν”„λ‘œλͺ¨μ…˜ 적용이 κ°€λŠ₯ν•œ μƒν’ˆμ— λŒ€ν•΄ 고객이 ν•΄λ‹Ή μˆ˜λŸ‰λ³΄λ‹€ 적게 κ°€μ Έμ˜¨ 경우, ν•„μš”ν•œ μˆ˜λŸ‰μ„ μΆ”κ°€λ‘œ κ°€μ Έμ˜€λ©΄ ν˜œνƒμ„ 받을 수 μžˆμŒμ„ μ•ˆλ‚΄ν•œλ‹€. +- ν”„λ‘œλͺ¨μ…˜ μž¬κ³ κ°€ λΆ€μ‘±ν•˜μ—¬ 일뢀 μˆ˜λŸ‰μ„ ν”„λ‘œλͺ¨μ…˜ ν˜œνƒ 없이 κ²°μ œν•΄μ•Ό ν•˜λŠ” 경우, 일뢀 μˆ˜λŸ‰μ— λŒ€ν•΄ μ •κ°€λ‘œ κ²°μ œν•˜κ²Œ 됨을 μ•ˆλ‚΄ν•œλ‹€. + +### 멀버십 할인 + +- 멀버십 νšŒμ›μ€ ν”„λ‘œλͺ¨μ…˜ 미적용 κΈˆμ•‘μ˜ 30%λ₯Ό ν• μΈλ°›λŠ”λ‹€. +- ν”„λ‘œλͺ¨μ…˜ 적용 ν›„ 남은 κΈˆμ•‘μ— λŒ€ν•΄ 멀버십 할인을 μ μš©ν•œλ‹€. +- 멀버십 ν• μΈμ˜ μ΅œλŒ€ ν•œλ„λŠ” 8,000원이닀. + +### 영수증 좜λ ₯ + +- μ˜μˆ˜μ¦μ€ 고객의 ꡬ맀 λ‚΄μ—­κ³Ό 할인을 μš”μ•½ν•˜μ—¬ 좜λ ₯ν•œλ‹€. +- 영수증 ν•­λͺ©μ€ μ•„λž˜μ™€ κ°™λ‹€. + - ꡬ맀 μƒν’ˆ λ‚΄μ—­: κ΅¬λ§€ν•œ μƒν’ˆλͺ…, μˆ˜λŸ‰, 가격 + - 증정 μƒν’ˆ λ‚΄μ—­: ν”„λ‘œλͺ¨μ…˜μ— 따라 무료둜 제곡된 증정 μƒν’ˆμ˜ λͺ©λ‘ + - κΈˆμ•‘ 정보 + - 총ꡬ맀앑: κ΅¬λ§€ν•œ μƒν’ˆμ˜ 총 μˆ˜λŸ‰κ³Ό 총 κΈˆμ•‘ + - 행사할인: ν”„λ‘œλͺ¨μ…˜μ— μ˜ν•΄ ν• μΈλœ κΈˆμ•‘ + - 멀버십할인: 멀버십에 μ˜ν•΄ μΆ”κ°€λ‘œ ν• μΈλœ κΈˆμ•‘ + - λ‚΄μ‹€λˆ: μ΅œμ’… 결제 κΈˆμ•‘ +- 영수증의 ꡬ성 μš”μ†Œλ₯Ό 보기 μ’‹κ²Œ μ •λ ¬ν•˜μ—¬ 고객이 μ‰½κ²Œ κΈˆμ•‘κ³Ό μˆ˜λŸ‰μ„ 확인할 수 있게 ν•œλ‹€. + +### μž…μΆœλ ₯ + +- μž…λ ₯ 1 + - λ‚΄μš© : ꡬ맀할 μƒν’ˆκ³Ό μˆ˜λŸ‰ + - ν˜•μ‹ : μƒν’ˆ([]), μˆ˜λŸ‰(-), μ—΄κ±°(,) + - ex) [콜라-10],[사이닀-3] +- μž…λ ₯ 2 + - λ‚΄μš© : ν”„λ‘œλͺ¨μ…˜ μƒν’ˆ μΆ”κ°€ μ—¬λΆ€, μ •κ°€ 결제 μ—¬λΆ€, 멀버십 할인 적용 μ—¬λΆ€, μΆ”κ°€ ꡬ맀 μ—¬λΆ€ + - ν˜•μ‹ : Y / N +- μ—λŸ¬ λ©”μ‹œμ§€ + - [ERROR]둜 μ‹œμž‘ν•΄μ•Όν•¨ + - ex) `[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` + +ex) + +```java +μ•ˆλ…•ν•˜μ„Έμš”. WνŽΈμ˜μ μž…λ‹ˆλ‹€. +ν˜„μž¬ λ³΄μœ ν•˜κ³  μžˆλŠ” μƒν’ˆμž…λ‹ˆλ‹€. + +- 콜라 1,000원 10개 탄산2+1 +- 콜라 1,000원 10개 +- 사이닀 1,000원 8개 탄산2+1 +- 사이닀 1,000원 7개 +- μ˜€λ Œμ§€μ£ΌμŠ€ 1,800원 9개 MDμΆ”μ²œμƒν’ˆ +- μ˜€λ Œμ§€μ£ΌμŠ€ 1,800원 재고 μ—†μŒ +- νƒ„μ‚°μˆ˜ 1,200원 5개 탄산2+1 +- νƒ„μ‚°μˆ˜ 1,200원 재고 μ—†μŒ +- λ¬Ό 500원 10개 +- λΉ„νƒ€λ―Όμ›Œν„° 1,500원 6개 +- 감자칩 1,500원 5개 λ°˜μ§ν• μΈ +- 감자칩 1,500원 5개 +- μ΄ˆμ½”λ°” 1,200원 5개 MDμΆ”μ²œμƒν’ˆ +- μ΄ˆμ½”λ°” 1,200원 5개 +- μ—λ„ˆμ§€λ°” 2,000원 5개 +- μ •μ‹λ„μ‹œλ½ 6,400원 8개 +- 컡라면 1,700원 1개 MDμΆ”μ²œμƒν’ˆ +- 컡라면 1,700원 10개 + +κ΅¬λ§€ν•˜μ‹€ μƒν’ˆλͺ…κ³Ό μˆ˜λŸ‰μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”. (예: [사이닀-2],[감자칩-1]) +[콜라-3],[μ—λ„ˆμ§€λ°”-5] + +멀버십 할인을 λ°›μœΌμ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N) +Y + +===========W 편의점============= +μƒν’ˆλͺ… μˆ˜λŸ‰ κΈˆμ•‘ +콜라 3 3,000 +μ—λ„ˆμ§€λ°” 5 10,000 +===========증 μ •============= +콜라 1 +============================== +총ꡬ맀앑 8 13,000 +행사할인 -1,000 +멀버십할인 -3,000 +λ‚΄μ‹€λˆ 9,000 + +κ°μ‚¬ν•©λ‹ˆλ‹€. κ΅¬λ§€ν•˜κ³  싢은 λ‹€λ₯Έ μƒν’ˆμ΄ μžˆλ‚˜μš”? (Y/N) +Y + +μ•ˆλ…•ν•˜μ„Έμš”. WνŽΈμ˜μ μž…λ‹ˆλ‹€. +ν˜„μž¬ λ³΄μœ ν•˜κ³  μžˆλŠ” μƒν’ˆμž…λ‹ˆλ‹€. + +- 콜라 1,000원 7개 탄산2+1 +- 콜라 1,000원 10개 +- 사이닀 1,000원 8개 탄산2+1 +- 사이닀 1,000원 7개 +- μ˜€λ Œμ§€μ£ΌμŠ€ 1,800원 9개 MDμΆ”μ²œμƒν’ˆ +- μ˜€λ Œμ§€μ£ΌμŠ€ 1,800원 재고 μ—†μŒ +- νƒ„μ‚°μˆ˜ 1,200원 5개 탄산2+1 +- νƒ„μ‚°μˆ˜ 1,200원 재고 μ—†μŒ +- λ¬Ό 500원 10개 +- λΉ„νƒ€λ―Όμ›Œν„° 1,500원 6개 +- 감자칩 1,500원 5개 λ°˜μ§ν• μΈ +- 감자칩 1,500원 5개 +- μ΄ˆμ½”λ°” 1,200원 5개 MDμΆ”μ²œμƒν’ˆ +- μ΄ˆμ½”λ°” 1,200원 5개 +- μ—λ„ˆμ§€λ°” 2,000원 재고 μ—†μŒ +- μ •μ‹λ„μ‹œλ½ 6,400원 8개 +- 컡라면 1,700원 1개 MDμΆ”μ²œμƒν’ˆ +- 컡라면 1,700원 10개 + +κ΅¬λ§€ν•˜μ‹€ μƒν’ˆλͺ…κ³Ό μˆ˜λŸ‰μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”. (예: [사이닀-2],[감자칩-1]) +[콜라-10] + +ν˜„μž¬ 콜라 4κ°œλŠ” ν”„λ‘œλͺ¨μ…˜ 할인이 μ μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ·Έλž˜λ„ κ΅¬λ§€ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N) +Y + +멀버십 할인을 λ°›μœΌμ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N) +N + +===========W 편의점============= +μƒν’ˆλͺ… μˆ˜λŸ‰ κΈˆμ•‘ +콜라 10 10,000 +===========증 μ •============= +콜라 2 +============================== +총ꡬ맀앑 10 10,000 +행사할인 -2,000 +멀버십할인 -0 +λ‚΄μ‹€λˆ 8,000 + +κ°μ‚¬ν•©λ‹ˆλ‹€. κ΅¬λ§€ν•˜κ³  싢은 λ‹€λ₯Έ μƒν’ˆμ΄ μžˆλ‚˜μš”? (Y/N) +Y + +μ•ˆλ…•ν•˜μ„Έμš”. WνŽΈμ˜μ μž…λ‹ˆλ‹€. +ν˜„μž¬ λ³΄μœ ν•˜κ³  μžˆλŠ” μƒν’ˆμž…λ‹ˆλ‹€. + +- 콜라 1,000원 재고 μ—†μŒ 탄산2+1 +- 콜라 1,000원 7개 +- 사이닀 1,000원 8개 탄산2+1 +- 사이닀 1,000원 7개 +- μ˜€λ Œμ§€μ£ΌμŠ€ 1,800원 9개 MDμΆ”μ²œμƒν’ˆ +- μ˜€λ Œμ§€μ£ΌμŠ€ 1,800원 재고 μ—†μŒ +- νƒ„μ‚°μˆ˜ 1,200원 5개 탄산2+1 +- νƒ„μ‚°μˆ˜ 1,200원 재고 μ—†μŒ +- λ¬Ό 500원 10개 +- λΉ„νƒ€λ―Όμ›Œν„° 1,500원 6개 +- 감자칩 1,500원 5개 λ°˜μ§ν• μΈ +- 감자칩 1,500원 5개 +- μ΄ˆμ½”λ°” 1,200원 5개 MDμΆ”μ²œμƒν’ˆ +- μ΄ˆμ½”λ°” 1,200원 5개 +- μ—λ„ˆμ§€λ°” 2,000원 재고 μ—†μŒ +- μ •μ‹λ„μ‹œλ½ 6,400원 8개 +- 컡라면 1,700원 1개 MDμΆ”μ²œμƒν’ˆ +- 컡라면 1,700원 10개 + +κ΅¬λ§€ν•˜μ‹€ μƒν’ˆλͺ…κ³Ό μˆ˜λŸ‰μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”. (예: [사이닀-2],[감자칩-1]) +[μ˜€λ Œμ§€μ£ΌμŠ€-1] + +ν˜„μž¬ μ˜€λ Œμ§€μ£ΌμŠ€μ€(λŠ”) 1개λ₯Ό 무료둜 더 받을 수 μžˆμŠ΅λ‹ˆλ‹€. μΆ”κ°€ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N) +Y + +멀버십 할인을 λ°›μœΌμ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N) +Y + +===========W 편의점============= +μƒν’ˆλͺ… μˆ˜λŸ‰ κΈˆμ•‘ +μ˜€λ Œμ§€μ£ΌμŠ€ 2 3,600 +===========증 μ •============= +μ˜€λ Œμ§€μ£ΌμŠ€ 1 +============================== +총ꡬ맀앑 2 3,600 +행사할인 -1,800 +멀버십할인 -0 +λ‚΄μ‹€λˆ 1,800 + +κ°μ‚¬ν•©λ‹ˆλ‹€. κ΅¬λ§€ν•˜κ³  싢은 λ‹€λ₯Έ μƒν’ˆμ΄ μžˆλ‚˜μš”? (Y/N) +N + +``` + +--- + +## 🎯 Java - ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬ 사항 + +- JDK 17 λ²„μ „μ—μ„œ μ‹€ν–‰ κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€. **JDK 17μ—μ„œ μ •μƒμ μœΌλ‘œ λ™μž‘ν•˜μ§€ μ•Šμ„ 경우 0점 μ²˜λ¦¬ν•œλ‹€.** +- ν”„λ‘œκ·Έλž¨ μ‹€ν–‰μ˜ μ‹œμž‘μ μ€ `Application`의 `main()`이닀. +- `build.gradle` νŒŒμΌμ„ λ³€κ²½ν•  수 μ—†κ³ , μ™ΈλΆ€ 라이브러리λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€. +- [Java μ½”λ“œ μ»¨λ²€μ…˜](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) κ°€μ΄λ“œλ₯Ό μ€€μˆ˜ν•˜λ©° ν”„λ‘œκ·Έλž˜λ°ν•œλ‹€. +- ν”„λ‘œκ·Έλž¨ μ’…λ£Œ μ‹œ `System.exit()`λ₯Ό ν˜ΈμΆœν•˜μ§€ μ•ŠλŠ”λ‹€. +- ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„μ΄ μ™„λ£Œλ˜λ©΄ `ApplicationTest`의 λͺ¨λ“  ν…ŒμŠ€νŠΈκ°€ 성곡해야 ν•œλ‹€. **ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•  경우 0점 μ²˜λ¦¬ν•œλ‹€.** +- ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬ μ‚¬ν•­μ—μ„œ 달리 λͺ…μ‹œν•˜μ§€ μ•ŠλŠ” ν•œ 파일, νŒ¨ν‚€μ§€ 이름을 μˆ˜μ •ν•˜κ±°λ‚˜ μ΄λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. +- indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό 3이 λ„˜μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€. 2κΉŒμ§€λ§Œ ν—ˆμš©ν•œλ‹€. + - 예λ₯Ό λ“€μ–΄ whileλ¬Έ μ•ˆμ— if문이 있으면 λ“€μ—¬μ“°κΈ°λŠ” 2이닀. + - 힌트: indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό μ€„μ΄λŠ” 쒋은 방법은 ν•¨μˆ˜(λ˜λŠ” λ©”μ„œλ“œ)λ₯Ό λΆ„λ¦¬ν•˜λ©΄ λœλ‹€. +- 3ν•­ μ—°μ‚°μžλ₯Ό μ“°μ§€ μ•ŠλŠ”λ‹€. +- ν•¨μˆ˜(λ˜λŠ” λ©”μ„œλ“œ)κ°€ ν•œ κ°€μ§€ 일만 ν•˜λ„λ‘ μ΅œλŒ€ν•œ μž‘κ²Œ λ§Œλ“€μ–΄λΌ. +- JUnit 5와 AssertJλ₯Ό μ΄μš©ν•˜μ—¬ 본인이 μ •λ¦¬ν•œ κΈ°λŠ₯ λͺ©λ‘μ΄ 정상 λ™μž‘ν•¨μ„ ν…ŒμŠ€νŠΈ μ½”λ“œλ‘œ ν™•μΈν•œλ‹€. + - ν…ŒμŠ€νŠΈ 도ꡬ μ‚¬μš©λ²•μ΄ μ΅μˆ™ν•˜μ§€ μ•Šλ‹€λ©΄ `test/java/study`λ₯Ό μ°Έκ³ ν•˜μ—¬ ν•™μŠ΅ν•œ ν›„ ν…ŒμŠ€νŠΈλ₯Ό κ΅¬ν˜„ν•œλ‹€. + +### 라이브러리 + +- `camp.nextstep.edu.missionutils`μ—μ„œ μ œκ³΅ν•˜λŠ” APIλ₯Ό μ‚¬μš©ν•œλ‹€. + - ν˜„μž¬ λ‚ μ§œμ™€ μ‹œκ°„μ„ κ°€μ Έμ˜€λ €λ©΄Β `camp.nextstep.edu.missionutils.DateTimes`의 `now()`λ₯Ό ν™œμš©ν•œλ‹€. + - μ‚¬μš©μžκ°€ μž…λ ₯ν•˜λŠ” 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 ν™œμš©ν•œλ‹€. + +## \ No newline at end of file From e2f94bddb9cd9e6b3e35fec534c3feb6cda016b4 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Tue, 25 Feb 2025 20:50:06 +0900 Subject: [PATCH 2/6] =?UTF-8?q?update(resources):=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EB=AA=A8=EC=85=98=20=EB=82=A0=EC=A7=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/promotions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/promotions.md b/src/main/resources/promotions.md index d89887c..128b576 100644 --- a/src/main/resources/promotions.md +++ b/src/main/resources/promotions.md @@ -1,4 +1,4 @@ name,buy,get,start_date,end_date -탄산2+1,2,1,2024-01-01,2024-12-31 -MDμΆ”μ²œμƒν’ˆ,1,1,2024-01-01,2024-12-31 -λ°˜μ§ν• μΈ,1,1,2024-11-01,2024-11-30 +탄산2+1,2,1,2024-01-01,2025-03-08 +MDμΆ”μ²œμƒν’ˆ,1,1,2024-01-01,2025-03-08 +λ°˜μ§ν• μΈ,1,1,2024-11-01,2025-04-10 From 76ab41fcc38ef238fb1ddd80050bfe4c0b4edf11 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:03:03 +0900 Subject: [PATCH 3/6] init commit --- README.md | 45 +++++----- src/main/java/store/Application.java | 7 +- src/main/java/store/config/StoreConfig.java | 32 +++++++ .../store/controller/StoreController.java | 40 +++++++++ src/main/java/store/enums/PromotionType.java | 27 ++++++ .../exception/InvalidInputException.java | 7 ++ src/main/java/store/model/domain/Product.java | 73 ++++++++++++++++ .../java/store/model/domain/Promotion.java | 39 +++++++++ src/main/java/store/model/domain/Receipt.java | 53 ++++++++++++ .../java/store/model/domain/ShoppingCart.java | 49 +++++++++++ src/main/java/store/model/dto/FreeItem.java | 19 +++++ .../java/store/model/dto/PurchaseItem.java | 25 ++++++ .../java/store/service/CheckoutService.java | 30 +++++++ .../java/store/service/ProductService.java | 37 ++++++++ .../store/service/PromotionCalculator.java | 47 +++++++++++ .../java/store/service/PromotionService.java | 80 ++++++++++++++++++ .../store/service/ShoppingCartService.java | 40 +++++++++ src/main/java/store/utils/DateUtils.java | 4 + src/main/java/store/utils/FileParser.java | 84 +++++++++++++++++++ .../java/store/utils/ShoppingCartParser.java | 44 ++++++++++ .../java/store/validator/InputValidator.java | 11 +++ src/main/java/store/view/InputView.java | 42 ++++++++++ src/main/java/store/view/OutputView.java | 46 ++++++++++ 23 files changed, 857 insertions(+), 24 deletions(-) create mode 100644 src/main/java/store/config/StoreConfig.java create mode 100644 src/main/java/store/controller/StoreController.java create mode 100644 src/main/java/store/enums/PromotionType.java create mode 100644 src/main/java/store/exception/InvalidInputException.java create mode 100644 src/main/java/store/model/domain/Product.java create mode 100644 src/main/java/store/model/domain/Promotion.java create mode 100644 src/main/java/store/model/domain/Receipt.java create mode 100644 src/main/java/store/model/domain/ShoppingCart.java create mode 100644 src/main/java/store/model/dto/FreeItem.java create mode 100644 src/main/java/store/model/dto/PurchaseItem.java create mode 100644 src/main/java/store/service/CheckoutService.java create mode 100644 src/main/java/store/service/ProductService.java create mode 100644 src/main/java/store/service/PromotionCalculator.java create mode 100644 src/main/java/store/service/PromotionService.java create mode 100644 src/main/java/store/service/ShoppingCartService.java create mode 100644 src/main/java/store/utils/DateUtils.java create mode 100644 src/main/java/store/utils/FileParser.java create mode 100644 src/main/java/store/utils/ShoppingCartParser.java create mode 100644 src/main/java/store/validator/InputValidator.java create mode 100644 src/main/java/store/view/InputView.java create mode 100644 src/main/java/store/view/OutputView.java diff --git a/README.md b/README.md index eb6ebf6..c425af8 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ # java-convenience-store-precourse + ## πŸš€ κΈ°λŠ₯ μš”κ΅¬ 사항 ꡬ맀자의 할인 ν˜œνƒκ³Ό 재고 상황을 κ³ λ €ν•˜μ—¬ μ΅œμ’… 결제 κΈˆμ•‘μ„ κ³„μ‚°ν•˜κ³  μ•ˆλ‚΄ν•˜λŠ” 결제 μ‹œμŠ€ν…œμ„ κ΅¬ν˜„ν•œλ‹€. - μ‚¬μš©μžκ°€ μž…λ ₯ν•œ μƒν’ˆμ˜ 가격과 μˆ˜λŸ‰μ„ 기반으둜 μ΅œμ’… 결제 κΈˆμ•‘μ„ κ³„μ‚°ν•œλ‹€. - - 총ꡬ맀앑은 μƒν’ˆλ³„ 가격과 μˆ˜λŸ‰μ„ κ³±ν•˜μ—¬ κ³„μ‚°ν•˜λ©°, ν”„λ‘œλͺ¨μ…˜ 및 멀버십 할인 정책을 λ°˜μ˜ν•˜μ—¬ μ΅œμ’… 결제 κΈˆμ•‘μ„ μ‚°μΆœν•œλ‹€. + - 총ꡬ맀앑은 μƒν’ˆλ³„ 가격과 μˆ˜λŸ‰μ„ κ³±ν•˜μ—¬ κ³„μ‚°ν•˜λ©°, ν”„λ‘œλͺ¨μ…˜ 및 멀버십 할인 정책을 λ°˜μ˜ν•˜μ—¬ μ΅œμ’… 결제 κΈˆμ•‘μ„ μ‚°μΆœν•œλ‹€. - ꡬ맀 λ‚΄μ—­κ³Ό μ‚°μΆœν•œ κΈˆμ•‘ 정보λ₯Ό 영수증으둜 좜λ ₯ν•œλ‹€. - 영수증 좜λ ₯ ν›„ μΆ”κ°€ ꡬ맀λ₯Ό μ§„ν–‰ν• μ§€ λ˜λŠ” μ’…λ£Œν• μ§€λ₯Ό 선택할 수 μžˆλ‹€. - μ‚¬μš©μžκ°€ 잘λͺ»λœ 값을 μž…λ ₯ν•  경우 `IllegalArgumentException`λ₯Ό λ°œμƒμ‹œν‚€κ³ , "[ERROR]"둜 μ‹œμž‘ν•˜λŠ” μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ ν›„ κ·Έ λΆ€λΆ„λΆ€ν„° μž…λ ₯을 λ‹€μ‹œ λ°›λŠ”λ‹€. - - `Exception`이 μ•„λ‹ŒΒ `IllegalArgumentException`,Β `IllegalStateException`Β λ“±κ³Ό 같은 λͺ…ν™•ν•œ μœ ν˜•μ„ μ²˜λ¦¬ν•œλ‹€. + - `Exception`이 μ•„λ‹ŒΒ `IllegalArgumentException`,Β `IllegalStateException`Β λ“±κ³Ό 같은 λͺ…ν™•ν•œ μœ ν˜•μ„ μ²˜λ¦¬ν•œλ‹€. ### 재고 관리 @@ -36,27 +37,27 @@ - μ˜μˆ˜μ¦μ€ 고객의 ꡬ맀 λ‚΄μ—­κ³Ό 할인을 μš”μ•½ν•˜μ—¬ 좜λ ₯ν•œλ‹€. - 영수증 ν•­λͺ©μ€ μ•„λž˜μ™€ κ°™λ‹€. - - ꡬ맀 μƒν’ˆ λ‚΄μ—­: κ΅¬λ§€ν•œ μƒν’ˆλͺ…, μˆ˜λŸ‰, 가격 - - 증정 μƒν’ˆ λ‚΄μ—­: ν”„λ‘œλͺ¨μ…˜μ— 따라 무료둜 제곡된 증정 μƒν’ˆμ˜ λͺ©λ‘ - - κΈˆμ•‘ 정보 - - 총ꡬ맀앑: κ΅¬λ§€ν•œ μƒν’ˆμ˜ 총 μˆ˜λŸ‰κ³Ό 총 κΈˆμ•‘ - - 행사할인: ν”„λ‘œλͺ¨μ…˜μ— μ˜ν•΄ ν• μΈλœ κΈˆμ•‘ - - 멀버십할인: 멀버십에 μ˜ν•΄ μΆ”κ°€λ‘œ ν• μΈλœ κΈˆμ•‘ - - λ‚΄μ‹€λˆ: μ΅œμ’… 결제 κΈˆμ•‘ + - ꡬ맀 μƒν’ˆ λ‚΄μ—­: κ΅¬λ§€ν•œ μƒν’ˆλͺ…, μˆ˜λŸ‰, 가격 + - 증정 μƒν’ˆ λ‚΄μ—­: ν”„λ‘œλͺ¨μ…˜μ— 따라 무료둜 제곡된 증정 μƒν’ˆμ˜ λͺ©λ‘ + - κΈˆμ•‘ 정보 + - 총ꡬ맀앑: κ΅¬λ§€ν•œ μƒν’ˆμ˜ 총 μˆ˜λŸ‰κ³Ό 총 κΈˆμ•‘ + - 행사할인: ν”„λ‘œλͺ¨μ…˜μ— μ˜ν•΄ ν• μΈλœ κΈˆμ•‘ + - 멀버십할인: 멀버십에 μ˜ν•΄ μΆ”κ°€λ‘œ ν• μΈλœ κΈˆμ•‘ + - λ‚΄μ‹€λˆ: μ΅œμ’… 결제 κΈˆμ•‘ - 영수증의 ꡬ성 μš”μ†Œλ₯Ό 보기 μ’‹κ²Œ μ •λ ¬ν•˜μ—¬ 고객이 μ‰½κ²Œ κΈˆμ•‘κ³Ό μˆ˜λŸ‰μ„ 확인할 수 있게 ν•œλ‹€. ### μž…μΆœλ ₯ - μž…λ ₯ 1 - - λ‚΄μš© : ꡬ맀할 μƒν’ˆκ³Ό μˆ˜λŸ‰ - - ν˜•μ‹ : μƒν’ˆ([]), μˆ˜λŸ‰(-), μ—΄κ±°(,) - - ex) [콜라-10],[사이닀-3] + - λ‚΄μš© : ꡬ맀할 μƒν’ˆκ³Ό μˆ˜λŸ‰ + - ν˜•μ‹ : μƒν’ˆ([]), μˆ˜λŸ‰(-), μ—΄κ±°(,) + - ex) [콜라-10],[사이닀-3] - μž…λ ₯ 2 - - λ‚΄μš© : ν”„λ‘œλͺ¨μ…˜ μƒν’ˆ μΆ”κ°€ μ—¬λΆ€, μ •κ°€ 결제 μ—¬λΆ€, 멀버십 할인 적용 μ—¬λΆ€, μΆ”κ°€ ꡬ맀 μ—¬λΆ€ - - ν˜•μ‹ : Y / N + - λ‚΄μš© : ν”„λ‘œλͺ¨μ…˜ μƒν’ˆ μΆ”κ°€ μ—¬λΆ€, μ •κ°€ 결제 μ—¬λΆ€, 멀버십 할인 적용 μ—¬λΆ€, μΆ”κ°€ ꡬ맀 μ—¬λΆ€ + - ν˜•μ‹ : Y / N - μ—λŸ¬ λ©”μ‹œμ§€ - - [ERROR]둜 μ‹œμž‘ν•΄μ•Όν•¨ - - ex) `[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` + - [ERROR]둜 μ‹œμž‘ν•΄μ•Όν•¨ + - ex) `[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` ex) @@ -208,17 +209,15 @@ N - ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„μ΄ μ™„λ£Œλ˜λ©΄ `ApplicationTest`의 λͺ¨λ“  ν…ŒμŠ€νŠΈκ°€ 성곡해야 ν•œλ‹€. **ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•  경우 0점 μ²˜λ¦¬ν•œλ‹€.** - ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬ μ‚¬ν•­μ—μ„œ 달리 λͺ…μ‹œν•˜μ§€ μ•ŠλŠ” ν•œ 파일, νŒ¨ν‚€μ§€ 이름을 μˆ˜μ •ν•˜κ±°λ‚˜ μ΄λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. - indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό 3이 λ„˜μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€. 2κΉŒμ§€λ§Œ ν—ˆμš©ν•œλ‹€. - - 예λ₯Ό λ“€μ–΄ whileλ¬Έ μ•ˆμ— if문이 있으면 λ“€μ—¬μ“°κΈ°λŠ” 2이닀. - - 힌트: indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό μ€„μ΄λŠ” 쒋은 방법은 ν•¨μˆ˜(λ˜λŠ” λ©”μ„œλ“œ)λ₯Ό λΆ„λ¦¬ν•˜λ©΄ λœλ‹€. + - 예λ₯Ό λ“€μ–΄ whileλ¬Έ μ•ˆμ— if문이 있으면 λ“€μ—¬μ“°κΈ°λŠ” 2이닀. + - 힌트: indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό μ€„μ΄λŠ” 쒋은 방법은 ν•¨μˆ˜(λ˜λŠ” λ©”μ„œλ“œ)λ₯Ό λΆ„λ¦¬ν•˜λ©΄ λœλ‹€. - 3ν•­ μ—°μ‚°μžλ₯Ό μ“°μ§€ μ•ŠλŠ”λ‹€. - ν•¨μˆ˜(λ˜λŠ” λ©”μ„œλ“œ)κ°€ ν•œ κ°€μ§€ 일만 ν•˜λ„λ‘ μ΅œλŒ€ν•œ μž‘κ²Œ λ§Œλ“€μ–΄λΌ. - JUnit 5와 AssertJλ₯Ό μ΄μš©ν•˜μ—¬ 본인이 μ •λ¦¬ν•œ κΈ°λŠ₯ λͺ©λ‘μ΄ 정상 λ™μž‘ν•¨μ„ ν…ŒμŠ€νŠΈ μ½”λ“œλ‘œ ν™•μΈν•œλ‹€. - - ν…ŒμŠ€νŠΈ 도ꡬ μ‚¬μš©λ²•μ΄ μ΅μˆ™ν•˜μ§€ μ•Šλ‹€λ©΄ `test/java/study`λ₯Ό μ°Έκ³ ν•˜μ—¬ ν•™μŠ΅ν•œ ν›„ ν…ŒμŠ€νŠΈλ₯Ό κ΅¬ν˜„ν•œλ‹€. + - ν…ŒμŠ€νŠΈ 도ꡬ μ‚¬μš©λ²•μ΄ μ΅μˆ™ν•˜μ§€ μ•Šλ‹€λ©΄ `test/java/study`λ₯Ό μ°Έκ³ ν•˜μ—¬ ν•™μŠ΅ν•œ ν›„ ν…ŒμŠ€νŠΈλ₯Ό κ΅¬ν˜„ν•œλ‹€. ### 라이브러리 - `camp.nextstep.edu.missionutils`μ—μ„œ μ œκ³΅ν•˜λŠ” APIλ₯Ό μ‚¬μš©ν•œλ‹€. - - ν˜„μž¬ λ‚ μ§œμ™€ μ‹œκ°„μ„ κ°€μ Έμ˜€λ €λ©΄Β `camp.nextstep.edu.missionutils.DateTimes`의 `now()`λ₯Ό ν™œμš©ν•œλ‹€. - - μ‚¬μš©μžκ°€ μž…λ ₯ν•˜λŠ” 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 ν™œμš©ν•œλ‹€. - -## \ No newline at end of file + - ν˜„μž¬ λ‚ μ§œμ™€ μ‹œκ°„μ„ κ°€μ Έμ˜€λ €λ©΄Β `camp.nextstep.edu.missionutils.DateTimes`의 `now()`λ₯Ό ν™œμš©ν•œλ‹€. + - μ‚¬μš©μžκ°€ μž…λ ₯ν•˜λŠ” 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 ν™œμš©ν•œλ‹€. diff --git a/src/main/java/store/Application.java b/src/main/java/store/Application.java index ec4afd8..dbf6bf1 100644 --- a/src/main/java/store/Application.java +++ b/src/main/java/store/Application.java @@ -1,7 +1,12 @@ package store; +import store.config.StoreConfig; +import store.controller.StoreController; + public class Application { public static void main(String[] args) { - // TODO: ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„ + StoreConfig config = new StoreConfig(); + StoreController storeController = config.storeController(); + storeController.run(); } } diff --git a/src/main/java/store/config/StoreConfig.java b/src/main/java/store/config/StoreConfig.java new file mode 100644 index 0000000..d123b93 --- /dev/null +++ b/src/main/java/store/config/StoreConfig.java @@ -0,0 +1,32 @@ +package store.config; + +import store.controller.StoreController; +import store.service.*; +import store.view.InputView; +import store.view.OutputView; + +public class StoreConfig { + public StoreController storeController() { + return new StoreController(productService(), promotionService(), checkoutService(), inputView(), outputView()); + } + + public ProductService productService() { + return new ProductService(promotionService()); + } + + public PromotionService promotionService() { + return new PromotionService(inputView(), outputView()); + } + + public CheckoutService checkoutService() { + return new CheckoutService(); + } + + public InputView inputView() { + return new InputView(new ShoppingCartService(productService())); + } + + public OutputView outputView() { + return new OutputView(); + } +} \ No newline at end of file diff --git a/src/main/java/store/controller/StoreController.java b/src/main/java/store/controller/StoreController.java new file mode 100644 index 0000000..052ec05 --- /dev/null +++ b/src/main/java/store/controller/StoreController.java @@ -0,0 +1,40 @@ +package store.controller; + + +import store.model.domain.Receipt; +import store.model.domain.ShoppingCart; +import store.service.CheckoutService; +import store.service.ProductService; +import store.service.PromotionService; +import store.view.InputView; +import store.view.OutputView; + +public class StoreController { + private final ProductService productService; + private final PromotionService promotionService; + private final CheckoutService checkoutService; + private final InputView inputView; + private final OutputView outputView; + + public StoreController(ProductService productService, PromotionService promotionService, + CheckoutService checkoutService, InputView inputView, OutputView outputView) { + this.productService = productService; + this.promotionService = promotionService; + this.checkoutService = checkoutService; + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + outputView.displayWelcomeMessage(); + productService.displayProducts(); + + ShoppingCart cart = inputView.getUserShoppingCart(); + promotionService.applyPromotions(cart); + + boolean isMembership = inputView.askMembership(); + Receipt receipt = checkoutService.checkout(cart, isMembership); + + outputView.displayReceipt(receipt); + } +} \ No newline at end of file diff --git a/src/main/java/store/enums/PromotionType.java b/src/main/java/store/enums/PromotionType.java new file mode 100644 index 0000000..2a37333 --- /dev/null +++ b/src/main/java/store/enums/PromotionType.java @@ -0,0 +1,27 @@ +package store.enums; + +import java.util.Arrays; + +public enum PromotionType { + BUY_TWO_GET_ONE("탄산2+1"), + MD_RECOMMENDED("MDμΆ”μ²œμƒν’ˆ"), + FLASH_DISCOUNT("λ°˜μ§ν• μΈ"), + NONE("null"); + + private final String label; + + PromotionType(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public static PromotionType of(String label) { + return Arrays.stream(values()) + .filter(type -> type.label.equals(label)) + .findFirst() + .orElse(NONE); + } +} \ No newline at end of file diff --git a/src/main/java/store/exception/InvalidInputException.java b/src/main/java/store/exception/InvalidInputException.java new file mode 100644 index 0000000..047749c --- /dev/null +++ b/src/main/java/store/exception/InvalidInputException.java @@ -0,0 +1,7 @@ +package store.exception; + +public class InvalidInputException extends IllegalArgumentException { + public InvalidInputException(String message) { + super(message); + } +} 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..949950a --- /dev/null +++ b/src/main/java/store/model/domain/Product.java @@ -0,0 +1,73 @@ +package store.model.domain; + +import java.text.NumberFormat; +import java.util.Locale; + +public class Product { + private static final NumberFormat PRICE_FORMAT = NumberFormat.getInstance(Locale.KOREA); + + private final String name; + private final int price; + private int stock; + private int promotionStock; + private final Promotion promotion; + + public Product(String name, int price, int stock, int promotionStock, Promotion promotion) { + this.name = name; + this.price = price; + this.stock = stock; + this.promotionStock = promotionStock; + this.promotion = promotion; + } + + public String getName() { + return name; + } + + public int getPrice() { + return price; + } + + public int getStock() { + return stock; + } + + public int getPromotionStock() { + return promotionStock; + } + + public Promotion getPromotion() { + return promotion; + } + + /** + * βœ… 재고 κ°μ†Œ λ©”μ„œλ“œ + * - PromotionCalculatorμ—μ„œ ν”„λ‘œλͺ¨μ…˜ 적용 μ—¬λΆ€λ₯Ό νŒλ‹¨ν•œ ν›„ ν˜ΈμΆœν•΄μ•Ό 함. + */ + public void reduceStock(int quantity) { + if (quantity > stock) { + throw new IllegalArgumentException("[ERROR] 재고 λΆ€μ‘±"); + } + stock -= quantity; + } + + /** + * βœ… ν”„λ‘œλͺ¨μ…˜ 재고 κ°μ†Œ λ©”μ„œλ“œ + * - PromotionCalculatorμ—μ„œ ν”„λ‘œλͺ¨μ…˜μ„ ν™•μΈν•œ ν›„ ν˜ΈμΆœν•΄μ•Ό 함. + */ + public void reducePromotionStock(int quantity) { + if (quantity > promotionStock) { + throw new IllegalArgumentException("[ERROR] ν”„λ‘œλͺ¨μ…˜ 재고 λΆ€μ‘±"); + } + promotionStock -= quantity; + } + + @Override + public String toString() { + String formattedPrice = PRICE_FORMAT.format(price) + "원"; + String formattedStock = (stock > 0) ? stock + "개" : "재고 μ—†μŒ"; + String promoText = (promotion != null) ? promotion.getName() : ""; + + return "- " + name + " " + formattedPrice + " " + formattedStock + (promoText.isEmpty() ? "" : " " + promoText); + } +} \ No newline at end of file diff --git a/src/main/java/store/model/domain/Promotion.java b/src/main/java/store/model/domain/Promotion.java new file mode 100644 index 0000000..583a9b4 --- /dev/null +++ b/src/main/java/store/model/domain/Promotion.java @@ -0,0 +1,39 @@ +package store.model.domain; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class Promotion { + private final String name; + private final int buyCount; + private final int getCount; + private final LocalDate startDate; + private final LocalDate endDate; + + public Promotion(String name, int buyCount, int getCount, String startDate, String endDate) { + this.name = name; + this.buyCount = buyCount; + this.getCount = getCount; + this.startDate = LocalDate.parse(startDate, DateTimeFormatter.ISO_DATE); + this.endDate = LocalDate.parse(endDate, DateTimeFormatter.ISO_DATE); + } + + public String getName() { + return name; + } + + public int getBuyCount() { + return buyCount; + } + + public int getGetCount() { + return getCount; + } + + /** + * βœ… ν”„λ‘œλͺ¨μ…˜μ΄ ν˜„μž¬ λ‚ μ§œμ— μœ νš¨ν•œμ§€ 확인 + */ + public boolean isActive(LocalDate today) { + return !today.isBefore(startDate) && !today.isAfter(endDate); + } +} \ No newline at end of file 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..3dbd2e8 --- /dev/null +++ b/src/main/java/store/model/domain/Receipt.java @@ -0,0 +1,53 @@ +package store.model.domain; + +import java.util.List; +import store.model.dto.FreeItem; +import store.model.dto.PurchaseItem; + +public class Receipt { + private final List purchasedItems; + private final List freeItems; + private final int totalAmount; + private final int promotionDiscount; + private final int membershipDiscount; + private final int finalAmount; + + public Receipt(List purchasedItems, List freeItems, int totalAmount, int promotionDiscount, int membershipDiscount, int finalAmount) { + this.purchasedItems = purchasedItems; + this.freeItems = freeItems; + this.totalAmount = totalAmount; + this.promotionDiscount = promotionDiscount; + this.membershipDiscount = membershipDiscount; + this.finalAmount = finalAmount; + + // βœ… λ””λ²„κΉ…μš© 둜그 (μ΅œμ’… κ³„μ‚°λœ κ°’ 확인) + System.out.println("[DEBUG] 총 ꡬ맀앑: " + totalAmount); + System.out.println("[DEBUG] 행사 할인: " + promotionDiscount); + System.out.println("[DEBUG] 멀버십 할인: " + membershipDiscount); + System.out.println("[DEBUG] μ΅œμ’… 결제 κΈˆμ•‘: " + finalAmount); + } + + public List getPurchasedItems() { + return purchasedItems; + } + + public List getFreeItems() { + return freeItems; + } + + public int getTotalAmount() { + return totalAmount; + } + + public int getPromotionDiscount() { + return promotionDiscount; + } + + public int getMembershipDiscount() { + return membershipDiscount; + } + + public int getFinalAmount() { + return finalAmount; + } +} \ No newline at end of file diff --git a/src/main/java/store/model/domain/ShoppingCart.java b/src/main/java/store/model/domain/ShoppingCart.java new file mode 100644 index 0000000..cf5c355 --- /dev/null +++ b/src/main/java/store/model/domain/ShoppingCart.java @@ -0,0 +1,49 @@ +package store.model.domain; + +import java.util.HashMap; +import java.util.Map; + +public class ShoppingCart { + private final Map items; + + public ShoppingCart() { + this.items = new HashMap<>(); + } + + public void addProduct(Product product, int quantity) { + if (quantity <= 0) { + throw new IllegalArgumentException("[ERROR] μΆ”κ°€ν•  μˆ˜λŸ‰μ€ 1개 이상이어야 ν•©λ‹ˆλ‹€."); + } + + // κΈ°μ‘΄ 동일 μƒν’ˆμ΄ μžˆλŠ”μ§€ 확인 ν›„ μˆ˜λŸ‰ 증가 + items.put(product, items.getOrDefault(product, 0) + quantity); + } + + public void addFreeItem(Product product, int quantity) { + if (quantity <= 0) { + throw new IllegalArgumentException("[ERROR] μΆ”κ°€ν•  증정 μˆ˜λŸ‰μ΄ 0개 μ΄ν•˜μΌ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + System.out.println(product.getName() + " " + quantity + "κ°œκ°€ 무료둜 μ¦μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + addProduct(product, quantity); + } + + public void removeItem(Product product, int quantity) { + if (!items.containsKey(product)) { + throw new IllegalArgumentException("[ERROR] ν•΄λ‹Ή μƒν’ˆμ€ μž₯λ°”κ΅¬λ‹ˆμ— μ—†μŠ΅λ‹ˆλ‹€."); + } + int currentQuantity = items.get(product); + if (quantity > currentQuantity) { + throw new IllegalArgumentException("[ERROR] μ œκ±°ν•  μˆ˜λŸ‰μ΄ μž₯λ°”κ΅¬λ‹ˆμ— μžˆλŠ” μˆ˜λŸ‰λ³΄λ‹€ λ§ŽμŠ΅λ‹ˆλ‹€."); + } + if (quantity == currentQuantity) { + items.remove(product); // μˆ˜λŸ‰μ΄ 0이면 μž₯λ°”κ΅¬λ‹ˆμ—μ„œ 제거 + } else { + items.put(product, currentQuantity - quantity); + } + System.out.println(product.getName() + " " + quantity + "κ°œκ°€ μž₯λ°”κ΅¬λ‹ˆμ—μ„œ μ œκ±°λ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + } + + public Map getItems() { + return items; + } +} \ No newline at end of file diff --git a/src/main/java/store/model/dto/FreeItem.java b/src/main/java/store/model/dto/FreeItem.java new file mode 100644 index 0000000..829bf02 --- /dev/null +++ b/src/main/java/store/model/dto/FreeItem.java @@ -0,0 +1,19 @@ +package store.model.dto; + +public class FreeItem { + private final String productName; + private final int quantity; + + public FreeItem(String productName, int quantity) { + this.productName = productName; + this.quantity = quantity; + } + + public String getProductName() { + return productName; + } + + public int getQuantity() { + return quantity; + } +} \ No newline at end of file diff --git a/src/main/java/store/model/dto/PurchaseItem.java b/src/main/java/store/model/dto/PurchaseItem.java new file mode 100644 index 0000000..f505fd8 --- /dev/null +++ b/src/main/java/store/model/dto/PurchaseItem.java @@ -0,0 +1,25 @@ +package store.model.dto; + +public class PurchaseItem { + private final String productName; + private final int quantity; + private final int totalPrice; + + public PurchaseItem(String productName, int quantity, int totalPrice) { + this.productName = productName; + this.quantity = quantity; + this.totalPrice = totalPrice; + } + + public String getProductName() { + return productName; + } + + public int getQuantity() { + return quantity; + } + + public int getTotalPrice() { + return totalPrice; + } +} \ No newline at end of file diff --git a/src/main/java/store/service/CheckoutService.java b/src/main/java/store/service/CheckoutService.java new file mode 100644 index 0000000..1105c76 --- /dev/null +++ b/src/main/java/store/service/CheckoutService.java @@ -0,0 +1,30 @@ +package store.service; + +import store.model.domain.Receipt; +import store.model.domain.ShoppingCart; + +import store.model.dto.FreeItem; + +import java.util.List; + +public class CheckoutService { + private static final double MEMBERSHIP_DISCOUNT_RATE = 0.3; + private static final int MAX_MEMBERSHIP_DISCOUNT = 8000; + + public Receipt checkout(ShoppingCart cart, boolean isMembership) { + int totalAmount = calculateTotalAmount(cart); + + List freeItems = applyPromotions(cart); + int promotionDiscount = calculatePromotionDiscount(freeItems, cart); + + int membershipDiscount = isMembership + ? Math.min((int) ((totalAmount - promotionDiscount) * MEMBERSHIP_DISCOUNT_RATE), + MAX_MEMBERSHIP_DISCOUNT) + : 0; + + int finalAmount = totalAmount - promotionDiscount - membershipDiscount; + + return new Receipt(cart.getPurchasedItems(), freeItems, totalAmount, promotionDiscount, membershipDiscount, + finalAmount); + } +} \ No newline at end of file diff --git a/src/main/java/store/service/ProductService.java b/src/main/java/store/service/ProductService.java new file mode 100644 index 0000000..6987070 --- /dev/null +++ b/src/main/java/store/service/ProductService.java @@ -0,0 +1,37 @@ +package store.service; + +import java.util.Optional; +import store.model.domain.Product; +import store.model.domain.Promotion; +import store.utils.FileParser; +import java.util.List; + +public class ProductService { + private final List products; + + public ProductService(PromotionService promotionService) { + this.products = FileParser.parseProducts("products.md", promotionService.getPromotions()); // βœ… Promotion 리슀트 전달 + } + + public void displayProducts() { + products.forEach(System.out::println); + } + + public List getProducts() { + return products; + } + + public Optional findProductByName(String name) { + return products.stream() + .filter(product -> product.getName().equals(name)) + .findFirst(); + } + + public int getPromotionStock(Product product) { + Promotion promotion = product.getPromotion(); + if (promotion == null) { + return 0; // ν”„λ‘œλͺ¨μ…˜μ΄ μ—†μœΌλ©΄ ν”„λ‘œλͺ¨μ…˜ 재고 μ—†μŒ + } + return product.getStock(); // 기본적으둜 재고λ₯Ό ν”„λ‘œλͺ¨μ…˜ 재고둜 ν™œμš© + } +} \ No newline at end of file diff --git a/src/main/java/store/service/PromotionCalculator.java b/src/main/java/store/service/PromotionCalculator.java new file mode 100644 index 0000000..e978e48 --- /dev/null +++ b/src/main/java/store/service/PromotionCalculator.java @@ -0,0 +1,47 @@ +package store.service; + +import camp.nextstep.edu.missionutils.DateTimes; +import store.model.domain.Promotion; +import store.model.domain.Product; +import java.time.LocalDate; +import java.util.Optional; + +public class PromotionCalculator { + + /** + * βœ… ν”„λ‘œλͺ¨μ…˜ 적용 κ°€λŠ₯ μ—¬λΆ€ 확인 ν›„ 증정 μˆ˜λŸ‰ 계산 + */ + public static Optional calculateFreeItems(Product product, int quantity) { + Promotion promotion = product.getPromotion(); + + // βœ… `DateTimes.now()`μ—μ„œ λ‚ μ§œ λΆ€λΆ„λ§Œ μΆ”μΆœν•˜μ—¬ LocalDate둜 λ³€ν™˜ + LocalDate today = DateTimes.now().toLocalDate(); + + // βœ… 였늘 λ‚ μ§œκ°€ ν”„λ‘œλͺ¨μ…˜ κΈ°κ°„ 내에 ν¬ν•¨λ˜μ§€ μ•ŠμœΌλ©΄ ν”„λ‘œλͺ¨μ…˜ 적용 λΆˆκ°€ + if (promotion == null || !promotion.isActive(today)) { + return Optional.empty(); + } + + int buyCount = promotion.getBuyCount(); + int getCount = promotion.getGetCount(); + int freeQuantity = (quantity / buyCount) * getCount; + + return freeQuantity > 0 ? Optional.of(freeQuantity) : Optional.empty(); + } + + /** + * βœ… ν”„λ‘œλͺ¨μ…˜μ„ μ μš©ν•  수 μžˆλ‹€λ©΄, ProductServiceμ—μ„œ ν”„λ‘œλͺ¨μ…˜ 재고 확인 ν›„ 적용 + */ + public static boolean applyPromotion(Product product, int quantity, ProductService productService) { + Optional freeItemCount = calculateFreeItems(product, quantity); + + if (freeItemCount.isEmpty()) { + return false; // ν”„λ‘œλͺ¨μ…˜ 적용 λΆˆκ°€ + } + + int freeQuantity = freeItemCount.get(); + int promotionStock = productService.getPromotionStock(product); + + return freeQuantity <= promotionStock; // ν”„λ‘œλͺ¨μ…˜ 재고 λΆ€μ‘± + } +} \ No newline at end of file diff --git a/src/main/java/store/service/PromotionService.java b/src/main/java/store/service/PromotionService.java new file mode 100644 index 0000000..d6d96ae --- /dev/null +++ b/src/main/java/store/service/PromotionService.java @@ -0,0 +1,80 @@ +package store.service; + +import store.model.domain.Promotion; +import store.model.domain.Product; +import store.model.domain.ShoppingCart; +import store.utils.FileParser; +import store.view.InputView; +import store.view.OutputView; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +public class PromotionService { + private final List promotions; + private final InputView inputView; + private final OutputView outputView; + + public PromotionService(InputView inputView, OutputView outputView) { + this.promotions = FileParser.parsePromotions("promotions.md"); + this.inputView = inputView; + this.outputView = outputView; + } + + public List getPromotions() { + return promotions; + } + + public void applyPromotions(ShoppingCart cart) { + LocalDate today = LocalDate.now(); + + for (Map.Entry entry : cart.getItems().entrySet()) { + Product product = entry.getKey(); + int quantity = entry.getValue(); + Promotion promotion = product.getPromotion(); + + if (promotion == null || promotion.isActive(today)) { + continue; + } + + int buyCount = promotion.getBuyCount(); + int getCount = promotion.getGetCount(); + + // ν”„λ‘œλͺ¨μ…˜ 적용 κ°€λŠ₯ν•œ μΆ”κ°€ 증정 개수 계산 + int freeQuantity = (quantity / buyCount) * getCount; + if (freeQuantity > 0) { + int availableStock = product.getStock(); + freeQuantity = Math.min(freeQuantity, availableStock); + + if (freeQuantity > 0) { + outputView.askPromotionConfirmation(product.getName(), freeQuantity); + boolean confirm = inputView.askYesOrNo(); + + if (confirm) { + cart.addFreeItem(product, freeQuantity); + product.reduceStock(freeQuantity); + } + } + } + + // μΆ”κ°€ ꡬ맀 μ•ˆλ‚΄ + int requiredForPromotion = buyCount - (quantity % buyCount); + if (requiredForPromotion > 0 && requiredForPromotion != buyCount) { + outputView.suggestAdditionalPurchase(product.getName(), requiredForPromotion); + } + + // ν”„λ‘œλͺ¨μ…˜ 재고 λΆ€μ‘± μ‹œ μ •κ°€ 결제 μ—¬λΆ€ 확인 + int promoApplicableQuantity = (quantity / buyCount) * buyCount; + int nonPromoQuantity = quantity - promoApplicableQuantity; + if (nonPromoQuantity > 0) { + outputView.askNonPromotionPurchase(product.getName(), nonPromoQuantity); + boolean confirm = inputView.askYesOrNo(); + + if (!confirm) { + cart.removeItem(product, nonPromoQuantity); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/store/service/ShoppingCartService.java b/src/main/java/store/service/ShoppingCartService.java new file mode 100644 index 0000000..2e5672b --- /dev/null +++ b/src/main/java/store/service/ShoppingCartService.java @@ -0,0 +1,40 @@ +package store.service; + +import store.model.domain.ShoppingCart; +import store.model.domain.Product; +import store.utils.ShoppingCartParser; +import store.exception.InvalidInputException; + +import java.util.Map; +import java.util.Optional; + +public class ShoppingCartService { + private final ProductService productService; + + public ShoppingCartService(ProductService productService) { + this.productService = productService; + } + + /** + * βœ… μž₯λ°”κ΅¬λ‹ˆμ— μƒν’ˆ μΆ”κ°€ + */ + public ShoppingCart createShoppingCart(String input) { + Map parsedItems = ShoppingCartParser.parse(input); + ShoppingCart cart = new ShoppingCart(); + + for (Map.Entry entry : parsedItems.entrySet()) { + String productName = entry.getKey(); + int quantity = entry.getValue(); + + // βœ… μƒν’ˆμ„ ProductServiceμ—μ„œ 쑰회 + Optional productOpt = productService.findProductByName(productName); + if (productOpt.isEmpty()) { + throw new InvalidInputException("[ERROR] μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μƒν’ˆμž…λ‹ˆλ‹€: " + productName); + } + + cart.addProduct(productOpt.get(), quantity); + } + + return cart; + } +} \ No newline at end of file diff --git a/src/main/java/store/utils/DateUtils.java b/src/main/java/store/utils/DateUtils.java new file mode 100644 index 0000000..d7184cf --- /dev/null +++ b/src/main/java/store/utils/DateUtils.java @@ -0,0 +1,4 @@ +package store.utils; + +public class DateUtils { +} diff --git a/src/main/java/store/utils/FileParser.java b/src/main/java/store/utils/FileParser.java new file mode 100644 index 0000000..2128a36 --- /dev/null +++ b/src/main/java/store/utils/FileParser.java @@ -0,0 +1,84 @@ +package store.utils; + +import store.model.domain.Product; +import store.model.domain.Promotion; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; + +public class FileParser { + public static List parseProducts(String filePath, List promotions) { + try (BufferedReader reader = getBufferedReader(filePath)) { + return reader.lines() + .skip(1) // 첫 번째 쀄(헀더) κ±΄λ„ˆλ›°κΈ° + .map(line -> parseProduct(line, promotions)) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IllegalStateException("[ERROR] νŒŒμΌμ„ 읽을 수 μ—†μŠ΅λ‹ˆλ‹€: " + filePath, e); + } + } + + public static List parsePromotions(String filePath) { + try (BufferedReader reader = getBufferedReader(filePath)) { + return reader.lines() + .skip(1) + .map(FileParser::parsePromotion) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IllegalStateException("[ERROR] νŒŒμΌμ„ 읽을 수 μ—†μŠ΅λ‹ˆλ‹€: " + filePath, e); + } + } + + private static BufferedReader getBufferedReader(String filePath) { + ClassLoader classLoader = FileParser.class.getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream(filePath); + + if (inputStream == null) { + throw new IllegalStateException("[ERROR] λ¦¬μ†ŒμŠ€ νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: " + filePath); + } + + return new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + } + + /** + * βœ… `promotionStock` 제거 ν›„ Product 객체 생성 + */ + private static Product parseProduct(String line, List promotions) { + String[] parts = line.split(","); + if (parts.length < 4) { + throw new IllegalArgumentException("[ERROR] μƒν’ˆ 데이터 ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."); + } + + String name = parts[0].trim(); + int price = Integer.parseInt(parts[1].trim()); + int stock = Integer.parseInt(parts[2].trim()); + String promotionName = parts[3].trim(); + + Promotion promotion = promotions.stream() + .filter(p -> p.getName().equals(promotionName)) + .findFirst() + .orElse(null); + + return new Product(name, price, stock, promotion); // βœ… `promotionStock` 제거 + } + + private static Promotion parsePromotion(String line) { + String[] parts = line.split(","); + if (parts.length < 5) { + throw new IllegalArgumentException("[ERROR] ν”„λ‘œλͺ¨μ…˜ 데이터 ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."); + } + + String name = parts[0].trim(); + int buyCount = Integer.parseInt(parts[1].trim()); + int getCount = Integer.parseInt(parts[2].trim()); + String startDate = parts[3].trim(); + String endDate = parts[4].trim(); + + return new Promotion(name, buyCount, getCount, startDate, endDate); + } +} \ No newline at end of file diff --git a/src/main/java/store/utils/ShoppingCartParser.java b/src/main/java/store/utils/ShoppingCartParser.java new file mode 100644 index 0000000..291617c --- /dev/null +++ b/src/main/java/store/utils/ShoppingCartParser.java @@ -0,0 +1,44 @@ +package store.utils; + +import store.exception.InvalidInputException; +import java.util.HashMap; +import java.util.Map; + +public class ShoppingCartParser { + + /** + * βœ… μž…λ ₯값을 κ²€μ¦ν•˜κ³  μƒν’ˆλͺ…-μˆ˜λŸ‰μ„ Map으둜 λ°˜ν™˜ + */ + public static Map parse(String input) { + // βœ… μž…λ ₯값이 λΉ„μ–΄μžˆλŠ” 경우 μ˜ˆμ™Έ λ°œμƒ + if (input.trim().isEmpty()) { + throw new InvalidInputException("[ERROR] μž…λ ₯값이 λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ˜¬λ°”λ₯Έ ν˜•μ‹μœΌλ‘œ μž…λ ₯ν•˜μ„Έμš”."); + } + + Map parsedItems = new HashMap<>(); + String[] items = input.replaceAll("[\\[\\]]", "").split(","); + + for (String item : items) { + String[] parts = item.split("-"); + if (parts.length != 2) { + throw new InvalidInputException("[ERROR] μ˜¬λ°”λ₯Έ μž…λ ₯ ν˜•μ‹μ΄ μ•„λ‹™λ‹ˆλ‹€. 예: [콜라-3],[사이닀-1]"); + } + + String productName = parts[0].trim(); + int quantity; + try { + quantity = Integer.parseInt(parts[1].trim()); + if (quantity <= 0) { + throw new InvalidInputException("[ERROR] μˆ˜λŸ‰μ€ 1 이상이어야 ν•©λ‹ˆλ‹€."); + } + } catch (NumberFormatException e) { + throw new InvalidInputException("[ERROR] μˆ˜λŸ‰μ€ 숫자둜 μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€."); + } + + // βœ… Map에 μƒν’ˆλͺ…κ³Ό μˆ˜λŸ‰ μ €μž₯ + parsedItems.put(productName, parsedItems.getOrDefault(productName, 0) + quantity); + } + + return parsedItems; + } +} \ No newline at end of file diff --git a/src/main/java/store/validator/InputValidator.java b/src/main/java/store/validator/InputValidator.java new file mode 100644 index 0000000..59e55aa --- /dev/null +++ b/src/main/java/store/validator/InputValidator.java @@ -0,0 +1,11 @@ +package store.validator; + +import store.exception.InvalidInputException; + +public class InputValidator { + public static void validateProductSelection(String input) { + if (!input.matches("\\[.*-\\d+]")) { + throw new InvalidInputException("[ERROR] μ˜¬λ°”λ₯Έ μž…λ ₯ ν˜•μ‹μ΄ μ•„λ‹™λ‹ˆλ‹€."); + } + } +} \ No newline at end of file diff --git a/src/main/java/store/view/InputView.java b/src/main/java/store/view/InputView.java new file mode 100644 index 0000000..8ad6d55 --- /dev/null +++ b/src/main/java/store/view/InputView.java @@ -0,0 +1,42 @@ +package store.view; + +import camp.nextstep.edu.missionutils.Console; +import store.model.domain.ShoppingCart; +import store.service.ShoppingCartService; +import store.exception.InvalidInputException; + +public class InputView { + private final ShoppingCartService shoppingCartService; + + public InputView(ShoppingCartService shoppingCartService) { + this.shoppingCartService = shoppingCartService; + } + + public ShoppingCart getUserShoppingCart() { + System.out.println("ꡬ맀할 μƒν’ˆλͺ…κ³Ό μˆ˜λŸ‰μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”. (예: [사이닀-2],[감자칩-1])"); + + while (true) { + try { + String input = Console.readLine(); + return shoppingCartService.createShoppingCart(input); // βœ… ShoppingCartService ν™œμš© + } catch (InvalidInputException e) { + System.out.println(e.getMessage()); + } + } + } + + public boolean askMembership() { + System.out.println("\n멀버십 할인을 λ°›μœΌμ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N)"); + return askYesOrNo(); + } + + public boolean askYesOrNo() { + String input; + while (true) { + input = Console.readLine().trim().toUpperCase(); + if (input.equals("Y")) return true; + if (input.equals("N")) return false; + System.out.println("[ERROR] Y λ˜λŠ” N만 μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + } +} \ No newline at end of file diff --git a/src/main/java/store/view/OutputView.java b/src/main/java/store/view/OutputView.java new file mode 100644 index 0000000..4b13398 --- /dev/null +++ b/src/main/java/store/view/OutputView.java @@ -0,0 +1,46 @@ +package store.view; + +import store.model.domain.Receipt; +import store.model.dto.FreeItem; +import store.model.dto.PurchaseItem; + +public class OutputView { + public void displayWelcomeMessage() { + System.out.println("μ•ˆλ…•ν•˜μ„Έμš”. W νŽΈμ˜μ μž…λ‹ˆλ‹€."); + System.out.println("ν˜„μž¬ λ³΄μœ ν•˜κ³  μžˆλŠ” μƒν’ˆμž…λ‹ˆλ‹€.\n"); + } + + public void displayReceipt(Receipt receipt) { + System.out.println("\n=========== W 편의점 ============="); + + System.out.println("μƒν’ˆλͺ… μˆ˜λŸ‰ κΈˆμ•‘"); + for (PurchaseItem item : receipt.getPurchasedItems()) { + System.out.printf("%s %d %d%n", item.getProductName(), item.getQuantity(), item.getTotalPrice()); + } + + if (!receipt.getFreeItems().isEmpty()) { + System.out.println("=========== 증정 ============="); + for (FreeItem item : receipt.getFreeItems()) { + System.out.printf("%s %d%n", item.getProductName(), item.getQuantity()); + } + } + + System.out.println("=============================="); + System.out.printf("총ꡬ맀앑: %,d%n", receipt.getTotalAmount()); + System.out.printf("행사할인: -%,d%n", receipt.getPromotionDiscount()); + System.out.printf("멀버십할인: -%,d%n", receipt.getMembershipDiscount()); + System.out.printf("λ‚΄μ‹€λˆ: %,d%n", receipt.getFinalAmount()); + System.out.println(); + } + public void askPromotionConfirmation(String productName, int freeQuantity) { + System.out.println("ν˜„μž¬ " + productName + "은(λŠ”) " + freeQuantity + "개λ₯Ό 무료둜 더 받을 수 μžˆμŠ΅λ‹ˆλ‹€. μΆ”κ°€ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N)"); + } + + public void suggestAdditionalPurchase(String productName, int requiredForPromotion) { + System.out.println("ν˜„μž¬ " + productName + "을(λ₯Ό) " + requiredForPromotion + "개 더 κ΅¬λ§€ν•˜λ©΄ ν˜œνƒμ„ 받을 수 μžˆμŠ΅λ‹ˆλ‹€."); + } + + public void askNonPromotionPurchase(String productName, int nonPromoQuantity) { + System.out.println("ν˜„μž¬ " + productName + " " + nonPromoQuantity + "κ°œλŠ” ν”„λ‘œλͺ¨μ…˜ 할인이 μ μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ·Έλž˜λ„ κ΅¬λ§€ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ? (Y/N)"); + } +} \ No newline at end of file From 23126dfd739c1c4549ed0f3bf0f96261c24c3c74 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:48:28 +0900 Subject: [PATCH 4/6] =?UTF-8?q?bug(config&file):=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=EC=97=90=EB=9F=AC&=ED=8C=8C=EC=9D=BC=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=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 --- src/main/java/store/config/StoreConfig.java | 4 +- src/main/java/store/model/domain/Product.java | 2 +- .../java/store/service/CheckoutService.java | 67 ++++++++++++++----- .../java/store/service/ProductService.java | 18 ++--- .../store/service/PromotionCalculator.java | 26 ++++--- .../java/store/service/PromotionService.java | 4 +- src/main/java/store/utils/FileParser.java | 20 ++++-- 7 files changed, 95 insertions(+), 46 deletions(-) diff --git a/src/main/java/store/config/StoreConfig.java b/src/main/java/store/config/StoreConfig.java index d123b93..3cc3213 100644 --- a/src/main/java/store/config/StoreConfig.java +++ b/src/main/java/store/config/StoreConfig.java @@ -11,11 +11,11 @@ public StoreController storeController() { } public ProductService productService() { - return new ProductService(promotionService()); + return new ProductService(); } public PromotionService promotionService() { - return new PromotionService(inputView(), outputView()); + return new PromotionService(productService(), inputView(), outputView()); // βœ… productServiceλ₯Ό PromotionService에 μ£Όμž… } public CheckoutService checkoutService() { diff --git a/src/main/java/store/model/domain/Product.java b/src/main/java/store/model/domain/Product.java index 949950a..23b7e58 100644 --- a/src/main/java/store/model/domain/Product.java +++ b/src/main/java/store/model/domain/Product.java @@ -12,7 +12,7 @@ public class Product { private int promotionStock; private final Promotion promotion; - public Product(String name, int price, int stock, int promotionStock, Promotion promotion) { + public Product(String name, int price, int stock, Promotion promotion) { this.name = name; this.price = price; this.stock = stock; diff --git a/src/main/java/store/service/CheckoutService.java b/src/main/java/store/service/CheckoutService.java index 1105c76..163c6a1 100644 --- a/src/main/java/store/service/CheckoutService.java +++ b/src/main/java/store/service/CheckoutService.java @@ -2,29 +2,60 @@ import store.model.domain.Receipt; import store.model.domain.ShoppingCart; - +import store.model.domain.Product; +import store.model.dto.PurchaseItem; import store.model.dto.FreeItem; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; public class CheckoutService { - private static final double MEMBERSHIP_DISCOUNT_RATE = 0.3; - private static final int MAX_MEMBERSHIP_DISCOUNT = 8000; - public Receipt checkout(ShoppingCart cart, boolean isMembership) { - int totalAmount = calculateTotalAmount(cart); - - List freeItems = applyPromotions(cart); - int promotionDiscount = calculatePromotionDiscount(freeItems, cart); - - int membershipDiscount = isMembership - ? Math.min((int) ((totalAmount - promotionDiscount) * MEMBERSHIP_DISCOUNT_RATE), - MAX_MEMBERSHIP_DISCOUNT) - : 0; - - int finalAmount = totalAmount - promotionDiscount - membershipDiscount; - - return new Receipt(cart.getPurchasedItems(), freeItems, totalAmount, promotionDiscount, membershipDiscount, - finalAmount); + Map items = cart.getItems(); + + if (items.isEmpty()) { + throw new IllegalStateException("[ERROR] μž₯λ°”κ΅¬λ‹ˆκ°€ λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€. ꡬ맀할 μƒν’ˆμ„ μΆ”κ°€ν•˜μ„Έμš”."); + } + + List purchasedItems = items.entrySet().stream() + .map(entry -> new PurchaseItem( + entry.getKey().getName(), + entry.getValue(), + entry.getKey().getPrice() * entry.getValue())) + .collect(Collectors.toList()); + + int totalAmount = purchasedItems.stream() + .mapToInt(PurchaseItem::getTotalPrice) + .sum(); + + List freeItems = items.entrySet().stream() + .map(entry -> { + Product product = entry.getKey(); + int quantity = entry.getValue(); + + // βœ… PromotionCalculatorμ—μ„œ ν”„λ‘œλͺ¨μ…˜ 적용 μ—¬λΆ€ νŒλ‹¨ ν›„ 적용 + boolean isPromotionApplied = PromotionCalculator.applyPromotion(product, quantity); + + if (isPromotionApplied) { + return new FreeItem(product.getName(), quantity / product.getPromotion().getBuyCount()); + } + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + int promotionDiscount = freeItems.stream() + .mapToInt(item -> { + Product product = items.keySet().stream() + .filter(p -> p.getName().equals(item.getProductName())) + .findFirst() + .orElse(null); + return (product != null ? product.getPrice() : 0) * item.getQuantity(); + }) + .sum(); + + return new Receipt(purchasedItems, freeItems, totalAmount, promotionDiscount, 0, totalAmount - promotionDiscount); } } \ No newline at end of file diff --git a/src/main/java/store/service/ProductService.java b/src/main/java/store/service/ProductService.java index 6987070..85fdc02 100644 --- a/src/main/java/store/service/ProductService.java +++ b/src/main/java/store/service/ProductService.java @@ -9,8 +9,8 @@ public class ProductService { private final List products; - public ProductService(PromotionService promotionService) { - this.products = FileParser.parseProducts("products.md", promotionService.getPromotions()); // βœ… Promotion 리슀트 전달 + public ProductService() { + this.products = FileParser.parseProducts("products.md", List.of()); // βœ… Promotion 리슀트 전달 } public void displayProducts() { @@ -27,11 +27,11 @@ public Optional findProductByName(String name) { .findFirst(); } - public int getPromotionStock(Product product) { - Promotion promotion = product.getPromotion(); - if (promotion == null) { - return 0; // ν”„λ‘œλͺ¨μ…˜μ΄ μ—†μœΌλ©΄ ν”„λ‘œλͺ¨μ…˜ 재고 μ—†μŒ - } - return product.getStock(); // 기본적으둜 재고λ₯Ό ν”„λ‘œλͺ¨μ…˜ 재고둜 ν™œμš© - } +// public int getPromotionStock(Product product) { +// Promotion promotion = product.getPromotion(); +// if (promotion == null) { +// return 0; // ν”„λ‘œλͺ¨μ…˜μ΄ μ—†μœΌλ©΄ ν”„λ‘œλͺ¨μ…˜ 재고 μ—†μŒ +// } +// return product.getStock(); // 기본적으둜 재고λ₯Ό ν”„λ‘œλͺ¨μ…˜ 재고둜 ν™œμš© +// } } \ No newline at end of file diff --git a/src/main/java/store/service/PromotionCalculator.java b/src/main/java/store/service/PromotionCalculator.java index e978e48..7d141ea 100644 --- a/src/main/java/store/service/PromotionCalculator.java +++ b/src/main/java/store/service/PromotionCalculator.java @@ -32,16 +32,20 @@ public static Optional calculateFreeItems(Product product, int quantity /** * βœ… ν”„λ‘œλͺ¨μ…˜μ„ μ μš©ν•  수 μžˆλ‹€λ©΄, ProductServiceμ—μ„œ ν”„λ‘œλͺ¨μ…˜ 재고 확인 ν›„ 적용 */ - public static boolean applyPromotion(Product product, int quantity, ProductService productService) { - Optional freeItemCount = calculateFreeItems(product, quantity); - - if (freeItemCount.isEmpty()) { - return false; // ν”„λ‘œλͺ¨μ…˜ 적용 λΆˆκ°€ - } - - int freeQuantity = freeItemCount.get(); - int promotionStock = productService.getPromotionStock(product); - - return freeQuantity <= promotionStock; // ν”„λ‘œλͺ¨μ…˜ 재고 λΆ€μ‘± +// public static boolean applyPromotion(Product product, int quantity, ProductService productService) { +// Optional freeItemCount = calculateFreeItems(product, quantity); +// +// if (freeItemCount.isEmpty()) { +// return false; // ν”„λ‘œλͺ¨μ…˜ 적용 λΆˆκ°€ +// } +// +// int freeQuantity = freeItemCount.get(); +// int promotionStock = productService.getPromotionStock(product); +// +// return freeQuantity <= promotionStock; // ν”„λ‘œλͺ¨μ…˜ 재고 λΆ€μ‘± +// } + + public static boolean applyPromotion(Product product, int quantity) { + return false; } } \ No newline at end of file diff --git a/src/main/java/store/service/PromotionService.java b/src/main/java/store/service/PromotionService.java index d6d96ae..76c00a4 100644 --- a/src/main/java/store/service/PromotionService.java +++ b/src/main/java/store/service/PromotionService.java @@ -15,9 +15,11 @@ public class PromotionService { private final List promotions; private final InputView inputView; private final OutputView outputView; + private final ProductService productService; - public PromotionService(InputView inputView, OutputView outputView) { + public PromotionService(ProductService productService, InputView inputView, OutputView outputView) { this.promotions = FileParser.parsePromotions("promotions.md"); + this.productService = productService; // βœ… λͺ…μ‹œμ μœΌλ‘œ ProductService μ£Όμž… this.inputView = inputView; this.outputView = outputView; } diff --git a/src/main/java/store/utils/FileParser.java b/src/main/java/store/utils/FileParser.java index 2128a36..ddd1a1f 100644 --- a/src/main/java/store/utils/FileParser.java +++ b/src/main/java/store/utils/FileParser.java @@ -1,7 +1,9 @@ package store.utils; +import java.util.Arrays; import store.model.domain.Product; import store.model.domain.Promotion; +import store.enums.PromotionType; import java.io.BufferedReader; import java.io.IOException; @@ -46,12 +48,18 @@ private static BufferedReader getBufferedReader(String filePath) { } /** - * βœ… `promotionStock` 제거 ν›„ Product 객체 생성 + * βœ… `promotionStock`을 κ³ λ €ν•œ Product 객체 생성 */ private static Product parseProduct(String line, List promotions) { + System.out.println("[DEBUG] Parsing line: " + line); // βœ… 둜그 μΆ”κ°€ + String[] parts = line.split(","); - if (parts.length < 4) { - throw new IllegalArgumentException("[ERROR] μƒν’ˆ 데이터 ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."); + + System.out.println("[DEBUG] Parsed fields: " + Arrays.toString(parts)); + + // βœ… CSV ν˜•μ‹ 체크 (μƒν’ˆλͺ…, 가격, μˆ˜λŸ‰, ν”„λ‘œλͺ¨μ…˜ 포함) + if (parts.length != 4) { + throw new IllegalArgumentException("[ERROR] μƒν’ˆ 데이터 ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€: " + line); } String name = parts[0].trim(); @@ -59,12 +67,16 @@ private static Product parseProduct(String line, List promotions) { int stock = Integer.parseInt(parts[2].trim()); String promotionName = parts[3].trim(); + + // βœ… 가격과 재고λ₯Ό λ³€ν™˜ν•˜κΈ° 전에 둜그 좜λ ₯ + System.out.println("[DEBUG] Extracting - Name: " + name + ", Price: " + price + ", Stock: " + stock+ ", Promotion: " + promotionName); + Promotion promotion = promotions.stream() .filter(p -> p.getName().equals(promotionName)) .findFirst() .orElse(null); - return new Product(name, price, stock, promotion); // βœ… `promotionStock` 제거 + return new Product(name, price, stock, promotion); // βœ… promotionStock 포함 } private static Promotion parsePromotion(String line) { From f8fa141e9d46c803fc779579be51dc86c530e316 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Wed, 5 Mar 2025 23:58:20 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat(checkout):=20=EB=A9=A4=EB=B2=84?= =?UTF-8?q?=EC=8B=AD=ED=95=A0=EC=9D=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/store/service/CheckoutService.java | 14 ++++++++++++-- src/main/java/store/view/InputView.java | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/store/service/CheckoutService.java b/src/main/java/store/service/CheckoutService.java index 163c6a1..4784f13 100644 --- a/src/main/java/store/service/CheckoutService.java +++ b/src/main/java/store/service/CheckoutService.java @@ -11,7 +11,11 @@ import java.util.Objects; import java.util.stream.Collectors; + public class CheckoutService { + private static final double MEMBERSHIP_DISCOUNT_RATE = 0.3; + private static final int MAX_MEMBERSHIP_DISCOUNT = 8000; + public Receipt checkout(ShoppingCart cart, boolean isMembership) { Map items = cart.getItems(); @@ -35,7 +39,6 @@ public Receipt checkout(ShoppingCart cart, boolean isMembership) { Product product = entry.getKey(); int quantity = entry.getValue(); - // βœ… PromotionCalculatorμ—μ„œ ν”„λ‘œλͺ¨μ…˜ 적용 μ—¬λΆ€ νŒλ‹¨ ν›„ 적용 boolean isPromotionApplied = PromotionCalculator.applyPromotion(product, quantity); if (isPromotionApplied) { @@ -56,6 +59,13 @@ public Receipt checkout(ShoppingCart cart, boolean isMembership) { }) .sum(); - return new Receipt(purchasedItems, freeItems, totalAmount, promotionDiscount, 0, totalAmount - promotionDiscount); + int discountBase = Math.max(0, totalAmount - promotionDiscount); + int membershipDiscount = isMembership + ? Math.min((int) (discountBase * MEMBERSHIP_DISCOUNT_RATE), MAX_MEMBERSHIP_DISCOUNT) + : 0; + + int finalAmount = totalAmount - promotionDiscount - membershipDiscount; + + return new Receipt(purchasedItems, freeItems, totalAmount, promotionDiscount, membershipDiscount , finalAmount); } } \ No newline at end of file diff --git a/src/main/java/store/view/InputView.java b/src/main/java/store/view/InputView.java index 8ad6d55..7f98bff 100644 --- a/src/main/java/store/view/InputView.java +++ b/src/main/java/store/view/InputView.java @@ -13,7 +13,7 @@ public InputView(ShoppingCartService shoppingCartService) { } public ShoppingCart getUserShoppingCart() { - System.out.println("ꡬ맀할 μƒν’ˆλͺ…κ³Ό μˆ˜λŸ‰μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”. (예: [사이닀-2],[감자칩-1])"); + System.out.println("\nꡬ맀할 μƒν’ˆλͺ…κ³Ό μˆ˜λŸ‰μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”. (예: [사이닀-2],[감자칩-1])"); while (true) { try { From 13914706f100fa29c9f5a2886b1b812e7b1b186a Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Thu, 6 Mar 2025 00:49:07 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat(controller):=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EA=B5=AC=EB=A7=A4=EC=97=AC=EB=B6=80=20=EB=A3=A8=ED=94=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 20 ++++++++++++------- src/main/java/store/model/domain/Receipt.java | 6 ------ src/main/java/store/utils/FileParser.java | 7 ------- src/main/java/store/view/InputView.java | 5 +++++ 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/java/store/controller/StoreController.java b/src/main/java/store/controller/StoreController.java index 052ec05..ee6c16a 100644 --- a/src/main/java/store/controller/StoreController.java +++ b/src/main/java/store/controller/StoreController.java @@ -26,15 +26,21 @@ public StoreController(ProductService productService, PromotionService promotion } public void run() { - outputView.displayWelcomeMessage(); - productService.displayProducts(); + boolean continueShopping = true; - ShoppingCart cart = inputView.getUserShoppingCart(); - promotionService.applyPromotions(cart); + while (continueShopping) { + outputView.displayWelcomeMessage(); + productService.displayProducts(); - boolean isMembership = inputView.askMembership(); - Receipt receipt = checkoutService.checkout(cart, isMembership); + ShoppingCart cart = inputView.getUserShoppingCart(); + promotionService.applyPromotions(cart); - outputView.displayReceipt(receipt); + boolean isMembership = inputView.askMembership(); + Receipt receipt = checkoutService.checkout(cart, isMembership); + + outputView.displayReceipt(receipt); + + continueShopping = inputView.askContinueShopping(); + } } } \ No newline at end of file diff --git a/src/main/java/store/model/domain/Receipt.java b/src/main/java/store/model/domain/Receipt.java index 3dbd2e8..2998cc3 100644 --- a/src/main/java/store/model/domain/Receipt.java +++ b/src/main/java/store/model/domain/Receipt.java @@ -19,12 +19,6 @@ public Receipt(List purchasedItems, List freeItems, int this.promotionDiscount = promotionDiscount; this.membershipDiscount = membershipDiscount; this.finalAmount = finalAmount; - - // βœ… λ””λ²„κΉ…μš© 둜그 (μ΅œμ’… κ³„μ‚°λœ κ°’ 확인) - System.out.println("[DEBUG] 총 ꡬ맀앑: " + totalAmount); - System.out.println("[DEBUG] 행사 할인: " + promotionDiscount); - System.out.println("[DEBUG] 멀버십 할인: " + membershipDiscount); - System.out.println("[DEBUG] μ΅œμ’… 결제 κΈˆμ•‘: " + finalAmount); } public List getPurchasedItems() { diff --git a/src/main/java/store/utils/FileParser.java b/src/main/java/store/utils/FileParser.java index ddd1a1f..b23cba1 100644 --- a/src/main/java/store/utils/FileParser.java +++ b/src/main/java/store/utils/FileParser.java @@ -51,13 +51,8 @@ private static BufferedReader getBufferedReader(String filePath) { * βœ… `promotionStock`을 κ³ λ €ν•œ Product 객체 생성 */ private static Product parseProduct(String line, List promotions) { - System.out.println("[DEBUG] Parsing line: " + line); // βœ… 둜그 μΆ”κ°€ - String[] parts = line.split(","); - System.out.println("[DEBUG] Parsed fields: " + Arrays.toString(parts)); - - // βœ… CSV ν˜•μ‹ 체크 (μƒν’ˆλͺ…, 가격, μˆ˜λŸ‰, ν”„λ‘œλͺ¨μ…˜ 포함) if (parts.length != 4) { throw new IllegalArgumentException("[ERROR] μƒν’ˆ 데이터 ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€: " + line); } @@ -67,8 +62,6 @@ private static Product parseProduct(String line, List promotions) { int stock = Integer.parseInt(parts[2].trim()); String promotionName = parts[3].trim(); - - // βœ… 가격과 재고λ₯Ό λ³€ν™˜ν•˜κΈ° 전에 둜그 좜λ ₯ System.out.println("[DEBUG] Extracting - Name: " + name + ", Price: " + price + ", Stock: " + stock+ ", Promotion: " + promotionName); Promotion promotion = promotions.stream() diff --git a/src/main/java/store/view/InputView.java b/src/main/java/store/view/InputView.java index 7f98bff..d9098b4 100644 --- a/src/main/java/store/view/InputView.java +++ b/src/main/java/store/view/InputView.java @@ -39,4 +39,9 @@ public boolean askYesOrNo() { System.out.println("[ERROR] Y λ˜λŠ” N만 μž…λ ₯ν•΄μ£Όμ„Έμš”."); } } + + public boolean askContinueShopping() { + System.out.println("κ°μ‚¬ν•©λ‹ˆλ‹€. κ΅¬λ§€ν•˜κ³  싢은 λ‹€λ₯Έ μƒν’ˆμ΄ μžˆλ‚˜μš”? (Y/N)"); + return askYesOrNo(); + } } \ No newline at end of file