From 85f6090ddc7b1c800169a64a9da416a41bc6ae71 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Wed, 26 Apr 2023 14:58:16 +0900 Subject: [PATCH 01/58] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 제출자 수정 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0eb52bb..90986d77 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
### 자바 토이프로젝트

-> 제출자 - 최은빈 +> 제출자 - 신용호 > 출시일 - 23.04.27. > 제출일 - 23.05.10. From 347053ff75cb4697e4e795e3040ef31f4130d797 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Wed, 26 Apr 2023 14:59:08 +0900 Subject: [PATCH 02/58] Init Project --- .gitignore | 322 +++++++++++++++++++++++ build.gradle | 19 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 240 +++++++++++++++++ gradlew.bat | 91 +++++++ settings.gradle | 1 + 7 files changed, 678 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..318f7069 --- /dev/null +++ b/.gitignore @@ -0,0 +1,322 @@ +# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,java,maven,linux,windows,macos,vim,eclipse +# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,gradle,java,maven,linux,windows,macos,vim,eclipse + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +### Eclipse Patch ### +# Spring Boot Tooling +.sts4-cache/ + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +# JDT-specific (Eclipse Java Development Tools) + +### Gradle Patch ### +# Java heap dump +*.hprof + +# End of https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,java,maven,linux,windows,macos,vim,eclipse \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..7530e256 --- /dev/null +++ b/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'me.smartstore' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..f127cfd4 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..f7628a7c --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name='KDTBE5_Java_ToyProject' \ No newline at end of file From 0ae19951c1836a5c1c3e2485579b1e3ecf1aa8bb Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 1 May 2023 16:34:10 +0900 Subject: [PATCH 03/58] Add doc directory --- doc/About.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/About.md diff --git a/doc/About.md b/doc/About.md new file mode 100644 index 00000000..e69de29b From bc460857398e832f927da424ad6fc79a600edace Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 1 May 2023 16:34:39 +0900 Subject: [PATCH 04/58] Add Sequence Diagram --- doc/SmartStore.drawio.svg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/SmartStore.drawio.svg diff --git a/doc/SmartStore.drawio.svg b/doc/SmartStore.drawio.svg new file mode 100644 index 00000000..ad207d8c --- /dev/null +++ b/doc/SmartStore.drawio.svg @@ -0,0 +1,4 @@ + + + +
Lamp doesn't work
Lamp doesn't work
Yes
Yes
No
No
Lamp
plugged in?
Lamp...
Plug in lamp
Plug in lamp
No
No
Yes
Yes
Bulb
burned out?
Bulb...
Repair Lamp
Repair Lamp
Replace Bulb
Replace Bulb
Text is not SVG - cannot display
\ No newline at end of file From 45b032ec92ae7ea7f7a56f87aa55be886ee72c73 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 1 May 2023 18:50:31 +0900 Subject: [PATCH 05/58] Update View Diagram --- doc/SmartStore.drawio.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/SmartStore.drawio.svg b/doc/SmartStore.drawio.svg index ad207d8c..21765e7d 100644 --- a/doc/SmartStore.drawio.svg +++ b/doc/SmartStore.drawio.svg @@ -1,4 +1,4 @@ -
Lamp doesn't work
Lamp doesn't work
Yes
Yes
No
No
Lamp
plugged in?
Lamp...
Plug in lamp
Plug in lamp
No
No
Yes
Yes
Bulb
burned out?
Bulb...
Repair Lamp
Repair Lamp
Replace Bulb
Replace Bulb
Text is not SVG - cannot display
\ No newline at end of file +
===========================================
Title : SmartStore Customer Segmentation
Release Date : 23.04.24
Copyright 2023 YongHo All rights reserved.
===========================================
===========================================...
1
1
 ==============================
1. Parameter
2. Customer Data
3. Classification Summary
4. Quit
==============================
==============================...
1
1
==============================
1. Set Parameter
2. View Parameter
3. Update Parameter
4. Back
==============================
==============================...
==============================
1. Add Customer Data
2. View Customer Data
3. Update Customer Data
4. Delete Customer Data
5. Back
==============================
==============================...
==============================
1. Summary
2. Summary (Sorted By Name)
3. Summary (Sorted By Spent Time)
4. Summary (Sorted By Total Payment)
5. Back
==============================
==============================...
2
2
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?
** Press 'end'. if you want to exit! **
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?...
==============================
1. Minimum Spent Time
2. Minimum Total Pay
3. Back
==============================
==============================...
3
3
G
G
Input Minimum Spent Time:
** Press 'end'. if you want to exit! **
Input Minimum Spent Time:...
Input Minimum Total Pay:
** Press 'end'. if you want to exit! **
Input Minimum Total Pay:...
1
1
How many customers to input?
** Press 'end'. if you want to exit! **
How many customers to input?...
==============================
====== Customer 1 Info. ======
==============================
1. Customer Name
2. Customer ID
3. Customer Spent Time
4. Customer Total Pay
5. Back
==============================
==============================...
Input Customer's Name:
** Press 'end'. if you want to exit! **
Input Customer's Name:...
==============================
Group : NONE ( Time : null, Pay : null )
==============================
Null.

==============================
Group : GENERAL ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VIP ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VVIP ( Time : null, Pay : null )
==============================
Null.
==============================...
==============================
Group : GENERAL ( Time : 10, Pay : 1000000 )
==============================
No. 1 => Customer{userId='TEST1', name='TEST', spentTime=11, totalPay=10000000, group=GroupType: GENERAL
Parameter: Parameter{minimumSpentTime=10, minimumTotalPay=1000000}}
==============================...
Text is not SVG - cannot display
\ No newline at end of file From 10a5adce20d7b4b1d87f6ea89f768bc9642816eb Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 1 May 2023 18:58:00 +0900 Subject: [PATCH 06/58] =?UTF-8?q?SmartStore.drawio.svg=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/SmartStore.drawio.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/SmartStore.drawio.svg b/doc/SmartStore.drawio.svg index 21765e7d..b2b507a2 100644 --- a/doc/SmartStore.drawio.svg +++ b/doc/SmartStore.drawio.svg @@ -1,4 +1,4 @@ -
===========================================
Title : SmartStore Customer Segmentation
Release Date : 23.04.24
Copyright 2023 YongHo All rights reserved.
===========================================
===========================================...
1
1
 ==============================
1. Parameter
2. Customer Data
3. Classification Summary
4. Quit
==============================
==============================...
1
1
==============================
1. Set Parameter
2. View Parameter
3. Update Parameter
4. Back
==============================
==============================...
==============================
1. Add Customer Data
2. View Customer Data
3. Update Customer Data
4. Delete Customer Data
5. Back
==============================
==============================...
==============================
1. Summary
2. Summary (Sorted By Name)
3. Summary (Sorted By Spent Time)
4. Summary (Sorted By Total Payment)
5. Back
==============================
==============================...
2
2
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?
** Press 'end'. if you want to exit! **
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?...
==============================
1. Minimum Spent Time
2. Minimum Total Pay
3. Back
==============================
==============================...
3
3
G
G
Input Minimum Spent Time:
** Press 'end'. if you want to exit! **
Input Minimum Spent Time:...
Input Minimum Total Pay:
** Press 'end'. if you want to exit! **
Input Minimum Total Pay:...
1
1
How many customers to input?
** Press 'end'. if you want to exit! **
How many customers to input?...
==============================
====== Customer 1 Info. ======
==============================
1. Customer Name
2. Customer ID
3. Customer Spent Time
4. Customer Total Pay
5. Back
==============================
==============================...
Input Customer's Name:
** Press 'end'. if you want to exit! **
Input Customer's Name:...
==============================
Group : NONE ( Time : null, Pay : null )
==============================
Null.

==============================
Group : GENERAL ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VIP ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VVIP ( Time : null, Pay : null )
==============================
Null.
==============================...
==============================
Group : GENERAL ( Time : 10, Pay : 1000000 )
==============================
No. 1 => Customer{userId='TEST1', name='TEST', spentTime=11, totalPay=10000000, group=GroupType: GENERAL
Parameter: Parameter{minimumSpentTime=10, minimumTotalPay=1000000}}
==============================...
Text is not SVG - cannot display
\ No newline at end of file +
===========================================
Title : SmartStore Customer Segmentation
Release Date : 23.04.24
Copyright 2023 YongHo All rights reserved.
===========================================
===========================================...
1
1
 ==============================
1. Parameter
2. Customer Data
3. Classification Summary
4. Quit
==============================
==============================...
1
1
==============================
1. Set Parameter
2. View Parameter
3. Update Parameter
4. Back
==============================
==============================...
==============================
1. Add Customer Data
2. View Customer Data
3. Update Customer Data
4. Delete Customer Data
5. Back
==============================
==============================...
1
1
==============================
1. Summary
2. Summary (Sorted By Name)
3. Summary (Sorted By Spent Time)
4. Summary (Sorted By Total Payment)
5. Back
==============================
==============================...
2
2
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?
** Press 'end'. if you want to exit! **
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?...
==============================
1. Minimum Spent Time
2. Minimum Total Pay
3. Back
==============================
==============================...
3
3
G
G
Input Minimum Spent Time:
** Press 'end'. if you want to exit! **
Input Minimum Spent Time:...
Input Minimum Total Pay:
** Press 'end'. if you want to exit! **
Input Minimum Total Pay:...
1
1
How many customers to input?
** Press 'end'. if you want to exit! **
How many customers to input?...
==============================
====== Customer 1 Info. ======
==============================
1. Customer Name
2. Customer ID
3. Customer Spent Time
4. Customer Total Pay
5. Back
==============================
==============================...
Input Customer's Name:
** Press 'end'. if you want to exit! **
Input Customer's Name:...
==============================
Group : NONE ( Time : null, Pay : null )
==============================
Null.

==============================
Group : GENERAL ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VIP ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VVIP ( Time : null, Pay : null )
==============================
Null.
==============================...
==============================
Group : GENERAL ( Time : 10, Pay : 1000000 )
==============================
No. 1 => Customer{userId='TEST1', name='TEST', spentTime=11, totalPay=10000000, group=GroupType: GENERAL
Parameter: Parameter{minimumSpentTime=10, minimumTotalPay=1000000}}
==============================...
Text is not SVG - cannot display
\ No newline at end of file From b0ca56e71dd0a3d5903e1a4d63021c99dbb6b1d9 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 1 May 2023 18:58:23 +0900 Subject: [PATCH 07/58] =?UTF-8?q?SmartStore.drawio.svg=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/SmartStore.drawio.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/SmartStore.drawio.svg b/doc/SmartStore.drawio.svg index b2b507a2..35fbd29c 100644 --- a/doc/SmartStore.drawio.svg +++ b/doc/SmartStore.drawio.svg @@ -1,4 +1,4 @@ -
===========================================
Title : SmartStore Customer Segmentation
Release Date : 23.04.24
Copyright 2023 YongHo All rights reserved.
===========================================
===========================================...
1
1
 ==============================
1. Parameter
2. Customer Data
3. Classification Summary
4. Quit
==============================
==============================...
1
1
==============================
1. Set Parameter
2. View Parameter
3. Update Parameter
4. Back
==============================
==============================...
==============================
1. Add Customer Data
2. View Customer Data
3. Update Customer Data
4. Delete Customer Data
5. Back
==============================
==============================...
1
1
==============================
1. Summary
2. Summary (Sorted By Name)
3. Summary (Sorted By Spent Time)
4. Summary (Sorted By Total Payment)
5. Back
==============================
==============================...
2
2
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?
** Press 'end'. if you want to exit! **
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?...
==============================
1. Minimum Spent Time
2. Minimum Total Pay
3. Back
==============================
==============================...
3
3
G
G
Input Minimum Spent Time:
** Press 'end'. if you want to exit! **
Input Minimum Spent Time:...
Input Minimum Total Pay:
** Press 'end'. if you want to exit! **
Input Minimum Total Pay:...
1
1
How many customers to input?
** Press 'end'. if you want to exit! **
How many customers to input?...
==============================
====== Customer 1 Info. ======
==============================
1. Customer Name
2. Customer ID
3. Customer Spent Time
4. Customer Total Pay
5. Back
==============================
==============================...
Input Customer's Name:
** Press 'end'. if you want to exit! **
Input Customer's Name:...
==============================
Group : NONE ( Time : null, Pay : null )
==============================
Null.

==============================
Group : GENERAL ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VIP ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VVIP ( Time : null, Pay : null )
==============================
Null.
==============================...
==============================
Group : GENERAL ( Time : 10, Pay : 1000000 )
==============================
No. 1 => Customer{userId='TEST1', name='TEST', spentTime=11, totalPay=10000000, group=GroupType: GENERAL
Parameter: Parameter{minimumSpentTime=10, minimumTotalPay=1000000}}
==============================...
Text is not SVG - cannot display
\ No newline at end of file +
===========================================
Title : SmartStore Customer Segmentation
Release Date : 23.04.24
Copyright 2023 YongHo All rights reserved.
===========================================
===========================================...
1
1
 ==============================
1. Parameter
2. Customer Data
3. Classification Summary
4. Quit
==============================
==============================...
1
1
==============================
1. Set Parameter
2. View Parameter
3. Update Parameter
4. Back
==============================
==============================...
==============================
1. Add Customer Data
2. View Customer Data
3. Update Customer Data
4. Delete Customer Data
5. Back
==============================
==============================...
1
1
==============================
1. Summary
2. Summary (Sorted By Name)
3. Summary (Sorted By Spent Time)
4. Summary (Sorted By Total Payment)
5. Back
==============================
==============================...
2
2
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?
** Press 'end'. if you want to exit! **
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?...
==============================
1. Minimum Spent Time
2. Minimum Total Pay
3. Back
==============================
==============================...
3
3
G
G
Input Minimum Spent Time:
** Press 'end'. if you want to exit! **
Input Minimum Spent Time:...
Input Minimum Total Pay:
** Press 'end'. if you want to exit! **
Input Minimum Total Pay:...
1
1
How many customers to input?
** Press 'end'. if you want to exit! **
How many customers to input?...
==============================
====== Customer 1 Info. ======
==============================
1. Customer Name
2. Customer ID
3. Customer Spent Time
4. Customer Total Pay
5. Back
==============================
==============================...
Input Customer's Name:
** Press 'end'. if you want to exit! **
Input Customer's Name:...
==============================
Group : NONE ( Time : null, Pay : null )
==============================
Null.

==============================
Group : GENERAL ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VIP ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VVIP ( Time : null, Pay : null )
==============================
Null.
==============================...
==============================
Group : GENERAL ( Time : 10, Pay : 1000000 )
==============================
No. 1 => Customer{userId='TEST1', name='TEST', spentTime=11, totalPay=10000000, group=GroupType: GENERAL
Parameter: Parameter{minimumSpentTime=10, minimumTotalPay=1000000}}
==============================...
Text is not SVG - cannot display
\ No newline at end of file From 29ee2ba98d0d0d4526bfa32c2c242270e5b38d58 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Tue, 9 May 2023 14:48:00 +0900 Subject: [PATCH 08/58] feat: add SmartStoreApplication, AppStarter, Menu views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 스토어 실행 후 메뉴 출력 부분 구현. --- .../me/smartstore/SmartStoreApplication.java | 9 ++++ .../java/me/smartstore/core/AppStarter.java | 35 +++++++++++++ .../me/smartstore/menus/AbstractMenu.java | 51 +++++++++++++++++++ .../menus/ClassificationSummaryMenu.java | 40 +++++++++++++++ .../me/smartstore/menus/CustomerDataMenu.java | 39 ++++++++++++++ .../java/me/smartstore/menus/MainMenu.java | 30 +++++++++++ .../me/smartstore/menus/ParameterMenu.java | 31 +++++++++++ 7 files changed, 235 insertions(+) create mode 100644 src/main/java/me/smartstore/SmartStoreApplication.java create mode 100644 src/main/java/me/smartstore/core/AppStarter.java create mode 100644 src/main/java/me/smartstore/menus/AbstractMenu.java create mode 100644 src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java create mode 100644 src/main/java/me/smartstore/menus/CustomerDataMenu.java create mode 100644 src/main/java/me/smartstore/menus/MainMenu.java create mode 100644 src/main/java/me/smartstore/menus/ParameterMenu.java diff --git a/src/main/java/me/smartstore/SmartStoreApplication.java b/src/main/java/me/smartstore/SmartStoreApplication.java new file mode 100644 index 00000000..3814c60b --- /dev/null +++ b/src/main/java/me/smartstore/SmartStoreApplication.java @@ -0,0 +1,9 @@ +package me.smartstore; + +import me.smartstore.core.AppStarter; + +public class SmartStoreApplication { + public static void main(String[] args) { + AppStarter.getInstance().run(); + } +} diff --git a/src/main/java/me/smartstore/core/AppStarter.java b/src/main/java/me/smartstore/core/AppStarter.java new file mode 100644 index 00000000..b1ce35d7 --- /dev/null +++ b/src/main/java/me/smartstore/core/AppStarter.java @@ -0,0 +1,35 @@ +package me.smartstore.core; + +import me.smartstore.menus.MainMenu; + +public class AppStarter { + private AppStarter() {} + + private static class StoreAppContext { + private static final AppStarter app = new AppStarter(); + } + + public static AppStarter getInstance() { + return StoreAppContext.app; + } + + public void run() { + title(); + MainMenu.launch(); + finish(); + } + + private void title() { + System.out.println( + """ + =========================================== + Title : SmartStore Customer Segmentation + Release Date : 23.04.24 + Copyright 2023 YongHo All rights reserved. + =========================================== + """); + } + private void finish() { + System.out.println("Program Finished."); + } +} diff --git a/src/main/java/me/smartstore/menus/AbstractMenu.java b/src/main/java/me/smartstore/menus/AbstractMenu.java new file mode 100644 index 00000000..8a0cbe18 --- /dev/null +++ b/src/main/java/me/smartstore/menus/AbstractMenu.java @@ -0,0 +1,51 @@ +package me.smartstore.menus; + +import me.smartstore.exceptions.ErrorCode; +import me.smartstore.exceptions.StoreException; +import me.smartstore.utils.ScannerHolder; + +/** + * 메뉴 출력 및 선택 + * + * @author YongHo Shin + * @version v0.1 + * @since 2023-04-28 + */ +public abstract class AbstractMenu { + private final String[] items; + + public AbstractMenu(String[] items) { + this.items = items; + } + + /** 메뉴 출력 */ + void show() { + StringBuilder menuBuilder = new StringBuilder().append("==============================\n"); + for (int i = 0; i < items.length; i++) { + menuBuilder.append(i + 1).append(". ").append(items[i]).append("\n"); + } + menuBuilder.append("=============================="); + System.out.println(menuBuilder); + } + + /** + * 메뉴 입력 + * + * @return 입력된 메뉴 번호 + */ + int selectMenuNumber() { + System.out.print("Choose One: "); + + int selection = ScannerHolder.getIntegerInputSafely(); + + if (selection == -1) { + throw new StoreException(ErrorCode.INVALID_FORMAT); + } + + if (selection < 1 || selection > items.length) { + throw new StoreException(ErrorCode.INVALID_INPUT); + } + + return selection; + } +} diff --git a/src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java b/src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java new file mode 100644 index 00000000..f2cc154e --- /dev/null +++ b/src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java @@ -0,0 +1,40 @@ +package me.smartstore.menus; + +import me.smartstore.core.CustomerManager; +import me.smartstore.exceptions.StoreException; + +public class ClassificationSummaryMenu extends AbstractMenu { + private static final ClassificationSummaryMenu classificationSummaryMenu = + new ClassificationSummaryMenu(); + + private ClassificationSummaryMenu() { + super( + new String[] { + "Summary", + "Summary (Sorted By Name)", + "Summary (Sorted By Spent Time)", + "Summary (Sorted By Total Pay Amount)", + "Back" + }); + } + + public static void launch() { + loop: + while (true) { + classificationSummaryMenu.show(); + try { + switch (classificationSummaryMenu.selectMenuNumber()) { + case 1 -> CustomerManager.printSummary(); + case 2 -> CustomerManager.printSummary(OrderBy.NAME); + case 3 -> CustomerManager.printSummary(OrderBy.SPENT_TIME); + case 4 -> CustomerManager.printSummary(OrderBy.TOTAL_PAY_AMOUNT); + case 5 -> { + break loop; + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/src/main/java/me/smartstore/menus/CustomerDataMenu.java b/src/main/java/me/smartstore/menus/CustomerDataMenu.java new file mode 100644 index 00000000..a794290c --- /dev/null +++ b/src/main/java/me/smartstore/menus/CustomerDataMenu.java @@ -0,0 +1,39 @@ +package me.smartstore.menus; + +import me.smartstore.core.CustomerManager; +import me.smartstore.exceptions.StoreException; + +public class CustomerDataMenu extends AbstractMenu { + private static final CustomerDataMenu customerDataMenu = new CustomerDataMenu(); + + private CustomerDataMenu() { + super( + new String[] { + "Add Customer Data", + "View Customer Data", + "Update Customer Data", + "Delete Customer Data", + "Back" + }); + } + + public static void launch() { + loop: + while (true) { + customerDataMenu.show(); + try { + switch (customerDataMenu.selectMenuNumber()) { + case 1 -> CustomerManager.launchCustomerManager(1); + case 2 -> CustomerManager.launchCustomerManager(2); + case 3 -> CustomerManager.launchCustomerManager(3); + case 4 -> CustomerManager.launchCustomerManager(4); + case 5 -> { + break loop; + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/src/main/java/me/smartstore/menus/MainMenu.java b/src/main/java/me/smartstore/menus/MainMenu.java new file mode 100644 index 00000000..ff827eae --- /dev/null +++ b/src/main/java/me/smartstore/menus/MainMenu.java @@ -0,0 +1,30 @@ +package me.smartstore.menus; + +import me.smartstore.exceptions.StoreException; + +public class MainMenu extends AbstractMenu { + private static final MainMenu mainMenu = new MainMenu(); + + private MainMenu() { + super(new String[] {"Parameter", "Customer Data", "Classification Summary", "Quit"}); + } + + public static void launch() { + loop: + while (true) { + mainMenu.show(); + try { + switch (mainMenu.selectMenuNumber()) { + case 1 -> ParameterMenu.launch(); + case 2 -> CustomerDataMenu.launch(); + case 3 -> ClassificationSummaryMenu.launch(); + case 4 -> { + break loop; + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/src/main/java/me/smartstore/menus/ParameterMenu.java b/src/main/java/me/smartstore/menus/ParameterMenu.java new file mode 100644 index 00000000..fce33120 --- /dev/null +++ b/src/main/java/me/smartstore/menus/ParameterMenu.java @@ -0,0 +1,31 @@ +package me.smartstore.menus; + +import me.smartstore.core.GroupManager; +import me.smartstore.exceptions.StoreException; + +public class ParameterMenu extends AbstractMenu { + private static final ParameterMenu parameterMenu = new ParameterMenu(); + + private ParameterMenu() { + super(new String[] {"Set Parameter", "View Parameter", "Update Parameter", "Back"}); + } + + public static void launch() { + loop: + while (true) { + parameterMenu.show(); + try { + switch (parameterMenu.selectMenuNumber()) { + case 1 -> GroupManager.launchGroupManager(1); + case 2 -> GroupManager.launchGroupManager(2); + case 3 -> GroupManager.launchGroupManager(3); + case 4 -> { + break loop; + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } +} From f839445e5f2771c57005321ea633371d933ef5b0 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Tue, 9 May 2023 14:49:25 +0900 Subject: [PATCH 09/58] =?UTF-8?q?feat:=20add=20ScannerHolder=20-=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../me/smartstore/utils/ScannerHolder.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/me/smartstore/utils/ScannerHolder.java diff --git a/src/main/java/me/smartstore/utils/ScannerHolder.java b/src/main/java/me/smartstore/utils/ScannerHolder.java new file mode 100644 index 00000000..8f151e2a --- /dev/null +++ b/src/main/java/me/smartstore/utils/ScannerHolder.java @@ -0,0 +1,26 @@ +package me.smartstore.utils; + +import me.smartstore.exceptions.ErrorCode; +import me.smartstore.exceptions.StoreException; + +import java.util.NoSuchElementException; +import java.util.Scanner; + +public class ScannerHolder { + private static final Scanner scanner = new Scanner(System.in); + + public static int getIntegerInputSafely() { + String converted = scanner.nextLine().replaceAll("[^0-9]", ""); + return "".equals(converted) ? -1 : Integer.parseInt(converted); + } + + public static String getInput() { + try { + return scanner.nextLine(); + } catch (NoSuchElementException e) { + throw new StoreException(ErrorCode.EMPTY_INPUT); + } catch (IllegalStateException e) { + throw new StoreException(ErrorCode.INTERNAL_ERROR); + } + } +} From b9b8ac5eae3236d9d2e3d65f1b3d0d1d27e5b34e Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Tue, 9 May 2023 14:56:04 +0900 Subject: [PATCH 10/58] =?UTF-8?q?feat:=20add=20StoreErrorCode,=20StoreExce?= =?UTF-8?q?ption=20-=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../smartstore/exceptions/StoreErrorCode.java | 27 ++++++++++++++ .../smartstore/exceptions/StoreException.java | 35 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/main/java/me/smartstore/exceptions/StoreErrorCode.java create mode 100644 src/main/java/me/smartstore/exceptions/StoreException.java diff --git a/src/main/java/me/smartstore/exceptions/StoreErrorCode.java b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java new file mode 100644 index 00000000..ec527778 --- /dev/null +++ b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java @@ -0,0 +1,27 @@ +package me.smartstore.exceptions; + +public enum StoreErrorCode { + NO_GROUP("No matching group."), + NO_CUSTOMER("No Customers. Please input one first."), + CANT_SORT("Elements in Array has null. Array can't be sorted."), + NULL_INPUT("Null Input. Please input something."), + EMPTY_INPUT("Empty Input. Please input something."), + INVALID_INPUT("Invalid Input. Please try again."), + INVALID_TYPE("Invalid Type for Input. Please try again."), + INVALID_FORMAT("Invalid Format for Input. Please try again."), + GROUP_ALREADY_SET("group already exists."), + NO_PARAMETER("No parameter. Set the parameter first"), + INTERNAL_ERROR("Internal Error."), + INPUT_END("END is pressed. Exit this menu."), + END_MSG("END"); + + private final String message; + + StoreErrorCode(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/me/smartstore/exceptions/StoreException.java b/src/main/java/me/smartstore/exceptions/StoreException.java new file mode 100644 index 00000000..225a7d0f --- /dev/null +++ b/src/main/java/me/smartstore/exceptions/StoreException.java @@ -0,0 +1,35 @@ +package me.smartstore.exceptions; + +public class StoreException extends RuntimeException { + private final StoreErrorCode storeErrorCode; + private final String message; + + public StoreException() { + this.storeErrorCode = StoreErrorCode.INTERNAL_ERROR; + this.message = StoreErrorCode.INTERNAL_ERROR.getMessage(); + } + + public StoreException(StoreErrorCode storeErrorCode) { + this.storeErrorCode = storeErrorCode; + this.message = storeErrorCode.getMessage(); + } + + public StoreException(StoreErrorCode storeErrorCode, String message) { + this.storeErrorCode = storeErrorCode; + this.message = message; + } + + public StoreException(String message) { + this.storeErrorCode = StoreErrorCode.INTERNAL_ERROR; + this.message = message; + } + + public StoreErrorCode getErrorCode() { + return storeErrorCode; + } + + @Override + public String getMessage() { + return message; + } +} From 60d14934fc0ed443a10f2f88b3dabf764c1ac4ba Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Tue, 9 May 2023 14:57:15 +0900 Subject: [PATCH 11/58] =?UTF-8?q?update:=20AbstractMenu,=20ScannerHolder?= =?UTF-8?q?=20-=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20static=20import?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/me/smartstore/menus/AbstractMenu.java | 8 +++++--- src/main/java/me/smartstore/utils/ScannerHolder.java | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/me/smartstore/menus/AbstractMenu.java b/src/main/java/me/smartstore/menus/AbstractMenu.java index 8a0cbe18..8327b331 100644 --- a/src/main/java/me/smartstore/menus/AbstractMenu.java +++ b/src/main/java/me/smartstore/menus/AbstractMenu.java @@ -1,6 +1,8 @@ package me.smartstore.menus; -import me.smartstore.exceptions.ErrorCode; +import static me.smartstore.exceptions.StoreErrorCode.INVALID_FORMAT; +import static me.smartstore.exceptions.StoreErrorCode.INVALID_INPUT; + import me.smartstore.exceptions.StoreException; import me.smartstore.utils.ScannerHolder; @@ -39,11 +41,11 @@ int selectMenuNumber() { int selection = ScannerHolder.getIntegerInputSafely(); if (selection == -1) { - throw new StoreException(ErrorCode.INVALID_FORMAT); + throw new StoreException(INVALID_FORMAT); } if (selection < 1 || selection > items.length) { - throw new StoreException(ErrorCode.INVALID_INPUT); + throw new StoreException(INVALID_INPUT); } return selection; diff --git a/src/main/java/me/smartstore/utils/ScannerHolder.java b/src/main/java/me/smartstore/utils/ScannerHolder.java index 8f151e2a..7ab39500 100644 --- a/src/main/java/me/smartstore/utils/ScannerHolder.java +++ b/src/main/java/me/smartstore/utils/ScannerHolder.java @@ -1,11 +1,13 @@ package me.smartstore.utils; -import me.smartstore.exceptions.ErrorCode; import me.smartstore.exceptions.StoreException; import java.util.NoSuchElementException; import java.util.Scanner; +import static me.smartstore.exceptions.StoreErrorCode.EMPTY_INPUT; +import static me.smartstore.exceptions.StoreErrorCode.INTERNAL_ERROR; + public class ScannerHolder { private static final Scanner scanner = new Scanner(System.in); @@ -18,9 +20,9 @@ public static String getInput() { try { return scanner.nextLine(); } catch (NoSuchElementException e) { - throw new StoreException(ErrorCode.EMPTY_INPUT); + throw new StoreException(EMPTY_INPUT); } catch (IllegalStateException e) { - throw new StoreException(ErrorCode.INTERNAL_ERROR); + throw new StoreException(INTERNAL_ERROR); } } } From a51d8bb9dd64e667ab4462a03b4bb3951507519a Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Tue, 9 May 2023 15:52:56 +0900 Subject: [PATCH 12/58] feat: implement Group Parameter Management --- .../java/me/smartstore/core/GroupManager.java | 92 +++++++++++++++++++ src/main/java/me/smartstore/domain/Group.java | 43 +++++++++ .../java/me/smartstore/domain/Parameter.java | 42 +++++++++ .../java/me/smartstore/enums/GroupType.java | 25 +++++ .../me/smartstore/menus/ParameterSubMenu.java | 69 ++++++++++++++ 5 files changed, 271 insertions(+) create mode 100644 src/main/java/me/smartstore/core/GroupManager.java create mode 100644 src/main/java/me/smartstore/domain/Group.java create mode 100644 src/main/java/me/smartstore/domain/Parameter.java create mode 100644 src/main/java/me/smartstore/enums/GroupType.java create mode 100644 src/main/java/me/smartstore/menus/ParameterSubMenu.java diff --git a/src/main/java/me/smartstore/core/GroupManager.java b/src/main/java/me/smartstore/core/GroupManager.java new file mode 100644 index 00000000..4d4048ca --- /dev/null +++ b/src/main/java/me/smartstore/core/GroupManager.java @@ -0,0 +1,92 @@ +package me.smartstore.core; + +import static me.smartstore.exceptions.StoreErrorCode.*; +import static me.smartstore.enums.GroupType.*; +import static me.smartstore.menus.ParameterSubMenu.inputParameter; + +import java.util.Arrays; + +import me.smartstore.domain.Group; +import me.smartstore.domain.Parameter; +import me.smartstore.exceptions.StoreErrorCode; +import me.smartstore.exceptions.StoreException; +import me.smartstore.enums.GroupType; +import me.smartstore.utils.ScannerHolder; + +public class GroupManager { + private static final Group[] groups = + new Group[] { + new Group(NONE, null), new Group(GENERAL, null), new Group(VIP, null), new Group(VVIP, null) + }; + + public static void launchGroupManager(int menuNumber) { + while (true) { + printGroupTypeInputMessage(); + + try { + String input = ScannerHolder.getInput().toUpperCase(); + + if ("end".equalsIgnoreCase(input)) return; + if ("".equals(input)) throw new StoreException(NULL_INPUT); + + GroupType targetType = convertInputStrToGroupType(input); + switch (menuNumber) { + case 1 -> setGroupParameter(targetType); + case 2 -> viewGroupParameter(targetType); + case 3 -> updateGroupParameter(targetType); + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } + + private static void printGroupTypeInputMessage() { + System.out.println( + "Which group (GENERAL (G), VIP (V), VVIP (VV))?\n" + + "** Press 'end', if you want to exit! **"); + } + + private static GroupType convertInputStrToGroupType(String input) throws StoreException { + try { + return GroupType.valueOf(input).replaceFullName(); + } catch (IllegalArgumentException e) { + throw new StoreException(StoreErrorCode.INVALID_INPUT); + } + } + + private static void setGroupParameter(GroupType targetType) throws StoreException { + Group targetGroup = getTargetGroup(targetType); + + if (targetGroup.getParameter() != null) { + throw new StoreException(GROUP_ALREADY_SET); + } + + Parameter newParameter = inputParameter(); + targetGroup.setParameter(newParameter); + } + + private static void updateGroupParameter(GroupType targetType) throws StoreException { + Group targetGroup = getTargetGroup(targetType); + + if (targetGroup.getParameter() == null) { + throw new StoreException(NO_PARAMETER); + } + + Parameter inputParams = inputParameter(); + + targetGroup.setParameter(inputParams); + } + + private static void viewGroupParameter(GroupType targetType) throws StoreException { + Group targetGroup = getTargetGroup(targetType); + System.out.println(targetGroup); + } + + private static Group getTargetGroup(GroupType targetType) throws StoreException { + return Arrays.stream(groups) + .filter(group -> group.getGroupType() == targetType) + .findFirst() + .orElseThrow(() -> new StoreException(NO_GROUP)); + } +} diff --git a/src/main/java/me/smartstore/domain/Group.java b/src/main/java/me/smartstore/domain/Group.java new file mode 100644 index 00000000..bb0074fc --- /dev/null +++ b/src/main/java/me/smartstore/domain/Group.java @@ -0,0 +1,43 @@ +package me.smartstore.domain; + +import me.smartstore.enums.GroupType; + +public class Group { + private GroupType groupType; + private Parameter parameter; + + public Group(GroupType groupType, Parameter parameter) { + this.groupType = groupType; + this.parameter = parameter; + } + + public GroupType getGroupType() { + return groupType; + } + + public void setGroupType(GroupType groupType) { + this.groupType = groupType; + } + + public Parameter getParameter() { + return parameter; + } + + public void setParameter(Parameter parameter) { + if (this.parameter == null) { + this.parameter = parameter; + } else { + if (parameter.getMinSpentTime() != null) { + this.parameter.setMinSpentTime(parameter.getMinSpentTime()); + } + if (parameter.getMinPayAmount() != null) { + this.parameter.setMinPayAmount(parameter.getMinPayAmount()); + } + } + } + + @Override + public String toString() { + return "GroupType:" + groupType + "\nParameter: " + parameter.toString(); + } +} diff --git a/src/main/java/me/smartstore/domain/Parameter.java b/src/main/java/me/smartstore/domain/Parameter.java new file mode 100644 index 00000000..1f018a6d --- /dev/null +++ b/src/main/java/me/smartstore/domain/Parameter.java @@ -0,0 +1,42 @@ +package me.smartstore.domain; + +public class Parameter { + private Integer minSpentTime; + private Integer minPayAmount; + + public Parameter() { + this.minSpentTime = null; + this.minPayAmount = null; + } + + public Parameter(Integer minSpentTime, Integer minPayAmount) { + this.minSpentTime = minSpentTime; + this.minPayAmount = minPayAmount; + } + + public Integer getMinSpentTime() { + return minSpentTime; + } + + public void setMinSpentTime(Integer minSpentTime) { + this.minSpentTime = minSpentTime; + } + + public Integer getMinPayAmount() { + return minPayAmount; + } + + public void setMinPayAmount(Integer minPayAmount) { + this.minPayAmount = minPayAmount; + } + + @Override + public String toString() { + return "Parameter{" + + "minimumSpentTime=" + + minSpentTime + + ", minimumTotalPayment=" + + minPayAmount + + '}'; + } +} diff --git a/src/main/java/me/smartstore/enums/GroupType.java b/src/main/java/me/smartstore/enums/GroupType.java new file mode 100644 index 00000000..b255f49a --- /dev/null +++ b/src/main/java/me/smartstore/enums/GroupType.java @@ -0,0 +1,25 @@ +package me.smartstore.enums; + +public enum GroupType { + NONE("해당없음"), + GENERAL("일반고객"), + VIP("우수고객"), + VVIP("최우수고객"), + N("해당없음"), + G("일반고객"), + V("우수고객"), + VV("최우수고객"); + + private final String description; + + GroupType(String description) { + this.description = description; + } + + public GroupType replaceFullName() { + if (this == G) return GENERAL; + else if (this == V) return VIP; + else if (this == VV) return VVIP; + else return this; + } +} diff --git a/src/main/java/me/smartstore/menus/ParameterSubMenu.java b/src/main/java/me/smartstore/menus/ParameterSubMenu.java new file mode 100644 index 00000000..f8125626 --- /dev/null +++ b/src/main/java/me/smartstore/menus/ParameterSubMenu.java @@ -0,0 +1,69 @@ +package me.smartstore.menus; + +import me.smartstore.domain.Parameter; +import me.smartstore.exceptions.StoreErrorCode; +import me.smartstore.exceptions.StoreException; +import me.smartstore.utils.ScannerHolder; + +public class ParameterSubMenu extends AbstractMenu { + private static final ParameterSubMenu parameterSubMenu = new ParameterSubMenu(); + + private ParameterSubMenu() { + super(new String[] {"Minimum Spent Time", "Minimum Total PayAmount", "Back"}); + } + + public static Parameter inputParameter() { + Integer minimumSpentTime = null; + Integer minimumTotalPayAmount = null; + + while (true) { + parameterSubMenu.show(); + + try { + switch (parameterSubMenu.selectMenuNumber()) { + case 1 -> { + while (true) { + System.out.println( + "Input Minimum Spent Time.\n** Press 'end', if you want to exit! **"); + + String input = ScannerHolder.getInput(); + + if ("end".equals(input)) break; + + try { + minimumSpentTime = Integer.parseInt(input); + break; + } catch (NumberFormatException e) { + System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); + } + } + } + + case 2 -> { + while (true) { + System.out.println( + "Input Minimum Total Pay Amount.\n** Press 'end', if you want to exit! **"); + + String input = ScannerHolder.getInput(); + + if ("end".equals(input)) break; + + try { + minimumTotalPayAmount = Integer.parseInt(input); + break; + } catch (NumberFormatException e) { + System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); + } + } + } + + case 3 -> { + return new Parameter(minimumSpentTime, minimumTotalPayAmount); + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } +} From 256a74d0581519af0a177e0c06343426c3d00f46 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Wed, 10 May 2023 23:57:15 +0900 Subject: [PATCH 13/58] =?UTF-8?q?update:=20=EC=A4=91=EA=B0=84=20=EC=A0=9C?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/SmartStore.drawio.svg | 2 +- .../me/smartstore/{core => }/AppStarter.java | 4 +- .../me/smartstore/SmartStoreApplication.java | 2 - .../java/me/smartstore/core/GroupManager.java | 92 ---------- .../me/smartstore/core/domain/Customer.java | 129 +++++++++++++ .../smartstore/core/domain/CustomerDTO.java | 50 +++++ .../smartstore/core/domain/CustomerGroup.java | 71 +++++++ .../{ => core}/domain/Parameter.java | 2 +- .../core/manager/CustomerGroupManager.java | 85 +++++++++ .../core/manager/CustomerManager.java | 146 +++++++++++++++ .../core/service/CustomerGroupService.java | 42 +++++ .../core/service/CustomerService.java | 173 ++++++++++++++++++ .../{menus => core/view}/AbstractMenu.java | 11 +- .../core/view/ClassificationSummaryMenu.java | 63 +++++++ .../me/smartstore/core/view/CustomerMenu.java | 123 +++++++++++++ .../smartstore/core/view/CustomerSubMenu.java | 93 ++++++++++ .../{menus => core/view}/MainMenu.java | 4 +- .../{menus => core/view}/ParameterMenu.java | 9 +- .../core/view/ParameterSubMenu.java | 122 ++++++++++++ src/main/java/me/smartstore/domain/Group.java | 43 ----- .../{GroupType.java => CustomerType.java} | 13 +- .../smartstore/enums/SmartStoreMessage.java | 37 ++++ src/main/java/me/smartstore/enums/SortBy.java | 26 +++ .../java/me/smartstore/enums/SortOrder.java | 25 +++ .../smartstore/exceptions/StoreErrorCode.java | 7 + .../smartstore/exceptions/StoreException.java | 5 + .../menus/ClassificationSummaryMenu.java | 40 ---- .../me/smartstore/menus/CustomerDataMenu.java | 39 ---- .../me/smartstore/menus/ParameterSubMenu.java | 69 ------- ...ScannerHolder.java => ScannerUtility.java} | 14 +- .../me/smartstore/utils/StoreUtility.java | 32 ++++ .../core/CustomerGroupManagerTest.java | 9 + .../smartstore/core/CustomerManagerTest.java | 80 ++++++++ 33 files changed, 1356 insertions(+), 306 deletions(-) rename src/main/java/me/smartstore/{core => }/AppStarter.java (91%) delete mode 100644 src/main/java/me/smartstore/core/GroupManager.java create mode 100644 src/main/java/me/smartstore/core/domain/Customer.java create mode 100644 src/main/java/me/smartstore/core/domain/CustomerDTO.java create mode 100644 src/main/java/me/smartstore/core/domain/CustomerGroup.java rename src/main/java/me/smartstore/{ => core}/domain/Parameter.java (96%) create mode 100644 src/main/java/me/smartstore/core/manager/CustomerGroupManager.java create mode 100644 src/main/java/me/smartstore/core/manager/CustomerManager.java create mode 100644 src/main/java/me/smartstore/core/service/CustomerGroupService.java create mode 100644 src/main/java/me/smartstore/core/service/CustomerService.java rename src/main/java/me/smartstore/{menus => core/view}/AbstractMenu.java (84%) create mode 100644 src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java create mode 100644 src/main/java/me/smartstore/core/view/CustomerMenu.java create mode 100644 src/main/java/me/smartstore/core/view/CustomerSubMenu.java rename src/main/java/me/smartstore/{menus => core/view}/MainMenu.java (89%) rename src/main/java/me/smartstore/{menus => core/view}/ParameterMenu.java (72%) create mode 100644 src/main/java/me/smartstore/core/view/ParameterSubMenu.java delete mode 100644 src/main/java/me/smartstore/domain/Group.java rename src/main/java/me/smartstore/enums/{GroupType.java => CustomerType.java} (69%) create mode 100644 src/main/java/me/smartstore/enums/SmartStoreMessage.java create mode 100644 src/main/java/me/smartstore/enums/SortBy.java create mode 100644 src/main/java/me/smartstore/enums/SortOrder.java delete mode 100644 src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java delete mode 100644 src/main/java/me/smartstore/menus/CustomerDataMenu.java delete mode 100644 src/main/java/me/smartstore/menus/ParameterSubMenu.java rename src/main/java/me/smartstore/utils/{ScannerHolder.java => ScannerUtility.java} (72%) create mode 100644 src/main/java/me/smartstore/utils/StoreUtility.java create mode 100644 src/test/java/me/smartstore/core/CustomerGroupManagerTest.java create mode 100644 src/test/java/me/smartstore/core/CustomerManagerTest.java diff --git a/doc/SmartStore.drawio.svg b/doc/SmartStore.drawio.svg index 35fbd29c..fe635528 100644 --- a/doc/SmartStore.drawio.svg +++ b/doc/SmartStore.drawio.svg @@ -1,4 +1,4 @@ -
===========================================
Title : SmartStore Customer Segmentation
Release Date : 23.04.24
Copyright 2023 YongHo All rights reserved.
===========================================
===========================================...
1
1
 ==============================
1. Parameter
2. Customer Data
3. Classification Summary
4. Quit
==============================
==============================...
1
1
==============================
1. Set Parameter
2. View Parameter
3. Update Parameter
4. Back
==============================
==============================...
==============================
1. Add Customer Data
2. View Customer Data
3. Update Customer Data
4. Delete Customer Data
5. Back
==============================
==============================...
1
1
==============================
1. Summary
2. Summary (Sorted By Name)
3. Summary (Sorted By Spent Time)
4. Summary (Sorted By Total Payment)
5. Back
==============================
==============================...
2
2
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?
** Press 'end'. if you want to exit! **
Which group (GENERAL (G), VIP(V), VVIP(VV)) ?...
==============================
1. Minimum Spent Time
2. Minimum Total Pay
3. Back
==============================
==============================...
3
3
G
G
Input Minimum Spent Time:
** Press 'end'. if you want to exit! **
Input Minimum Spent Time:...
Input Minimum Total Pay:
** Press 'end'. if you want to exit! **
Input Minimum Total Pay:...
1
1
How many customers to input?
** Press 'end'. if you want to exit! **
How many customers to input?...
==============================
====== Customer 1 Info. ======
==============================
1. Customer Name
2. Customer ID
3. Customer Spent Time
4. Customer Total Pay
5. Back
==============================
==============================...
Input Customer's Name:
** Press 'end'. if you want to exit! **
Input Customer's Name:...
==============================
Group : NONE ( Time : null, Pay : null )
==============================
Null.

==============================
Group : GENERAL ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VIP ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VVIP ( Time : null, Pay : null )
==============================
Null.
==============================...
==============================
Group : GENERAL ( Time : 10, Pay : 1000000 )
==============================
No. 1 => Customer{userId='TEST1', name='TEST', spentTime=11, totalPay=10000000, group=GroupType: GENERAL
Parameter: Parameter{minimumSpentTime=10, minimumTotalPay=1000000}}
==============================...
Text is not SVG - cannot display
\ No newline at end of file +
===========================================
Title : SmartStore Customer Segmentation
Release Date : 23.04.24
Copyright 2023 YongHo All rights reserved.
===========================================
===========================================...
1
1
 ==============================
1. Parameter
2. Customer Data
3. Classification Summary
4. Quit
==============================
==============================...
1
1
==============================
1. Set Parameter
2. View Parameter
3. Update Parameter
4. Back
==============================
==============================...
==============================
1. Add Customer Data
2. View Customer Data
3. Update Customer Data
4. Delete Customer Data
5. Back
==============================
==============================...
1
1
==============================
1. Summary
2. Summary (Sorted By Name)
3. Summary (Sorted By Spent Time)
4. Summary (Sorted By Total Payment)
5. Back
==============================
==============================...
2
2
Which customerGroup (GENERAL (G), VIP(V), VVIP(VV)) ?
** Press 'end'. if you want to exit! **
Which customerGroup (GENERAL (G), VIP(V), VVIP(VV)) ?...
==============================
1. Minimum Spent Time
2. Minimum Total Pay
3. Back
==============================
==============================...
3
3
G
G
Input Minimum Spent Time:
** Press 'end'. if you want to exit! **
Input Minimum Spent Time:...
Input Minimum Total Pay:
** Press 'end'. if you want to exit! **
Input Minimum Total Pay:...
1
1
How many customers to input?
** Press 'end'. if you want to exit! **
How many customers to input?...
==============================
====== Customer 1 Info. ======
==============================
1. Customer Name
2. Customer ID
3. Customer Spent Time
4. Customer Total Pay
5. Back
==============================
==============================...
Input Customer's Name:
** Press 'end'. if you want to exit! **
Input Customer's Name:...
==============================
Group : NONE ( Time : null, Pay : null )
==============================
Null.

==============================
Group : GENERAL ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VIP ( Time : null, Pay : null )
==============================
Null.

==============================
Group : VVIP ( Time : null, Pay : null )
==============================
Null.
==============================...
==============================
Group : GENERAL ( Time : 10, Pay : 1000000 )
==============================
No. 1 => Customer{userId='TEST1', name='TEST', spentTime=11, totalPay=10000000, customerGroup=GroupType: GENERAL
Parameter: Parameter{minimumSpentTime=10, minimumTotalPay=1000000}}
==============================...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/src/main/java/me/smartstore/core/AppStarter.java b/src/main/java/me/smartstore/AppStarter.java similarity index 91% rename from src/main/java/me/smartstore/core/AppStarter.java rename to src/main/java/me/smartstore/AppStarter.java index b1ce35d7..ccbe6085 100644 --- a/src/main/java/me/smartstore/core/AppStarter.java +++ b/src/main/java/me/smartstore/AppStarter.java @@ -1,6 +1,6 @@ -package me.smartstore.core; +package me.smartstore; -import me.smartstore.menus.MainMenu; +import me.smartstore.core.view.MainMenu; public class AppStarter { private AppStarter() {} diff --git a/src/main/java/me/smartstore/SmartStoreApplication.java b/src/main/java/me/smartstore/SmartStoreApplication.java index 3814c60b..0a17569c 100644 --- a/src/main/java/me/smartstore/SmartStoreApplication.java +++ b/src/main/java/me/smartstore/SmartStoreApplication.java @@ -1,7 +1,5 @@ package me.smartstore; -import me.smartstore.core.AppStarter; - public class SmartStoreApplication { public static void main(String[] args) { AppStarter.getInstance().run(); diff --git a/src/main/java/me/smartstore/core/GroupManager.java b/src/main/java/me/smartstore/core/GroupManager.java deleted file mode 100644 index 4d4048ca..00000000 --- a/src/main/java/me/smartstore/core/GroupManager.java +++ /dev/null @@ -1,92 +0,0 @@ -package me.smartstore.core; - -import static me.smartstore.exceptions.StoreErrorCode.*; -import static me.smartstore.enums.GroupType.*; -import static me.smartstore.menus.ParameterSubMenu.inputParameter; - -import java.util.Arrays; - -import me.smartstore.domain.Group; -import me.smartstore.domain.Parameter; -import me.smartstore.exceptions.StoreErrorCode; -import me.smartstore.exceptions.StoreException; -import me.smartstore.enums.GroupType; -import me.smartstore.utils.ScannerHolder; - -public class GroupManager { - private static final Group[] groups = - new Group[] { - new Group(NONE, null), new Group(GENERAL, null), new Group(VIP, null), new Group(VVIP, null) - }; - - public static void launchGroupManager(int menuNumber) { - while (true) { - printGroupTypeInputMessage(); - - try { - String input = ScannerHolder.getInput().toUpperCase(); - - if ("end".equalsIgnoreCase(input)) return; - if ("".equals(input)) throw new StoreException(NULL_INPUT); - - GroupType targetType = convertInputStrToGroupType(input); - switch (menuNumber) { - case 1 -> setGroupParameter(targetType); - case 2 -> viewGroupParameter(targetType); - case 3 -> updateGroupParameter(targetType); - } - } catch (StoreException e) { - System.out.println(e.getMessage()); - } - } - } - - private static void printGroupTypeInputMessage() { - System.out.println( - "Which group (GENERAL (G), VIP (V), VVIP (VV))?\n" - + "** Press 'end', if you want to exit! **"); - } - - private static GroupType convertInputStrToGroupType(String input) throws StoreException { - try { - return GroupType.valueOf(input).replaceFullName(); - } catch (IllegalArgumentException e) { - throw new StoreException(StoreErrorCode.INVALID_INPUT); - } - } - - private static void setGroupParameter(GroupType targetType) throws StoreException { - Group targetGroup = getTargetGroup(targetType); - - if (targetGroup.getParameter() != null) { - throw new StoreException(GROUP_ALREADY_SET); - } - - Parameter newParameter = inputParameter(); - targetGroup.setParameter(newParameter); - } - - private static void updateGroupParameter(GroupType targetType) throws StoreException { - Group targetGroup = getTargetGroup(targetType); - - if (targetGroup.getParameter() == null) { - throw new StoreException(NO_PARAMETER); - } - - Parameter inputParams = inputParameter(); - - targetGroup.setParameter(inputParams); - } - - private static void viewGroupParameter(GroupType targetType) throws StoreException { - Group targetGroup = getTargetGroup(targetType); - System.out.println(targetGroup); - } - - private static Group getTargetGroup(GroupType targetType) throws StoreException { - return Arrays.stream(groups) - .filter(group -> group.getGroupType() == targetType) - .findFirst() - .orElseThrow(() -> new StoreException(NO_GROUP)); - } -} diff --git a/src/main/java/me/smartstore/core/domain/Customer.java b/src/main/java/me/smartstore/core/domain/Customer.java new file mode 100644 index 00000000..1f3a2c3c --- /dev/null +++ b/src/main/java/me/smartstore/core/domain/Customer.java @@ -0,0 +1,129 @@ +package me.smartstore.core.domain; + +import me.smartstore.enums.CustomerType; + +public class Customer { + private static Long seqNo = 0L; + private Long id; + private String name; + private String userId; + private Integer spentTime; + private Integer payAmount; + private CustomerType customerType; + + public Customer(String name, String userId, Integer spentTime, Integer payAmount) { + synchronized (this) { + this.id = seqNo++; + } + this.name = name; + this.userId = userId; + this.spentTime = spentTime; + this.payAmount = payAmount; + this.customerType = null; + } + + public Customer(CustomerDTO dto) { + if (dto.id() == null) { + synchronized (this) { + this.id = seqNo++; + } + } else { + this.id = dto.id(); + } + this.name = dto.name(); + this.userId = dto.userId(); + this.spentTime = dto.spentTime(); + this.payAmount = dto.payAmount(); + this.customerType = null; + } + + public Customer( + String name, String userId, Integer spentTime, Integer payAmount, CustomerType customerType) { + this.name = name; + this.userId = userId; + this.spentTime = spentTime; + this.payAmount = payAmount; + this.customerType = customerType; + } + + public Long getId() { + return id; + } + + public String getUserId() { + return userId; + } + + public String getName() { + return name; + } + + public Integer getSpentTime() { + return spentTime; + } + + public Integer getPayAmount() { + return payAmount; + } + + public CustomerType getCustomerType() { + return customerType; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public void setName(String name) { + this.name = name; + } + + public void setSpentTime(int spentTime) { + this.spentTime = spentTime; + } + + public void setPayAmount(int payAmount) { + this.payAmount = payAmount; + } + + public void setCustomerType(CustomerType customerType) { + this.customerType = customerType; + } + + public void updateCustomerInfo(Customer customer) { + if (customer.getName() != null) { + this.name = customer.getName(); + } + if (customer.getUserId() != null) { + this.userId = customer.getUserId(); + } + if (customer.getSpentTime() != null) { + this.spentTime = customer.getSpentTime(); + } + if (customer.getPayAmount() != null) { + this.payAmount = customer.getPayAmount(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Customer customer = (Customer) o; + + return getId().equals(customer.getId()); + } + + @Override + public int hashCode() { + return getId().hashCode(); + } + + @Override + public String toString() { + return String.format( + "Customer{userId='%s`, name='%s', spentTime=%d, totalPayAmount=%d, group=%s}", + userId, name, spentTime, payAmount, customerType); + } +} diff --git a/src/main/java/me/smartstore/core/domain/CustomerDTO.java b/src/main/java/me/smartstore/core/domain/CustomerDTO.java new file mode 100644 index 00000000..4a1991ef --- /dev/null +++ b/src/main/java/me/smartstore/core/domain/CustomerDTO.java @@ -0,0 +1,50 @@ +package me.smartstore.core.domain; + +import me.smartstore.enums.CustomerType; + +public record CustomerDTO( + Long id, + String name, + String userId, + Integer spentTime, + Integer payAmount, + CustomerType customerType) { + + public static CustomerDTO from(Customer customer) { + return new CustomerDTO( + customer.getId(), + customer.getName(), + customer.getName(), + customer.getSpentTime(), + customer.getPayAmount(), + customer.getCustomerType()); + } + + public static CustomerDTO of( + String customerName, + String customerID, + Integer customerSpentTime, + Integer customerPayAmount) { + return new CustomerDTO( + null, customerName, customerID, customerSpentTime, customerPayAmount, null); + } + + @Override + public String toString() { + // Customer{userId='TEST123', name='TEST', spentTime=null, payAmount=null, group=null} + return "Customer{" + + "userId='" + + userId + + '\'' + + ", name='" + + name + + '\'' + + ", spentTime=" + + spentTime + + ", payAmount=" + + payAmount + + ", customerType=" + + customerType + + '}'; + } +} diff --git a/src/main/java/me/smartstore/core/domain/CustomerGroup.java b/src/main/java/me/smartstore/core/domain/CustomerGroup.java new file mode 100644 index 00000000..1b4c9658 --- /dev/null +++ b/src/main/java/me/smartstore/core/domain/CustomerGroup.java @@ -0,0 +1,71 @@ +package me.smartstore.core.domain; + +import me.smartstore.enums.CustomerType; + +/** + * 고객 유형별 그룹화 클래스 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + * @see CustomerType + * @see Parameter + */ +public class CustomerGroup { + private CustomerType customerType; + private Parameter parameter; + + public CustomerGroup(CustomerType customerType, Parameter parameter) { + this.customerType = customerType; + this.parameter = parameter; + } + + public CustomerType getCustomerType() { + return customerType; + } + + public Parameter getParameter() { + return parameter; + } + + public void setCustomerType(CustomerType customerType) { + this.customerType = customerType; + } + + public void setParameter(Parameter parameter) { + if (this.parameter == null) { + this.parameter = parameter; + } else { + if (parameter.getMinSpentTime() != null) { + this.parameter.setMinSpentTime(parameter.getMinSpentTime()); + } + if (parameter.getMinPayAmount() != null) { + this.parameter.setMinPayAmount(parameter.getMinPayAmount()); + } + } + } + + public String groupTitle() { + StringBuilder sb = new StringBuilder(); + sb.append("==============================\n"); + sb.append("Group: ").append(customerType); + if (parameter == null) { + sb.append(" ( Time : null, Pay Amount : null )\n"); + } else { + sb.append(" ( Time :") + .append(parameter.getMinSpentTime()) + .append(", Pay Amount :") + .append(parameter.getMinPayAmount()) + .append(" )\n"); + } + sb.append("=============================="); + + return sb.toString(); + } + + @Override + public String toString() { + String parameterStr = parameter == null ? "null" : parameter.toString(); + return "GroupType: " + customerType + "\nParameter: " + parameterStr; + } +} diff --git a/src/main/java/me/smartstore/domain/Parameter.java b/src/main/java/me/smartstore/core/domain/Parameter.java similarity index 96% rename from src/main/java/me/smartstore/domain/Parameter.java rename to src/main/java/me/smartstore/core/domain/Parameter.java index 1f018a6d..a12acdfa 100644 --- a/src/main/java/me/smartstore/domain/Parameter.java +++ b/src/main/java/me/smartstore/core/domain/Parameter.java @@ -1,4 +1,4 @@ -package me.smartstore.domain; +package me.smartstore.core.domain; public class Parameter { private Integer minSpentTime; diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java new file mode 100644 index 00000000..154b5a1a --- /dev/null +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -0,0 +1,85 @@ +package me.smartstore.core.manager; + +import static me.smartstore.enums.CustomerType.*; +import static me.smartstore.exceptions.StoreErrorCode.*; + +import java.util.Arrays; + +import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.Parameter; +import me.smartstore.enums.CustomerType; +import me.smartstore.exceptions.StoreException; + +/** + * 고객 그룹, 세분화 기준 관리 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ +public class CustomerGroupManager { + private static final CustomerGroupManager customerGroupManager = new CustomerGroupManager(); + private final CustomerGroup[] customerGroups; + + public CustomerGroupManager() { + customerGroups = + new CustomerGroup[] { + new CustomerGroup(NONE, null), + new CustomerGroup(GENERAL, null), + new CustomerGroup(VIP, null), + new CustomerGroup(VVIP, null) + }; + } + + public static CustomerGroupManager getInstance() { + return customerGroupManager; + } + + /** + * @param customerType 고객 유형 + * @return 일치하는 그룹 + * @throws StoreException 일치하는 그룹 없음 + */ + public CustomerGroup select(CustomerType customerType) throws StoreException { + return Arrays.stream(customerGroups) + .filter(customerGroup -> customerGroup.getCustomerType() == customerType) + .findFirst() + .orElseThrow( + () -> { + throw new StoreException(NO_GROUP); + }); + } + + /** + * @return 모든 고객 그룹 + */ + public CustomerGroup[] selectAll() { + return Arrays.stream(customerGroups).toArray(CustomerGroup[]::new); + } + + /** + * 그룹별 세분화 기준 저장 + * + * @param customerType 고객 유형 + * @param parameter 세부화 기준 + * @return 설정이 완료된 고객 그룹 + */ + public CustomerGroup save(CustomerType customerType, Parameter parameter) { + CustomerGroup customerGroup = select(customerType); + + if (customerGroup.getParameter() == null) { + customerGroup.setParameter(parameter); + return customerGroup; + } + + if (parameter.getMinSpentTime() != null) { + customerGroup.getParameter().setMinPayAmount(parameter.getMinSpentTime()); + } + + if (parameter.getMinPayAmount() != null) { + customerGroup.getParameter().setMinPayAmount(parameter.getMinPayAmount()); + } + + return customerGroup; + } +} diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java new file mode 100644 index 00000000..34d3aa96 --- /dev/null +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -0,0 +1,146 @@ +package me.smartstore.core.manager; + +import static me.smartstore.enums.SortBy.*; +import static me.smartstore.exceptions.StoreErrorCode.*; + +import java.util.Arrays; +import java.util.Objects; +import me.smartstore.core.domain.Customer; +import me.smartstore.enums.CustomerType; +import me.smartstore.exceptions.StoreException; + +public class CustomerManager { + private static final CustomerManager customerManager = new CustomerManager(); + private static final int DEFAULT_SIZE = 10; + private static int size = 0; + private static Customer[] customers = new Customer[DEFAULT_SIZE]; + + private CustomerManager() {} + + public static CustomerManager getInstance() { + + return customerManager; + } + + public int size() { + return size; + } + + public Boolean isEmpty() { + return size == 0; + } + + public Boolean isFull() { + return size == customers.length; + } + + public Customer save(Customer customer) throws StoreException { + if (isFull()) { + customers = Arrays.copyOf(customers, size * 2); + } + + customers[size++] = customer; + + return customer; + } + + public Customer findByIndex(int index) { + return customers[index - 1]; + } + + public Customer findById(Long id) throws StoreException { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> id.equals(customer.getId())) + .findFirst() + .orElseThrow( + () -> { + throw new StoreException(NO_CUSTOMER); + }); + } + + public Customer[] selectAll() { + return Arrays.stream(customers).filter(Objects::nonNull).toArray(Customer[]::new); + } + + public Customer[] selectByCustomerType(CustomerType customerType) { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> customer.getCustomerType() == customerType) + .toArray(Customer[]::new); + } + + public Customer[] selectByCustomerTypeOrderByName(CustomerType customerType) { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> customer.getCustomerType() == customerType) + .sorted(NAME.getCustomerComparator()) + .toArray(Customer[]::new); + } + + public Customer[] selectByCustomerTypeOrderByNameDesc(CustomerType customerType) { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> customer.getCustomerType() == customerType) + .sorted(NAME.getCustomerComparator().reversed()) + .toArray(Customer[]::new); + } + + public Customer[] selectByCustomerTypeOrderBySpentTime(CustomerType customerType) { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> customer.getCustomerType() == customerType) + .sorted(SPENT_TIME.getCustomerComparator()) + .toArray(Customer[]::new); + } + + public Customer[] selectByCustomerTypeOrderBySpentTimeDesc(CustomerType customerType) { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> customer.getCustomerType() == customerType) + .sorted(SPENT_TIME.getCustomerComparator().reversed()) + .toArray(Customer[]::new); + } + + public Customer[] selectByCustomerTypeOrderByPayAmount(CustomerType customerType) { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> customer.getCustomerType() == customerType) + .sorted(PAY_AMOUNT.getCustomerComparator()) + .toArray(Customer[]::new); + } + + public Customer[] selectByCustomerTypeOrderByPayAmountDesc(CustomerType customerType) { + return Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> customer.getCustomerType() == customerType) + .sorted(PAY_AMOUNT.getCustomerComparator().reversed()) + .toArray(Customer[]::new); + } + + public void deleteById(Long id) throws StoreException { + Customer target = + Arrays.stream(customers) + .filter(Objects::nonNull) + .filter(customer -> id.equals(customer.getId())) + .findFirst() + .orElseThrow( + () -> { + throw new StoreException(NOT_EXIST_ID); + }); + + delete(target); + } + + public void delete(Customer customer) throws StoreException { + int idx = -1; + for (int i = 0; i < size; i++) { + if (customer.getId().equals(customers[i].getId())) { + idx = i; + } + } + if (idx == -1) throw new StoreException(NOT_EXIST_CUSTOMER); + System.arraycopy(customers, idx, customers, idx - 1, customers.length - idx); + size--; + } +} diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java new file mode 100644 index 00000000..81c1d962 --- /dev/null +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -0,0 +1,42 @@ +package me.smartstore.core.service; + +import static me.smartstore.exceptions.StoreErrorCode.*; + +import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.Parameter; +import me.smartstore.core.manager.CustomerGroupManager; +import me.smartstore.enums.CustomerType; +import me.smartstore.exceptions.StoreException; + +public class CustomerGroupService { + private static final CustomerGroupService customerGroupService = new CustomerGroupService(); + private final CustomerGroupManager customerGroupManager = CustomerGroupManager.getInstance(); + + public static CustomerGroupService getInstance() { + return customerGroupService; + } + + public CustomerGroup setParameter(CustomerType customerType, Parameter parameter) + throws StoreException { + CustomerGroup customerGroup = customerGroupManager.select(customerType); + + if (customerGroup.getParameter() != null) { + throw new StoreException(GROUP_ALREADY_SET); + } + + return customerGroupManager.save(customerType, parameter); + } + + public CustomerGroup updateParameter(CustomerType customerType, Parameter parameter) + throws StoreException { + return customerGroupManager.save(customerType, parameter); + } + + public CustomerGroup find(CustomerType customerType) throws StoreException { + return customerGroupManager.select(customerType); + } + + public CustomerGroup[] findAll() throws StoreException { + return customerGroupManager.selectAll(); + } +} diff --git a/src/main/java/me/smartstore/core/service/CustomerService.java b/src/main/java/me/smartstore/core/service/CustomerService.java new file mode 100644 index 00000000..29536fdf --- /dev/null +++ b/src/main/java/me/smartstore/core/service/CustomerService.java @@ -0,0 +1,173 @@ +package me.smartstore.core.service; + +import me.smartstore.core.domain.Customer; +import me.smartstore.core.domain.CustomerDTO; +import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.manager.CustomerManager; +import me.smartstore.enums.SortBy; +import me.smartstore.enums.SortOrder; +import me.smartstore.exceptions.StoreException; + +import java.util.Arrays; + +public class CustomerService { + private static final CustomerService customerService = new CustomerService(); + private static final CustomerManager customerManager = CustomerManager.getInstance(); + private static final CustomerGroupService customerGroupService = + CustomerGroupService.getInstance(); + + private static SortBy lastRequestSortBy = SortBy.NAME; + private static SortOrder lastRequestedSortOrder = SortOrder.ASCENDING; + + private static CustomerGroup[] customerGroups; + + public static CustomerService getInstance() { + return customerService; + } + + public void saveNewCustomer(CustomerDTO newCustomerDTO) throws StoreException { + Customer customer = new Customer(newCustomerDTO); + System.out.println(customerManager.save(customer)); + } + + public CustomerDTO[] findAll() { + return Arrays.stream(customerManager.selectAll()) + .map(CustomerDTO::from) + .toArray(CustomerDTO[]::new); + } + + public int getNumberOfCustomers() { + return customerManager.size(); + } + + public void updateCustomerById(Long id, CustomerDTO updated) { + Customer customer = customerManager.findById(id); + } + + public void deleteCustomerById(Long id) { + customerManager.deleteById(id); + } + + public void classifyCustomer() { + // Arrays.stream(customerGroups) + // .filter(customerGroup -> customerGroup.getParameter() != null) + // .forEach( + // customerGroup -> { + // Arrays.stream(customerManager.selectAll()) + // .filter( + // customer -> + // customer.getSpentTime() != null && customer.getPayAmount() != null) + // .forEach( + // customer -> { + // if (customerGroup.getParameter().getMinSpentTime() + // <= customer.getSpentTime() + // && customerGroup.getParameter().getMinPayAmount() + // <= customer.getPayAmount()) { + // customer.setCustomerType(customerGroup.getCustomerType()); + // } + // }); + // }); + } + + public void classifyAllCustomers() { + customerGroups = customerGroupService.findAll(); + Arrays.stream(customerGroups) + .filter(customerGroup -> customerGroup.getParameter() != null) + .forEach( + customerGroup -> { + Arrays.stream(customerManager.selectAll()) + .filter( + customer -> + customer.getSpentTime() != null && customer.getPayAmount() != null) + .forEach( + customer -> { + if (customerGroup.getParameter().getMinSpentTime() + <= customer.getSpentTime() + && customerGroup.getParameter().getMinPayAmount() + <= customer.getPayAmount()) { + customer.setCustomerType(customerGroup.getCustomerType()); + } + }); + }); + } + + public void displayClassificationSummary() { + displayClassificationSummary(lastRequestSortBy, lastRequestedSortOrder); + } + + public void displayClassificationSummary(SortBy sortby, SortOrder sortOrder) { + classifyAllCustomers(); + lastRequestSortBy = sortby; + lastRequestedSortOrder = sortOrder; + switch (sortby) { + case NAME -> displayClassificationSummaryByName(sortOrder); + case SPENT_TIME -> displayClassificationSummaryBySpentTime(sortOrder); + case PAY_AMOUNT -> displayClassificationSummaryByPayAmount(sortOrder); + } + } + + public void displayClassificationSummaryByName(SortOrder sortOrder) { + Arrays.stream(customerGroups) + .forEach( + group -> { + System.out.println(group.groupTitle()); + if (sortOrder == SortOrder.ASCENDING) { + Customer[] customers = + customerManager.selectByCustomerTypeOrderByName(group.getCustomerType()); + for (int idx = 0; idx < customers.length; idx++) { + System.out.println("No. " + (idx + 1) + " => " + customers[idx]); + } + } else { + Customer[] customers = + customerManager.selectByCustomerTypeOrderByNameDesc(group.getCustomerType()); + for (int idx = 0; idx < customers.length; idx++) { + System.out.println("No. " + (idx + 1) + " => " + customers[idx]); + } + } + }); + } + + public void displayClassificationSummaryBySpentTime(SortOrder sortOrder) { + Arrays.stream(customerGroups) + .forEach( + group -> { + System.out.println(group.groupTitle()); + if (sortOrder == SortOrder.ASCENDING) { + Customer[] customers = + customerManager.selectByCustomerTypeOrderBySpentTime(group.getCustomerType()); + for (int idx = 0; idx < customers.length; idx++) { + System.out.println("No. " + (idx + 1) + " => " + customers[idx]); + } + } else { + Customer[] customers = + customerManager.selectByCustomerTypeOrderBySpentTimeDesc( + group.getCustomerType()); + for (int idx = 0; idx < customers.length; idx++) { + System.out.println("No. " + (idx + 1) + " => " + customers[idx]); + } + } + }); + } + + public void displayClassificationSummaryByPayAmount(SortOrder sortOrder) { + Arrays.stream(customerGroups) + .forEach( + group -> { + System.out.println(group.groupTitle()); + if (sortOrder == SortOrder.ASCENDING) { + Customer[] customers = + customerManager.selectByCustomerTypeOrderByPayAmount(group.getCustomerType()); + for (int idx = 0; idx < customers.length; idx++) { + System.out.println("No. " + (idx + 1) + " => " + customers[idx]); + } + } else { + Customer[] customers = + customerManager.selectByCustomerTypeOrderByPayAmountDesc( + group.getCustomerType()); + for (int idx = 0; idx < customers.length; idx++) { + System.out.println("No. " + (idx + 1) + " => " + customers[idx]); + } + } + }); + } +} diff --git a/src/main/java/me/smartstore/menus/AbstractMenu.java b/src/main/java/me/smartstore/core/view/AbstractMenu.java similarity index 84% rename from src/main/java/me/smartstore/menus/AbstractMenu.java rename to src/main/java/me/smartstore/core/view/AbstractMenu.java index 8327b331..628a9f24 100644 --- a/src/main/java/me/smartstore/menus/AbstractMenu.java +++ b/src/main/java/me/smartstore/core/view/AbstractMenu.java @@ -1,19 +1,20 @@ -package me.smartstore.menus; +package me.smartstore.core.view; import static me.smartstore.exceptions.StoreErrorCode.INVALID_FORMAT; import static me.smartstore.exceptions.StoreErrorCode.INVALID_INPUT; import me.smartstore.exceptions.StoreException; -import me.smartstore.utils.ScannerHolder; +import me.smartstore.utils.ScannerUtility; /** * 메뉴 출력 및 선택 * * @author YongHo Shin - * @version v0.1 - * @since 2023-04-28 + * @version v1.0 + * @since 2023-05-10 */ public abstract class AbstractMenu { + /** 선택 가능 메뉴 항목 */ private final String[] items; public AbstractMenu(String[] items) { @@ -38,7 +39,7 @@ void show() { int selectMenuNumber() { System.out.print("Choose One: "); - int selection = ScannerHolder.getIntegerInputSafely(); + int selection = ScannerUtility.getIntegerInputSafely(); if (selection == -1) { throw new StoreException(INVALID_FORMAT); diff --git a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java new file mode 100644 index 00000000..c91c2264 --- /dev/null +++ b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java @@ -0,0 +1,63 @@ +package me.smartstore.core.view; + +import static me.smartstore.enums.SmartStoreMessage.INPUT_SORT_ORDER; +import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; +import static me.smartstore.enums.SortBy.*; +import static me.smartstore.exceptions.StoreErrorCode.INPUT_END; +import static me.smartstore.utils.StoreUtility.convertInputStrToSortOrder; + +import me.smartstore.core.service.CustomerService; +import me.smartstore.enums.SortOrder; +import me.smartstore.exceptions.StoreException; +import me.smartstore.utils.ScannerUtility; + +public class ClassificationSummaryMenu extends AbstractMenu { + private static final ClassificationSummaryMenu classificationSummaryMenu = + new ClassificationSummaryMenu(); + + private static final CustomerService customerService = CustomerService.getInstance(); + + private ClassificationSummaryMenu() { + super( + new String[] { + "Summary", + "Summary (Sorted By Name)", + "Summary (Sorted By Spent Time)", + "Summary (Sorted By Total Pay Amount)", + "Back" + }); + } + + public static void launch() { + loop: + while (true) { + classificationSummaryMenu.show(); + try { + switch (classificationSummaryMenu.selectMenuNumber()) { + case 1 -> customerService.displayClassificationSummary(); + case 2 -> customerService.displayClassificationSummary(NAME, inputSortOrder()); + case 3 -> customerService.displayClassificationSummary(SPENT_TIME, inputSortOrder()); + case 4 -> customerService.displayClassificationSummary(PAY_AMOUNT, inputSortOrder()); + case 5 -> { + break loop; + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } + + private static SortOrder inputSortOrder() { + while (true) { + System.out.println(INPUT_SORT_ORDER + "\n" + PRESS_END_MSG); + try { + String input = ScannerUtility.getInput(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); + return convertInputStrToSortOrder(input); + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/src/main/java/me/smartstore/core/view/CustomerMenu.java b/src/main/java/me/smartstore/core/view/CustomerMenu.java new file mode 100644 index 00000000..cadaa0f9 --- /dev/null +++ b/src/main/java/me/smartstore/core/view/CustomerMenu.java @@ -0,0 +1,123 @@ +package me.smartstore.core.view; + +import static me.smartstore.enums.SmartStoreMessage.INPUT_NUMBER_OF_CUSTOMERS; +import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; +import static me.smartstore.exceptions.StoreErrorCode.*; +import static me.smartstore.utils.ScannerUtility.getInput; +import static me.smartstore.utils.ScannerUtility.getIntegerInputSafely; + +import me.smartstore.core.domain.CustomerDTO; +import me.smartstore.core.service.CustomerService; +import me.smartstore.exceptions.StoreException; + +public class CustomerMenu extends AbstractMenu { + private static final CustomerMenu customerMenu = new CustomerMenu(); + private static final CustomerService customerService = CustomerService.getInstance(); + private static final CustomerSubMenu customerSubMenu = CustomerSubMenu.getInstance(); + + private static CustomerDTO[] dtoCache = new CustomerDTO[] {}; + + private CustomerMenu() { + super( + new String[] { + "Add Customer Data", + "View Customer Data", + "Update Customer Data", + "Delete Customer Data", + "Back" + }); + } + + public static void launch() { + loop: + while (true) { + customerMenu.show(); + try { + switch (customerMenu.selectMenuNumber()) { + // Add Customer Data + case 1 -> { + int numberOfCustomers = inputNumberOfCustomers(); + for (int idx = 0; idx < numberOfCustomers; idx++) { + System.out.println("====== Customer " + (idx + 1) + ". Info. ======\n"); + CustomerDTO newCustomer = customerSubMenu.inputCustomerInfo(); + customerService.saveNewCustomer(newCustomer); + } + } + + // View Customer Data + case 2 -> showCustomerInfo(); + + // Update Customer Data + case 3 -> { + showCustomerInfo(); + int customerNumber = inputCustomerNumber(); + Long id = dtoCache[customerNumber - 1].id(); + CustomerDTO updateDTO = customerSubMenu.inputCustomerInfo(); + customerService.updateCustomerById(id, updateDTO); + } + + // Delete Customer Data + case 4 -> { + showCustomerInfo(); + int customerNumber = inputCustomerNumber(); + Long id = dtoCache[customerNumber - 1].id(); + customerService.deleteCustomerById(id); + } + + case 5 -> { + break loop; + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } + + private static void showCustomerInfo() throws StoreException { + // No. 1 => Customer{userId='TEST123', name='TEST', spentTime=null, totalPay=null, group=null} + dtoCache = customerService.findAll(); + if (dtoCache.length == 0) { + throw new StoreException(NO_CUSTOMER); + } + + System.out.println("======= Customer Info. =======\n"); + for (int idx = 0; idx < dtoCache.length; idx++) { + System.out.println("No. " + (idx + 1) + " => " + dtoCache[idx]); + } + } + + private static int inputNumberOfCustomers() throws StoreException { + while (true) { + System.out.println(INPUT_NUMBER_OF_CUSTOMERS + "\n" + PRESS_END_MSG); + String input = getInput(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); + try { + int numberOfCustomers = Integer.parseInt(input); + if (numberOfCustomers == -1) { + throw new StoreException(INVALID_FORMAT); + } + return numberOfCustomers; + } catch (NumberFormatException e) { + System.out.println(INVALID_FORMAT.getMessage()); + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } + + private static int inputCustomerNumber() throws StoreException { + while (true) { + System.out.println("Which customer ( 1 ~ " + customerService.getNumberOfCustomers() + " )? "); + try { + int customerNumber = getIntegerInputSafely(); + if (customerNumber < 1 || customerNumber > customerService.getNumberOfCustomers()) { + throw new StoreException(INVALID_FORMAT); + } + return customerNumber; + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/src/main/java/me/smartstore/core/view/CustomerSubMenu.java b/src/main/java/me/smartstore/core/view/CustomerSubMenu.java new file mode 100644 index 00000000..b8c10080 --- /dev/null +++ b/src/main/java/me/smartstore/core/view/CustomerSubMenu.java @@ -0,0 +1,93 @@ +package me.smartstore.core.view; + +import static me.smartstore.enums.SmartStoreMessage.*; +import static me.smartstore.exceptions.StoreErrorCode.*; + +import me.smartstore.core.domain.CustomerDTO; +import me.smartstore.exceptions.StoreErrorCode; +import me.smartstore.exceptions.StoreException; +import me.smartstore.utils.ScannerUtility; + +public class CustomerSubMenu extends AbstractMenu { + private static final CustomerSubMenu customerSubMenu = new CustomerSubMenu(); + + private CustomerSubMenu() { + super( + new String[] { + "Customer Name", "Customer ID", "Customer Spent Time", "Customer Total Pay Amount", "Back" + }); + } + + public static CustomerSubMenu getInstance() { + return customerSubMenu; + } + + public CustomerDTO inputCustomerInfo() { + String customerName = null; + String customerID = null; + Integer customerSpentTime = null; + Integer customerPayAmount = null; + + loop: + while (true) { + customerSubMenu.show(); + + try { + switch (customerSubMenu.selectMenuNumber()) { + case 1 -> customerName = customerSubMenu.inputCustomerName(); + case 2 -> customerID = customerSubMenu.inputCustomerId(); + case 3 -> customerSpentTime = customerSubMenu.inputCustomerSpentTime(); + case 4 -> customerPayAmount = customerSubMenu.inputCustomerPayAmount(); + case 5 -> { + break loop; + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + if (e.getErrorCode() == INPUT_END) break; + } + } + + return CustomerDTO.of(customerName, customerID, customerSpentTime, customerPayAmount); + } + + private String inputCustomerName() throws StoreException { + System.out.println(INPUT_CUSTOMER_NAME + "\n" + PRESS_END_MSG); + String input = ScannerUtility.getInput(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); + return input; + } + + private String inputCustomerId() throws StoreException { + System.out.println(INPUT_CUSTOMER_ID + "\n" + PRESS_END_MSG); + String input = ScannerUtility.getInput(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); + return input; + } + + private int inputCustomerSpentTime() throws StoreException { + while (true) { + System.out.println(INPUT_CUSTOMER_SPENT_TIME + "\n" + PRESS_END_MSG); + String input = ScannerUtility.getInput(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); + } + } + } + + private int inputCustomerPayAmount() throws StoreException { + while (true) { + System.out.println(INPUT_CUSTOMER_PAY_AMOUNT + "\n" + PRESS_END_MSG); + String input = ScannerUtility.getInput(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); + } + } + } +} diff --git a/src/main/java/me/smartstore/menus/MainMenu.java b/src/main/java/me/smartstore/core/view/MainMenu.java similarity index 89% rename from src/main/java/me/smartstore/menus/MainMenu.java rename to src/main/java/me/smartstore/core/view/MainMenu.java index ff827eae..c7eb5a50 100644 --- a/src/main/java/me/smartstore/menus/MainMenu.java +++ b/src/main/java/me/smartstore/core/view/MainMenu.java @@ -1,4 +1,4 @@ -package me.smartstore.menus; +package me.smartstore.core.view; import me.smartstore.exceptions.StoreException; @@ -16,7 +16,7 @@ public static void launch() { try { switch (mainMenu.selectMenuNumber()) { case 1 -> ParameterMenu.launch(); - case 2 -> CustomerDataMenu.launch(); + case 2 -> CustomerMenu.launch(); case 3 -> ClassificationSummaryMenu.launch(); case 4 -> { break loop; diff --git a/src/main/java/me/smartstore/menus/ParameterMenu.java b/src/main/java/me/smartstore/core/view/ParameterMenu.java similarity index 72% rename from src/main/java/me/smartstore/menus/ParameterMenu.java rename to src/main/java/me/smartstore/core/view/ParameterMenu.java index fce33120..a6b4238a 100644 --- a/src/main/java/me/smartstore/menus/ParameterMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterMenu.java @@ -1,6 +1,5 @@ -package me.smartstore.menus; +package me.smartstore.core.view; -import me.smartstore.core.GroupManager; import me.smartstore.exceptions.StoreException; public class ParameterMenu extends AbstractMenu { @@ -16,9 +15,9 @@ public static void launch() { parameterMenu.show(); try { switch (parameterMenu.selectMenuNumber()) { - case 1 -> GroupManager.launchGroupManager(1); - case 2 -> GroupManager.launchGroupManager(2); - case 3 -> GroupManager.launchGroupManager(3); + case 1 -> ParameterSubMenu.launch(1); + case 2 -> ParameterSubMenu.launch(2); + case 3 -> ParameterSubMenu.launch(3); case 4 -> { break loop; } diff --git a/src/main/java/me/smartstore/core/view/ParameterSubMenu.java b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java new file mode 100644 index 00000000..7b59fb36 --- /dev/null +++ b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java @@ -0,0 +1,122 @@ +package me.smartstore.core.view; + +import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.Parameter; +import me.smartstore.core.service.CustomerGroupService; +import me.smartstore.enums.CustomerType; +import me.smartstore.exceptions.StoreErrorCode; +import me.smartstore.exceptions.StoreException; +import me.smartstore.utils.ScannerUtility; + +import static me.smartstore.enums.SmartStoreMessage.*; +import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; +import static me.smartstore.exceptions.StoreErrorCode.*; +import static me.smartstore.utils.StoreUtility.convertInputStrToCustomerType; + +public class ParameterSubMenu extends AbstractMenu { + + private static final CustomerGroupService customerGroupService = + CustomerGroupService.getInstance(); + private static final ParameterSubMenu parameterSubMenu = new ParameterSubMenu(); + + private ParameterSubMenu() { + super(new String[] {"Minimum Spent Time", "Minimum Pay Amount", "Back"}); + } + + public static void launch(int parameterMenuNumber) { + while (true) { + System.out.println(INPUT_CUSTOMER_GROUP_MSG + "\n" + PRESS_END_MSG); + + try { + String input = ScannerUtility.getInput().toUpperCase(); + + if ("end".equalsIgnoreCase(input)) return; + if ("".equals(input)) throw new StoreException(NULL_INPUT); + + CustomerType inputCustomerType = convertInputStrToCustomerType(input); + + switch (parameterMenuNumber) { + + // Set Parameter + case 1 -> { + Parameter inputParameter = inputParameter(); + CustomerGroup customerGroup = + customerGroupService.setParameter(inputCustomerType, inputParameter); + System.out.println(customerGroup); + } + + // View Parameter + case 2 -> System.out.println(customerGroupService.find(inputCustomerType)); + + // Update Parameter + case 3 -> { + CustomerGroup customerGroup = customerGroupService.find(inputCustomerType); + if (customerGroup.getParameter() == null) { + throw new StoreException(NO_PARAMETER); + } + Parameter inputParameter = inputParameter(); + customerGroup = customerGroupService.updateParameter(inputCustomerType, inputParameter); + System.out.println(customerGroup); + } + } + + } catch (StoreException e) { + System.out.println(e.getMessage()); + if (e.getErrorCode() == NO_PARAMETER) return; + } + } + } + + private static Parameter inputParameter() { + Integer minimumSpentTime = null; + Integer minimumPayAmount = null; + + while (true) { + parameterSubMenu.show(); + + try { + switch (parameterSubMenu.selectMenuNumber()) { + case 1 -> minimumSpentTime = inputMinimumSpentTime(); + case 2 -> minimumPayAmount = inputMinimumPayAmount(); + case 3 -> { + return new Parameter(minimumSpentTime, minimumPayAmount); + } + } + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } + + private static int inputMinimumSpentTime() throws StoreException { + while (true) { + System.out.println(INPUT_MINIMUM_SPENT_TIME_MSG + "\n" + PRESS_END_MSG); + + String input = ScannerUtility.getInput(); + + if ("end".equals(input)) throw new StoreException(INPUT_END); + + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); + } + } + } + + private static int inputMinimumPayAmount() throws StoreException { + while (true) { + System.out.println(INPUT_MINIMUM_PAY_AMOUNT_MSG + "\n" + PRESS_END_MSG); + + String input = ScannerUtility.getInput(); + + if ("end".equals(input)) throw new StoreException(INPUT_END); + + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); + } + } + } +} diff --git a/src/main/java/me/smartstore/domain/Group.java b/src/main/java/me/smartstore/domain/Group.java deleted file mode 100644 index bb0074fc..00000000 --- a/src/main/java/me/smartstore/domain/Group.java +++ /dev/null @@ -1,43 +0,0 @@ -package me.smartstore.domain; - -import me.smartstore.enums.GroupType; - -public class Group { - private GroupType groupType; - private Parameter parameter; - - public Group(GroupType groupType, Parameter parameter) { - this.groupType = groupType; - this.parameter = parameter; - } - - public GroupType getGroupType() { - return groupType; - } - - public void setGroupType(GroupType groupType) { - this.groupType = groupType; - } - - public Parameter getParameter() { - return parameter; - } - - public void setParameter(Parameter parameter) { - if (this.parameter == null) { - this.parameter = parameter; - } else { - if (parameter.getMinSpentTime() != null) { - this.parameter.setMinSpentTime(parameter.getMinSpentTime()); - } - if (parameter.getMinPayAmount() != null) { - this.parameter.setMinPayAmount(parameter.getMinPayAmount()); - } - } - } - - @Override - public String toString() { - return "GroupType:" + groupType + "\nParameter: " + parameter.toString(); - } -} diff --git a/src/main/java/me/smartstore/enums/GroupType.java b/src/main/java/me/smartstore/enums/CustomerType.java similarity index 69% rename from src/main/java/me/smartstore/enums/GroupType.java rename to src/main/java/me/smartstore/enums/CustomerType.java index b255f49a..f7a6f08d 100644 --- a/src/main/java/me/smartstore/enums/GroupType.java +++ b/src/main/java/me/smartstore/enums/CustomerType.java @@ -1,6 +1,13 @@ package me.smartstore.enums; -public enum GroupType { +/** + * 고객 유형 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ +public enum CustomerType { NONE("해당없음"), GENERAL("일반고객"), VIP("우수고객"), @@ -12,11 +19,11 @@ public enum GroupType { private final String description; - GroupType(String description) { + CustomerType(String description) { this.description = description; } - public GroupType replaceFullName() { + public CustomerType replaceFullName() { if (this == G) return GENERAL; else if (this == V) return VIP; else if (this == VV) return VVIP; diff --git a/src/main/java/me/smartstore/enums/SmartStoreMessage.java b/src/main/java/me/smartstore/enums/SmartStoreMessage.java new file mode 100644 index 00000000..336295ed --- /dev/null +++ b/src/main/java/me/smartstore/enums/SmartStoreMessage.java @@ -0,0 +1,37 @@ +package me.smartstore.enums; + +/** + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ +public enum SmartStoreMessage { + // Customer Sub Menu + INPUT_NUMBER_OF_CUSTOMERS("How many customers to input?"), + INPUT_CUSTOMER_NAME("Input Customer's Name:"), + INPUT_CUSTOMER_ID("Input Customer's ID:"), + INPUT_CUSTOMER_SPENT_TIME("Input Customer's Spent Time:"), + INPUT_CUSTOMER_PAY_AMOUNT("Input Customer's Total Pay Amount:"), + + // Parameter Sub Menu Message + INPUT_MINIMUM_SPENT_TIME_MSG("Input Minimum Spent Time."), + INPUT_MINIMUM_PAY_AMOUNT_MSG("Input Minimum Total Pay Amount."), + + // Customer Group Menu Message + INPUT_CUSTOMER_GROUP_MSG("Which group (GENERAL (G), VIP (V), VVIP (VV))?"), + + INPUT_SORT_ORDER("Which order (ASCENDING (A), DESCENDING (D))?"), + + PRESS_END_MSG("** Press 'end', if you want to exit! **"); + + private final String detailMessage; + + SmartStoreMessage(String detailMessage) { + this.detailMessage = detailMessage; + } + + @Override + public String toString() { + return detailMessage; + } +} diff --git a/src/main/java/me/smartstore/enums/SortBy.java b/src/main/java/me/smartstore/enums/SortBy.java new file mode 100644 index 00000000..634a078b --- /dev/null +++ b/src/main/java/me/smartstore/enums/SortBy.java @@ -0,0 +1,26 @@ +package me.smartstore.enums; + +import me.smartstore.core.domain.Customer; + +import java.util.Comparator; + +/** + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ +public enum SortBy { + NAME(Comparator.comparing(Customer::getName)), + SPENT_TIME(Comparator.comparing(Customer::getSpentTime)), + PAY_AMOUNT(Comparator.comparing(Customer::getPayAmount)); + + private final Comparator customerComparator; + + SortBy(Comparator customerComparator) { + this.customerComparator = customerComparator; + } + + public Comparator getCustomerComparator() { + return customerComparator; + } +} diff --git a/src/main/java/me/smartstore/enums/SortOrder.java b/src/main/java/me/smartstore/enums/SortOrder.java new file mode 100644 index 00000000..3ab47712 --- /dev/null +++ b/src/main/java/me/smartstore/enums/SortOrder.java @@ -0,0 +1,25 @@ +package me.smartstore.enums; + +/** + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ +public enum SortOrder { + A("Ascending"), + D("Descending"), + ASCENDING("Ascending"), + DESCENDING("Descending"); + + private final String sortOrder; + + SortOrder(String sortOrder) { + this.sortOrder = sortOrder; + } + + public SortOrder replaceAbbreviation() { + if (this == A) return ASCENDING; + else if (this == D) return DESCENDING; + else return this; + } +} diff --git a/src/main/java/me/smartstore/exceptions/StoreErrorCode.java b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java index ec527778..e7e11fab 100644 --- a/src/main/java/me/smartstore/exceptions/StoreErrorCode.java +++ b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java @@ -1,6 +1,13 @@ package me.smartstore.exceptions; +/** + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public enum StoreErrorCode { + NOT_EXIST_CUSTOMER("Customer doesn't exist."), + NOT_EXIST_ID("No matching id."), NO_GROUP("No matching group."), NO_CUSTOMER("No Customers. Please input one first."), CANT_SORT("Elements in Array has null. Array can't be sorted."), diff --git a/src/main/java/me/smartstore/exceptions/StoreException.java b/src/main/java/me/smartstore/exceptions/StoreException.java index 225a7d0f..84030078 100644 --- a/src/main/java/me/smartstore/exceptions/StoreException.java +++ b/src/main/java/me/smartstore/exceptions/StoreException.java @@ -1,5 +1,10 @@ package me.smartstore.exceptions; +/** + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class StoreException extends RuntimeException { private final StoreErrorCode storeErrorCode; private final String message; diff --git a/src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java b/src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java deleted file mode 100644 index f2cc154e..00000000 --- a/src/main/java/me/smartstore/menus/ClassificationSummaryMenu.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.smartstore.menus; - -import me.smartstore.core.CustomerManager; -import me.smartstore.exceptions.StoreException; - -public class ClassificationSummaryMenu extends AbstractMenu { - private static final ClassificationSummaryMenu classificationSummaryMenu = - new ClassificationSummaryMenu(); - - private ClassificationSummaryMenu() { - super( - new String[] { - "Summary", - "Summary (Sorted By Name)", - "Summary (Sorted By Spent Time)", - "Summary (Sorted By Total Pay Amount)", - "Back" - }); - } - - public static void launch() { - loop: - while (true) { - classificationSummaryMenu.show(); - try { - switch (classificationSummaryMenu.selectMenuNumber()) { - case 1 -> CustomerManager.printSummary(); - case 2 -> CustomerManager.printSummary(OrderBy.NAME); - case 3 -> CustomerManager.printSummary(OrderBy.SPENT_TIME); - case 4 -> CustomerManager.printSummary(OrderBy.TOTAL_PAY_AMOUNT); - case 5 -> { - break loop; - } - } - } catch (StoreException e) { - System.out.println(e.getMessage()); - } - } - } -} diff --git a/src/main/java/me/smartstore/menus/CustomerDataMenu.java b/src/main/java/me/smartstore/menus/CustomerDataMenu.java deleted file mode 100644 index a794290c..00000000 --- a/src/main/java/me/smartstore/menus/CustomerDataMenu.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.smartstore.menus; - -import me.smartstore.core.CustomerManager; -import me.smartstore.exceptions.StoreException; - -public class CustomerDataMenu extends AbstractMenu { - private static final CustomerDataMenu customerDataMenu = new CustomerDataMenu(); - - private CustomerDataMenu() { - super( - new String[] { - "Add Customer Data", - "View Customer Data", - "Update Customer Data", - "Delete Customer Data", - "Back" - }); - } - - public static void launch() { - loop: - while (true) { - customerDataMenu.show(); - try { - switch (customerDataMenu.selectMenuNumber()) { - case 1 -> CustomerManager.launchCustomerManager(1); - case 2 -> CustomerManager.launchCustomerManager(2); - case 3 -> CustomerManager.launchCustomerManager(3); - case 4 -> CustomerManager.launchCustomerManager(4); - case 5 -> { - break loop; - } - } - } catch (StoreException e) { - System.out.println(e.getMessage()); - } - } - } -} diff --git a/src/main/java/me/smartstore/menus/ParameterSubMenu.java b/src/main/java/me/smartstore/menus/ParameterSubMenu.java deleted file mode 100644 index f8125626..00000000 --- a/src/main/java/me/smartstore/menus/ParameterSubMenu.java +++ /dev/null @@ -1,69 +0,0 @@ -package me.smartstore.menus; - -import me.smartstore.domain.Parameter; -import me.smartstore.exceptions.StoreErrorCode; -import me.smartstore.exceptions.StoreException; -import me.smartstore.utils.ScannerHolder; - -public class ParameterSubMenu extends AbstractMenu { - private static final ParameterSubMenu parameterSubMenu = new ParameterSubMenu(); - - private ParameterSubMenu() { - super(new String[] {"Minimum Spent Time", "Minimum Total PayAmount", "Back"}); - } - - public static Parameter inputParameter() { - Integer minimumSpentTime = null; - Integer minimumTotalPayAmount = null; - - while (true) { - parameterSubMenu.show(); - - try { - switch (parameterSubMenu.selectMenuNumber()) { - case 1 -> { - while (true) { - System.out.println( - "Input Minimum Spent Time.\n** Press 'end', if you want to exit! **"); - - String input = ScannerHolder.getInput(); - - if ("end".equals(input)) break; - - try { - minimumSpentTime = Integer.parseInt(input); - break; - } catch (NumberFormatException e) { - System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); - } - } - } - - case 2 -> { - while (true) { - System.out.println( - "Input Minimum Total Pay Amount.\n** Press 'end', if you want to exit! **"); - - String input = ScannerHolder.getInput(); - - if ("end".equals(input)) break; - - try { - minimumTotalPayAmount = Integer.parseInt(input); - break; - } catch (NumberFormatException e) { - System.out.println(StoreErrorCode.INVALID_FORMAT.getMessage()); - } - } - } - - case 3 -> { - return new Parameter(minimumSpentTime, minimumTotalPayAmount); - } - } - } catch (StoreException e) { - System.out.println(e.getMessage()); - } - } - } -} diff --git a/src/main/java/me/smartstore/utils/ScannerHolder.java b/src/main/java/me/smartstore/utils/ScannerUtility.java similarity index 72% rename from src/main/java/me/smartstore/utils/ScannerHolder.java rename to src/main/java/me/smartstore/utils/ScannerUtility.java index 7ab39500..7e8c3227 100644 --- a/src/main/java/me/smartstore/utils/ScannerHolder.java +++ b/src/main/java/me/smartstore/utils/ScannerUtility.java @@ -8,15 +8,25 @@ import static me.smartstore.exceptions.StoreErrorCode.EMPTY_INPUT; import static me.smartstore.exceptions.StoreErrorCode.INTERNAL_ERROR; -public class ScannerHolder { +/** + * 사용자로부터 입력을 받기 위한 유틸리티 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ +public class ScannerUtility { private static final Scanner scanner = new Scanner(System.in); + /** + * @return 정수로 변환된 결과. 공백일 경우 -1. + */ public static int getIntegerInputSafely() { String converted = scanner.nextLine().replaceAll("[^0-9]", ""); return "".equals(converted) ? -1 : Integer.parseInt(converted); } - public static String getInput() { + public static String getInput() throws StoreException { try { return scanner.nextLine(); } catch (NoSuchElementException e) { diff --git a/src/main/java/me/smartstore/utils/StoreUtility.java b/src/main/java/me/smartstore/utils/StoreUtility.java new file mode 100644 index 00000000..33c65361 --- /dev/null +++ b/src/main/java/me/smartstore/utils/StoreUtility.java @@ -0,0 +1,32 @@ +package me.smartstore.utils; + +import me.smartstore.enums.CustomerType; +import me.smartstore.enums.SortOrder; +import me.smartstore.exceptions.StoreException; + +import static me.smartstore.exceptions.StoreErrorCode.INVALID_FORMAT; + +/** + * 스마트스토어에 필요한 부가 기능 제공 유틸리티 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ +public class StoreUtility { + public static CustomerType convertInputStrToCustomerType(String input) throws StoreException { + try { + return CustomerType.valueOf(input).replaceFullName(); + } catch (IllegalArgumentException e) { + throw new StoreException(INVALID_FORMAT); + } + } + + public static SortOrder convertInputStrToSortOrder(String input) throws StoreException { + try { + return SortOrder.valueOf(input.toUpperCase()).replaceAbbreviation(); + } catch (IllegalArgumentException e) { + throw new StoreException(INVALID_FORMAT); + } + } +} diff --git a/src/test/java/me/smartstore/core/CustomerGroupManagerTest.java b/src/test/java/me/smartstore/core/CustomerGroupManagerTest.java new file mode 100644 index 00000000..6365649b --- /dev/null +++ b/src/test/java/me/smartstore/core/CustomerGroupManagerTest.java @@ -0,0 +1,9 @@ +package me.smartstore.core; + +import org.junit.jupiter.api.Test; + +class CustomerGroupManagerTest { + + @Test + void launchGroupManager() {} +} diff --git a/src/test/java/me/smartstore/core/CustomerManagerTest.java b/src/test/java/me/smartstore/core/CustomerManagerTest.java new file mode 100644 index 00000000..89d94e67 --- /dev/null +++ b/src/test/java/me/smartstore/core/CustomerManagerTest.java @@ -0,0 +1,80 @@ +package me.smartstore.core; + +import me.smartstore.core.domain.Customer; +import me.smartstore.enums.CustomerType; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Comparator; + +import static me.smartstore.enums.CustomerType.*; +import static me.smartstore.enums.CustomerType.VVIP; + +class CustomerManagerTest { + private final Customer[] testCustomers = + new Customer[] { + new Customer("c1name", "c1id", 60, 6000000, CustomerType.GENERAL), + new Customer("c2name", "c2id", 30, 7000000, CustomerType.VIP), + new Customer("c3name", "c3id", 40, 3000000, CustomerType.VVIP), + new Customer("c4name", "c4id", 20, 1000000, CustomerType.VVIP), + new Customer("c5name", "c5id", 10, 5000000, CustomerType.NONE) + }; + + @Test + void printSummary() { + Comparator nameComparator = (c1, c2) -> c1.getName().compareTo(c2.getName()); + Comparator timeComparator = + (c1, c2) -> c1.getSpentTime().compareTo(c2.getSpentTime()); + Comparator amountComparator = + (c1, c2) -> c1.getPayAmount().compareTo(c2.getPayAmount()); + + Arrays.sort(testCustomers, nameComparator); + System.out.println(Arrays.toString(testCustomers)); + System.out.println(); + + Arrays.sort(testCustomers, nameComparator.reversed()); + System.out.println(Arrays.toString(testCustomers)); + System.out.println(); + + Arrays.sort(testCustomers, timeComparator); + System.out.println(Arrays.toString(testCustomers)); + System.out.println(); + + Arrays.sort(testCustomers, timeComparator.reversed()); + System.out.println(Arrays.toString(testCustomers)); + System.out.println(); + + Arrays.sort(testCustomers, amountComparator); + System.out.println(Arrays.toString(testCustomers)); + System.out.println(); + + Arrays.sort(testCustomers, amountComparator.reversed()); + System.out.println(Arrays.toString(testCustomers)); + System.out.println(); + } + + @Test + void filterTest() { + Customer[] noneGroup = + Arrays.stream(testCustomers) + .filter(customer -> customer.getCustomerType() == NONE) + .toArray(Customer[]::new); + Customer[] generalGroup = + Arrays.stream(testCustomers) + .filter(customer -> customer.getCustomerType() == GENERAL) + .toArray(Customer[]::new); + Customer[] vipGroup = + Arrays.stream(testCustomers) + .filter(customer -> customer.getCustomerType() == VIP) + .toArray(Customer[]::new); + Customer[] vvipGroup = + Arrays.stream(testCustomers) + .filter(customer -> customer.getCustomerType() == VVIP) + .toArray(Customer[]::new); + + System.out.println(Arrays.toString(noneGroup)); + System.out.println(Arrays.toString(generalGroup)); + System.out.println(Arrays.toString(vipGroup)); + System.out.println(Arrays.toString(vvipGroup)); + } +} From 2d2b6f84474277aeef29a75873ccc7b79104bfbb Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 00:07:06 +0900 Subject: [PATCH 14/58] comment: add javadoc - SmartStoreApplication, AppStarter --- src/main/java/me/smartstore/AppStarter.java | 7 +++++++ src/main/java/me/smartstore/SmartStoreApplication.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/me/smartstore/AppStarter.java b/src/main/java/me/smartstore/AppStarter.java index ccbe6085..d0b2f5f9 100644 --- a/src/main/java/me/smartstore/AppStarter.java +++ b/src/main/java/me/smartstore/AppStarter.java @@ -2,6 +2,13 @@ import me.smartstore.core.view.MainMenu; +/** + * 스마트스토어 메인 시작점 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class AppStarter { private AppStarter() {} diff --git a/src/main/java/me/smartstore/SmartStoreApplication.java b/src/main/java/me/smartstore/SmartStoreApplication.java index 0a17569c..f03fb502 100644 --- a/src/main/java/me/smartstore/SmartStoreApplication.java +++ b/src/main/java/me/smartstore/SmartStoreApplication.java @@ -1,5 +1,12 @@ package me.smartstore; +/** + * 스마트스토어 애플리케이션 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class SmartStoreApplication { public static void main(String[] args) { AppStarter.getInstance().run(); From 4a6317ded348f735be8d298cae2d279ace32cdeb Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 00:19:55 +0900 Subject: [PATCH 15/58] comment: add javadoc & comments - MainMenu --- .../java/me/smartstore/core/view/MainMenu.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/me/smartstore/core/view/MainMenu.java b/src/main/java/me/smartstore/core/view/MainMenu.java index c7eb5a50..b85c0d8c 100644 --- a/src/main/java/me/smartstore/core/view/MainMenu.java +++ b/src/main/java/me/smartstore/core/view/MainMenu.java @@ -2,6 +2,13 @@ import me.smartstore.exceptions.StoreException; +/** + * 메인 메뉴 화면 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class MainMenu extends AbstractMenu { private static final MainMenu mainMenu = new MainMenu(); @@ -15,9 +22,16 @@ public static void launch() { mainMenu.show(); try { switch (mainMenu.selectMenuNumber()) { + // 고객 유형 세분화 기준 설정 case 1 -> ParameterMenu.launch(); + + // 고객 정보 관리 case 2 -> CustomerMenu.launch(); + + // 고객 정보 요약 case 3 -> ClassificationSummaryMenu.launch(); + + // 종료 case 4 -> { break loop; } From d9a4cca8e340ab69cafea9580ca46471e70bfcc0 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 00:24:39 +0900 Subject: [PATCH 16/58] comment: add javadoc & comments - ParameterMenu --- .../me/smartstore/core/view/ParameterMenu.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/me/smartstore/core/view/ParameterMenu.java b/src/main/java/me/smartstore/core/view/ParameterMenu.java index a6b4238a..f39345db 100644 --- a/src/main/java/me/smartstore/core/view/ParameterMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterMenu.java @@ -2,6 +2,13 @@ import me.smartstore.exceptions.StoreException; +/** + * 고객 유형 세분화 기준 설정 메뉴 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class ParameterMenu extends AbstractMenu { private static final ParameterMenu parameterMenu = new ParameterMenu(); @@ -15,9 +22,16 @@ public static void launch() { parameterMenu.show(); try { switch (parameterMenu.selectMenuNumber()) { + // 기준 초기 설정 case 1 -> ParameterSubMenu.launch(1); + + // 설정된 기준 보기 case 2 -> ParameterSubMenu.launch(2); + + // 기준 수정 case 3 -> ParameterSubMenu.launch(3); + + // 이전 메뉴 case 4 -> { break loop; } From 05be6cfca7d2c600648dfd1a503629988e1eea3a Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 01:23:51 +0900 Subject: [PATCH 17/58] =?UTF-8?q?fix:=20ParameterMenu,=20ParameterSubMenu,?= =?UTF-8?q?=20CustomerGroupService,=20CustomerGroupManager=20-=20=EA=B3=A0?= =?UTF-8?q?=EA=B0=9D=20=EC=9C=A0=ED=98=95=EB=B3=84=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=A9=94=EB=89=B4=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파라미터 값이 제대로 설정되지 않던 문제 해결 - 메뉴 선택 및 이후 과정이 요구사항과 일치하지 않던 부분 수정 --- .../core/manager/CustomerGroupManager.java | 3 +- .../core/service/CustomerGroupService.java | 13 --- .../smartstore/core/view/ParameterMenu.java | 67 ++++++++++++++- .../core/view/ParameterSubMenu.java | 82 +++++++------------ 4 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index 154b5a1a..d797d2e6 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -4,7 +4,6 @@ import static me.smartstore.exceptions.StoreErrorCode.*; import java.util.Arrays; - import me.smartstore.core.domain.CustomerGroup; import me.smartstore.core.domain.Parameter; import me.smartstore.enums.CustomerType; @@ -73,7 +72,7 @@ public CustomerGroup save(CustomerType customerType, Parameter parameter) { } if (parameter.getMinSpentTime() != null) { - customerGroup.getParameter().setMinPayAmount(parameter.getMinSpentTime()); + customerGroup.getParameter().setMinSpentTime(parameter.getMinSpentTime()); } if (parameter.getMinPayAmount() != null) { diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java index 81c1d962..4ef7215c 100644 --- a/src/main/java/me/smartstore/core/service/CustomerGroupService.java +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -1,7 +1,5 @@ package me.smartstore.core.service; -import static me.smartstore.exceptions.StoreErrorCode.*; - import me.smartstore.core.domain.CustomerGroup; import me.smartstore.core.domain.Parameter; import me.smartstore.core.manager.CustomerGroupManager; @@ -18,17 +16,6 @@ public static CustomerGroupService getInstance() { public CustomerGroup setParameter(CustomerType customerType, Parameter parameter) throws StoreException { - CustomerGroup customerGroup = customerGroupManager.select(customerType); - - if (customerGroup.getParameter() != null) { - throw new StoreException(GROUP_ALREADY_SET); - } - - return customerGroupManager.save(customerType, parameter); - } - - public CustomerGroup updateParameter(CustomerType customerType, Parameter parameter) - throws StoreException { return customerGroupManager.save(customerType, parameter); } diff --git a/src/main/java/me/smartstore/core/view/ParameterMenu.java b/src/main/java/me/smartstore/core/view/ParameterMenu.java index f39345db..c7a5284f 100644 --- a/src/main/java/me/smartstore/core/view/ParameterMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterMenu.java @@ -1,6 +1,16 @@ package me.smartstore.core.view; +import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.Parameter; +import me.smartstore.core.service.CustomerGroupService; +import me.smartstore.enums.CustomerType; import me.smartstore.exceptions.StoreException; +import me.smartstore.utils.ScannerUtility; + +import static me.smartstore.enums.SmartStoreMessage.INPUT_CUSTOMER_GROUP_MSG; +import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; +import static me.smartstore.exceptions.StoreErrorCode.*; +import static me.smartstore.utils.StoreUtility.convertInputStrToCustomerType; /** * 고객 유형 세분화 기준 설정 메뉴 @@ -11,6 +21,9 @@ */ public class ParameterMenu extends AbstractMenu { private static final ParameterMenu parameterMenu = new ParameterMenu(); + private static final ParameterSubMenu parameterSubMenu = ParameterSubMenu.getInstance(); + private static final CustomerGroupService customerGroupService = + CustomerGroupService.getInstance(); private ParameterMenu() { super(new String[] {"Set Parameter", "View Parameter", "Update Parameter", "Back"}); @@ -23,13 +36,42 @@ public static void launch() { try { switch (parameterMenu.selectMenuNumber()) { // 기준 초기 설정 - case 1 -> ParameterSubMenu.launch(1); + case 1 -> { + while (true) { + CustomerType customerType = inputCustomerType(); + CustomerGroup customerGroup = customerGroupService.find(customerType); + if (customerGroup.getParameter() != null) { + System.out.println(GROUP_ALREADY_SET.getMessage()); + System.out.println(customerGroup); + continue; + } + Parameter parameter = parameterSubMenu.inputParameter(); + customerGroup = customerGroupService.setParameter(customerType, parameter); + System.out.println(customerGroup); + } + } // 설정된 기준 보기 - case 2 -> ParameterSubMenu.launch(2); + case 2 -> { + while (true) { + CustomerType customerType = inputCustomerType(); + System.out.println(customerGroupService.find(customerType)); + } + } // 기준 수정 - case 3 -> ParameterSubMenu.launch(3); + case 3 -> { + while (true) { + CustomerType customerType = inputCustomerType(); + CustomerGroup customerGroup = customerGroupService.find(customerType); + if (customerGroup.getParameter() == null) { + throw new StoreException(NO_PARAMETER); + } + Parameter parameter = parameterSubMenu.inputParameter(); + customerGroup = customerGroupService.setParameter(customerType, parameter); + System.out.println(customerGroup); + } + } // 이전 메뉴 case 4 -> { @@ -41,4 +83,23 @@ public static void launch() { } } } + + /** + * @return 입력받은 고객 유형 + * @throws StoreException 종료 선택시, 공백 입력시 발생 + */ + private static CustomerType inputCustomerType() throws StoreException { + while (true) { + System.out.println(INPUT_CUSTOMER_GROUP_MSG + "\n" + PRESS_END_MSG); + String input = ScannerUtility.getInput().toUpperCase(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); + + try { + if ("".equals(input)) throw new StoreException(NULL_INPUT); + return convertInputStrToCustomerType(input); + } catch (StoreException e) { + System.out.println(e.getMessage()); + } + } + } } diff --git a/src/main/java/me/smartstore/core/view/ParameterSubMenu.java b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java index 7b59fb36..536e6c43 100644 --- a/src/main/java/me/smartstore/core/view/ParameterSubMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java @@ -1,73 +1,39 @@ package me.smartstore.core.view; -import me.smartstore.core.domain.CustomerGroup; +import static me.smartstore.enums.SmartStoreMessage.*; +import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; +import static me.smartstore.exceptions.StoreErrorCode.*; + import me.smartstore.core.domain.Parameter; -import me.smartstore.core.service.CustomerGroupService; -import me.smartstore.enums.CustomerType; import me.smartstore.exceptions.StoreErrorCode; import me.smartstore.exceptions.StoreException; import me.smartstore.utils.ScannerUtility; -import static me.smartstore.enums.SmartStoreMessage.*; -import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; -import static me.smartstore.exceptions.StoreErrorCode.*; -import static me.smartstore.utils.StoreUtility.convertInputStrToCustomerType; - +/** + * 고객 유형별 기준 설정 메뉴 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class ParameterSubMenu extends AbstractMenu { - - private static final CustomerGroupService customerGroupService = - CustomerGroupService.getInstance(); private static final ParameterSubMenu parameterSubMenu = new ParameterSubMenu(); private ParameterSubMenu() { super(new String[] {"Minimum Spent Time", "Minimum Pay Amount", "Back"}); } - public static void launch(int parameterMenuNumber) { - while (true) { - System.out.println(INPUT_CUSTOMER_GROUP_MSG + "\n" + PRESS_END_MSG); - - try { - String input = ScannerUtility.getInput().toUpperCase(); - - if ("end".equalsIgnoreCase(input)) return; - if ("".equals(input)) throw new StoreException(NULL_INPUT); - - CustomerType inputCustomerType = convertInputStrToCustomerType(input); - - switch (parameterMenuNumber) { - - // Set Parameter - case 1 -> { - Parameter inputParameter = inputParameter(); - CustomerGroup customerGroup = - customerGroupService.setParameter(inputCustomerType, inputParameter); - System.out.println(customerGroup); - } - - // View Parameter - case 2 -> System.out.println(customerGroupService.find(inputCustomerType)); - - // Update Parameter - case 3 -> { - CustomerGroup customerGroup = customerGroupService.find(inputCustomerType); - if (customerGroup.getParameter() == null) { - throw new StoreException(NO_PARAMETER); - } - Parameter inputParameter = inputParameter(); - customerGroup = customerGroupService.updateParameter(inputCustomerType, inputParameter); - System.out.println(customerGroup); - } - } - - } catch (StoreException e) { - System.out.println(e.getMessage()); - if (e.getErrorCode() == NO_PARAMETER) return; - } - } + /** + * @return Singleton 객체 반환 + */ + public static ParameterSubMenu getInstance() { + return parameterSubMenu; } - private static Parameter inputParameter() { + /** + * @return 입력받은 고객 유형 분류 기준 + */ + public Parameter inputParameter() { Integer minimumSpentTime = null; Integer minimumPayAmount = null; @@ -88,6 +54,10 @@ private static Parameter inputParameter() { } } + /** + * @return 입력받은 최소 이용시간 + * @throws StoreException 종료 선택시. + */ private static int inputMinimumSpentTime() throws StoreException { while (true) { System.out.println(INPUT_MINIMUM_SPENT_TIME_MSG + "\n" + PRESS_END_MSG); @@ -104,6 +74,10 @@ private static int inputMinimumSpentTime() throws StoreException { } } + /** + * @return 입력받은 최소 결제금액 + * @throws StoreException 종료 선택시. + */ private static int inputMinimumPayAmount() throws StoreException { while (true) { System.out.println(INPUT_MINIMUM_PAY_AMOUNT_MSG + "\n" + PRESS_END_MSG); From f6ba244ebeaefc2aed38548ebb2995b4580b7b8f Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 01:51:59 +0900 Subject: [PATCH 18/58] update: Parameter // comment: add javadoc & comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Customer, CustomerDTO, CustomerGroup, Parameter 주석 정리 - Parameter : 불필요한 기본 생성자 정리 --- .../java/me/smartstore/core/domain/Customer.java | 11 ++++++++++- .../java/me/smartstore/core/domain/CustomerDTO.java | 13 +++++++++++++ .../me/smartstore/core/domain/CustomerGroup.java | 2 +- .../java/me/smartstore/core/domain/Parameter.java | 12 +++++++----- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/main/java/me/smartstore/core/domain/Customer.java b/src/main/java/me/smartstore/core/domain/Customer.java index 1f3a2c3c..ff299529 100644 --- a/src/main/java/me/smartstore/core/domain/Customer.java +++ b/src/main/java/me/smartstore/core/domain/Customer.java @@ -2,9 +2,18 @@ import me.smartstore.enums.CustomerType; +/** + * 고객 정보 저장을 위한 엔티티 클래스 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class Customer { + /** 관리 번호 부여용 */ private static Long seqNo = 0L; - private Long id; + + private Long id; // 고객 관리 번호 private String name; private String userId; private Integer spentTime; diff --git a/src/main/java/me/smartstore/core/domain/CustomerDTO.java b/src/main/java/me/smartstore/core/domain/CustomerDTO.java index 4a1991ef..1f9951f0 100644 --- a/src/main/java/me/smartstore/core/domain/CustomerDTO.java +++ b/src/main/java/me/smartstore/core/domain/CustomerDTO.java @@ -2,6 +2,19 @@ import me.smartstore.enums.CustomerType; +/** + * 스마트스토어 애플리케이션 내에서 고객 정보 전달 + * + * @param id 관리 번호 + * @param name 이름 + * @param userId 사용자 ID + * @param spentTime 이용시간 + * @param payAmount 이용금액 + * @param customerType 고객 유형 + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public record CustomerDTO( Long id, String name, diff --git a/src/main/java/me/smartstore/core/domain/CustomerGroup.java b/src/main/java/me/smartstore/core/domain/CustomerGroup.java index 1b4c9658..96fe3ed0 100644 --- a/src/main/java/me/smartstore/core/domain/CustomerGroup.java +++ b/src/main/java/me/smartstore/core/domain/CustomerGroup.java @@ -3,7 +3,7 @@ import me.smartstore.enums.CustomerType; /** - * 고객 유형별 그룹화 클래스 + * 고객 유형별 세부 기준 관리를 위한 그룹화 클래스 * * @author YongHo Shin * @version v1.0 diff --git a/src/main/java/me/smartstore/core/domain/Parameter.java b/src/main/java/me/smartstore/core/domain/Parameter.java index a12acdfa..83e48d18 100644 --- a/src/main/java/me/smartstore/core/domain/Parameter.java +++ b/src/main/java/me/smartstore/core/domain/Parameter.java @@ -1,14 +1,16 @@ package me.smartstore.core.domain; +/** + * 고객 유형별 세부 기준 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class Parameter { private Integer minSpentTime; private Integer minPayAmount; - public Parameter() { - this.minSpentTime = null; - this.minPayAmount = null; - } - public Parameter(Integer minSpentTime, Integer minPayAmount) { this.minSpentTime = minSpentTime; this.minPayAmount = minPayAmount; From 4169fec5845068431112635834a74035dcc92666 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 02:54:11 +0900 Subject: [PATCH 19/58] feat: add CustomerGroupDTO class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 애플리케이션 내 그룹 정보 전달 --- .../core/domain/CustomerGroupDTO.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java diff --git a/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java b/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java new file mode 100644 index 00000000..fe74f826 --- /dev/null +++ b/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java @@ -0,0 +1,19 @@ +package me.smartstore.core.domain; + +import me.smartstore.enums.CustomerType; + +/** + * 스마트스토어 애플리케이션 내에서 그룹 정보 전달 + * + * @param customerType 그룹별 고객 유형 + * @param parameter 그룹별 분류 기준 + */ +public record CustomerGroupDTO(CustomerType customerType, Parameter parameter) { + public static CustomerGroupDTO from(CustomerGroup customerGroup) { + return new CustomerGroupDTO(customerGroup.getCustomerType(), customerGroup.getParameter()); + } + + public static CustomerGroupDTO of(CustomerType customerType, Parameter parameter) { + return new CustomerGroupDTO(customerType, parameter); + } +} From 6572b1e27db912fc29269e3f3e4925a8168206a4 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 02:57:48 +0900 Subject: [PATCH 20/58] feat: refactor CustomerGroupManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 고객 그룹 정보를 저장하는 데이터베이스라는 취지에 맞게 불필요한 비즈니스 로직을 제거하고 저장, 조회 기능 구현에 충실하도록 리팩토링 --- .../core/manager/CustomerGroupManager.java | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index d797d2e6..1a76d9ff 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -5,12 +5,11 @@ import java.util.Arrays; import me.smartstore.core.domain.CustomerGroup; -import me.smartstore.core.domain.Parameter; import me.smartstore.enums.CustomerType; import me.smartstore.exceptions.StoreException; /** - * 고객 그룹, 세분화 기준 관리 + * 스마트스토어의 모든 고객 그룹, 세분화 기준 저장 (Database 대체용) * * @author YongHo Shin * @version v1.0 @@ -34,12 +33,28 @@ public static CustomerGroupManager getInstance() { return customerGroupManager; } + /** + * 설정이 완료된 고객 그룹 저장 + * + * @param customerGroup 고객 그룹 + * @return 저장된 고객 그룹 + */ + public CustomerGroup save(CustomerGroup customerGroup) { + for (int idx = 0; idx < customerGroups.length; idx++) { + if (customerGroup.getCustomerType() == customerGroups[idx].getCustomerType()) { + customerGroups[idx] = customerGroup; + } + } + return customerGroup; + } + /** * @param customerType 고객 유형 * @return 일치하는 그룹 * @throws StoreException 일치하는 그룹 없음 */ - public CustomerGroup select(CustomerType customerType) throws StoreException { + public CustomerGroup selectCustomerGroupByCustomerType(CustomerType customerType) + throws StoreException { return Arrays.stream(customerGroups) .filter(customerGroup -> customerGroup.getCustomerType() == customerType) .findFirst() @@ -52,33 +67,7 @@ public CustomerGroup select(CustomerType customerType) throws StoreException { /** * @return 모든 고객 그룹 */ - public CustomerGroup[] selectAll() { + public CustomerGroup[] selectAllCustomerGroup() { return Arrays.stream(customerGroups).toArray(CustomerGroup[]::new); } - - /** - * 그룹별 세분화 기준 저장 - * - * @param customerType 고객 유형 - * @param parameter 세부화 기준 - * @return 설정이 완료된 고객 그룹 - */ - public CustomerGroup save(CustomerType customerType, Parameter parameter) { - CustomerGroup customerGroup = select(customerType); - - if (customerGroup.getParameter() == null) { - customerGroup.setParameter(parameter); - return customerGroup; - } - - if (parameter.getMinSpentTime() != null) { - customerGroup.getParameter().setMinSpentTime(parameter.getMinSpentTime()); - } - - if (parameter.getMinPayAmount() != null) { - customerGroup.getParameter().setMinPayAmount(parameter.getMinPayAmount()); - } - - return customerGroup; - } } From 008bd25b3eab36021bdef98b1b86be5d7400d371 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 03:05:31 +0900 Subject: [PATCH 21/58] feat: refactor CustomerGroupService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 고객 그룹 관리 서비스라는 취지에 맞게 비즈니스 로직을 구현 - 분류 기준이 업데이트 된 경우 고객 정보도 함께 업데이트 - 주석 추가 --- .../core/service/CustomerGroupService.java | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java index 4ef7215c..b4383dc3 100644 --- a/src/main/java/me/smartstore/core/service/CustomerGroupService.java +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -6,24 +6,66 @@ import me.smartstore.enums.CustomerType; import me.smartstore.exceptions.StoreException; +/** + * 고객 유형 관리 서비스 제공 클래스 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class CustomerGroupService { private static final CustomerGroupService customerGroupService = new CustomerGroupService(); private final CustomerGroupManager customerGroupManager = CustomerGroupManager.getInstance(); + private final CustomerService customerService = CustomerService.getInstance(); public static CustomerGroupService getInstance() { return customerGroupService; } + /** + * 고객 그룹 분류 기준 설정. 분류 기준이 새로 설정된 경우 고객 정보도 함께 업데이트 + * + * @param customerType 고객 유형 + * @param parameter 분류 기준 + * @return 설정이 완료된 고객 그룹 + * @throws StoreException 일치하는 고객 그룹이 데이터베이스 없는 경우 + */ public CustomerGroup setParameter(CustomerType customerType, Parameter parameter) throws StoreException { - return customerGroupManager.save(customerType, parameter); + CustomerGroup customerGroup = + customerGroupManager.selectCustomerGroupByCustomerType(customerType); + + if (customerGroup.getParameter() == null) { + customerGroup.setParameter(parameter); + } else { + if (parameter.getMinSpentTime() != null) { + customerGroup.getParameter().setMinSpentTime(parameter.getMinSpentTime()); + } + + if (parameter.getMinPayAmount() != null) { + customerGroup.getParameter().setMinPayAmount(parameter.getMinPayAmount()); + } + } + + customerService.classifyAllCustomers(); + + return customerGroupManager.save(customerGroup); } + /** + * @param customerType 고객 유형 + * @return 고객 유형과 일치하는 그룹 + * @throws StoreException 데이터베이스에 일치하는 그룹이 없을 경우 + */ public CustomerGroup find(CustomerType customerType) throws StoreException { - return customerGroupManager.select(customerType); + return customerGroupManager.selectCustomerGroupByCustomerType(customerType); } + /** + * @return 데이터베이스에 저장된 모든 고객 그룹 + * @throws StoreException 데이터베이스 오류 + */ public CustomerGroup[] findAll() throws StoreException { - return customerGroupManager.selectAll(); + return customerGroupManager.selectAllCustomerGroup(); } } From 085202f800ec0423fa2cce4dd4129661fc0496eb Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 03:28:04 +0900 Subject: [PATCH 22/58] =?UTF-8?q?feat:=20update=20Customer=20-=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/me/smartstore/core/domain/Customer.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/me/smartstore/core/domain/Customer.java b/src/main/java/me/smartstore/core/domain/Customer.java index ff299529..902315ce 100644 --- a/src/main/java/me/smartstore/core/domain/Customer.java +++ b/src/main/java/me/smartstore/core/domain/Customer.java @@ -99,21 +99,6 @@ public void setCustomerType(CustomerType customerType) { this.customerType = customerType; } - public void updateCustomerInfo(Customer customer) { - if (customer.getName() != null) { - this.name = customer.getName(); - } - if (customer.getUserId() != null) { - this.userId = customer.getUserId(); - } - if (customer.getSpentTime() != null) { - this.spentTime = customer.getSpentTime(); - } - if (customer.getPayAmount() != null) { - this.payAmount = customer.getPayAmount(); - } - } - @Override public boolean equals(Object o) { if (this == o) return true; From d93a899ee587b9e503c0ca8e05d1afde115839aa Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 03:46:13 +0900 Subject: [PATCH 23/58] feat: update CustomerManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - save 메서드 수정. 기존에 저장된 고객 정보가 있는 경우 해당 정보를 업데이트 하도록 수정 - 등록된 정보가 없는 경우 예외를 던지도록 수정 - 주석 추가 --- .../core/manager/CustomerManager.java | 113 +++++++++++++++--- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java index 34d3aa96..ee55f077 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -9,6 +9,13 @@ import me.smartstore.enums.CustomerType; import me.smartstore.exceptions.StoreException; +/** + * 스마트스토어 이용 고객 정보 저장 (Database 대체용) + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class CustomerManager { private static final CustomerManager customerManager = new CustomerManager(); private static final int DEFAULT_SIZE = 10; @@ -34,20 +41,34 @@ public Boolean isFull() { return size == customers.length; } + /** + * 기존에 저장되어 있는 고객인 경우 고객 정보를 새롭게 업데이트. 그렇지 않은 경우 고객 정보를 새로 저장. + * + * @param customer 고객 정보 + * @return 저장 또는 업데이트 된 고객 정보 + * @throws StoreException 기타 데이터베이스 오류 + */ public Customer save(Customer customer) throws StoreException { - if (isFull()) { - customers = Arrays.copyOf(customers, size * 2); + + for (int idx = 0; idx < customers.length; idx++) { + if (customer.getId().equals(customers[idx].getId())) { + customers[idx] = customer; + return customer; + } } + if (isFull()) customers = Arrays.copyOf(customers, size * 2); + customers[size++] = customer; return customer; } - public Customer findByIndex(int index) { - return customers[index - 1]; - } - + /** + * @param id 고객정보 관리번호 + * @return 해당 관리번호의 고객정보 + * @throws StoreException 일치하는 고객정보가 없는 경우n + */ public Customer findById(Long id) throws StoreException { return Arrays.stream(customers) .filter(Objects::nonNull) @@ -59,18 +80,36 @@ public Customer findById(Long id) throws StoreException { }); } - public Customer[] selectAll() { + /** + * @return 저장되어 있는 모든 고객정보 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectAll() throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers).filter(Objects::nonNull).toArray(Customer[]::new); } - public Customer[] selectByCustomerType(CustomerType customerType) { + /** + * @param customerType 고객 유형 + * @return 유형이 일치하는 모든 고객정보 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectByCustomerType(CustomerType customerType) throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) .toArray(Customer[]::new); } - public Customer[] selectByCustomerTypeOrderByName(CustomerType customerType) { + /** + * @param customerType 고객 유형 + * @return 유형이 일치하는 모든 고객정보를 이름에 따라 오름차순으로 정렬한 결과 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectByCustomerTypeOrderByNameAsc(CustomerType customerType) + throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -78,7 +117,14 @@ public Customer[] selectByCustomerTypeOrderByName(CustomerType customerType) { .toArray(Customer[]::new); } - public Customer[] selectByCustomerTypeOrderByNameDesc(CustomerType customerType) { + /** + * @param customerType 고객 유형 + * @return 유형이 일치하는 모든 고객정보를 이름에 따라 내림차순으로 정렬한 결과 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectByCustomerTypeOrderByNameDesc(CustomerType customerType) + throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -86,7 +132,14 @@ public Customer[] selectByCustomerTypeOrderByNameDesc(CustomerType customerType) .toArray(Customer[]::new); } - public Customer[] selectByCustomerTypeOrderBySpentTime(CustomerType customerType) { + /** + * @param customerType 고객 유형 + * @return 유형이 일치하는 모든 고객정보를 이용시간에 따라 오름차순으로 정렬한 결과 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectByCustomerTypeOrderBySpentTimeAsc(CustomerType customerType) + throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -94,7 +147,14 @@ public Customer[] selectByCustomerTypeOrderBySpentTime(CustomerType customerType .toArray(Customer[]::new); } - public Customer[] selectByCustomerTypeOrderBySpentTimeDesc(CustomerType customerType) { + /** + * @param customerType 고객 유형 + * @return 유형이 일치하는 모든 고객정보를 이용시간에 따라 내림차순으로 정렬한 결과 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectByCustomerTypeOrderBySpentTimeDesc(CustomerType customerType) + throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -102,7 +162,14 @@ public Customer[] selectByCustomerTypeOrderBySpentTimeDesc(CustomerType customer .toArray(Customer[]::new); } - public Customer[] selectByCustomerTypeOrderByPayAmount(CustomerType customerType) { + /** + * @param customerType 고객 유형 + * @return 유형이 일치하는 모든 고객정보를 결제금액에 따라 오름차순으로 정렬한 결과 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectByCustomerTypeOrderByPayAmountAsc(CustomerType customerType) + throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -110,7 +177,14 @@ public Customer[] selectByCustomerTypeOrderByPayAmount(CustomerType customerType .toArray(Customer[]::new); } - public Customer[] selectByCustomerTypeOrderByPayAmountDesc(CustomerType customerType) { + /** + * @param customerType 고객 유형 + * @return 유형이 일치하는 모든 고객정보를 결제금액에 따라 내림차순으로 정렬한 결과 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public Customer[] selectByCustomerTypeOrderByPayAmountDesc(CustomerType customerType) + throws StoreException { + if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -118,6 +192,10 @@ public Customer[] selectByCustomerTypeOrderByPayAmountDesc(CustomerType customer .toArray(Customer[]::new); } + /** + * @param id 삭제 대상의 관리 번호 + * @throws StoreException 존재하지 않는 관리 번호 + */ public void deleteById(Long id) throws StoreException { Customer target = Arrays.stream(customers) @@ -132,14 +210,21 @@ public void deleteById(Long id) throws StoreException { delete(target); } + /** + * @param customer 삭제할 고객정보 + * @throws StoreException 데이터베이스에 해당 고객정보가 없는 경우 + */ public void delete(Customer customer) throws StoreException { int idx = -1; + for (int i = 0; i < size; i++) { if (customer.getId().equals(customers[i].getId())) { idx = i; } } + if (idx == -1) throw new StoreException(NOT_EXIST_CUSTOMER); + System.arraycopy(customers, idx, customers, idx - 1, customers.length - idx); size--; } From 54bfc19fed8930c686be5b8c746862ceac5e227f Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 04:21:13 +0900 Subject: [PATCH 24/58] =?UTF-8?q?feat:=20update=20AbstractMenu=20-=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=83=81=EB=8B=A8=20=EA=B3=B5=EB=B0=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/me/smartstore/core/view/AbstractMenu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/smartstore/core/view/AbstractMenu.java b/src/main/java/me/smartstore/core/view/AbstractMenu.java index 628a9f24..46fcaf61 100644 --- a/src/main/java/me/smartstore/core/view/AbstractMenu.java +++ b/src/main/java/me/smartstore/core/view/AbstractMenu.java @@ -23,7 +23,7 @@ public AbstractMenu(String[] items) { /** 메뉴 출력 */ void show() { - StringBuilder menuBuilder = new StringBuilder().append("==============================\n"); + StringBuilder menuBuilder = new StringBuilder().append("\n==============================\n"); for (int i = 0; i < items.length; i++) { menuBuilder.append(i + 1).append(". ").append(items[i]).append("\n"); } From b4736c38af7635027d27edcc6af0b0d83d980a72 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 04:55:13 +0900 Subject: [PATCH 25/58] fix: update AppStarter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 - 출력 형식 수정 --- src/main/java/me/smartstore/AppStarter.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/me/smartstore/AppStarter.java b/src/main/java/me/smartstore/AppStarter.java index d0b2f5f9..8332fa77 100644 --- a/src/main/java/me/smartstore/AppStarter.java +++ b/src/main/java/me/smartstore/AppStarter.java @@ -12,22 +12,24 @@ public class AppStarter { private AppStarter() {} - private static class StoreAppContext { - private static final AppStarter app = new AppStarter(); - } + private static AppStarter appStarter = new AppStarter(); public static AppStarter getInstance() { - return StoreAppContext.app; + if (appStarter == null) { + appStarter = new AppStarter(); + } + + return appStarter; } public void run() { title(); - MainMenu.launch(); + MainMenu.getInstance().launch(); finish(); } private void title() { - System.out.println( + System.out.print( """ =========================================== Title : SmartStore Customer Segmentation @@ -36,6 +38,7 @@ private void title() { =========================================== """); } + private void finish() { System.out.println("Program Finished."); } From 1c63099ed5ea4e1e4e396e3ae19438a90017ea10 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 04:59:23 +0900 Subject: [PATCH 26/58] fix: update ClassificationSummaryMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 - 주석 추가 --- .../core/view/ClassificationSummaryMenu.java | 67 +++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java index c91c2264..c8512ff2 100644 --- a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java +++ b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java @@ -11,8 +11,15 @@ import me.smartstore.exceptions.StoreException; import me.smartstore.utils.ScannerUtility; +/** + * 고객 분류 정보 출력 메뉴 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class ClassificationSummaryMenu extends AbstractMenu { - private static final ClassificationSummaryMenu classificationSummaryMenu = + private static ClassificationSummaryMenu classificationSummaryMenu = new ClassificationSummaryMenu(); private static final CustomerService customerService = CustomerService.getInstance(); @@ -28,16 +35,59 @@ private ClassificationSummaryMenu() { }); } - public static void launch() { + public static ClassificationSummaryMenu getInstance() { + if (classificationSummaryMenu == null) { + classificationSummaryMenu = new ClassificationSummaryMenu(); + } + return classificationSummaryMenu; + } + + public void launch() { loop: while (true) { classificationSummaryMenu.show(); try { switch (classificationSummaryMenu.selectMenuNumber()) { + // 일반 요약 정보 출력 - 가장 최근에 조회한 정렬 기준에 따라 자동으로 변환 case 1 -> customerService.displayClassificationSummary(); - case 2 -> customerService.displayClassificationSummary(NAME, inputSortOrder()); - case 3 -> customerService.displayClassificationSummary(SPENT_TIME, inputSortOrder()); - case 4 -> customerService.displayClassificationSummary(PAY_AMOUNT, inputSortOrder()); + + // 고객 그룹별 이름순 정렬 + case 2 -> { + while (true) { + try { + customerService.displayClassificationSummary(NAME, inputSortOrder()); + } catch (StoreException e) { + if (e.getErrorCode() == INPUT_END) break; + System.out.println(e.getMessage()); + } + } + } + + // 고객 그룹별 이용시간순 정렬 + case 3 -> { + while (true) { + try { + customerService.displayClassificationSummary(SPENT_TIME, inputSortOrder()); + } catch (StoreException e) { + if (e.getErrorCode() == INPUT_END) break; + System.out.println(e.getMessage()); + } + } + } + + // 고객 그룹별 사용금액순 정렬 + case 4 -> { + while (true) { + try { + customerService.displayClassificationSummary(PAY_AMOUNT, inputSortOrder()); + } catch (StoreException e) { + if (e.getErrorCode() == INPUT_END) break; + System.out.println(e.getMessage()); + } + } + } + + // 이전 메뉴 case 5 -> { break loop; } @@ -48,7 +98,11 @@ public static void launch() { } } - private static SortOrder inputSortOrder() { + /** + * @return 정렬 방향 (오름차순, 내림차순) + * @throws StoreException 종료 선택시 + */ + private static SortOrder inputSortOrder() throws StoreException { while (true) { System.out.println(INPUT_SORT_ORDER + "\n" + PRESS_END_MSG); try { @@ -57,6 +111,7 @@ private static SortOrder inputSortOrder() { return convertInputStrToSortOrder(input); } catch (StoreException e) { System.out.println(e.getMessage()); + if (e.getErrorCode() == INPUT_END) throw new StoreException(INPUT_END); } } } From e2944e7c0568ee5e29201e667c2ed99585840507 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:00:10 +0900 Subject: [PATCH 27/58] fix: update CustomerGroupManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 --- .../me/smartstore/core/manager/CustomerGroupManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index 1a76d9ff..d39ce105 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -16,7 +16,7 @@ * @since 2023-05-10 */ public class CustomerGroupManager { - private static final CustomerGroupManager customerGroupManager = new CustomerGroupManager(); + private static CustomerGroupManager customerGroupManager = new CustomerGroupManager(); private final CustomerGroup[] customerGroups; public CustomerGroupManager() { @@ -30,6 +30,9 @@ public CustomerGroupManager() { } public static CustomerGroupManager getInstance() { + if (customerGroupManager == null) { + customerGroupManager = new CustomerGroupManager(); + } return customerGroupManager; } From 5de79f4e523bc5de9c3bf9d6fccae63ab78d2760 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:01:13 +0900 Subject: [PATCH 28/58] fix: update CustomerGroupService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 - 분류 기준 업데이트 후 새로 정렬되지 않던 오류 수정 --- .../me/smartstore/core/service/CustomerGroupService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java index b4383dc3..6ece73b5 100644 --- a/src/main/java/me/smartstore/core/service/CustomerGroupService.java +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -14,11 +14,14 @@ * @since 2023-05-10 */ public class CustomerGroupService { - private static final CustomerGroupService customerGroupService = new CustomerGroupService(); + private static CustomerGroupService customerGroupService = new CustomerGroupService(); private final CustomerGroupManager customerGroupManager = CustomerGroupManager.getInstance(); private final CustomerService customerService = CustomerService.getInstance(); public static CustomerGroupService getInstance() { + if (customerGroupService == null) { + customerGroupService = new CustomerGroupService(); + } return customerGroupService; } @@ -46,10 +49,10 @@ public CustomerGroup setParameter(CustomerType customerType, Parameter parameter customerGroup.getParameter().setMinPayAmount(parameter.getMinPayAmount()); } } - + customerGroup = customerGroupManager.save(customerGroup); customerService.classifyAllCustomers(); - return customerGroupManager.save(customerGroup); + return customerGroup; } /** From d1f3c8932314219bddd07a1605a974a116317f29 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:02:09 +0900 Subject: [PATCH 29/58] fix: update CustomerManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 - 초기 분류 기준 설정시 오류 수정 --- .../core/manager/CustomerManager.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java index ee55f077..5f3196ae 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -17,7 +17,7 @@ * @since 2023-05-10 */ public class CustomerManager { - private static final CustomerManager customerManager = new CustomerManager(); + private static CustomerManager customerManager = new CustomerManager(); private static final int DEFAULT_SIZE = 10; private static int size = 0; private static Customer[] customers = new Customer[DEFAULT_SIZE]; @@ -25,7 +25,9 @@ public class CustomerManager { private CustomerManager() {} public static CustomerManager getInstance() { - + if (customerManager == null) { + customerManager = new CustomerManager(); + } return customerManager; } @@ -45,23 +47,24 @@ public Boolean isFull() { * 기존에 저장되어 있는 고객인 경우 고객 정보를 새롭게 업데이트. 그렇지 않은 경우 고객 정보를 새로 저장. * * @param customer 고객 정보 - * @return 저장 또는 업데이트 된 고객 정보 * @throws StoreException 기타 데이터베이스 오류 */ - public Customer save(Customer customer) throws StoreException { + public void save(Customer customer) throws StoreException { + if (isEmpty()) { + customers[size++] = customer; + return; + } for (int idx = 0; idx < customers.length; idx++) { if (customer.getId().equals(customers[idx].getId())) { customers[idx] = customer; - return customer; + return; } } if (isFull()) customers = Arrays.copyOf(customers, size * 2); customers[size++] = customer; - - return customer; } /** From b308238e5986a70853b1f40699c40d01f1bcee19 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:02:56 +0900 Subject: [PATCH 30/58] fix: update CustomerMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 - 주석 추가 --- .../me/smartstore/core/view/CustomerMenu.java | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/java/me/smartstore/core/view/CustomerMenu.java b/src/main/java/me/smartstore/core/view/CustomerMenu.java index cadaa0f9..29028622 100644 --- a/src/main/java/me/smartstore/core/view/CustomerMenu.java +++ b/src/main/java/me/smartstore/core/view/CustomerMenu.java @@ -10,11 +10,19 @@ import me.smartstore.core.service.CustomerService; import me.smartstore.exceptions.StoreException; +/** + * 고객 정보 관리 메뉴 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class CustomerMenu extends AbstractMenu { - private static final CustomerMenu customerMenu = new CustomerMenu(); + private static CustomerMenu customerMenu = new CustomerMenu(); private static final CustomerService customerService = CustomerService.getInstance(); private static final CustomerSubMenu customerSubMenu = CustomerSubMenu.getInstance(); + /** 고객 정보 캐싱 */ private static CustomerDTO[] dtoCache = new CustomerDTO[] {}; private CustomerMenu() { @@ -28,26 +36,34 @@ private CustomerMenu() { }); } - public static void launch() { + public static CustomerMenu getInstance() { + if (customerMenu == null) { + customerMenu = new CustomerMenu(); + } + return customerMenu; + } + + public void launch() { loop: while (true) { customerMenu.show(); try { switch (customerMenu.selectMenuNumber()) { - // Add Customer Data + + // 고객 등록 case 1 -> { int numberOfCustomers = inputNumberOfCustomers(); for (int idx = 0; idx < numberOfCustomers; idx++) { - System.out.println("====== Customer " + (idx + 1) + ". Info. ======\n"); + System.out.println("\n====== Customer " + (idx + 1) + ". Info. ======"); CustomerDTO newCustomer = customerSubMenu.inputCustomerInfo(); - customerService.saveNewCustomer(newCustomer); + customerService.save(newCustomer); } } - // View Customer Data + // 고객 명단 확인 case 2 -> showCustomerInfo(); - // Update Customer Data + // 고객 정보 수정 case 3 -> { showCustomerInfo(); int customerNumber = inputCustomerNumber(); @@ -56,7 +72,7 @@ public static void launch() { customerService.updateCustomerById(id, updateDTO); } - // Delete Customer Data + // 고객 정보 삭제 case 4 -> { showCustomerInfo(); int customerNumber = inputCustomerNumber(); @@ -74,6 +90,11 @@ public static void launch() { } } + /** + * 현재 등록된 고객 명단 출력 + * + * @throws StoreException 등록된 고객 정보가 없음 + */ private static void showCustomerInfo() throws StoreException { // No. 1 => Customer{userId='TEST123', name='TEST', spentTime=null, totalPay=null, group=null} dtoCache = customerService.findAll(); @@ -81,12 +102,16 @@ private static void showCustomerInfo() throws StoreException { throw new StoreException(NO_CUSTOMER); } - System.out.println("======= Customer Info. =======\n"); + System.out.println("\n======= Customer Info. ======="); for (int idx = 0; idx < dtoCache.length; idx++) { System.out.println("No. " + (idx + 1) + " => " + dtoCache[idx]); } } + /** + * @return 새로 등록할 고객수 + * @throws StoreException 종료 선택시 + */ private static int inputNumberOfCustomers() throws StoreException { while (true) { System.out.println(INPUT_NUMBER_OF_CUSTOMERS + "\n" + PRESS_END_MSG); @@ -106,7 +131,10 @@ private static int inputNumberOfCustomers() throws StoreException { } } - private static int inputCustomerNumber() throws StoreException { + /** + * @return 출력된 명단 중 수정하고자 하는 고객의 번호 + */ + private static int inputCustomerNumber() { while (true) { System.out.println("Which customer ( 1 ~ " + customerService.getNumberOfCustomers() + " )? "); try { From a9c26e6bb53a5ef8c1e2e53c269c0315a5398fe2 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:05:03 +0900 Subject: [PATCH 31/58] feat: update CustomerService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 고객정보 추가 및 업데이트 기능 구현 - 고객정보 분류 및 출력 기능 구현 - 싱글톤 생성 관련 오류 수정 - 주석 추가 --- .../core/service/CustomerService.java | 133 +++++++++++------- 1 file changed, 85 insertions(+), 48 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerService.java b/src/main/java/me/smartstore/core/service/CustomerService.java index 29536fdf..eba7398d 100644 --- a/src/main/java/me/smartstore/core/service/CustomerService.java +++ b/src/main/java/me/smartstore/core/service/CustomerService.java @@ -4,14 +4,22 @@ import me.smartstore.core.domain.CustomerDTO; import me.smartstore.core.domain.CustomerGroup; import me.smartstore.core.manager.CustomerManager; +import me.smartstore.enums.CustomerType; import me.smartstore.enums.SortBy; import me.smartstore.enums.SortOrder; import me.smartstore.exceptions.StoreException; import java.util.Arrays; +/** + * 고객 정보 관리 서비스 제공 클래스 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class CustomerService { - private static final CustomerService customerService = new CustomerService(); + private static CustomerService customerService = new CustomerService(); private static final CustomerManager customerManager = CustomerManager.getInstance(); private static final CustomerGroupService customerGroupService = CustomerGroupService.getInstance(); @@ -19,18 +27,30 @@ public class CustomerService { private static SortBy lastRequestSortBy = SortBy.NAME; private static SortOrder lastRequestedSortOrder = SortOrder.ASCENDING; - private static CustomerGroup[] customerGroups; + private static CustomerGroup[] customerGroups = customerGroupService.findAll(); public static CustomerService getInstance() { + if (customerService == null) { + customerService = new CustomerService(); + } return customerService; } - public void saveNewCustomer(CustomerDTO newCustomerDTO) throws StoreException { - Customer customer = new Customer(newCustomerDTO); - System.out.println(customerManager.save(customer)); + /** + * @param customerDTO 고객 정보 + * @throws StoreException 데이터베이스 오류 + */ + public void save(CustomerDTO customerDTO) throws StoreException { + Customer customer = new Customer(customerDTO); + classifyCustomer(customer); + customerManager.save(customer); } - public CustomerDTO[] findAll() { + /** + * @return 모든 고객 정보 + * @throws StoreException 등록된 고객 정보가 없는 경우 + */ + public CustomerDTO[] findAll() throws StoreException { return Arrays.stream(customerManager.selectAll()) .map(CustomerDTO::from) .toArray(CustomerDTO[]::new); @@ -40,55 +60,68 @@ public int getNumberOfCustomers() { return customerManager.size(); } - public void updateCustomerById(Long id, CustomerDTO updated) { + /** + * @param id 업데이트 할 고객정보 관리번호 + * @param customerDTO 업데이트할 고객정보 + * @throws StoreException 데이터베이스 오류 + */ + public void updateCustomerById(Long id, CustomerDTO customerDTO) throws StoreException { Customer customer = customerManager.findById(id); + + if (customerDTO.name() != null) { + customer.setName(customerDTO.name()); + } + if (customerDTO.userId() != null) { + customer.setUserId(customerDTO.userId()); + } + if (customerDTO.spentTime() != null) { + customer.setSpentTime(customerDTO.spentTime()); + } + if (customerDTO.payAmount() != null) { + customer.setPayAmount(customerDTO.payAmount()); + } + + classifyCustomer(customer); + + customerManager.save(customer); } public void deleteCustomerById(Long id) { customerManager.deleteById(id); } - public void classifyCustomer() { - // Arrays.stream(customerGroups) - // .filter(customerGroup -> customerGroup.getParameter() != null) - // .forEach( - // customerGroup -> { - // Arrays.stream(customerManager.selectAll()) - // .filter( - // customer -> - // customer.getSpentTime() != null && customer.getPayAmount() != null) - // .forEach( - // customer -> { - // if (customerGroup.getParameter().getMinSpentTime() - // <= customer.getSpentTime() - // && customerGroup.getParameter().getMinPayAmount() - // <= customer.getPayAmount()) { - // customer.setCustomerType(customerGroup.getCustomerType()); - // } - // }); - // }); + /** + * 고객정보를 바탕으로 고객 분류 + * + * @param customer 고객정보 + */ + public void classifyCustomer(Customer customer) { + for (CustomerGroup customerGroup : customerGroups) { + if (customerGroup.getParameter() == null) { + continue; + } + if (customer.getSpentTime() == null || customer.getPayAmount() == null) { + customer.setCustomerType(CustomerType.NONE); + } else { + if (customerGroup.getParameter().getMinSpentTime() <= customer.getSpentTime() + && customerGroup.getParameter().getMinPayAmount() <= customer.getPayAmount()) { + customer.setCustomerType(customerGroup.getCustomerType()); + } + } + } + if (customer.getCustomerType() == null) { + customer.setCustomerType(CustomerType.NONE); + } } public void classifyAllCustomers() { customerGroups = customerGroupService.findAll(); - Arrays.stream(customerGroups) - .filter(customerGroup -> customerGroup.getParameter() != null) - .forEach( - customerGroup -> { - Arrays.stream(customerManager.selectAll()) - .filter( - customer -> - customer.getSpentTime() != null && customer.getPayAmount() != null) - .forEach( - customer -> { - if (customerGroup.getParameter().getMinSpentTime() - <= customer.getSpentTime() - && customerGroup.getParameter().getMinPayAmount() - <= customer.getPayAmount()) { - customer.setCustomerType(customerGroup.getCustomerType()); - } - }); - }); + Customer[] customers = customerManager.selectAll(); + + for (Customer customer : customers) { + classifyCustomer(customer); + customerManager.save(customer); + } } public void displayClassificationSummary() { @@ -110,10 +143,10 @@ public void displayClassificationSummaryByName(SortOrder sortOrder) { Arrays.stream(customerGroups) .forEach( group -> { - System.out.println(group.groupTitle()); + System.out.println("\n" + group.groupTitle()); if (sortOrder == SortOrder.ASCENDING) { Customer[] customers = - customerManager.selectByCustomerTypeOrderByName(group.getCustomerType()); + customerManager.selectByCustomerTypeOrderByNameAsc(group.getCustomerType()); for (int idx = 0; idx < customers.length; idx++) { System.out.println("No. " + (idx + 1) + " => " + customers[idx]); } @@ -134,7 +167,8 @@ public void displayClassificationSummaryBySpentTime(SortOrder sortOrder) { System.out.println(group.groupTitle()); if (sortOrder == SortOrder.ASCENDING) { Customer[] customers = - customerManager.selectByCustomerTypeOrderBySpentTime(group.getCustomerType()); + customerManager.selectByCustomerTypeOrderBySpentTimeAsc( + group.getCustomerType()); for (int idx = 0; idx < customers.length; idx++) { System.out.println("No. " + (idx + 1) + " => " + customers[idx]); } @@ -146,6 +180,7 @@ public void displayClassificationSummaryBySpentTime(SortOrder sortOrder) { System.out.println("No. " + (idx + 1) + " => " + customers[idx]); } } + System.out.println(); }); } @@ -156,7 +191,8 @@ public void displayClassificationSummaryByPayAmount(SortOrder sortOrder) { System.out.println(group.groupTitle()); if (sortOrder == SortOrder.ASCENDING) { Customer[] customers = - customerManager.selectByCustomerTypeOrderByPayAmount(group.getCustomerType()); + customerManager.selectByCustomerTypeOrderByPayAmountAsc( + group.getCustomerType()); for (int idx = 0; idx < customers.length; idx++) { System.out.println("No. " + (idx + 1) + " => " + customers[idx]); } @@ -168,6 +204,7 @@ public void displayClassificationSummaryByPayAmount(SortOrder sortOrder) { System.out.println("No. " + (idx + 1) + " => " + customers[idx]); } } + System.out.println(); }); } } From 8f2a199f8a3aaea03d47fdfc9ed117358dca2c9f Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:05:29 +0900 Subject: [PATCH 32/58] comment: update CustomerSubMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 주석 추가 --- .../smartstore/core/view/CustomerSubMenu.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/me/smartstore/core/view/CustomerSubMenu.java b/src/main/java/me/smartstore/core/view/CustomerSubMenu.java index b8c10080..967e94b5 100644 --- a/src/main/java/me/smartstore/core/view/CustomerSubMenu.java +++ b/src/main/java/me/smartstore/core/view/CustomerSubMenu.java @@ -8,6 +8,13 @@ import me.smartstore.exceptions.StoreException; import me.smartstore.utils.ScannerUtility; +/** + * 고객 세부 정보 관리 메뉴 + * + * @author YongHo Shin + * @version v1.0 + * @since 2023-05-10 + */ public class CustomerSubMenu extends AbstractMenu { private static final CustomerSubMenu customerSubMenu = new CustomerSubMenu(); @@ -22,6 +29,9 @@ public static CustomerSubMenu getInstance() { return customerSubMenu; } + /** + * @return 입력 받은 고객 세부 정보 + */ public CustomerDTO inputCustomerInfo() { String customerName = null; String customerID = null; @@ -51,6 +61,10 @@ public CustomerDTO inputCustomerInfo() { return CustomerDTO.of(customerName, customerID, customerSpentTime, customerPayAmount); } + /** + * @return 입력 받은 고객 이름 + * @throws StoreException 종료 선택시 + */ private String inputCustomerName() throws StoreException { System.out.println(INPUT_CUSTOMER_NAME + "\n" + PRESS_END_MSG); String input = ScannerUtility.getInput(); @@ -58,6 +72,10 @@ private String inputCustomerName() throws StoreException { return input; } + /** + * @return 입력 받은 고객 ID + * @throws StoreException 종료 선택시 + */ private String inputCustomerId() throws StoreException { System.out.println(INPUT_CUSTOMER_ID + "\n" + PRESS_END_MSG); String input = ScannerUtility.getInput(); @@ -65,6 +83,10 @@ private String inputCustomerId() throws StoreException { return input; } + /** + * @return 입력 받은 고객의 이용 시간 + * @throws StoreException 종료 선택시 + */ private int inputCustomerSpentTime() throws StoreException { while (true) { System.out.println(INPUT_CUSTOMER_SPENT_TIME + "\n" + PRESS_END_MSG); @@ -78,6 +100,10 @@ private int inputCustomerSpentTime() throws StoreException { } } + /** + * @return 입력 받은 고객의 결제 금액 + * @throws StoreException 종료 선택시 + */ private int inputCustomerPayAmount() throws StoreException { while (true) { System.out.println(INPUT_CUSTOMER_PAY_AMOUNT + "\n" + PRESS_END_MSG); From 481a5278f3a696ea800ee8f9a55ca5051e708f84 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:06:02 +0900 Subject: [PATCH 33/58] fix: update MainMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 - 주석 추가 --- .../me/smartstore/core/view/MainMenu.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/me/smartstore/core/view/MainMenu.java b/src/main/java/me/smartstore/core/view/MainMenu.java index b85c0d8c..11fb7931 100644 --- a/src/main/java/me/smartstore/core/view/MainMenu.java +++ b/src/main/java/me/smartstore/core/view/MainMenu.java @@ -10,28 +10,35 @@ * @since 2023-05-10 */ public class MainMenu extends AbstractMenu { - private static final MainMenu mainMenu = new MainMenu(); + private static MainMenu mainMenu = new MainMenu(); private MainMenu() { super(new String[] {"Parameter", "Customer Data", "Classification Summary", "Quit"}); } - public static void launch() { + public static MainMenu getInstance() { + if (mainMenu == null) { + mainMenu = new MainMenu(); + } + return mainMenu; + } + + public void launch() { loop: while (true) { mainMenu.show(); try { switch (mainMenu.selectMenuNumber()) { - // 고객 유형 세분화 기준 설정 - case 1 -> ParameterMenu.launch(); + // 고객 유형 세분화 기준 설정 + case 1 -> ParameterMenu.getInstance().launch(); - // 고객 정보 관리 - case 2 -> CustomerMenu.launch(); + // 고객 정보 관리 + case 2 -> CustomerMenu.getInstance().launch(); - // 고객 정보 요약 - case 3 -> ClassificationSummaryMenu.launch(); + // 고객 정보 요약 + case 3 -> ClassificationSummaryMenu.getInstance().launch(); - // 종료 + // 종료 case 4 -> { break loop; } From 6e09146ebb68cf46cde76489daeba282cfd81786 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:06:33 +0900 Subject: [PATCH 34/58] fix: update ParameterMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 --- .../java/me/smartstore/core/view/ParameterMenu.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/smartstore/core/view/ParameterMenu.java b/src/main/java/me/smartstore/core/view/ParameterMenu.java index c7a5284f..0e13616c 100644 --- a/src/main/java/me/smartstore/core/view/ParameterMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterMenu.java @@ -20,7 +20,7 @@ * @since 2023-05-10 */ public class ParameterMenu extends AbstractMenu { - private static final ParameterMenu parameterMenu = new ParameterMenu(); + private static ParameterMenu parameterMenu = new ParameterMenu(); private static final ParameterSubMenu parameterSubMenu = ParameterSubMenu.getInstance(); private static final CustomerGroupService customerGroupService = CustomerGroupService.getInstance(); @@ -29,7 +29,13 @@ private ParameterMenu() { super(new String[] {"Set Parameter", "View Parameter", "Update Parameter", "Back"}); } - public static void launch() { + public static ParameterMenu getInstance() { + if (parameterMenu == null) { + parameterMenu = new ParameterMenu(); + } + return parameterMenu; + } + public void launch() { loop: while (true) { parameterMenu.show(); From 01347f23b3d3f45efe90476f027367edc14a8613 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:06:53 +0900 Subject: [PATCH 35/58] fix: update ParameterSubMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 싱글톤 생성 관련 오류 수정 --- src/main/java/me/smartstore/core/view/ParameterSubMenu.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/me/smartstore/core/view/ParameterSubMenu.java b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java index 536e6c43..aac5f41e 100644 --- a/src/main/java/me/smartstore/core/view/ParameterSubMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java @@ -17,7 +17,7 @@ * @since 2023-05-10 */ public class ParameterSubMenu extends AbstractMenu { - private static final ParameterSubMenu parameterSubMenu = new ParameterSubMenu(); + private static ParameterSubMenu parameterSubMenu = new ParameterSubMenu(); private ParameterSubMenu() { super(new String[] {"Minimum Spent Time", "Minimum Pay Amount", "Back"}); @@ -27,6 +27,9 @@ private ParameterSubMenu() { * @return Singleton 객체 반환 */ public static ParameterSubMenu getInstance() { + if(parameterSubMenu == null) { + parameterSubMenu = new ParameterSubMenu(); + } return parameterSubMenu; } From ea9cca2977cba52240fca0e04f1b544142cb34a4 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:23:05 +0900 Subject: [PATCH 36/58] comment: update CustomerService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 주석 보완 --- .../core/service/CustomerService.java | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerService.java b/src/main/java/me/smartstore/core/service/CustomerService.java index eba7398d..2c7d38f5 100644 --- a/src/main/java/me/smartstore/core/service/CustomerService.java +++ b/src/main/java/me/smartstore/core/service/CustomerService.java @@ -86,6 +86,11 @@ public void updateCustomerById(Long id, CustomerDTO customerDTO) throws StoreExc customerManager.save(customer); } + /** + * 고객 관리 번호를 통한 조회 및 삭제 + * + * @param id 고객 관리 번호 + */ public void deleteCustomerById(Long id) { customerManager.deleteById(id); } @@ -114,6 +119,9 @@ public void classifyCustomer(Customer customer) { } } + /** + * 모든 고객 분류 + */ public void classifyAllCustomers() { customerGroups = customerGroupService.findAll(); Customer[] customers = customerManager.selectAll(); @@ -124,21 +132,35 @@ public void classifyAllCustomers() { } } + /** + * 고객 분류 및 요약 정보 출력. 가장 최근에 조회한 기준에 따라 자동으로 정렬. + */ public void displayClassificationSummary() { displayClassificationSummary(lastRequestSortBy, lastRequestedSortOrder); } - public void displayClassificationSummary(SortBy sortby, SortOrder sortOrder) { + /** + * 고객 분류 결과를 기준에 따라 정렬하여 출력. + * + * @param sortBy 정렬기준 + * @param sortOrder 정렬순서 (오름차순, 내림차순) + */ + public void displayClassificationSummary(SortBy sortBy, SortOrder sortOrder) { classifyAllCustomers(); - lastRequestSortBy = sortby; + lastRequestSortBy = sortBy; lastRequestedSortOrder = sortOrder; - switch (sortby) { + switch (sortBy) { case NAME -> displayClassificationSummaryByName(sortOrder); case SPENT_TIME -> displayClassificationSummaryBySpentTime(sortOrder); case PAY_AMOUNT -> displayClassificationSummaryByPayAmount(sortOrder); } } + /** + * 고객 분류 결과를 이름순으로 정렬하여 출력. + * + * @param sortOrder 정렬순서 (오름차순, 내림차순) + */ public void displayClassificationSummaryByName(SortOrder sortOrder) { Arrays.stream(customerGroups) .forEach( @@ -160,6 +182,11 @@ public void displayClassificationSummaryByName(SortOrder sortOrder) { }); } + /** + * 고객 분류 결과를 이용시간순으로 정렬하여 출력. + * + * @param sortOrder 정렬순서 (오름차순, 내림차순) + */ public void displayClassificationSummaryBySpentTime(SortOrder sortOrder) { Arrays.stream(customerGroups) .forEach( @@ -184,6 +211,11 @@ public void displayClassificationSummaryBySpentTime(SortOrder sortOrder) { }); } + /** + * 고객 분류 결과를 사용금액순으로 정렬하여 출력. + * + * @param sortOrder 정렬순서 (오름차순, 내림차순) + */ public void displayClassificationSummaryByPayAmount(SortOrder sortOrder) { Arrays.stream(customerGroups) .forEach( From d3bc6efe3b2de92b93532d20f660f8ad6af08eb4 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:27:35 +0900 Subject: [PATCH 37/58] fix: remove unnecessary static keyword MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 불필요한 static 키워드 모두 제거 --- .../me/smartstore/core/view/ClassificationSummaryMenu.java | 2 +- src/main/java/me/smartstore/core/view/CustomerMenu.java | 6 +++--- src/main/java/me/smartstore/core/view/ParameterMenu.java | 2 +- src/main/java/me/smartstore/core/view/ParameterSubMenu.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java index c8512ff2..af8be84e 100644 --- a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java +++ b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java @@ -102,7 +102,7 @@ public void launch() { * @return 정렬 방향 (오름차순, 내림차순) * @throws StoreException 종료 선택시 */ - private static SortOrder inputSortOrder() throws StoreException { + private SortOrder inputSortOrder() throws StoreException { while (true) { System.out.println(INPUT_SORT_ORDER + "\n" + PRESS_END_MSG); try { diff --git a/src/main/java/me/smartstore/core/view/CustomerMenu.java b/src/main/java/me/smartstore/core/view/CustomerMenu.java index 29028622..f889ccd7 100644 --- a/src/main/java/me/smartstore/core/view/CustomerMenu.java +++ b/src/main/java/me/smartstore/core/view/CustomerMenu.java @@ -95,7 +95,7 @@ public void launch() { * * @throws StoreException 등록된 고객 정보가 없음 */ - private static void showCustomerInfo() throws StoreException { + private void showCustomerInfo() throws StoreException { // No. 1 => Customer{userId='TEST123', name='TEST', spentTime=null, totalPay=null, group=null} dtoCache = customerService.findAll(); if (dtoCache.length == 0) { @@ -112,7 +112,7 @@ private static void showCustomerInfo() throws StoreException { * @return 새로 등록할 고객수 * @throws StoreException 종료 선택시 */ - private static int inputNumberOfCustomers() throws StoreException { + private int inputNumberOfCustomers() throws StoreException { while (true) { System.out.println(INPUT_NUMBER_OF_CUSTOMERS + "\n" + PRESS_END_MSG); String input = getInput(); @@ -134,7 +134,7 @@ private static int inputNumberOfCustomers() throws StoreException { /** * @return 출력된 명단 중 수정하고자 하는 고객의 번호 */ - private static int inputCustomerNumber() { + private int inputCustomerNumber() { while (true) { System.out.println("Which customer ( 1 ~ " + customerService.getNumberOfCustomers() + " )? "); try { diff --git a/src/main/java/me/smartstore/core/view/ParameterMenu.java b/src/main/java/me/smartstore/core/view/ParameterMenu.java index 0e13616c..7ddef60c 100644 --- a/src/main/java/me/smartstore/core/view/ParameterMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterMenu.java @@ -94,7 +94,7 @@ public void launch() { * @return 입력받은 고객 유형 * @throws StoreException 종료 선택시, 공백 입력시 발생 */ - private static CustomerType inputCustomerType() throws StoreException { + private CustomerType inputCustomerType() throws StoreException { while (true) { System.out.println(INPUT_CUSTOMER_GROUP_MSG + "\n" + PRESS_END_MSG); String input = ScannerUtility.getInput().toUpperCase(); diff --git a/src/main/java/me/smartstore/core/view/ParameterSubMenu.java b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java index aac5f41e..00072114 100644 --- a/src/main/java/me/smartstore/core/view/ParameterSubMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterSubMenu.java @@ -61,7 +61,7 @@ public Parameter inputParameter() { * @return 입력받은 최소 이용시간 * @throws StoreException 종료 선택시. */ - private static int inputMinimumSpentTime() throws StoreException { + private int inputMinimumSpentTime() throws StoreException { while (true) { System.out.println(INPUT_MINIMUM_SPENT_TIME_MSG + "\n" + PRESS_END_MSG); @@ -81,7 +81,7 @@ private static int inputMinimumSpentTime() throws StoreException { * @return 입력받은 최소 결제금액 * @throws StoreException 종료 선택시. */ - private static int inputMinimumPayAmount() throws StoreException { + private int inputMinimumPayAmount() throws StoreException { while (true) { System.out.println(INPUT_MINIMUM_PAY_AMOUNT_MSG + "\n" + PRESS_END_MSG); From 7d47a81d5dd2615f7eee5221eae30b9c0e251447 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:33:08 +0900 Subject: [PATCH 38/58] fix: update CustomerManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 삭제시 발생하던 배열 인덱스 에러 수정 --- src/main/java/me/smartstore/core/manager/CustomerManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java index 5f3196ae..f0b4cebd 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -228,7 +228,7 @@ public void delete(Customer customer) throws StoreException { if (idx == -1) throw new StoreException(NOT_EXIST_CUSTOMER); - System.arraycopy(customers, idx, customers, idx - 1, customers.length - idx); + System.arraycopy(customers, idx + 1, customers, idx, size - idx); size--; } } From 21aaac0300d2a1eeee00a965918f3518c79983d2 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 05:51:25 +0900 Subject: [PATCH 39/58] fix: update AbstractMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예외 제대로 던지지 않던 부분 수정 --- src/main/java/me/smartstore/core/view/AbstractMenu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/smartstore/core/view/AbstractMenu.java b/src/main/java/me/smartstore/core/view/AbstractMenu.java index 46fcaf61..4914cc31 100644 --- a/src/main/java/me/smartstore/core/view/AbstractMenu.java +++ b/src/main/java/me/smartstore/core/view/AbstractMenu.java @@ -36,7 +36,7 @@ void show() { * * @return 입력된 메뉴 번호 */ - int selectMenuNumber() { + int selectMenuNumber() throws StoreException { System.out.print("Choose One: "); int selection = ScannerUtility.getIntegerInputSafely(); From 69d31e1b9c0e103c598096c944b14489eb621370 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 06:06:02 +0900 Subject: [PATCH 40/58] =?UTF-8?q?typo:=20CustomerService=20-=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/me/smartstore/core/service/CustomerService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerService.java b/src/main/java/me/smartstore/core/service/CustomerService.java index 2c7d38f5..f683471f 100644 --- a/src/main/java/me/smartstore/core/service/CustomerService.java +++ b/src/main/java/me/smartstore/core/service/CustomerService.java @@ -24,7 +24,7 @@ public class CustomerService { private static final CustomerGroupService customerGroupService = CustomerGroupService.getInstance(); - private static SortBy lastRequestSortBy = SortBy.NAME; + private static SortBy lastRequestedSortBy = SortBy.NAME; private static SortOrder lastRequestedSortOrder = SortOrder.ASCENDING; private static CustomerGroup[] customerGroups = customerGroupService.findAll(); @@ -136,7 +136,7 @@ public void classifyAllCustomers() { * 고객 분류 및 요약 정보 출력. 가장 최근에 조회한 기준에 따라 자동으로 정렬. */ public void displayClassificationSummary() { - displayClassificationSummary(lastRequestSortBy, lastRequestedSortOrder); + displayClassificationSummary(lastRequestedSortBy, lastRequestedSortOrder); } /** @@ -147,7 +147,7 @@ public void displayClassificationSummary() { */ public void displayClassificationSummary(SortBy sortBy, SortOrder sortOrder) { classifyAllCustomers(); - lastRequestSortBy = sortBy; + lastRequestedSortBy = sortBy; lastRequestedSortOrder = sortOrder; switch (sortBy) { case NAME -> displayClassificationSummaryByName(sortOrder); From e0d9d62708e060f21598a0c886894a9d6b002eba Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 06:14:25 +0900 Subject: [PATCH 41/58] feat: refactor CustomerManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 고객정보 삭제시 불필요한 중복 조회 생략하도록 수정. --- .../core/manager/CustomerManager.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java index f0b4cebd..0a15a912 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -200,17 +200,18 @@ public Customer[] selectByCustomerTypeOrderByPayAmountDesc(CustomerType customer * @throws StoreException 존재하지 않는 관리 번호 */ public void deleteById(Long id) throws StoreException { - Customer target = - Arrays.stream(customers) - .filter(Objects::nonNull) - .filter(customer -> id.equals(customer.getId())) - .findFirst() - .orElseThrow( - () -> { - throw new StoreException(NOT_EXIST_ID); - }); - - delete(target); + int idx = -1; + + for (int i = 0; i < size; i++) { + if (id.equals(customers[i].getId())) { + idx = i; + } + } + + if (idx == -1) throw new StoreException(NOT_EXIST_ID); + + System.arraycopy(customers, idx + 1, customers, idx, size - idx); + size--; } /** From d3169eba784cc09d1af53c8678610b16870ab0d6 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 16:10:14 +0900 Subject: [PATCH 42/58] feat: update CustomerService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 그룹 타이틀 위에 공백 삽입하여 가독성 재고 --- src/main/java/me/smartstore/core/service/CustomerService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerService.java b/src/main/java/me/smartstore/core/service/CustomerService.java index f683471f..2ea55e21 100644 --- a/src/main/java/me/smartstore/core/service/CustomerService.java +++ b/src/main/java/me/smartstore/core/service/CustomerService.java @@ -191,7 +191,7 @@ public void displayClassificationSummaryBySpentTime(SortOrder sortOrder) { Arrays.stream(customerGroups) .forEach( group -> { - System.out.println(group.groupTitle()); + System.out.println("\n" + group.groupTitle()); if (sortOrder == SortOrder.ASCENDING) { Customer[] customers = customerManager.selectByCustomerTypeOrderBySpentTimeAsc( @@ -220,7 +220,7 @@ public void displayClassificationSummaryByPayAmount(SortOrder sortOrder) { Arrays.stream(customerGroups) .forEach( group -> { - System.out.println(group.groupTitle()); + System.out.println("\n" + group.groupTitle()); if (sortOrder == SortOrder.ASCENDING) { Customer[] customers = customerManager.selectByCustomerTypeOrderByPayAmountAsc( From a115b5b4e4f8bb9b2db957bfafe56398f3950fdb Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 18:58:35 +0900 Subject: [PATCH 43/58] fix: update CustomerManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 둘이상의 고객 정보 등록시 발생하던 오류 수정 - 예제 프로그램과 동일한 동작을 위해 예외 처리 부분 수정 - 삭제 이후 불필요하게 남아있는 고객 정보 완벽하게 제거 --- .../core/manager/CustomerManager.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java index 0a15a912..17bbccb4 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -55,7 +55,7 @@ public void save(Customer customer) throws StoreException { return; } - for (int idx = 0; idx < customers.length; idx++) { + for (int idx = 0; idx < size; idx++) { if (customer.getId().equals(customers[idx].getId())) { customers[idx] = customer; return; @@ -88,14 +88,13 @@ public Customer findById(Long id) throws StoreException { * @throws StoreException 등록된 고객 정보가 없는 경우 */ public Customer[] selectAll() throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers).filter(Objects::nonNull).toArray(Customer[]::new); } /** * @param customerType 고객 유형 * @return 유형이 일치하는 모든 고객정보 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerType(CustomerType customerType) throws StoreException { if (isEmpty()) throw new StoreException(NO_CUSTOMER); @@ -108,11 +107,10 @@ public Customer[] selectByCustomerType(CustomerType customerType) throws StoreEx /** * @param customerType 고객 유형 * @return 유형이 일치하는 모든 고객정보를 이름에 따라 오름차순으로 정렬한 결과 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerTypeOrderByNameAsc(CustomerType customerType) throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -123,11 +121,10 @@ public Customer[] selectByCustomerTypeOrderByNameAsc(CustomerType customerType) /** * @param customerType 고객 유형 * @return 유형이 일치하는 모든 고객정보를 이름에 따라 내림차순으로 정렬한 결과 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerTypeOrderByNameDesc(CustomerType customerType) throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -138,11 +135,10 @@ public Customer[] selectByCustomerTypeOrderByNameDesc(CustomerType customerType) /** * @param customerType 고객 유형 * @return 유형이 일치하는 모든 고객정보를 이용시간에 따라 오름차순으로 정렬한 결과 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerTypeOrderBySpentTimeAsc(CustomerType customerType) throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -153,11 +149,10 @@ public Customer[] selectByCustomerTypeOrderBySpentTimeAsc(CustomerType customerT /** * @param customerType 고객 유형 * @return 유형이 일치하는 모든 고객정보를 이용시간에 따라 내림차순으로 정렬한 결과 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerTypeOrderBySpentTimeDesc(CustomerType customerType) throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -168,11 +163,10 @@ public Customer[] selectByCustomerTypeOrderBySpentTimeDesc(CustomerType customer /** * @param customerType 고객 유형 * @return 유형이 일치하는 모든 고객정보를 결제금액에 따라 오름차순으로 정렬한 결과 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerTypeOrderByPayAmountAsc(CustomerType customerType) throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -183,11 +177,10 @@ public Customer[] selectByCustomerTypeOrderByPayAmountAsc(CustomerType customerT /** * @param customerType 고객 유형 * @return 유형이 일치하는 모든 고객정보를 결제금액에 따라 내림차순으로 정렬한 결과 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerTypeOrderByPayAmountDesc(CustomerType customerType) throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -212,6 +205,10 @@ public void deleteById(Long id) throws StoreException { System.arraycopy(customers, idx + 1, customers, idx, size - idx); size--; + + for (int i = size; i < customers.length; i++) { + customers[i] = null; + } } /** @@ -231,5 +228,9 @@ public void delete(Customer customer) throws StoreException { System.arraycopy(customers, idx + 1, customers, idx, size - idx); size--; + + for (int i = size; i < customers.length; i++) { + customers[i] = null; + } } } From b4edfbf18b78678a328c6c22657c2dbab3a36e02 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 19:00:33 +0900 Subject: [PATCH 44/58] fix: update CustomerMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 요구사항 준수를 위해 고객 정보 삭제 후 결과 출력하도록 변경 --- src/main/java/me/smartstore/core/view/CustomerMenu.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/me/smartstore/core/view/CustomerMenu.java b/src/main/java/me/smartstore/core/view/CustomerMenu.java index f889ccd7..6a51d543 100644 --- a/src/main/java/me/smartstore/core/view/CustomerMenu.java +++ b/src/main/java/me/smartstore/core/view/CustomerMenu.java @@ -78,6 +78,7 @@ public void launch() { int customerNumber = inputCustomerNumber(); Long id = dtoCache[customerNumber - 1].id(); customerService.deleteCustomerById(id); + showCustomerInfo(); } case 5 -> { From 6884935d1f31a8f030a53beabb693bd4ecb3c0d6 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 19:04:44 +0900 Subject: [PATCH 45/58] feat: refactor CustomerGroup, update CustomerGroupDTO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 엔티티 역할에 충실하도록 toString, groupTitle 등 출력 부분 DTO 로 이동. 이후 메인 비즈니스 로직에서 DTO 활용. --- .../smartstore/core/domain/CustomerGroup.java | 32 ++----------------- .../core/domain/CustomerGroupDTO.java | 23 +++++++++++++ 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/main/java/me/smartstore/core/domain/CustomerGroup.java b/src/main/java/me/smartstore/core/domain/CustomerGroup.java index 96fe3ed0..3762b106 100644 --- a/src/main/java/me/smartstore/core/domain/CustomerGroup.java +++ b/src/main/java/me/smartstore/core/domain/CustomerGroup.java @@ -3,7 +3,7 @@ import me.smartstore.enums.CustomerType; /** - * 고객 유형별 세부 기준 관리를 위한 그룹화 클래스 + * 고객 유형별 세부 기준 관리를 위한 고객그룹 엔티티 클래스 * * @author YongHo Shin * @version v1.0 @@ -12,7 +12,7 @@ * @see Parameter */ public class CustomerGroup { - private CustomerType customerType; + private final CustomerType customerType; private Parameter parameter; public CustomerGroup(CustomerType customerType, Parameter parameter) { @@ -28,10 +28,6 @@ public Parameter getParameter() { return parameter; } - public void setCustomerType(CustomerType customerType) { - this.customerType = customerType; - } - public void setParameter(Parameter parameter) { if (this.parameter == null) { this.parameter = parameter; @@ -44,28 +40,4 @@ public void setParameter(Parameter parameter) { } } } - - public String groupTitle() { - StringBuilder sb = new StringBuilder(); - sb.append("==============================\n"); - sb.append("Group: ").append(customerType); - if (parameter == null) { - sb.append(" ( Time : null, Pay Amount : null )\n"); - } else { - sb.append(" ( Time :") - .append(parameter.getMinSpentTime()) - .append(", Pay Amount :") - .append(parameter.getMinPayAmount()) - .append(" )\n"); - } - sb.append("=============================="); - - return sb.toString(); - } - - @Override - public String toString() { - String parameterStr = parameter == null ? "null" : parameter.toString(); - return "GroupType: " + customerType + "\nParameter: " + parameterStr; - } } diff --git a/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java b/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java index fe74f826..c2813db0 100644 --- a/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java +++ b/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java @@ -16,4 +16,27 @@ public static CustomerGroupDTO from(CustomerGroup customerGroup) { public static CustomerGroupDTO of(CustomerType customerType, Parameter parameter) { return new CustomerGroupDTO(customerType, parameter); } + + public String groupTitle() { + StringBuilder sb = new StringBuilder(); + sb.append("==============================\n"); + sb.append("Group: ").append(customerType); + if (parameter == null) { + sb.append(" ( Time : null, Pay Amount : null )\n"); + } else { + sb.append(" ( Time :") + .append(parameter.getMinSpentTime()) + .append(", Pay Amount :") + .append(parameter.getMinPayAmount()) + .append(" )\n"); + } + sb.append("=============================="); + + return sb.toString(); + } + @Override + public String toString() { + String parameterStr = parameter == null ? "null" : parameter.toString(); + return "CustomerType: " + customerType + "\nParameter: " + parameterStr; + } } From 7a6439e64d09e312a295835c03d4efd5eaccc41b Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 19:09:36 +0900 Subject: [PATCH 46/58] feat: update ClassificationSummaryMenu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 비즈니스 로직과 뷰를 분리. 출력은 뷰에서 이루어지도록 변경 - DTO를 이용하여 데이터를 전달하도록 변경 --- .../core/service/CustomerGroupService.java | 18 +- .../core/service/CustomerService.java | 165 +++++++++--------- .../core/view/ClassificationSummaryMenu.java | 43 ++++- .../smartstore/core/view/ParameterMenu.java | 40 +++-- 4 files changed, 152 insertions(+), 114 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java index 6ece73b5..e03ed904 100644 --- a/src/main/java/me/smartstore/core/service/CustomerGroupService.java +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -1,6 +1,8 @@ package me.smartstore.core.service; +import java.util.Arrays; import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.CustomerGroupDTO; import me.smartstore.core.domain.Parameter; import me.smartstore.core.manager.CustomerGroupManager; import me.smartstore.enums.CustomerType; @@ -33,7 +35,7 @@ public static CustomerGroupService getInstance() { * @return 설정이 완료된 고객 그룹 * @throws StoreException 일치하는 고객 그룹이 데이터베이스 없는 경우 */ - public CustomerGroup setParameter(CustomerType customerType, Parameter parameter) + public CustomerGroupDTO setParameter(CustomerType customerType, Parameter parameter) throws StoreException { CustomerGroup customerGroup = customerGroupManager.selectCustomerGroupByCustomerType(customerType); @@ -49,10 +51,11 @@ public CustomerGroup setParameter(CustomerType customerType, Parameter parameter customerGroup.getParameter().setMinPayAmount(parameter.getMinPayAmount()); } } + customerGroup = customerGroupManager.save(customerGroup); customerService.classifyAllCustomers(); - return customerGroup; + return CustomerGroupDTO.from(customerGroup); } /** @@ -60,15 +63,18 @@ public CustomerGroup setParameter(CustomerType customerType, Parameter parameter * @return 고객 유형과 일치하는 그룹 * @throws StoreException 데이터베이스에 일치하는 그룹이 없을 경우 */ - public CustomerGroup find(CustomerType customerType) throws StoreException { - return customerGroupManager.selectCustomerGroupByCustomerType(customerType); + public CustomerGroupDTO find(CustomerType customerType) throws StoreException { + return CustomerGroupDTO.from( + customerGroupManager.selectCustomerGroupByCustomerType(customerType)); } /** * @return 데이터베이스에 저장된 모든 고객 그룹 * @throws StoreException 데이터베이스 오류 */ - public CustomerGroup[] findAll() throws StoreException { - return customerGroupManager.selectAllCustomerGroup(); + public CustomerGroupDTO[] findAll() throws StoreException { + return Arrays.stream(customerGroupManager.selectAllCustomerGroup()) + .map(CustomerGroupDTO::from) + .toArray(CustomerGroupDTO[]::new); } } diff --git a/src/main/java/me/smartstore/core/service/CustomerService.java b/src/main/java/me/smartstore/core/service/CustomerService.java index 2ea55e21..1da03c58 100644 --- a/src/main/java/me/smartstore/core/service/CustomerService.java +++ b/src/main/java/me/smartstore/core/service/CustomerService.java @@ -1,16 +1,15 @@ package me.smartstore.core.service; +import java.util.Arrays; import me.smartstore.core.domain.Customer; import me.smartstore.core.domain.CustomerDTO; -import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.CustomerGroupDTO; import me.smartstore.core.manager.CustomerManager; import me.smartstore.enums.CustomerType; import me.smartstore.enums.SortBy; import me.smartstore.enums.SortOrder; import me.smartstore.exceptions.StoreException; -import java.util.Arrays; - /** * 고객 정보 관리 서비스 제공 클래스 * @@ -24,10 +23,13 @@ public class CustomerService { private static final CustomerGroupService customerGroupService = CustomerGroupService.getInstance(); + ///////////////////////////////////////////////////////// + // 요청 사항 처리를 위한 캐싱 역할 변수들 private static SortBy lastRequestedSortBy = SortBy.NAME; private static SortOrder lastRequestedSortOrder = SortOrder.ASCENDING; - - private static CustomerGroup[] customerGroups = customerGroupService.findAll(); + private static CustomerGroupDTO[] customerGroupDTOs = customerGroupService.findAll(); + private static final CustomerDTO[][] classifiedCustomerDTOs = + new CustomerDTO[customerGroupDTOs.length][]; public static CustomerService getInstance() { if (customerService == null) { @@ -48,7 +50,7 @@ public void save(CustomerDTO customerDTO) throws StoreException { /** * @return 모든 고객 정보 - * @throws StoreException 등록된 고객 정보가 없는 경우 + * @throws StoreException 데이터베이스 오류 */ public CustomerDTO[] findAll() throws StoreException { return Arrays.stream(customerManager.selectAll()) @@ -101,16 +103,16 @@ public void deleteCustomerById(Long id) { * @param customer 고객정보 */ public void classifyCustomer(Customer customer) { - for (CustomerGroup customerGroup : customerGroups) { - if (customerGroup.getParameter() == null) { + for (CustomerGroupDTO customerGroupDTO : customerGroupDTOs) { + if (customerGroupDTO.parameter() == null) { continue; } if (customer.getSpentTime() == null || customer.getPayAmount() == null) { customer.setCustomerType(CustomerType.NONE); } else { - if (customerGroup.getParameter().getMinSpentTime() <= customer.getSpentTime() - && customerGroup.getParameter().getMinPayAmount() <= customer.getPayAmount()) { - customer.setCustomerType(customerGroup.getCustomerType()); + if (customerGroupDTO.parameter().getMinSpentTime() <= customer.getSpentTime() + && customerGroupDTO.parameter().getMinPayAmount() <= customer.getPayAmount()) { + customer.setCustomerType(customerGroupDTO.customerType()); } } } @@ -119,11 +121,9 @@ public void classifyCustomer(Customer customer) { } } - /** - * 모든 고객 분류 - */ + /** 모든 고객 분류 */ public void classifyAllCustomers() { - customerGroups = customerGroupService.findAll(); + customerGroupDTOs = customerGroupService.findAll(); Customer[] customers = customerManager.selectAll(); for (Customer customer : customers) { @@ -132,111 +132,102 @@ public void classifyAllCustomers() { } } - /** - * 고객 분류 및 요약 정보 출력. 가장 최근에 조회한 기준에 따라 자동으로 정렬. - */ - public void displayClassificationSummary() { - displayClassificationSummary(lastRequestedSortBy, lastRequestedSortOrder); + /** 고객 분류 및 요약 정보 출력. 가장 최근에 조회한 기준에 따라 자동으로 정렬. */ + public CustomerDTO[][] getClassifiedCustomerData() { + return getClassifiedCustomerData(lastRequestedSortBy, lastRequestedSortOrder); } /** - * 고객 분류 결과를 기준에 따라 정렬하여 출력. + * 고객 분류 결과를 기준에 따라 정렬한 결과를 뷰로 전달 * * @param sortBy 정렬기준 * @param sortOrder 정렬순서 (오름차순, 내림차순) */ - public void displayClassificationSummary(SortBy sortBy, SortOrder sortOrder) { + public CustomerDTO[][] getClassifiedCustomerData(SortBy sortBy, SortOrder sortOrder) { classifyAllCustomers(); lastRequestedSortBy = sortBy; lastRequestedSortOrder = sortOrder; switch (sortBy) { - case NAME -> displayClassificationSummaryByName(sortOrder); - case SPENT_TIME -> displayClassificationSummaryBySpentTime(sortOrder); - case PAY_AMOUNT -> displayClassificationSummaryByPayAmount(sortOrder); + case NAME -> groupingCustomersByCustomerTypeSortByName(sortOrder); + case SPENT_TIME -> groupingCustomersByCustomerTypeSortBySpentTime(sortOrder); + case PAY_AMOUNT -> groupingCustomersByCustomerTypeSortByPayAmount(sortOrder); } + + return classifiedCustomerDTOs; } /** - * 고객 분류 결과를 이름순으로 정렬하여 출력. + * 고객 분류 결과를 이름순으로 정렬. * * @param sortOrder 정렬순서 (오름차순, 내림차순) */ - public void displayClassificationSummaryByName(SortOrder sortOrder) { - Arrays.stream(customerGroups) - .forEach( - group -> { - System.out.println("\n" + group.groupTitle()); - if (sortOrder == SortOrder.ASCENDING) { - Customer[] customers = - customerManager.selectByCustomerTypeOrderByNameAsc(group.getCustomerType()); - for (int idx = 0; idx < customers.length; idx++) { - System.out.println("No. " + (idx + 1) + " => " + customers[idx]); - } - } else { - Customer[] customers = - customerManager.selectByCustomerTypeOrderByNameDesc(group.getCustomerType()); - for (int idx = 0; idx < customers.length; idx++) { - System.out.println("No. " + (idx + 1) + " => " + customers[idx]); - } - } - }); + private void groupingCustomersByCustomerTypeSortByName(SortOrder sortOrder) { + for (int gIdx = 0; gIdx < customerGroupDTOs.length; gIdx++) { + if (sortOrder == SortOrder.ASCENDING) { + classifiedCustomerDTOs[gIdx] = + Arrays.stream( + customerManager.selectByCustomerTypeOrderByNameAsc( + customerGroupDTOs[gIdx].customerType())) + .map(CustomerDTO::from) + .toArray(CustomerDTO[]::new); + } else { + classifiedCustomerDTOs[gIdx] = + Arrays.stream( + customerManager.selectByCustomerTypeOrderByNameDesc( + customerGroupDTOs[gIdx].customerType())) + .map(CustomerDTO::from) + .toArray(CustomerDTO[]::new); + } + } } /** - * 고객 분류 결과를 이용시간순으로 정렬하여 출력. + * 고객 분류 결과를 이용시간순으로 정렬. * * @param sortOrder 정렬순서 (오름차순, 내림차순) */ - public void displayClassificationSummaryBySpentTime(SortOrder sortOrder) { - Arrays.stream(customerGroups) - .forEach( - group -> { - System.out.println("\n" + group.groupTitle()); - if (sortOrder == SortOrder.ASCENDING) { - Customer[] customers = + private void groupingCustomersByCustomerTypeSortBySpentTime(SortOrder sortOrder) { + for (int gIdx = 0; gIdx < customerGroupDTOs.length; gIdx++) { + if (sortOrder == SortOrder.ASCENDING) { + classifiedCustomerDTOs[gIdx] = + Arrays.stream( customerManager.selectByCustomerTypeOrderBySpentTimeAsc( - group.getCustomerType()); - for (int idx = 0; idx < customers.length; idx++) { - System.out.println("No. " + (idx + 1) + " => " + customers[idx]); - } - } else { - Customer[] customers = + customerGroupDTOs[gIdx].customerType())) + .map(CustomerDTO::from) + .toArray(CustomerDTO[]::new); + } else { + classifiedCustomerDTOs[gIdx] = + Arrays.stream( customerManager.selectByCustomerTypeOrderBySpentTimeDesc( - group.getCustomerType()); - for (int idx = 0; idx < customers.length; idx++) { - System.out.println("No. " + (idx + 1) + " => " + customers[idx]); - } - } - System.out.println(); - }); + customerGroupDTOs[gIdx].customerType())) + .map(CustomerDTO::from) + .toArray(CustomerDTO[]::new); + } + } } /** - * 고객 분류 결과를 사용금액순으로 정렬하여 출력. + * 고객 분류 결과를 사용금액순으로 정렬. * * @param sortOrder 정렬순서 (오름차순, 내림차순) */ - public void displayClassificationSummaryByPayAmount(SortOrder sortOrder) { - Arrays.stream(customerGroups) - .forEach( - group -> { - System.out.println("\n" + group.groupTitle()); - if (sortOrder == SortOrder.ASCENDING) { - Customer[] customers = + private void groupingCustomersByCustomerTypeSortByPayAmount(SortOrder sortOrder) { + for (int gIdx = 0; gIdx < customerGroupDTOs.length; gIdx++) { + if (sortOrder == SortOrder.ASCENDING) { + classifiedCustomerDTOs[gIdx] = + Arrays.stream( customerManager.selectByCustomerTypeOrderByPayAmountAsc( - group.getCustomerType()); - for (int idx = 0; idx < customers.length; idx++) { - System.out.println("No. " + (idx + 1) + " => " + customers[idx]); - } - } else { - Customer[] customers = + customerGroupDTOs[gIdx].customerType())) + .map(CustomerDTO::from) + .toArray(CustomerDTO[]::new); + } else { + classifiedCustomerDTOs[gIdx] = + Arrays.stream( customerManager.selectByCustomerTypeOrderByPayAmountDesc( - group.getCustomerType()); - for (int idx = 0; idx < customers.length; idx++) { - System.out.println("No. " + (idx + 1) + " => " + customers[idx]); - } - } - System.out.println(); - }); + customerGroupDTOs[gIdx].customerType())) + .map(CustomerDTO::from) + .toArray(CustomerDTO[]::new); + } + } } } diff --git a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java index af8be84e..34a52090 100644 --- a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java +++ b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java @@ -6,7 +6,11 @@ import static me.smartstore.exceptions.StoreErrorCode.INPUT_END; import static me.smartstore.utils.StoreUtility.convertInputStrToSortOrder; +import me.smartstore.core.domain.CustomerDTO; +import me.smartstore.core.domain.CustomerGroupDTO; +import me.smartstore.core.service.CustomerGroupService; import me.smartstore.core.service.CustomerService; +import me.smartstore.enums.SortBy; import me.smartstore.enums.SortOrder; import me.smartstore.exceptions.StoreException; import me.smartstore.utils.ScannerUtility; @@ -22,6 +26,8 @@ public class ClassificationSummaryMenu extends AbstractMenu { private static ClassificationSummaryMenu classificationSummaryMenu = new ClassificationSummaryMenu(); + private static final CustomerGroupService customerGroupService = + CustomerGroupService.getInstance(); private static final CustomerService customerService = CustomerService.getInstance(); private ClassificationSummaryMenu() { @@ -49,13 +55,13 @@ public void launch() { try { switch (classificationSummaryMenu.selectMenuNumber()) { // 일반 요약 정보 출력 - 가장 최근에 조회한 정렬 기준에 따라 자동으로 변환 - case 1 -> customerService.displayClassificationSummary(); + case 1 -> displayClassifiedCustomerData(); // 고객 그룹별 이름순 정렬 case 2 -> { while (true) { try { - customerService.displayClassificationSummary(NAME, inputSortOrder()); + displayClassifiedCustomerData(NAME, inputSortOrder()); } catch (StoreException e) { if (e.getErrorCode() == INPUT_END) break; System.out.println(e.getMessage()); @@ -67,7 +73,7 @@ public void launch() { case 3 -> { while (true) { try { - customerService.displayClassificationSummary(SPENT_TIME, inputSortOrder()); + displayClassifiedCustomerData(SPENT_TIME, inputSortOrder()); } catch (StoreException e) { if (e.getErrorCode() == INPUT_END) break; System.out.println(e.getMessage()); @@ -79,7 +85,7 @@ public void launch() { case 4 -> { while (true) { try { - customerService.displayClassificationSummary(PAY_AMOUNT, inputSortOrder()); + displayClassifiedCustomerData(PAY_AMOUNT, inputSortOrder()); } catch (StoreException e) { if (e.getErrorCode() == INPUT_END) break; System.out.println(e.getMessage()); @@ -115,4 +121,33 @@ private SortOrder inputSortOrder() throws StoreException { } } } + + private void displayClassifiedCustomerData() { + CustomerGroupDTO[] customerGroupDTOs = customerGroupService.findAll(); + CustomerDTO[][] classifiedCustomerDTOs = customerService.getClassifiedCustomerData(); + + displayClassifiedCustomerData(customerGroupDTOs, classifiedCustomerDTOs); + } + + private void displayClassifiedCustomerData(SortBy sortBy, SortOrder sortOrder) { + CustomerGroupDTO[] customerGroupDTOs = customerGroupService.findAll(); + CustomerDTO[][] classifiedCustomerDTOs = + customerService.getClassifiedCustomerData(sortBy, sortOrder); + + displayClassifiedCustomerData(customerGroupDTOs, classifiedCustomerDTOs); + } + + private void displayClassifiedCustomerData( + CustomerGroupDTO[] customerGroupDTOs, CustomerDTO[][] classifiedCustomerDTOs) { + for (int gIdx = 0; gIdx < customerGroupDTOs.length; gIdx++) { + System.out.println("\n" + customerGroupDTOs[gIdx].groupTitle()); + + if (classifiedCustomerDTOs[gIdx] == null || classifiedCustomerDTOs[gIdx].length == 0) + System.out.println("Null."); + + for (int cIdx = 0; cIdx < classifiedCustomerDTOs[gIdx].length; cIdx++) { + System.out.println("No. " + (cIdx + 1) + " => " + classifiedCustomerDTOs[gIdx][cIdx]); + } + } + } } diff --git a/src/main/java/me/smartstore/core/view/ParameterMenu.java b/src/main/java/me/smartstore/core/view/ParameterMenu.java index 7ddef60c..f66dfcb5 100644 --- a/src/main/java/me/smartstore/core/view/ParameterMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterMenu.java @@ -1,17 +1,17 @@ package me.smartstore.core.view; -import me.smartstore.core.domain.CustomerGroup; +import static me.smartstore.enums.SmartStoreMessage.INPUT_CUSTOMER_GROUP_MSG; +import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; +import static me.smartstore.exceptions.StoreErrorCode.*; +import static me.smartstore.utils.StoreUtility.convertInputStrToCustomerType; + +import me.smartstore.core.domain.CustomerGroupDTO; import me.smartstore.core.domain.Parameter; import me.smartstore.core.service.CustomerGroupService; import me.smartstore.enums.CustomerType; import me.smartstore.exceptions.StoreException; import me.smartstore.utils.ScannerUtility; -import static me.smartstore.enums.SmartStoreMessage.INPUT_CUSTOMER_GROUP_MSG; -import static me.smartstore.enums.SmartStoreMessage.PRESS_END_MSG; -import static me.smartstore.exceptions.StoreErrorCode.*; -import static me.smartstore.utils.StoreUtility.convertInputStrToCustomerType; - /** * 고객 유형 세분화 기준 설정 메뉴 * @@ -35,6 +35,7 @@ public static ParameterMenu getInstance() { } return parameterMenu; } + public void launch() { loop: while (true) { @@ -45,15 +46,18 @@ public void launch() { case 1 -> { while (true) { CustomerType customerType = inputCustomerType(); - CustomerGroup customerGroup = customerGroupService.find(customerType); - if (customerGroup.getParameter() != null) { + CustomerGroupDTO customerGroupDTO = customerGroupService.find(customerType); + + if (customerGroupDTO.parameter() != null) { System.out.println(GROUP_ALREADY_SET.getMessage()); - System.out.println(customerGroup); + System.out.println(customerGroupDTO); continue; } + Parameter parameter = parameterSubMenu.inputParameter(); - customerGroup = customerGroupService.setParameter(customerType, parameter); - System.out.println(customerGroup); + customerGroupDTO = customerGroupService.setParameter(customerType, parameter); + + System.out.println(customerGroupDTO); } } @@ -69,13 +73,14 @@ public void launch() { case 3 -> { while (true) { CustomerType customerType = inputCustomerType(); - CustomerGroup customerGroup = customerGroupService.find(customerType); - if (customerGroup.getParameter() == null) { - throw new StoreException(NO_PARAMETER); - } + CustomerGroupDTO customerGroupDTO = customerGroupService.find(customerType); + + if (customerGroupDTO.parameter() == null) throw new StoreException(NO_PARAMETER); + Parameter parameter = parameterSubMenu.inputParameter(); - customerGroup = customerGroupService.setParameter(customerType, parameter); - System.out.println(customerGroup); + customerGroupDTO = customerGroupService.setParameter(customerType, parameter); + + System.out.println(customerGroupDTO); } } @@ -98,6 +103,7 @@ private CustomerType inputCustomerType() throws StoreException { while (true) { System.out.println(INPUT_CUSTOMER_GROUP_MSG + "\n" + PRESS_END_MSG); String input = ScannerUtility.getInput().toUpperCase(); + if ("end".equalsIgnoreCase(input)) throw new StoreException(INPUT_END); try { From 5345c72c40dd90c613e33c4dc62221b4bdb9b3a3 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 21:13:45 +0900 Subject: [PATCH 47/58] delete: Test class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 새로 작성하기 위해 기존 테스트 모두 삭제 --- .../core/CustomerGroupManagerTest.java | 9 --- .../smartstore/core/CustomerManagerTest.java | 80 ------------------- 2 files changed, 89 deletions(-) delete mode 100644 src/test/java/me/smartstore/core/CustomerGroupManagerTest.java delete mode 100644 src/test/java/me/smartstore/core/CustomerManagerTest.java diff --git a/src/test/java/me/smartstore/core/CustomerGroupManagerTest.java b/src/test/java/me/smartstore/core/CustomerGroupManagerTest.java deleted file mode 100644 index 6365649b..00000000 --- a/src/test/java/me/smartstore/core/CustomerGroupManagerTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.smartstore.core; - -import org.junit.jupiter.api.Test; - -class CustomerGroupManagerTest { - - @Test - void launchGroupManager() {} -} diff --git a/src/test/java/me/smartstore/core/CustomerManagerTest.java b/src/test/java/me/smartstore/core/CustomerManagerTest.java deleted file mode 100644 index 89d94e67..00000000 --- a/src/test/java/me/smartstore/core/CustomerManagerTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.smartstore.core; - -import me.smartstore.core.domain.Customer; -import me.smartstore.enums.CustomerType; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.Comparator; - -import static me.smartstore.enums.CustomerType.*; -import static me.smartstore.enums.CustomerType.VVIP; - -class CustomerManagerTest { - private final Customer[] testCustomers = - new Customer[] { - new Customer("c1name", "c1id", 60, 6000000, CustomerType.GENERAL), - new Customer("c2name", "c2id", 30, 7000000, CustomerType.VIP), - new Customer("c3name", "c3id", 40, 3000000, CustomerType.VVIP), - new Customer("c4name", "c4id", 20, 1000000, CustomerType.VVIP), - new Customer("c5name", "c5id", 10, 5000000, CustomerType.NONE) - }; - - @Test - void printSummary() { - Comparator nameComparator = (c1, c2) -> c1.getName().compareTo(c2.getName()); - Comparator timeComparator = - (c1, c2) -> c1.getSpentTime().compareTo(c2.getSpentTime()); - Comparator amountComparator = - (c1, c2) -> c1.getPayAmount().compareTo(c2.getPayAmount()); - - Arrays.sort(testCustomers, nameComparator); - System.out.println(Arrays.toString(testCustomers)); - System.out.println(); - - Arrays.sort(testCustomers, nameComparator.reversed()); - System.out.println(Arrays.toString(testCustomers)); - System.out.println(); - - Arrays.sort(testCustomers, timeComparator); - System.out.println(Arrays.toString(testCustomers)); - System.out.println(); - - Arrays.sort(testCustomers, timeComparator.reversed()); - System.out.println(Arrays.toString(testCustomers)); - System.out.println(); - - Arrays.sort(testCustomers, amountComparator); - System.out.println(Arrays.toString(testCustomers)); - System.out.println(); - - Arrays.sort(testCustomers, amountComparator.reversed()); - System.out.println(Arrays.toString(testCustomers)); - System.out.println(); - } - - @Test - void filterTest() { - Customer[] noneGroup = - Arrays.stream(testCustomers) - .filter(customer -> customer.getCustomerType() == NONE) - .toArray(Customer[]::new); - Customer[] generalGroup = - Arrays.stream(testCustomers) - .filter(customer -> customer.getCustomerType() == GENERAL) - .toArray(Customer[]::new); - Customer[] vipGroup = - Arrays.stream(testCustomers) - .filter(customer -> customer.getCustomerType() == VIP) - .toArray(Customer[]::new); - Customer[] vvipGroup = - Arrays.stream(testCustomers) - .filter(customer -> customer.getCustomerType() == VVIP) - .toArray(Customer[]::new); - - System.out.println(Arrays.toString(noneGroup)); - System.out.println(Arrays.toString(generalGroup)); - System.out.println(Arrays.toString(vipGroup)); - System.out.println(Arrays.toString(vvipGroup)); - } -} From cb67d7c9f5e34a44fe87337eede21eca263e54aa Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Thu, 11 May 2023 21:20:23 +0900 Subject: [PATCH 48/58] fix: update Customer, CustomerDTO, CustomerManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Customer : 불필요한 생성자 삭제, 생성자 id 부여 부분 수정. - CustomerDTO : id에 이름이 입력되던 오류 수정. - CustomerManager : 삭제시 배열 인덱스 오류 수정. 불필요한 코드 제거. --- .../me/smartstore/core/domain/Customer.java | 19 +++------------ .../smartstore/core/domain/CustomerDTO.java | 6 ++--- .../core/manager/CustomerManager.java | 24 ++++--------------- 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/src/main/java/me/smartstore/core/domain/Customer.java b/src/main/java/me/smartstore/core/domain/Customer.java index 902315ce..93bccb31 100644 --- a/src/main/java/me/smartstore/core/domain/Customer.java +++ b/src/main/java/me/smartstore/core/domain/Customer.java @@ -13,7 +13,7 @@ public class Customer { /** 관리 번호 부여용 */ private static Long seqNo = 0L; - private Long id; // 고객 관리 번호 + private final Long id; // 고객 관리 번호 private String name; private String userId; private Integer spentTime; @@ -32,12 +32,8 @@ public Customer(String name, String userId, Integer spentTime, Integer payAmount } public Customer(CustomerDTO dto) { - if (dto.id() == null) { - synchronized (this) { - this.id = seqNo++; - } - } else { - this.id = dto.id(); + synchronized (this) { + this.id = dto.id() == null ? seqNo++ : dto.id(); } this.name = dto.name(); this.userId = dto.userId(); @@ -46,15 +42,6 @@ public Customer(CustomerDTO dto) { this.customerType = null; } - public Customer( - String name, String userId, Integer spentTime, Integer payAmount, CustomerType customerType) { - this.name = name; - this.userId = userId; - this.spentTime = spentTime; - this.payAmount = payAmount; - this.customerType = customerType; - } - public Long getId() { return id; } diff --git a/src/main/java/me/smartstore/core/domain/CustomerDTO.java b/src/main/java/me/smartstore/core/domain/CustomerDTO.java index 1f9951f0..47e621f7 100644 --- a/src/main/java/me/smartstore/core/domain/CustomerDTO.java +++ b/src/main/java/me/smartstore/core/domain/CustomerDTO.java @@ -27,7 +27,7 @@ public static CustomerDTO from(Customer customer) { return new CustomerDTO( customer.getId(), customer.getName(), - customer.getName(), + customer.getUserId(), customer.getSpentTime(), customer.getPayAmount(), customer.getCustomerType()); @@ -35,11 +35,11 @@ public static CustomerDTO from(Customer customer) { public static CustomerDTO of( String customerName, - String customerID, + String customerUserId, Integer customerSpentTime, Integer customerPayAmount) { return new CustomerDTO( - null, customerName, customerID, customerSpentTime, customerPayAmount, null); + null, customerName, customerUserId, customerSpentTime, customerPayAmount, null); } @Override diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java index 17bbccb4..1db6b0a2 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -18,7 +18,7 @@ */ public class CustomerManager { private static CustomerManager customerManager = new CustomerManager(); - private static final int DEFAULT_SIZE = 10; + private static final int DEFAULT_SIZE = 20; private static int size = 0; private static Customer[] customers = new Customer[DEFAULT_SIZE]; @@ -50,11 +50,6 @@ public Boolean isFull() { * @throws StoreException 기타 데이터베이스 오류 */ public void save(Customer customer) throws StoreException { - if (isEmpty()) { - customers[size++] = customer; - return; - } - for (int idx = 0; idx < size; idx++) { if (customer.getId().equals(customers[idx].getId())) { customers[idx] = customer; @@ -97,7 +92,6 @@ public Customer[] selectAll() throws StoreException { * @throws StoreException 데이터베이스 내부 오류 */ public Customer[] selectByCustomerType(CustomerType customerType) throws StoreException { - if (isEmpty()) throw new StoreException(NO_CUSTOMER); return Arrays.stream(customers) .filter(Objects::nonNull) .filter(customer -> customer.getCustomerType() == customerType) @@ -203,12 +197,8 @@ public void deleteById(Long id) throws StoreException { if (idx == -1) throw new StoreException(NOT_EXIST_ID); - System.arraycopy(customers, idx + 1, customers, idx, size - idx); - size--; - - for (int i = size; i < customers.length; i++) { - customers[i] = null; - } + System.arraycopy(customers, idx + 1, customers, idx, --size - idx); + customers[size] = null; } /** @@ -226,11 +216,7 @@ public void delete(Customer customer) throws StoreException { if (idx == -1) throw new StoreException(NOT_EXIST_CUSTOMER); - System.arraycopy(customers, idx + 1, customers, idx, size - idx); - size--; - - for (int i = size; i < customers.length; i++) { - customers[i] = null; - } + System.arraycopy(customers, idx + 1, customers, idx, --size - idx); + customers[size] = null; } } From 88960bd57adc2ef4244bd28f4134eb2f4dee8db4 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Fri, 12 May 2023 09:37:19 +0900 Subject: [PATCH 49/58] chore: replace JUnit5 dependency --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7530e256..d266a6fe 100644 --- a/build.gradle +++ b/build.gradle @@ -10,8 +10,8 @@ repositories { } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + testImplementation(platform('org.junit:junit-bom:5.9.3')) + testImplementation('org.junit.jupiter:junit-jupiter') } test { From d66f5cc4eff92a996a09a33620a1321e6130573b Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Fri, 12 May 2023 09:41:29 +0900 Subject: [PATCH 50/58] feat: test & update CustomerGroupManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CustomerGroupManager 테스트 코드 추가 - 테스트에 따라 CustomerType 보완 - 테스트에 따라 StoreErrorCode 보완 --- .../core/manager/CustomerGroupManager.java | 8 +- .../me/smartstore/enums/CustomerType.java | 7 +- .../smartstore/exceptions/StoreErrorCode.java | 3 +- .../manager/CustomerGroupManagerTest.java | 89 +++++++++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index d39ce105..ee36bbef 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -41,13 +41,17 @@ public static CustomerGroupManager getInstance() { * * @param customerGroup 고객 그룹 * @return 저장된 고객 그룹 + * @throws StoreException 그룹 타입을 알 수 없는 경우 */ - public CustomerGroup save(CustomerGroup customerGroup) { + public CustomerGroup save(CustomerGroup customerGroup) throws StoreException { + if (customerGroup.getCustomerType() == null) throw new StoreException(UNKNOWN_TYPE); + for (int idx = 0; idx < customerGroups.length; idx++) { if (customerGroup.getCustomerType() == customerGroups[idx].getCustomerType()) { customerGroups[idx] = customerGroup; } } + return customerGroup; } @@ -63,7 +67,7 @@ public CustomerGroup selectCustomerGroupByCustomerType(CustomerType customerType .findFirst() .orElseThrow( () -> { - throw new StoreException(NO_GROUP); + throw new StoreException(NOT_EXIST_GROUP); }); } diff --git a/src/main/java/me/smartstore/enums/CustomerType.java b/src/main/java/me/smartstore/enums/CustomerType.java index f7a6f08d..de1a5ecb 100644 --- a/src/main/java/me/smartstore/enums/CustomerType.java +++ b/src/main/java/me/smartstore/enums/CustomerType.java @@ -23,8 +23,13 @@ public enum CustomerType { this.description = description; } + public String getDescription() { + return description; + } + public CustomerType replaceFullName() { - if (this == G) return GENERAL; + if (this == N) return NONE; + else if (this == G) return GENERAL; else if (this == V) return VIP; else if (this == VV) return VVIP; else return this; diff --git a/src/main/java/me/smartstore/exceptions/StoreErrorCode.java b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java index e7e11fab..f061d5fd 100644 --- a/src/main/java/me/smartstore/exceptions/StoreErrorCode.java +++ b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java @@ -6,9 +6,10 @@ * @since 2023-05-10 */ public enum StoreErrorCode { + UNKNOWN_TYPE("Can't identify customer type"), NOT_EXIST_CUSTOMER("Customer doesn't exist."), NOT_EXIST_ID("No matching id."), - NO_GROUP("No matching group."), + NOT_EXIST_GROUP("No matching group."), NO_CUSTOMER("No Customers. Please input one first."), CANT_SORT("Elements in Array has null. Array can't be sorted."), NULL_INPUT("Null Input. Please input something."), diff --git a/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java new file mode 100644 index 00000000..a3c4d4e1 --- /dev/null +++ b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java @@ -0,0 +1,89 @@ +package me.smartstore.core.manager; + +import static me.smartstore.exceptions.StoreErrorCode.NOT_EXIST_GROUP; +import static me.smartstore.exceptions.StoreErrorCode.UNKNOWN_TYPE; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.enums.CustomerType; +import me.smartstore.exceptions.StoreException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class CustomerGroupManagerTest { + private static final CustomerGroupManager customerGroupManager = + CustomerGroupManager.getInstance(); + + @DisplayName("[SAVE] 고객 그룹 - 저장 성공") + @Test + void givenCustomerGroup_whenSave_thenReturnsSavedCustomerGroup() { + CustomerGroup customerGroup = new CustomerGroup(CustomerType.GENERAL, null); + + CustomerGroup actual = customerGroupManager.save(customerGroup); + + assertEquals(customerGroup, actual); + assertEquals(actual.getCustomerType(), customerGroup.getCustomerType()); + assertEquals(actual.getParameter(), customerGroup.getParameter()); + } + + @DisplayName("[SAVE] 고객 그룹을 알 수 없는 경우 - 저장 실패") + @Test + void givenUnknownCustomerType_whenSave_thenThrowsUnknownTypeStoreException() { + // Given + CustomerGroup customerGroup = new CustomerGroup(null, null); + + // When & Then + StoreException storeException = + assertThrows(StoreException.class, () -> customerGroupManager.save(customerGroup)); + assertEquals(UNKNOWN_TYPE, storeException.getErrorCode()); + assertEquals(UNKNOWN_TYPE.getMessage(), storeException.getMessage()); + } + + @DisplayName("[SELECT] 고객 유형별 그룹 조회 - 조회 성공") + @ParameterizedTest(name = "고객 유형 (CustomerType) : {0}") + @EnumSource(CustomerType.class) + void givenCustomerType_whenSelectCustomerGroup_thenReturnsSelectedCustomerGroup( + CustomerType customerType) { + // Given + CustomerType replaced = customerType.replaceFullName(); + + // Given && When + CustomerGroup actual = customerGroupManager.selectCustomerGroupByCustomerType(replaced); + + // Then + assertEquals(replaced, actual.getCustomerType()); + } + + @DisplayName("[SELECT] 고객 유형이 주어지지 않은 경우 - 조회 실패") + @Test + void givenNothing_whenSelectCustomerGroup_thenReturnsSelectedCustomerGroup() { + // Given + + // When & Then + StoreException exception = + assertThrows( + StoreException.class, + () -> customerGroupManager.selectCustomerGroupByCustomerType(null)); + assertEquals(NOT_EXIST_GROUP, exception.getErrorCode()); + assertEquals(NOT_EXIST_GROUP.getMessage(), exception.getMessage()); + } + + @DisplayName("[SELECT] 모든 고객 그룹 조회") + @ParameterizedTest(name = "고객 유형 (CustomerType): {0}") + @EnumSource(CustomerType.class) + void givenNothing_whenSelectAllCustomerGroup_thenReturnsAllCustomerGroupArray( + CustomerType customerType) { + // Given + + // When + CustomerGroup[] actual = customerGroupManager.selectAllCustomerGroup(); + + // Then + assertTrue( + Arrays.stream(actual) + .anyMatch(group -> group.getCustomerType() == customerType.replaceFullName())); + } +} From b00c7bc32894b1203cdeac5c4c81d483a141adb7 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Fri, 12 May 2023 11:31:19 +0900 Subject: [PATCH 51/58] feat: refactor CustomerType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CustomerType 에서 단축어 사용을 위해 불필요하게 중복 선언되어 있던 부분들을 제거 - CustomerType 에 선언된 값을 통해 CustomerGroupManager 초기화를 하도록 선언하여 새로운 유형 추가에 유연하게 대처가능하도록 변경 - 리팩토링 영향을 반영하여 StoreUtility, CustomerGroupManagerTest 적절하게 변경 --- .../core/manager/CustomerGroupManager.java | 12 ++---- .../me/smartstore/enums/CustomerType.java | 43 ++++++++++++------- .../me/smartstore/utils/StoreUtility.java | 4 +- .../manager/CustomerGroupManagerTest.java | 9 ++-- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index ee36bbef..7de0084b 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -1,6 +1,5 @@ package me.smartstore.core.manager; -import static me.smartstore.enums.CustomerType.*; import static me.smartstore.exceptions.StoreErrorCode.*; import java.util.Arrays; @@ -21,12 +20,9 @@ public class CustomerGroupManager { public CustomerGroupManager() { customerGroups = - new CustomerGroup[] { - new CustomerGroup(NONE, null), - new CustomerGroup(GENERAL, null), - new CustomerGroup(VIP, null), - new CustomerGroup(VVIP, null) - }; + Arrays.stream(CustomerType.values()) + .map(customerType -> new CustomerGroup(customerType, null)) + .toArray(CustomerGroup[]::new); } public static CustomerGroupManager getInstance() { @@ -67,7 +63,7 @@ public CustomerGroup selectCustomerGroupByCustomerType(CustomerType customerType .findFirst() .orElseThrow( () -> { - throw new StoreException(NOT_EXIST_GROUP); + throw new StoreException(NO_MATCHING_GROUP); }); } diff --git a/src/main/java/me/smartstore/enums/CustomerType.java b/src/main/java/me/smartstore/enums/CustomerType.java index de1a5ecb..ebd872f6 100644 --- a/src/main/java/me/smartstore/enums/CustomerType.java +++ b/src/main/java/me/smartstore/enums/CustomerType.java @@ -1,5 +1,10 @@ package me.smartstore.enums; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * 고객 유형 * @@ -8,30 +13,38 @@ * @since 2023-05-10 */ public enum CustomerType { - NONE("해당없음"), - GENERAL("일반고객"), - VIP("우수고객"), - VVIP("최우수고객"), - N("해당없음"), - G("일반고객"), - V("우수고객"), - VV("최우수고객"); + NONE("N", "해당없음"), + GENERAL("G", "일반고객"), + VIP("V", "우수고객"), + VVIP("VV", "최우수고객"); + + private static final Map ABBREVIATION_MAP; + + static { + Map map = + Stream.of(values()) + .collect(Collectors.toMap(CustomerType::getAbbreviation, CustomerType::name)); + Stream.of(values()).forEach(type -> map.put(type.name(), type.name())); + ABBREVIATION_MAP = Collections.unmodifiableMap(map); + } + private final String abbreviation; private final String description; - CustomerType(String description) { + CustomerType(String abbreviation, String description) { + this.abbreviation = abbreviation; this.description = description; } + public String getAbbreviation() { + return abbreviation; + } + public String getDescription() { return description; } - public CustomerType replaceFullName() { - if (this == N) return NONE; - else if (this == G) return GENERAL; - else if (this == V) return VIP; - else if (this == VV) return VVIP; - else return this; + public static CustomerType of(final String valueStr) { + return CustomerType.valueOf(ABBREVIATION_MAP.get(valueStr)); } } diff --git a/src/main/java/me/smartstore/utils/StoreUtility.java b/src/main/java/me/smartstore/utils/StoreUtility.java index 33c65361..1e863c89 100644 --- a/src/main/java/me/smartstore/utils/StoreUtility.java +++ b/src/main/java/me/smartstore/utils/StoreUtility.java @@ -16,8 +16,8 @@ public class StoreUtility { public static CustomerType convertInputStrToCustomerType(String input) throws StoreException { try { - return CustomerType.valueOf(input).replaceFullName(); - } catch (IllegalArgumentException e) { + return CustomerType.of(input); + } catch (RuntimeException e) { throw new StoreException(INVALID_FORMAT); } } diff --git a/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java index a3c4d4e1..b4dbff73 100644 --- a/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java +++ b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java @@ -48,13 +48,12 @@ void givenUnknownCustomerType_whenSave_thenThrowsUnknownTypeStoreException() { void givenCustomerType_whenSelectCustomerGroup_thenReturnsSelectedCustomerGroup( CustomerType customerType) { // Given - CustomerType replaced = customerType.replaceFullName(); // Given && When - CustomerGroup actual = customerGroupManager.selectCustomerGroupByCustomerType(replaced); + CustomerGroup actual = customerGroupManager.selectCustomerGroupByCustomerType(customerType); // Then - assertEquals(replaced, actual.getCustomerType()); + assertEquals(customerType, actual.getCustomerType()); } @DisplayName("[SELECT] 고객 유형이 주어지지 않은 경우 - 조회 실패") @@ -82,8 +81,6 @@ void givenNothing_whenSelectAllCustomerGroup_thenReturnsAllCustomerGroupArray( CustomerGroup[] actual = customerGroupManager.selectAllCustomerGroup(); // Then - assertTrue( - Arrays.stream(actual) - .anyMatch(group -> group.getCustomerType() == customerType.replaceFullName())); + assertTrue(Arrays.stream(actual).anyMatch(group -> group.getCustomerType() == customerType)); } } From 493a81f53d745669023e0916fa8344798e817381 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Fri, 12 May 2023 11:35:52 +0900 Subject: [PATCH 52/58] feat: update StoreErrorCode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 고객 ID, 그룹 관련 에러코드 수정 --- .../me/smartstore/core/manager/CustomerGroupManager.java | 3 ++- .../java/me/smartstore/core/manager/CustomerManager.java | 2 +- src/main/java/me/smartstore/exceptions/StoreErrorCode.java | 4 ++-- .../smartstore/core/manager/CustomerGroupManagerTest.java | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index 7de0084b..3eee21cf 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -40,7 +40,8 @@ public static CustomerGroupManager getInstance() { * @throws StoreException 그룹 타입을 알 수 없는 경우 */ public CustomerGroup save(CustomerGroup customerGroup) throws StoreException { - if (customerGroup.getCustomerType() == null) throw new StoreException(UNKNOWN_TYPE); + if (customerGroup == null || customerGroup.getCustomerType() == null) + throw new StoreException(UNKNOWN_TYPE); for (int idx = 0; idx < customerGroups.length; idx++) { if (customerGroup.getCustomerType() == customerGroups[idx].getCustomerType()) { diff --git a/src/main/java/me/smartstore/core/manager/CustomerManager.java b/src/main/java/me/smartstore/core/manager/CustomerManager.java index 1db6b0a2..a7fa0852 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerManager.java @@ -195,7 +195,7 @@ public void deleteById(Long id) throws StoreException { } } - if (idx == -1) throw new StoreException(NOT_EXIST_ID); + if (idx == -1) throw new StoreException(NO_MATCHING_ID); System.arraycopy(customers, idx + 1, customers, idx, --size - idx); customers[size] = null; diff --git a/src/main/java/me/smartstore/exceptions/StoreErrorCode.java b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java index f061d5fd..29d58f59 100644 --- a/src/main/java/me/smartstore/exceptions/StoreErrorCode.java +++ b/src/main/java/me/smartstore/exceptions/StoreErrorCode.java @@ -8,8 +8,8 @@ public enum StoreErrorCode { UNKNOWN_TYPE("Can't identify customer type"), NOT_EXIST_CUSTOMER("Customer doesn't exist."), - NOT_EXIST_ID("No matching id."), - NOT_EXIST_GROUP("No matching group."), + NO_MATCHING_ID("No matching id."), + NO_MATCHING_GROUP("No matching group."), NO_CUSTOMER("No Customers. Please input one first."), CANT_SORT("Elements in Array has null. Array can't be sorted."), NULL_INPUT("Null Input. Please input something."), diff --git a/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java index b4dbff73..bff5bb22 100644 --- a/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java +++ b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java @@ -1,6 +1,6 @@ package me.smartstore.core.manager; -import static me.smartstore.exceptions.StoreErrorCode.NOT_EXIST_GROUP; +import static me.smartstore.exceptions.StoreErrorCode.NO_MATCHING_GROUP; import static me.smartstore.exceptions.StoreErrorCode.UNKNOWN_TYPE; import static org.junit.jupiter.api.Assertions.*; @@ -66,8 +66,8 @@ void givenNothing_whenSelectCustomerGroup_thenReturnsSelectedCustomerGroup() { assertThrows( StoreException.class, () -> customerGroupManager.selectCustomerGroupByCustomerType(null)); - assertEquals(NOT_EXIST_GROUP, exception.getErrorCode()); - assertEquals(NOT_EXIST_GROUP.getMessage(), exception.getMessage()); + assertEquals(NO_MATCHING_GROUP, exception.getErrorCode()); + assertEquals(NO_MATCHING_GROUP.getMessage(), exception.getMessage()); } @DisplayName("[SELECT] 모든 고객 그룹 조회") From 4eba19e209d8f99edea05d73f1777302bade8ebc Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 15 May 2023 19:33:51 +0900 Subject: [PATCH 53/58] update: change method name --- .../smartstore/core/view/ClassificationSummaryMenu.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java index 34a52090..ded6e47f 100644 --- a/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java +++ b/src/main/java/me/smartstore/core/view/ClassificationSummaryMenu.java @@ -61,7 +61,7 @@ public void launch() { case 2 -> { while (true) { try { - displayClassifiedCustomerData(NAME, inputSortOrder()); + displayClassifiedCustomerDataSortBy(NAME, inputSortOrder()); } catch (StoreException e) { if (e.getErrorCode() == INPUT_END) break; System.out.println(e.getMessage()); @@ -73,7 +73,7 @@ public void launch() { case 3 -> { while (true) { try { - displayClassifiedCustomerData(SPENT_TIME, inputSortOrder()); + displayClassifiedCustomerDataSortBy(SPENT_TIME, inputSortOrder()); } catch (StoreException e) { if (e.getErrorCode() == INPUT_END) break; System.out.println(e.getMessage()); @@ -85,7 +85,7 @@ public void launch() { case 4 -> { while (true) { try { - displayClassifiedCustomerData(PAY_AMOUNT, inputSortOrder()); + displayClassifiedCustomerDataSortBy(PAY_AMOUNT, inputSortOrder()); } catch (StoreException e) { if (e.getErrorCode() == INPUT_END) break; System.out.println(e.getMessage()); @@ -129,7 +129,7 @@ private void displayClassifiedCustomerData() { displayClassifiedCustomerData(customerGroupDTOs, classifiedCustomerDTOs); } - private void displayClassifiedCustomerData(SortBy sortBy, SortOrder sortOrder) { + private void displayClassifiedCustomerDataSortBy(SortBy sortBy, SortOrder sortOrder) { CustomerGroupDTO[] customerGroupDTOs = customerGroupService.findAll(); CustomerDTO[][] classifiedCustomerDTOs = customerService.getClassifiedCustomerData(sortBy, sortOrder); From cf022e5809a6531b043c32e5938c4f2a77febcc9 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 15 May 2023 19:36:05 +0900 Subject: [PATCH 54/58] feat: update CustomerGroupDTO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DTO 로부터 Entity 객체 생성하는 메서드 추가 - 구분을 위한 메서드명 변경 - 포맷팅 적용 --- .../smartstore/core/domain/CustomerGroupDTO.java | 15 ++++++++++----- .../core/service/CustomerGroupService.java | 6 +++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java b/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java index c2813db0..83fe0aa1 100644 --- a/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java +++ b/src/main/java/me/smartstore/core/domain/CustomerGroupDTO.java @@ -9,10 +9,14 @@ * @param parameter 그룹별 분류 기준 */ public record CustomerGroupDTO(CustomerType customerType, Parameter parameter) { - public static CustomerGroupDTO from(CustomerGroup customerGroup) { + public static CustomerGroupDTO fromEntity(CustomerGroup customerGroup) { return new CustomerGroupDTO(customerGroup.getCustomerType(), customerGroup.getParameter()); } + public static CustomerGroup toEntity(CustomerGroupDTO customerGroupDTO) { + return new CustomerGroup(customerGroupDTO.customerType(), customerGroupDTO.parameter()); + } + public static CustomerGroupDTO of(CustomerType customerType, Parameter parameter) { return new CustomerGroupDTO(customerType, parameter); } @@ -25,15 +29,16 @@ public String groupTitle() { sb.append(" ( Time : null, Pay Amount : null )\n"); } else { sb.append(" ( Time :") - .append(parameter.getMinSpentTime()) - .append(", Pay Amount :") - .append(parameter.getMinPayAmount()) - .append(" )\n"); + .append(parameter.getMinSpentTime()) + .append(", Pay Amount :") + .append(parameter.getMinPayAmount()) + .append(" )\n"); } sb.append("=============================="); return sb.toString(); } + @Override public String toString() { String parameterStr = parameter == null ? "null" : parameter.toString(); diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java index e03ed904..a4fb4c6e 100644 --- a/src/main/java/me/smartstore/core/service/CustomerGroupService.java +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -1,7 +1,7 @@ package me.smartstore.core.service; import java.util.Arrays; -import me.smartstore.core.domain.CustomerGroup; + import me.smartstore.core.domain.CustomerGroupDTO; import me.smartstore.core.domain.Parameter; import me.smartstore.core.manager.CustomerGroupManager; @@ -64,7 +64,7 @@ public CustomerGroupDTO setParameter(CustomerType customerType, Parameter parame * @throws StoreException 데이터베이스에 일치하는 그룹이 없을 경우 */ public CustomerGroupDTO find(CustomerType customerType) throws StoreException { - return CustomerGroupDTO.from( + return CustomerGroupDTO.fromEntity( customerGroupManager.selectCustomerGroupByCustomerType(customerType)); } @@ -74,7 +74,7 @@ public CustomerGroupDTO find(CustomerType customerType) throws StoreException { */ public CustomerGroupDTO[] findAll() throws StoreException { return Arrays.stream(customerGroupManager.selectAllCustomerGroup()) - .map(CustomerGroupDTO::from) + .map(CustomerGroupDTO::fromEntity) .toArray(CustomerGroupDTO[]::new); } } From 098b26a9cf6ae9ec0f180c3387b0f5e07a5b5f95 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 15 May 2023 20:15:03 +0900 Subject: [PATCH 55/58] update: refactor CustomerGroupManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 불필요한 예외 던지는 부분 모두 삭제. - null 반환 시 CustomerGroupService 에서 예외 던지도록 수정. --- .../core/manager/CustomerGroupManager.java | 22 +++++-------------- .../core/service/CustomerGroupService.java | 8 +++++-- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index 3eee21cf..535cf46a 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -1,11 +1,8 @@ package me.smartstore.core.manager; -import static me.smartstore.exceptions.StoreErrorCode.*; - import java.util.Arrays; import me.smartstore.core.domain.CustomerGroup; import me.smartstore.enums.CustomerType; -import me.smartstore.exceptions.StoreException; /** * 스마트스토어의 모든 고객 그룹, 세분화 기준 저장 (Database 대체용) @@ -33,16 +30,12 @@ public static CustomerGroupManager getInstance() { } /** - * 설정이 완료된 고객 그룹 저장 + * 고객 그룹 저장 * * @param customerGroup 고객 그룹 * @return 저장된 고객 그룹 - * @throws StoreException 그룹 타입을 알 수 없는 경우 */ - public CustomerGroup save(CustomerGroup customerGroup) throws StoreException { - if (customerGroup == null || customerGroup.getCustomerType() == null) - throw new StoreException(UNKNOWN_TYPE); - + public CustomerGroup save(CustomerGroup customerGroup) { for (int idx = 0; idx < customerGroups.length; idx++) { if (customerGroup.getCustomerType() == customerGroups[idx].getCustomerType()) { customerGroups[idx] = customerGroup; @@ -54,18 +47,13 @@ public CustomerGroup save(CustomerGroup customerGroup) throws StoreException { /** * @param customerType 고객 유형 - * @return 일치하는 그룹 - * @throws StoreException 일치하는 그룹 없음 + * @return 해당 고객 그룹 */ - public CustomerGroup selectCustomerGroupByCustomerType(CustomerType customerType) - throws StoreException { + public CustomerGroup selectCustomerGroupByCustomerType(CustomerType customerType) { return Arrays.stream(customerGroups) .filter(customerGroup -> customerGroup.getCustomerType() == customerType) .findFirst() - .orElseThrow( - () -> { - throw new StoreException(NO_MATCHING_GROUP); - }); + .orElse(null); } /** diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java index a4fb4c6e..420fc524 100644 --- a/src/main/java/me/smartstore/core/service/CustomerGroupService.java +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -64,8 +64,12 @@ public CustomerGroupDTO setParameter(CustomerType customerType, Parameter parame * @throws StoreException 데이터베이스에 일치하는 그룹이 없을 경우 */ public CustomerGroupDTO find(CustomerType customerType) throws StoreException { - return CustomerGroupDTO.fromEntity( - customerGroupManager.selectCustomerGroupByCustomerType(customerType)); + CustomerGroup customerGroup = + customerGroupManager.selectCustomerGroupByCustomerType(customerType); + + if (customerGroup == null) throw new StoreException(NO_MATCHING_GROUP); + + return CustomerGroupDTO.fromEntity(customerGroup); } /** From 36e095b26559172e091ef2c84b9b28c01be9b005 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Mon, 15 May 2023 20:28:50 +0900 Subject: [PATCH 56/58] update: refactor CustomerGroupService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DTO 작성 취지에 맞게 DTO 를 통해 그룹 정보를 전달받도록 수정 - 전달받은 Parameter 로 DTO 업데이트 --- .../core/service/CustomerGroupService.java | 38 +++++++++---------- .../smartstore/core/view/ParameterMenu.java | 15 ++++---- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/main/java/me/smartstore/core/service/CustomerGroupService.java b/src/main/java/me/smartstore/core/service/CustomerGroupService.java index 420fc524..0d9dab25 100644 --- a/src/main/java/me/smartstore/core/service/CustomerGroupService.java +++ b/src/main/java/me/smartstore/core/service/CustomerGroupService.java @@ -1,7 +1,9 @@ package me.smartstore.core.service; -import java.util.Arrays; +import static me.smartstore.exceptions.StoreErrorCode.NO_MATCHING_GROUP; +import java.util.Arrays; +import me.smartstore.core.domain.CustomerGroup; import me.smartstore.core.domain.CustomerGroupDTO; import me.smartstore.core.domain.Parameter; import me.smartstore.core.manager.CustomerGroupManager; @@ -28,34 +30,32 @@ public static CustomerGroupService getInstance() { } /** - * 고객 그룹 분류 기준 설정. 분류 기준이 새로 설정된 경우 고객 정보도 함께 업데이트 + * 그룹 파라미터 설정 후 저장 * - * @param customerType 고객 유형 + * @param customerGroupDTO 고객 그룹 DTO * @param parameter 분류 기준 * @return 설정이 완료된 고객 그룹 - * @throws StoreException 일치하는 고객 그룹이 데이터베이스 없는 경우 */ - public CustomerGroupDTO setParameter(CustomerType customerType, Parameter parameter) - throws StoreException { - CustomerGroup customerGroup = - customerGroupManager.selectCustomerGroupByCustomerType(customerType); + public CustomerGroupDTO setGroupParameter( + CustomerGroupDTO customerGroupDTO, Parameter parameter) { + Parameter updatedParameter = customerGroupDTO.parameter(); - if (customerGroup.getParameter() == null) { - customerGroup.setParameter(parameter); + if (customerGroupDTO.parameter() != null) { + if (parameter.getMinSpentTime() != null) + updatedParameter.setMinSpentTime(parameter.getMinSpentTime()); + if (parameter.getMinPayAmount() != null) + updatedParameter.setMinPayAmount(parameter.getMinPayAmount()); } else { - if (parameter.getMinSpentTime() != null) { - customerGroup.getParameter().setMinSpentTime(parameter.getMinSpentTime()); - } - - if (parameter.getMinPayAmount() != null) { - customerGroup.getParameter().setMinPayAmount(parameter.getMinPayAmount()); - } + updatedParameter = parameter; } - customerGroup = customerGroupManager.save(customerGroup); + CustomerGroupDTO updatedDTO = + CustomerGroupDTO.of(customerGroupDTO.customerType(), updatedParameter); + + customerGroupManager.save(CustomerGroupDTO.toEntity(updatedDTO)); customerService.classifyAllCustomers(); - return CustomerGroupDTO.from(customerGroup); + return updatedDTO; } /** diff --git a/src/main/java/me/smartstore/core/view/ParameterMenu.java b/src/main/java/me/smartstore/core/view/ParameterMenu.java index f66dfcb5..0e923c6a 100644 --- a/src/main/java/me/smartstore/core/view/ParameterMenu.java +++ b/src/main/java/me/smartstore/core/view/ParameterMenu.java @@ -6,7 +6,6 @@ import static me.smartstore.utils.StoreUtility.convertInputStrToCustomerType; import me.smartstore.core.domain.CustomerGroupDTO; -import me.smartstore.core.domain.Parameter; import me.smartstore.core.service.CustomerGroupService; import me.smartstore.enums.CustomerType; import me.smartstore.exceptions.StoreException; @@ -50,13 +49,12 @@ public void launch() { if (customerGroupDTO.parameter() != null) { System.out.println(GROUP_ALREADY_SET.getMessage()); - System.out.println(customerGroupDTO); - continue; + } else { + customerGroupDTO = + customerGroupService.setGroupParameter( + customerGroupDTO, parameterSubMenu.inputParameter()); } - Parameter parameter = parameterSubMenu.inputParameter(); - customerGroupDTO = customerGroupService.setParameter(customerType, parameter); - System.out.println(customerGroupDTO); } } @@ -77,8 +75,9 @@ public void launch() { if (customerGroupDTO.parameter() == null) throw new StoreException(NO_PARAMETER); - Parameter parameter = parameterSubMenu.inputParameter(); - customerGroupDTO = customerGroupService.setParameter(customerType, parameter); + customerGroupDTO = + customerGroupService.setGroupParameter( + customerGroupDTO, parameterSubMenu.inputParameter()); System.out.println(customerGroupDTO); } From e9b51940727a03a5ed03fe3fe66e5a79feefdef1 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Wed, 17 May 2023 00:14:16 +0900 Subject: [PATCH 57/58] test: add CustomerGroupServiceTest class --- .../service/CustomerGroupServiceTest.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/test/java/me/smartstore/core/service/CustomerGroupServiceTest.java diff --git a/src/test/java/me/smartstore/core/service/CustomerGroupServiceTest.java b/src/test/java/me/smartstore/core/service/CustomerGroupServiceTest.java new file mode 100644 index 00000000..85ce441e --- /dev/null +++ b/src/test/java/me/smartstore/core/service/CustomerGroupServiceTest.java @@ -0,0 +1,83 @@ +package me.smartstore.core.service; + +import static me.smartstore.exceptions.StoreErrorCode.NO_MATCHING_GROUP; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; + +import java.util.Arrays; +import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.CustomerGroupDTO; +import me.smartstore.core.domain.Parameter; +import me.smartstore.enums.CustomerType; +import me.smartstore.exceptions.StoreException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CustomerGroupServiceTest { + private final CustomerGroupService customerGroupService = CustomerGroupService.getInstance(); + + @DisplayName("[setGroupParameter] 그룹 DTO, 파라미터 전달하면 파라미터 업데이트 후 저장하고 업데이트 된 DTO 반환하고 고객 재분류") + @Test + void givenCustomerGroupDTOAndParameter_whenSetGroupParameter_thenSavesAndReturnsUpdatedDTO() { + // Given + CustomerGroupDTO beforeGroupDTO = + CustomerGroupDTO.of(CustomerType.GENERAL, new Parameter(123, 123456)); + Parameter updateParameter = new Parameter(987, 987654); + + // When + CustomerGroupDTO afterGroupDTO = + customerGroupService.setGroupParameter(beforeGroupDTO, updateParameter); + + // Then + assertEquals(afterGroupDTO.customerType(), beforeGroupDTO.customerType()); + assertEquals(afterGroupDTO.parameter().getMinSpentTime(), 987); + assertEquals(afterGroupDTO.parameter().getMinPayAmount(), 987654); + } + + @DisplayName("[find] 고객 유형을 전달하면 일치하는 그룹 DTO 를 반환") + @Test + void givenCustomerType_whenFind_thenReturnsCustomerGroupDTO() { + // Given + CustomerType customerType = CustomerType.GENERAL; + CustomerGroup customerGroup = new CustomerGroup(customerType, any()); + CustomerGroupDTO customerGroupDTO = CustomerGroupDTO.fromEntity(customerGroup); + + // When + CustomerGroupDTO actual = customerGroupService.find(customerType); + + // Then + assertEquals(customerGroupDTO.customerType(), actual.customerType()); + assertEquals(customerGroupDTO.parameter(), actual.parameter()); + } + + @DisplayName("[find] 고객 유형을 전달하지 않는 경우 예외 발생") + @Test + void givenNothing_whenFind_thenOccursStoreException() { + // Given + + // When & Then + StoreException exception = + assertThrows(StoreException.class, () -> customerGroupService.find(null)); + assertEquals(NO_MATCHING_GROUP.getMessage(), exception.getMessage()); + } + + @DisplayName("[findAll] 모든 고객 그룹 요청시 고객 그룹 DTO 를 배열로 반환") + @Test + void givenCustomerTypeAndParameter_thenSetGroupDTOParameter() { + // Given + CustomerGroup[] customerGroups = + Arrays.stream(CustomerType.values()) + .map(type -> new CustomerGroup(type, null)) + .toArray(CustomerGroup[]::new); + CustomerGroupDTO[] customerGroupDTOs = + Arrays.stream(customerGroups) + .map(CustomerGroupDTO::fromEntity) + .toArray(CustomerGroupDTO[]::new); + + // When + CustomerGroupDTO[] actual = customerGroupService.findAll(); + + // Then + assertArrayEquals(customerGroupDTOs, actual); + } +} From aa599803f59f33e097bd140d24835c1e4c348780 Mon Sep 17 00:00:00 2001 From: YongHo Shin Date: Wed, 17 May 2023 00:34:51 +0900 Subject: [PATCH 58/58] feat: update CustomerGroupManager, add CustomerGroupManagerTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Application 초기화시 모든 CustomerGroup 은 CustomerType 에 정의되어 있는 모든 고객 유형에 대해서 자동으로 생성 - CustomerMenu 에선 Service class 의 find()를 통해 CustomerGroup 을 DTO 형태로 전달받음 - 해당 DTO 를 통해 파라미터 설정, 업데이트 여부를 판단하고 파라미터를 입력받을지 말지 결정 - 입력받은 파라미터와 해당 DTO 를 다시 Service class 에 전달하여 저장을 요청함 - 이 때 CustomerGroup Entity 객체는 Service class 에서 반드시 생성됨. - 애초에 Manager 에서 관리 중인 CustomerGroup 을 대상으로 모든 과정이 진행되므로 Null 체크가 불필요함 - 따라서 저장은 항상 정상적으로 이루어지므로 별도 예외 처리가 불필요함 - 테스트 작성 --- .../core/manager/CustomerGroupManager.java | 7 +- .../manager/CustomerGroupManagerTest.java | 64 +++++++++++-------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java index 535cf46a..35ed5fad 100644 --- a/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java +++ b/src/main/java/me/smartstore/core/manager/CustomerGroupManager.java @@ -15,7 +15,7 @@ public class CustomerGroupManager { private static CustomerGroupManager customerGroupManager = new CustomerGroupManager(); private final CustomerGroup[] customerGroups; - public CustomerGroupManager() { + private CustomerGroupManager() { customerGroups = Arrays.stream(CustomerType.values()) .map(customerType -> new CustomerGroup(customerType, null)) @@ -33,16 +33,13 @@ public static CustomerGroupManager getInstance() { * 고객 그룹 저장 * * @param customerGroup 고객 그룹 - * @return 저장된 고객 그룹 */ - public CustomerGroup save(CustomerGroup customerGroup) { + public void save(CustomerGroup customerGroup) { for (int idx = 0; idx < customerGroups.length; idx++) { if (customerGroup.getCustomerType() == customerGroups[idx].getCustomerType()) { customerGroups[idx] = customerGroup; } } - - return customerGroup; } /** diff --git a/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java index bff5bb22..6d0d2a9a 100644 --- a/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java +++ b/src/test/java/me/smartstore/core/manager/CustomerGroupManagerTest.java @@ -1,13 +1,13 @@ package me.smartstore.core.manager; -import static me.smartstore.exceptions.StoreErrorCode.NO_MATCHING_GROUP; -import static me.smartstore.exceptions.StoreErrorCode.UNKNOWN_TYPE; +import static me.smartstore.enums.CustomerType.GENERAL; import static org.junit.jupiter.api.Assertions.*; import java.util.Arrays; import me.smartstore.core.domain.CustomerGroup; +import me.smartstore.core.domain.Parameter; import me.smartstore.enums.CustomerType; -import me.smartstore.exceptions.StoreException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -17,29 +17,46 @@ class CustomerGroupManagerTest { private static final CustomerGroupManager customerGroupManager = CustomerGroupManager.getInstance(); - @DisplayName("[SAVE] 고객 그룹 - 저장 성공") + @AfterEach + void restoreCustomerGroup() { + // Instance 를 직접 사용하기 때문에 각 테스트가 끝난 이후 직접 초기화 해주어야 각각의 테스트가 의도한대로 수행됨 + // 이 과정이 없을 경우 먼저 수행된 테스트의 결과가 이후 테스트에 영향을 미침. + Arrays.stream(CustomerType.values()) + .forEach(type -> customerGroupManager.save(new CustomerGroup(type, null))); + } + + @DisplayName("[SAVE] 정상 고객 그룹 - 저장 성공") @Test - void givenCustomerGroup_whenSave_thenReturnsSavedCustomerGroup() { - CustomerGroup customerGroup = new CustomerGroup(CustomerType.GENERAL, null); + void givenCustomerGroup_whenSaves_thenSavesCustomerGroup() { + // Given + Parameter parameter = new Parameter(123, 123456); + CustomerGroup testGroup = new CustomerGroup(GENERAL, parameter); - CustomerGroup actual = customerGroupManager.save(customerGroup); + // When + customerGroupManager.save(testGroup); + CustomerGroup updated = customerGroupManager.selectCustomerGroupByCustomerType(GENERAL); - assertEquals(customerGroup, actual); - assertEquals(actual.getCustomerType(), customerGroup.getCustomerType()); - assertEquals(actual.getParameter(), customerGroup.getParameter()); + // Then + assertEquals(GENERAL, updated.getCustomerType()); + assertEquals(parameter, updated.getParameter()); + assertEquals(123, updated.getParameter().getMinSpentTime()); + assertEquals(123456, updated.getParameter().getMinPayAmount()); } - @DisplayName("[SAVE] 고객 그룹을 알 수 없는 경우 - 저장 실패") - @Test - void givenUnknownCustomerType_whenSave_thenThrowsUnknownTypeStoreException() { + @DisplayName("[SAVE] 알 수 없는 고객 그룹 - 저장 실패") + @ParameterizedTest(name = "고객 유형 (CustomerType): {0}") + @EnumSource(CustomerType.class) + void givenUnknownCustomerType_whenSave_thenChangesNothing(CustomerType customerType) { // Given - CustomerGroup customerGroup = new CustomerGroup(null, null); + Parameter parameter = new Parameter(123, 123456); + CustomerGroup testGroup = new CustomerGroup(null, parameter); - // When & Then - StoreException storeException = - assertThrows(StoreException.class, () -> customerGroupManager.save(customerGroup)); - assertEquals(UNKNOWN_TYPE, storeException.getErrorCode()); - assertEquals(UNKNOWN_TYPE.getMessage(), storeException.getMessage()); + // When + customerGroupManager.save(testGroup); + CustomerGroup actual = customerGroupManager.selectCustomerGroupByCustomerType(customerType); + + // Then + assertNull(actual.getParameter()); } @DisplayName("[SELECT] 고객 유형별 그룹 조회 - 조회 성공") @@ -56,18 +73,13 @@ void givenCustomerType_whenSelectCustomerGroup_thenReturnsSelectedCustomerGroup( assertEquals(customerType, actual.getCustomerType()); } - @DisplayName("[SELECT] 고객 유형이 주어지지 않은 경우 - 조회 실패") + @DisplayName("[SELECT] 고객 유형이 주어지지 않은 경우 - Null") @Test void givenNothing_whenSelectCustomerGroup_thenReturnsSelectedCustomerGroup() { // Given // When & Then - StoreException exception = - assertThrows( - StoreException.class, - () -> customerGroupManager.selectCustomerGroupByCustomerType(null)); - assertEquals(NO_MATCHING_GROUP, exception.getErrorCode()); - assertEquals(NO_MATCHING_GROUP.getMessage(), exception.getMessage()); + assertNull(customerGroupManager.selectCustomerGroupByCustomerType(null)); } @DisplayName("[SELECT] 모든 고객 그룹 조회")