Add 'desktop/' from commit 'bc9a54b29a8a8c8272609082438f36b92de7f4d1'
git-subtree-dir: desktop
git-subtree-mainline: 737028e06d5207fc69425cf4431b7d047cf3110f
git-subtree-split: bc9a54b29a
This commit is contained in:
50
desktop/.gitignore
vendored
Normal file
50
desktop/.gitignore
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
.kotlin
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/*
|
||||
!.idea/runConfigurations/
|
||||
!.idea/encodings.xml
|
||||
!.idea/misc.xml
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
## Database related
|
||||
connectionpetstore.properties
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
log.txt
|
||||
*.class
|
||||
*.out
|
||||
*.err
|
||||
tmp/
|
||||
BIN
desktop/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
desktop/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
2
desktop/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
desktop/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
|
||||
48
desktop/README.md
Normal file
48
desktop/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Pet Shop Desktop (JavaFX)
|
||||
|
||||
Desktop pet shop management app built with JavaFX and MySQL.
|
||||
|
||||
Made by **Group 2**, Shiv, Nikitha, Alex, Harkamal.
|
||||
|
||||
## Requirements
|
||||
|
||||
- IntelliJ IDEA (Community or Ultimate)
|
||||
- Java 17+
|
||||
- Maven (handled through IntelliJ)
|
||||
- Docker and Docker Compose (for the local MySQL container)
|
||||
|
||||
## Database setup (IntelliJ)
|
||||
|
||||
1. Open the project in IntelliJ.
|
||||
2. Open **View → Tool Windows → Services**.
|
||||
3. Add a Docker connection if needed, then open the **Docker** section in Services.
|
||||
4. Start the Compose stack from `docker-compose.yml` (Compose Up, or Start).
|
||||
5. Confirm the `mysql` service is running.
|
||||
|
||||
The container uses `mysql:8.4`, creates the `Petstoredb` database, and imports `Petstoredata.sql`.
|
||||
|
||||
## App configuration
|
||||
|
||||
An example connection file is provided at `connectionpetstore.properties.example`. Copy it to `connectionpetstore.properties` and edit the values to match the local database setup.
|
||||
|
||||
## Run the app (IntelliJ, Maven)
|
||||
|
||||
1. Open **View → Tool Windows → Maven**.
|
||||
2. Click **Reload All Maven Projects** if the dependencies have not loaded yet.
|
||||
3. In the Maven tool window, expand **Plugins → javafx**.
|
||||
4. Double click **javafx:run**.
|
||||
|
||||
Optional, run a clean first:
|
||||
- In the Maven tool window, expand **Lifecycle** and run **clean**.
|
||||
|
||||
## Default accounts
|
||||
|
||||
On first run, the app creates a `users` table (if missing) and seeds two accounts:
|
||||
|
||||
- Admin: `admin` / `admin123`
|
||||
- Staff: `staff` / `staff123`
|
||||
|
||||
## Notes
|
||||
|
||||
- `connectionpetstore.properties` is gitignored so credentials are not committed.
|
||||
- If the app cannot connect to MySQL, confirm the Compose stack is running and MySQL is available.
|
||||
3
desktop/connectionpetstore.properties.example
Normal file
3
desktop/connectionpetstore.properties.example
Normal file
@@ -0,0 +1,3 @@
|
||||
url=jdbc:mysql://127.0.0.1:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
|
||||
user=petapp
|
||||
password=petapppass
|
||||
316
desktop/mvnw
vendored
Executable file
316
desktop/mvnw
vendored
Executable file
@@ -0,0 +1,316 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||
. /usr/local/etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
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
|
||||
else
|
||||
JAVACMD="`\\unset -f command; \\command -v java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
else
|
||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if $cygwin; then
|
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
else
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl -o "$wrapperJarPath" "$jarUrl" -f
|
||||
else
|
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
||||
fi
|
||||
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaClass=`cygpath --path --windows "$javaClass"`
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
$MAVEN_DEBUG_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" \
|
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
188
desktop/mvnw.cmd
vendored
Normal file
188
desktop/mvnw.cmd
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. 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,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% ^
|
||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||
%MAVEN_OPTS% ^
|
||||
%MAVEN_DEBUG_OPTS% ^
|
||||
-classpath %WRAPPER_JAR% ^
|
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||
|
||||
cmd /C exit /B %ERROR_CODE%
|
||||
101
desktop/pom.xml
Normal file
101
desktop/pom.xml
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>PetShopDesktop</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<name>PetShopDesktop</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<javafx.version>25.0.1</javafx.version>
|
||||
<junit.version>5.12.1</junit.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.18.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.18.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.18.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>2.18.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<release>25</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
<configuration>
|
||||
<mainClass>org.example.petshopdesktop/org.example.petshopdesktop.PetShopApplication</mainClass>
|
||||
<launcher>app</launcher>
|
||||
<jlinkZipName>app</jlinkZipName>
|
||||
<jlinkImageName>app</jlinkImageName>
|
||||
<noManPages>true</noManPages>
|
||||
<stripDebug>true</stripDebug>
|
||||
<noHeaderFiles>true</noHeaderFiles>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
40
desktop/src/main/java/module-info.java
Normal file
40
desktop/src/main/java/module-info.java
Normal file
@@ -0,0 +1,40 @@
|
||||
module org.example.petshopdesktop {
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
requires javafx.web;
|
||||
requires java.sql;
|
||||
requires java.net.http;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.annotation;
|
||||
requires com.fasterxml.jackson.datatype.jsr310;
|
||||
|
||||
opens org.example.petshopdesktop.DTOs to javafx.base;
|
||||
opens org.example.petshopdesktop.models to javafx.base;
|
||||
opens org.example.petshopdesktop to javafx.fxml;
|
||||
opens org.example.petshopdesktop.controllers.dialogcontrollers to javafx.fxml;
|
||||
opens org.example.petshopdesktop.controllers to javafx.fxml;
|
||||
opens org.example.petshopdesktop.auth to javafx.fxml;
|
||||
opens org.example.petshopdesktop.ui to javafx.fxml;
|
||||
|
||||
opens org.example.petshopdesktop.api.dto.common to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.auth to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.product to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.pet to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.service to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.supplier to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.productsupplier to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.inventory to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.appointment to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.adoption to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.chat to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.sale to com.fasterxml.jackson.databind, javafx.base;
|
||||
opens org.example.petshopdesktop.api.dto.user to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.employee to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.analytics to com.fasterxml.jackson.databind;
|
||||
opens org.example.petshopdesktop.api.dto.purchaseorder to com.fasterxml.jackson.databind;
|
||||
|
||||
exports org.example.petshopdesktop;
|
||||
exports org.example.petshopdesktop.controllers;
|
||||
exports org.example.petshopdesktop.auth;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.example.petshopdesktop.DTOs;
|
||||
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
||||
public class AppointmentDTO {
|
||||
|
||||
private SimpleIntegerProperty appointmentId;
|
||||
|
||||
private SimpleIntegerProperty customerId;
|
||||
private SimpleStringProperty customerName;
|
||||
|
||||
private SimpleIntegerProperty petId;
|
||||
private SimpleStringProperty petName;
|
||||
|
||||
private SimpleIntegerProperty serviceId;
|
||||
private SimpleStringProperty serviceName;
|
||||
|
||||
private SimpleStringProperty appointmentDate;
|
||||
private SimpleStringProperty appointmentTime;
|
||||
private SimpleStringProperty appointmentStatus;
|
||||
|
||||
// Constructor
|
||||
public AppointmentDTO(int appointmentId,
|
||||
int customerId, String customerName,
|
||||
int petId, String petName,
|
||||
int serviceId, String serviceName,
|
||||
String appointmentDate,
|
||||
String appointmentTime,
|
||||
String appointmentStatus) {
|
||||
|
||||
this.appointmentId = new SimpleIntegerProperty(appointmentId);
|
||||
this.customerId = new SimpleIntegerProperty(customerId);
|
||||
this.customerName = new SimpleStringProperty(customerName);
|
||||
this.petId = new SimpleIntegerProperty(petId);
|
||||
this.petName = new SimpleStringProperty(petName);
|
||||
this.serviceId = new SimpleIntegerProperty(serviceId);
|
||||
this.serviceName = new SimpleStringProperty(serviceName);
|
||||
this.appointmentDate = new SimpleStringProperty(appointmentDate);
|
||||
this.appointmentTime = new SimpleStringProperty(appointmentTime);
|
||||
this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public int getAppointmentId() { return appointmentId.get(); }
|
||||
|
||||
public int getCustomerId() { return customerId.get(); }
|
||||
public String getCustomerName() { return customerName.get(); }
|
||||
|
||||
public int getPetId() { return petId.get(); }
|
||||
public String getPetName() { return petName.get(); }
|
||||
|
||||
public int getServiceId() { return serviceId.get(); }
|
||||
public String getServiceName() { return serviceName.get(); }
|
||||
|
||||
public String getAppointmentDate() { return appointmentDate.get(); }
|
||||
public String getAppointmentTime() { return appointmentTime.get(); }
|
||||
public String getAppointmentStatus() { return appointmentStatus.get(); }
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.example.petshopdesktop.DTOs;
|
||||
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import org.example.petshopdesktop.models.Product;
|
||||
|
||||
/**
|
||||
* The class for productDTO, all product data is store here but also gets categoryName
|
||||
*/
|
||||
public class ProductDTO {
|
||||
private SimpleIntegerProperty prodId;
|
||||
private SimpleStringProperty prodName;
|
||||
private SimpleDoubleProperty prodPrice;
|
||||
private SimpleIntegerProperty categoryId; //used for edit and delete
|
||||
private SimpleStringProperty categoryName;
|
||||
private SimpleStringProperty prodDesc;
|
||||
|
||||
//constructor
|
||||
public ProductDTO(int prodId, String prodName, double prodPrice, int categoryId, String categoryName, String prodDesc) {
|
||||
this.prodId = new SimpleIntegerProperty(prodId);
|
||||
this.prodName = new SimpleStringProperty(prodName);
|
||||
this.prodPrice = new SimpleDoubleProperty(prodPrice);
|
||||
this.categoryId = new SimpleIntegerProperty(categoryId);
|
||||
this.categoryName = new SimpleStringProperty(categoryName);
|
||||
this.prodDesc = new SimpleStringProperty(prodDesc);
|
||||
}
|
||||
|
||||
//getter and setters
|
||||
public int getProdId() {
|
||||
return prodId.get();
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty prodIdProperty() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(int prodId) {
|
||||
this.prodId.set(prodId);
|
||||
}
|
||||
|
||||
public String getProdName() {
|
||||
return prodName.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty prodNameProperty() {
|
||||
return prodName;
|
||||
}
|
||||
|
||||
public void setProdName(String prodName) {
|
||||
this.prodName.set(prodName);
|
||||
}
|
||||
|
||||
public double getProdPrice() {
|
||||
return prodPrice.get();
|
||||
}
|
||||
|
||||
public SimpleDoubleProperty prodPriceProperty() {
|
||||
return prodPrice;
|
||||
}
|
||||
|
||||
public void setProdPrice(double prodPrice) {
|
||||
this.prodPrice.set(prodPrice);
|
||||
}
|
||||
|
||||
public String getCategoryName() {
|
||||
return categoryName.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty categoryNameProperty() {
|
||||
return categoryName;
|
||||
}
|
||||
|
||||
public void setCategoryName(String categoryName) {
|
||||
this.categoryName.set(categoryName);
|
||||
}
|
||||
|
||||
public String getProdDesc() {
|
||||
return prodDesc.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty prodDescProperty() {
|
||||
return prodDesc;
|
||||
}
|
||||
|
||||
public void setProdDesc(String prodDesc) {
|
||||
this.prodDesc.set(prodDesc);
|
||||
}
|
||||
|
||||
public int getCategoryId() {
|
||||
return categoryId.get();
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty categoryIdProperty() {
|
||||
return categoryId;
|
||||
}
|
||||
|
||||
public void setCategoryId(int categoryId) {
|
||||
this.categoryId.set(categoryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts DTO into product for editing and deleting
|
||||
* @return
|
||||
*/
|
||||
public Product toProduct(){
|
||||
Product product = new Product(
|
||||
getProdId(),
|
||||
getProdName(),
|
||||
getProdPrice(),
|
||||
getCategoryId(),
|
||||
getProdDesc()
|
||||
);
|
||||
return product;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.example.petshopdesktop.DTOs;
|
||||
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
||||
public class ProductSupplierDTO {
|
||||
private SimpleIntegerProperty supId;
|
||||
private SimpleIntegerProperty prodId;
|
||||
private SimpleStringProperty supCompany;
|
||||
private SimpleStringProperty prodName;
|
||||
private SimpleDoubleProperty cost;
|
||||
|
||||
//constructor
|
||||
public ProductSupplierDTO(int supId, int prodId, String supCompany, String prodName, double cost) {
|
||||
this.supId = new SimpleIntegerProperty(supId);
|
||||
this.prodId = new SimpleIntegerProperty(prodId);
|
||||
this.supCompany = new SimpleStringProperty(supCompany);
|
||||
this.prodName = new SimpleStringProperty(prodName);
|
||||
this.cost = new SimpleDoubleProperty(cost);
|
||||
}
|
||||
|
||||
//getter and setters
|
||||
public int getSupId() {
|
||||
return supId.get();
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty supIdProperty() {
|
||||
return supId;
|
||||
}
|
||||
|
||||
public void setSupId(int supId) {
|
||||
this.supId.set(supId);
|
||||
}
|
||||
|
||||
public int getProdId() {
|
||||
return prodId.get();
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty prodIdProperty() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(int prodId) {
|
||||
this.prodId.set(prodId);
|
||||
}
|
||||
|
||||
public String getSupCompany() {
|
||||
return supCompany.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty supCompanyProperty() {
|
||||
return supCompany;
|
||||
}
|
||||
|
||||
public void setSupCompany(String supCompany) {
|
||||
this.supCompany.set(supCompany);
|
||||
}
|
||||
|
||||
public String getProdName() {
|
||||
return prodName.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty prodNameProperty() {
|
||||
return prodName;
|
||||
}
|
||||
|
||||
public void setProdName(String prodName) {
|
||||
this.prodName.set(prodName);
|
||||
}
|
||||
|
||||
public double getCost() {
|
||||
return cost.get();
|
||||
}
|
||||
|
||||
public SimpleDoubleProperty costProperty() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(double cost) {
|
||||
this.cost.set(cost);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.example.petshopdesktop.DTOs;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
|
||||
public class PurchaseOrderDTO {
|
||||
|
||||
private LongProperty purchaseOrderId;
|
||||
private StringProperty supplierName;
|
||||
private StringProperty orderDate;
|
||||
private StringProperty status;
|
||||
|
||||
public PurchaseOrderDTO(long id, String supplierName,
|
||||
String orderDate, String status) {
|
||||
|
||||
this.purchaseOrderId = new SimpleLongProperty(id);
|
||||
this.supplierName = new SimpleStringProperty(supplierName);
|
||||
this.orderDate = new SimpleStringProperty(orderDate);
|
||||
this.status = new SimpleStringProperty(status);
|
||||
}
|
||||
|
||||
public long getPurchaseOrderId() { return purchaseOrderId.get(); }
|
||||
public String getSupplierName() { return supplierName.get(); }
|
||||
public String getOrderDate() { return orderDate.get(); }
|
||||
public String getStatus() { return status.get(); }
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.example.petshopdesktop.DTOs;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
|
||||
public class SaleDTO {
|
||||
|
||||
private IntegerProperty saleId;
|
||||
private StringProperty saleDate;
|
||||
private StringProperty employeeName;
|
||||
private StringProperty productName;
|
||||
private IntegerProperty quantity;
|
||||
private DoubleProperty unitPrice;
|
||||
private DoubleProperty total;
|
||||
private StringProperty paymentMethod;
|
||||
|
||||
public SaleDTO(int saleId, String saleDate, String employeeName, String productName,
|
||||
int quantity, double unitPrice, double total, String paymentMethod) {
|
||||
this.saleId = new SimpleIntegerProperty(saleId);
|
||||
this.saleDate = new SimpleStringProperty(saleDate);
|
||||
this.employeeName = new SimpleStringProperty(employeeName);
|
||||
this.productName = new SimpleStringProperty(productName);
|
||||
this.quantity = new SimpleIntegerProperty(quantity);
|
||||
this.unitPrice = new SimpleDoubleProperty(unitPrice);
|
||||
this.total = new SimpleDoubleProperty(total);
|
||||
this.paymentMethod = new SimpleStringProperty(paymentMethod);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public int getSaleId() {
|
||||
return saleId.get();
|
||||
}
|
||||
|
||||
public String getSaleDate() {
|
||||
return saleDate.get();
|
||||
}
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName.get();
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName.get();
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return quantity.get();
|
||||
}
|
||||
|
||||
public double getUnitPrice() {
|
||||
return unitPrice.get();
|
||||
}
|
||||
|
||||
public double getTotal() {
|
||||
return total.get();
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod.get();
|
||||
}
|
||||
|
||||
// Properties
|
||||
public IntegerProperty saleIdProperty() {
|
||||
return saleId;
|
||||
}
|
||||
|
||||
public StringProperty saleDateProperty() {
|
||||
return saleDate;
|
||||
}
|
||||
|
||||
public StringProperty employeeNameProperty() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public StringProperty productNameProperty() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public IntegerProperty quantityProperty() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public DoubleProperty unitPriceProperty() {
|
||||
return unitPrice;
|
||||
}
|
||||
|
||||
public DoubleProperty totalProperty() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public StringProperty paymentMethodProperty() {
|
||||
return paymentMethod;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.example.petshopdesktop.DTOs;
|
||||
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import org.example.petshopdesktop.models.Service;
|
||||
|
||||
/**
|
||||
* The class for ServiceDTO, all service data stored here
|
||||
*/
|
||||
public class ServiceDTO {
|
||||
|
||||
private SimpleIntegerProperty serviceId;
|
||||
private SimpleStringProperty serviceName;
|
||||
private SimpleStringProperty serviceDesc;
|
||||
private SimpleIntegerProperty serviceDuration;
|
||||
private SimpleDoubleProperty servicePrice;
|
||||
|
||||
// constructor
|
||||
public ServiceDTO(int serviceId,
|
||||
String serviceName,
|
||||
String serviceDesc,
|
||||
int serviceDuration,
|
||||
double servicePrice) {
|
||||
|
||||
this.serviceId = new SimpleIntegerProperty(serviceId);
|
||||
this.serviceName = new SimpleStringProperty(serviceName);
|
||||
this.serviceDesc = new SimpleStringProperty(serviceDesc);
|
||||
this.serviceDuration = new SimpleIntegerProperty(serviceDuration);
|
||||
this.servicePrice = new SimpleDoubleProperty(servicePrice);
|
||||
}
|
||||
|
||||
// getters & setters
|
||||
|
||||
public int getServiceId() {
|
||||
return serviceId.get();
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty serviceIdProperty() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(int serviceId) {
|
||||
this.serviceId.set(serviceId);
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return serviceName.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty serviceNameProperty() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName.set(serviceName);
|
||||
}
|
||||
|
||||
public String getServiceDesc() {
|
||||
return serviceDesc.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty serviceDescProperty() {
|
||||
return serviceDesc;
|
||||
}
|
||||
|
||||
public void setServiceDesc(String serviceDesc) {
|
||||
this.serviceDesc.set(serviceDesc);
|
||||
}
|
||||
|
||||
public int getServiceDuration() {
|
||||
return serviceDuration.get();
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty serviceDurationProperty() {
|
||||
return serviceDuration;
|
||||
}
|
||||
|
||||
public void setServiceDuration(int serviceDuration) {
|
||||
this.serviceDuration.set(serviceDuration);
|
||||
}
|
||||
|
||||
public double getServicePrice() {
|
||||
return servicePrice.get();
|
||||
}
|
||||
|
||||
public SimpleDoubleProperty servicePriceProperty() {
|
||||
return servicePrice;
|
||||
}
|
||||
|
||||
public void setServicePrice(double servicePrice) {
|
||||
this.servicePrice.set(servicePrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts DTO into Service model (for edit/delete)
|
||||
*/
|
||||
public Service toService() {
|
||||
|
||||
Service service = new Service(
|
||||
getServiceId(),
|
||||
getServiceName(),
|
||||
getServiceDesc(),
|
||||
getServiceDuration(),
|
||||
getServicePrice()
|
||||
);
|
||||
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.example.petshopdesktop;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
public class Launcher {
|
||||
public static void main(String[] args) {
|
||||
Application.launch(PetShopApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.example.petshopdesktop;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PetShopApplication extends Application {
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("login-view.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load());
|
||||
stage.setTitle("Pet Shop Manager - Login");
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
}
|
||||
130
desktop/src/main/java/org/example/petshopdesktop/Validator.java
Normal file
130
desktop/src/main/java/org/example/petshopdesktop/Validator.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package org.example.petshopdesktop;
|
||||
|
||||
public class Validator {
|
||||
|
||||
/**
|
||||
* Checks if string is not blank
|
||||
* @param value string to check
|
||||
* @param name name of the input
|
||||
* @return error msg if string is blank, otherwise empty
|
||||
*/
|
||||
public static String isPresent(String value, String name){
|
||||
String msg = "";
|
||||
if (value == null || value.isBlank()){
|
||||
msg += name + " is required. \n";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input is a non-negative double
|
||||
* @param value input of string
|
||||
* @param name name of input
|
||||
* @return error msg if input is not a number or negative, otherwise empty
|
||||
*/
|
||||
public static String isNonNegativeDouble(String value, String name){
|
||||
String msg ="";
|
||||
double result;
|
||||
try{
|
||||
result = Double.parseDouble(value);
|
||||
if (result < 0){
|
||||
msg += name + " must be greater than or equal 0. \n";
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e){
|
||||
msg += name + " must be a number.\n";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input is a double in 2 different range
|
||||
* @param value input of string
|
||||
* @param name name of input
|
||||
* @param minValue min value of range
|
||||
* @param maxValue max value of range
|
||||
* @return error msg if input is out of range, otherwise empty
|
||||
*/
|
||||
public static String isDoubleInRange(String value, String name, double minValue, double maxValue){
|
||||
String msg ="";
|
||||
double result;
|
||||
try{
|
||||
result = Double.parseDouble(value);
|
||||
if (result < minValue || result > maxValue){
|
||||
msg += name + " must be between " + minValue + " and " + maxValue + "\n";
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e){
|
||||
msg += name + " must be a number.\n";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input is a non-negative integer
|
||||
* @param value input of string
|
||||
* @param name name of input
|
||||
* @return error msg if input is not a number or negative, otherwise empty
|
||||
*/
|
||||
public static String isNonNegativeInteger(String value, String name){
|
||||
String msg ="";
|
||||
int result;
|
||||
try{
|
||||
result = Integer.parseInt(value);
|
||||
if (result < 0){
|
||||
msg += name + " must be greater than or equal 0. \n";
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e){
|
||||
msg += name + " must be a whole number.\n";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the string is a given amount of characters or fewer
|
||||
* @param value input of string
|
||||
* @param name name of input
|
||||
* @param length max allowed length
|
||||
* @return error msg if input is more than given characters length
|
||||
*/
|
||||
public static String isLessThanVarChars(String value, String name, int length){
|
||||
String msg ="";
|
||||
if (value.length() > length){
|
||||
msg += name + " must be less than " + length + " characters. \n";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input is a valid email format
|
||||
* @param value input of string
|
||||
* @param name name of input
|
||||
* @return error msg if input is not a valid email format, otherwise empty
|
||||
*/
|
||||
public static String isValidEmail(String value, String name){
|
||||
String msg = "";
|
||||
String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
|
||||
|
||||
if (!value.matches(regex)){
|
||||
msg += name + " is not in a valid format. \n";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input is a valid phone number in format XXX-XXX-XXXX
|
||||
* @param value input of string
|
||||
* @param name name of input
|
||||
* @return error msg if input is not in valid phone format, otherwise empty
|
||||
*/
|
||||
public static String isValidPhoneNumber(String value, String name){
|
||||
String msg = "";
|
||||
String regex = "^\\d{3}-\\d{3}-\\d{4}$";
|
||||
|
||||
if (!value.matches(regex)){
|
||||
msg += name + " must be in format XXX-XXX-XXXX. \n";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package org.example.petshopdesktop.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ApiClient {
|
||||
private static final ApiClient INSTANCE = new ApiClient();
|
||||
private final HttpClient httpClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String baseUrl;
|
||||
|
||||
private ApiClient() {
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper.registerModule(new JavaTimeModule());
|
||||
this.objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
this.baseUrl = ApiConfig.getInstance().getBaseUrl();
|
||||
}
|
||||
|
||||
public static ApiClient getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public <T> T get(String path, Class<T> responseClass) throws Exception {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.GET()
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
|
||||
addAuthHeader(builder);
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
return handleResponse(response, responseClass);
|
||||
}
|
||||
|
||||
public String getRawResponse(String path) throws Exception {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.GET()
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
|
||||
addAuthHeader(builder);
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200 || response.statusCode() == 201) {
|
||||
return response.body();
|
||||
} else if (response.statusCode() == 401) {
|
||||
throw new RuntimeException("Authentication failed. Please log in again.");
|
||||
} else if (response.statusCode() == 403) {
|
||||
throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
|
||||
} else {
|
||||
throw new RuntimeException(parseErrorMessage(response));
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T post(String path, Object requestBody, Class<T> responseClass) throws Exception {
|
||||
String jsonBody = objectMapper.writeValueAsString(requestBody);
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
|
||||
addAuthHeader(builder);
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
return handleResponse(response, responseClass);
|
||||
}
|
||||
|
||||
public <T> T postMultipart(String path, String partName, Path filePath, Class<T> responseClass) throws Exception {
|
||||
String boundary = "----PetShopDesktop" + UUID.randomUUID();
|
||||
String mimeType = Files.probeContentType(filePath);
|
||||
if (mimeType == null || mimeType.isBlank()) {
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
|
||||
byte[] fileBytes = Files.readAllBytes(filePath);
|
||||
String fileName = filePath.getFileName().toString();
|
||||
byte[] prefix = ("--" + boundary + "\r\n"
|
||||
+ "Content-Disposition: form-data; name=\"" + partName + "\"; filename=\"" + fileName + "\"\r\n"
|
||||
+ "Content-Type: " + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8);
|
||||
byte[] suffix = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
|
||||
byte[] body = new byte[prefix.length + fileBytes.length + suffix.length];
|
||||
System.arraycopy(prefix, 0, body, 0, prefix.length);
|
||||
System.arraycopy(fileBytes, 0, body, prefix.length, fileBytes.length);
|
||||
System.arraycopy(suffix, 0, body, prefix.length + fileBytes.length, suffix.length);
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
|
||||
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
|
||||
addAuthHeader(builder);
|
||||
|
||||
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
return handleResponse(response, responseClass);
|
||||
}
|
||||
|
||||
public <T> T put(String path, Object requestBody, Class<T> responseClass) throws Exception {
|
||||
String jsonBody = objectMapper.writeValueAsString(requestBody);
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.header("Content-Type", "application/json")
|
||||
.PUT(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
|
||||
addAuthHeader(builder);
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
return handleResponse(response, responseClass);
|
||||
}
|
||||
|
||||
public void delete(String path) throws Exception {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.DELETE()
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
|
||||
addAuthHeader(builder);
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() != 204 && response.statusCode() != 200) {
|
||||
throw new RuntimeException(parseErrorMessage(response));
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteWithBody(String path, Object requestBody) throws Exception {
|
||||
String jsonBody = objectMapper.writeValueAsString(requestBody);
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.header("Content-Type", "application/json")
|
||||
.method("DELETE", HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
|
||||
addAuthHeader(builder);
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() != 204 && response.statusCode() != 200) {
|
||||
throw new RuntimeException(parseErrorMessage(response));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAuthHeader(HttpRequest.Builder builder) {
|
||||
String token = UserSession.getInstance().getJwtToken();
|
||||
if (token != null && !token.isEmpty()) {
|
||||
builder.header("Authorization", "Bearer " + token);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T handleResponse(HttpResponse<String> response, Class<T> responseClass) throws Exception {
|
||||
int statusCode = response.statusCode();
|
||||
|
||||
if (statusCode == 200 || statusCode == 201) {
|
||||
if (response.body() == null || response.body().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return objectMapper.readValue(response.body(), responseClass);
|
||||
} else if (statusCode == 204) {
|
||||
return null;
|
||||
} else if (statusCode == 401) {
|
||||
throw new RuntimeException("Authentication failed. Please log in again.");
|
||||
} else if (statusCode == 403) {
|
||||
throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
|
||||
} else {
|
||||
throw new RuntimeException(parseErrorMessage(response));
|
||||
}
|
||||
}
|
||||
|
||||
private String parseErrorMessage(HttpResponse<String> response) {
|
||||
try {
|
||||
if (response.body() != null && !response.body().isEmpty()) {
|
||||
var errorNode = objectMapper.readTree(response.body());
|
||||
if (errorNode.has("message")) {
|
||||
return errorNode.get("message").asText();
|
||||
}
|
||||
if (errorNode.has("errors")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
errorNode.get("errors").fields().forEachRemaining(entry -> {
|
||||
sb.append(entry.getValue().asText()).append("\n");
|
||||
});
|
||||
return sb.toString().trim();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error parsing error message: " + e.getMessage());
|
||||
}
|
||||
return "Request failed with status " + response.statusCode();
|
||||
}
|
||||
|
||||
public ObjectMapper getObjectMapper() {
|
||||
return objectMapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.example.petshopdesktop.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
public class ApiConfig {
|
||||
private static final ApiConfig INSTANCE = new ApiConfig();
|
||||
private final String baseUrl;
|
||||
|
||||
private ApiConfig() {
|
||||
Properties props = new Properties();
|
||||
String url = "http://localhost:8080";
|
||||
|
||||
try (InputStream input = getClass().getClassLoader().getResourceAsStream("connectionpetstore.properties")) {
|
||||
if (input != null) {
|
||||
props.load(input);
|
||||
url = props.getProperty("api.baseUrl", "http://localhost:8080");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to load api.baseUrl from properties: " + e.getMessage());
|
||||
}
|
||||
|
||||
this.baseUrl = url;
|
||||
}
|
||||
|
||||
public static ApiConfig getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package org.example.petshopdesktop.api;
|
||||
|
||||
import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.WebSocket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ChatRealtimeClient implements WebSocket.Listener {
|
||||
private static final ChatRealtimeClient INSTANCE = new ChatRealtimeClient();
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final StringBuilder frameBuffer = new StringBuilder();
|
||||
private final AtomicInteger subscriptionCounter = new AtomicInteger(1);
|
||||
private final Map<String, String> destinationBySubscription = new HashMap<>();
|
||||
private final Object lock = new Object();
|
||||
|
||||
private WebSocket webSocket;
|
||||
private boolean connecting;
|
||||
private boolean connected;
|
||||
private boolean conversationsSubscribed;
|
||||
private Long selectedConversationId;
|
||||
private String conversationsSubscriptionId;
|
||||
private String conversationMessagesSubscriptionId;
|
||||
private Consumer<ConversationResponse> conversationListener;
|
||||
private Consumer<MessageResponse> messageListener;
|
||||
private Consumer<String> statusListener;
|
||||
private volatile String currentStatus = "Chat disconnected";
|
||||
|
||||
private ChatRealtimeClient() {
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ChatRealtimeClient getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void setConversationListener(Consumer<ConversationResponse> conversationListener) {
|
||||
this.conversationListener = conversationListener;
|
||||
}
|
||||
|
||||
public void setMessageListener(Consumer<MessageResponse> messageListener) {
|
||||
this.messageListener = messageListener;
|
||||
}
|
||||
|
||||
public void setStatusListener(Consumer<String> statusListener) {
|
||||
this.statusListener = statusListener;
|
||||
if (statusListener != null) {
|
||||
statusListener.accept(currentStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
String token = UserSession.getInstance().getJwtToken();
|
||||
if (token == null || token.isBlank()) {
|
||||
publishStatus("Chat disconnected");
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
if (connected || connecting) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
}
|
||||
|
||||
String wsUrl = ApiConfig.getInstance().getBaseUrl()
|
||||
.replaceFirst("^http://", "ws://")
|
||||
.replaceFirst("^https://", "wss://") + "/ws/chat";
|
||||
|
||||
publishStatus("Connecting chat...");
|
||||
|
||||
httpClient.newWebSocketBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.buildAsync(URI.create(wsUrl), this)
|
||||
.thenAccept(socket -> {
|
||||
synchronized (lock) {
|
||||
webSocket = socket;
|
||||
}
|
||||
socket.sendText("CONNECT\naccept-version:1.2\nhost:localhost\nAuthorization:Bearer " + token + "\n\n\0", true);
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
synchronized (lock) {
|
||||
resetConnectionState();
|
||||
}
|
||||
publishStatus("Chat unavailable");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
WebSocket socket;
|
||||
synchronized (lock) {
|
||||
socket = webSocket;
|
||||
resetConnectionState();
|
||||
selectedConversationId = null;
|
||||
conversationsSubscribed = false;
|
||||
}
|
||||
if (socket != null) {
|
||||
socket.sendText("DISCONNECT\n\n\0", true);
|
||||
socket.sendClose(WebSocket.NORMAL_CLOSURE, "bye");
|
||||
}
|
||||
publishStatus("Chat disconnected");
|
||||
}
|
||||
|
||||
public void subscribeToConversations() {
|
||||
synchronized (lock) {
|
||||
conversationsSubscribed = true;
|
||||
}
|
||||
connect();
|
||||
synchronized (lock) {
|
||||
if (connected && conversationsSubscriptionId == null) {
|
||||
conversationsSubscriptionId = subscribeLocked("/topic/chat/conversations");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribeToConversation(Long conversationId) {
|
||||
synchronized (lock) {
|
||||
selectedConversationId = conversationId;
|
||||
}
|
||||
connect();
|
||||
synchronized (lock) {
|
||||
if (connected) {
|
||||
applySelectedConversationSubscriptionLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
synchronized (lock) {
|
||||
return connected;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean sendMessage(Long conversationId, String content) {
|
||||
String token = UserSession.getInstance().getJwtToken();
|
||||
if (token == null || token.isBlank()) {
|
||||
publishStatus("Chat send failed");
|
||||
return false;
|
||||
}
|
||||
String body;
|
||||
try {
|
||||
body = ApiClient.getInstance().getObjectMapper().writeValueAsString(new MessageRequest(content));
|
||||
} catch (Exception e) {
|
||||
publishStatus("Chat send failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
if (!connected || webSocket == null) {
|
||||
connect();
|
||||
return false;
|
||||
}
|
||||
webSocket.sendText(
|
||||
"SEND\ndestination:/app/chat/conversations/" + conversationId + "/messages\nAuthorization:Bearer " + token + "\ncontent-type:application/json\ncontent-length:" + body.getBytes(StandardCharsets.UTF_8).length + "\n\n" + body + "\0",
|
||||
true
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private String subscribeLocked(String destination) {
|
||||
String subscriptionId = "sub-" + subscriptionCounter.getAndIncrement();
|
||||
destinationBySubscription.put(subscriptionId, destination);
|
||||
webSocket.sendText("SUBSCRIBE\nid:" + subscriptionId + "\ndestination:" + destination + "\n\n\0", true);
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
private void unsubscribeLocked(String subscriptionId) {
|
||||
destinationBySubscription.remove(subscriptionId);
|
||||
if (webSocket != null) {
|
||||
webSocket.sendText("UNSUBSCRIBE\nid:" + subscriptionId + "\n\n\0", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySubscriptionsLocked() {
|
||||
if (webSocket == null || !connected) {
|
||||
return;
|
||||
}
|
||||
if (conversationsSubscribed && conversationsSubscriptionId == null) {
|
||||
conversationsSubscriptionId = subscribeLocked("/topic/chat/conversations");
|
||||
}
|
||||
applySelectedConversationSubscriptionLocked();
|
||||
}
|
||||
|
||||
private void applySelectedConversationSubscriptionLocked() {
|
||||
if (webSocket == null || !connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
String destination = selectedConversationId == null ? null : "/topic/chat/conversations/" + selectedConversationId;
|
||||
if (destination == null) {
|
||||
if (conversationMessagesSubscriptionId != null) {
|
||||
unsubscribeLocked(conversationMessagesSubscriptionId);
|
||||
conversationMessagesSubscriptionId = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversationMessagesSubscriptionId != null) {
|
||||
String currentDestination = destinationBySubscription.get(conversationMessagesSubscriptionId);
|
||||
if (destination.equals(currentDestination)) {
|
||||
return;
|
||||
}
|
||||
unsubscribeLocked(conversationMessagesSubscriptionId);
|
||||
}
|
||||
|
||||
conversationMessagesSubscriptionId = subscribeLocked(destination);
|
||||
}
|
||||
|
||||
private void resetConnectionState() {
|
||||
webSocket = null;
|
||||
connecting = false;
|
||||
connected = false;
|
||||
destinationBySubscription.clear();
|
||||
conversationsSubscriptionId = null;
|
||||
conversationMessagesSubscriptionId = null;
|
||||
}
|
||||
|
||||
private void handleFrame(String frame) {
|
||||
String normalized = frame.replace("\r\n", "\n");
|
||||
int separator = normalized.indexOf("\n\n");
|
||||
String headerPart = separator >= 0 ? normalized.substring(0, separator) : normalized;
|
||||
String bodyPart = separator >= 0 ? normalized.substring(separator + 2) : "";
|
||||
String[] headerLines = headerPart.split("\n");
|
||||
if (headerLines.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String command = headerLines[0];
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
for (int i = 1; i < headerLines.length; i++) {
|
||||
int idx = headerLines[i].indexOf(':');
|
||||
if (idx > 0) {
|
||||
headers.put(headerLines[i].substring(0, idx), headerLines[i].substring(idx + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if ("CONNECTED".equals(command)) {
|
||||
synchronized (lock) {
|
||||
connecting = false;
|
||||
connected = true;
|
||||
applySubscriptionsLocked();
|
||||
}
|
||||
publishStatus("Chat connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("MESSAGE".equals(command)) {
|
||||
String destination;
|
||||
synchronized (lock) {
|
||||
destination = destinationBySubscription.get(headers.get("subscription"));
|
||||
}
|
||||
try {
|
||||
if (destination != null && destination.startsWith("/topic/chat/conversations/")) {
|
||||
MessageResponse message = ApiClient.getInstance().getObjectMapper().readValue(bodyPart, MessageResponse.class);
|
||||
if (messageListener != null) {
|
||||
messageListener.accept(message);
|
||||
}
|
||||
} else {
|
||||
ConversationResponse conversation = ApiClient.getInstance().getObjectMapper().readValue(bodyPart, ConversationResponse.class);
|
||||
if (conversationListener != null) {
|
||||
conversationListener.accept(conversation);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
publishStatus("Chat update failed");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ("ERROR".equals(command)) {
|
||||
publishStatus("Chat error");
|
||||
}
|
||||
}
|
||||
|
||||
private void publishStatus(String status) {
|
||||
currentStatus = status;
|
||||
if (statusListener != null) {
|
||||
statusListener.accept(status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket) {
|
||||
webSocket.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
|
||||
synchronized (lock) {
|
||||
frameBuffer.append(data);
|
||||
int delimiter;
|
||||
while ((delimiter = frameBuffer.indexOf("\0")) >= 0) {
|
||||
String frame = frameBuffer.substring(0, delimiter);
|
||||
frameBuffer.delete(0, delimiter + 1);
|
||||
handleFrame(frame);
|
||||
}
|
||||
}
|
||||
webSocket.request(1);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<?> onBinary(WebSocket webSocket, ByteBuffer data, boolean last) {
|
||||
webSocket.request(1);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
|
||||
synchronized (lock) {
|
||||
resetConnectionState();
|
||||
}
|
||||
publishStatus("Chat disconnected");
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket webSocket, Throwable error) {
|
||||
synchronized (lock) {
|
||||
resetConnectionState();
|
||||
}
|
||||
publishStatus("Chat unavailable");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.example.petshopdesktop.api.dto.adoption;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class AdoptionRequest {
|
||||
private Long petId;
|
||||
private Long customerId;
|
||||
private LocalDate adoptionDate;
|
||||
private String adoptionStatus;
|
||||
|
||||
public AdoptionRequest() {
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public void setPetId(Long petId) {
|
||||
this.petId = petId;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public LocalDate getAdoptionDate() {
|
||||
return adoptionDate;
|
||||
}
|
||||
|
||||
public void setAdoptionDate(LocalDate adoptionDate) {
|
||||
this.adoptionDate = adoptionDate;
|
||||
}
|
||||
|
||||
public String getAdoptionStatus() {
|
||||
return adoptionStatus;
|
||||
}
|
||||
|
||||
public void setAdoptionStatus(String adoptionStatus) {
|
||||
this.adoptionStatus = adoptionStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.example.petshopdesktop.api.dto.adoption;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class AdoptionResponse {
|
||||
private Long adoptionId;
|
||||
private Long petId;
|
||||
private Long customerId;
|
||||
private String petName;
|
||||
private String customerName;
|
||||
private LocalDate adoptionDate;
|
||||
private java.math.BigDecimal adoptionFee;
|
||||
private String adoptionStatus;
|
||||
|
||||
public AdoptionResponse() {
|
||||
}
|
||||
|
||||
public Long getAdoptionId() {
|
||||
return adoptionId;
|
||||
}
|
||||
|
||||
public void setAdoptionId(Long adoptionId) {
|
||||
this.adoptionId = adoptionId;
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public void setPetId(Long petId) {
|
||||
this.petId = petId;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public LocalDate getAdoptionDate() {
|
||||
return adoptionDate;
|
||||
}
|
||||
|
||||
public void setAdoptionDate(LocalDate adoptionDate) {
|
||||
this.adoptionDate = adoptionDate;
|
||||
}
|
||||
|
||||
public java.math.BigDecimal getAdoptionFee() {
|
||||
return adoptionFee;
|
||||
}
|
||||
|
||||
public void setAdoptionFee(java.math.BigDecimal adoptionFee) {
|
||||
this.adoptionFee = adoptionFee;
|
||||
}
|
||||
|
||||
public String getAdoptionStatus() {
|
||||
return adoptionStatus;
|
||||
}
|
||||
|
||||
public void setAdoptionStatus(String adoptionStatus) {
|
||||
this.adoptionStatus = adoptionStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.example.petshopdesktop.api.dto.analytics;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class DailySales {
|
||||
private String date;
|
||||
private BigDecimal revenue;
|
||||
private Long salesCount;
|
||||
|
||||
public DailySales() {
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public BigDecimal getRevenue() {
|
||||
return revenue;
|
||||
}
|
||||
|
||||
public void setRevenue(BigDecimal revenue) {
|
||||
this.revenue = revenue;
|
||||
}
|
||||
|
||||
public Long getSalesCount() {
|
||||
return salesCount;
|
||||
}
|
||||
|
||||
public void setSalesCount(Long salesCount) {
|
||||
this.salesCount = salesCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.example.petshopdesktop.api.dto.analytics;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class DashboardResponse {
|
||||
private SalesSummary salesSummary;
|
||||
private InventorySummary inventorySummary;
|
||||
private List<TopProduct> topProducts;
|
||||
private List<DailySales> dailySales;
|
||||
|
||||
public DashboardResponse() {
|
||||
}
|
||||
|
||||
public SalesSummary getSalesSummary() {
|
||||
return salesSummary;
|
||||
}
|
||||
|
||||
public void setSalesSummary(SalesSummary salesSummary) {
|
||||
this.salesSummary = salesSummary;
|
||||
}
|
||||
|
||||
public InventorySummary getInventorySummary() {
|
||||
return inventorySummary;
|
||||
}
|
||||
|
||||
public void setInventorySummary(InventorySummary inventorySummary) {
|
||||
this.inventorySummary = inventorySummary;
|
||||
}
|
||||
|
||||
public List<TopProduct> getTopProducts() {
|
||||
return topProducts;
|
||||
}
|
||||
|
||||
public void setTopProducts(List<TopProduct> topProducts) {
|
||||
this.topProducts = topProducts;
|
||||
}
|
||||
|
||||
public List<DailySales> getDailySales() {
|
||||
return dailySales;
|
||||
}
|
||||
|
||||
public void setDailySales(List<DailySales> dailySales) {
|
||||
this.dailySales = dailySales;
|
||||
}
|
||||
|
||||
public static class SalesSummary {
|
||||
private BigDecimal totalRevenue;
|
||||
private Long totalSales;
|
||||
private BigDecimal totalRefunds;
|
||||
private Long totalRefundCount;
|
||||
|
||||
public SalesSummary() {
|
||||
}
|
||||
|
||||
public BigDecimal getTotalRevenue() {
|
||||
return totalRevenue;
|
||||
}
|
||||
|
||||
public void setTotalRevenue(BigDecimal totalRevenue) {
|
||||
this.totalRevenue = totalRevenue;
|
||||
}
|
||||
|
||||
public Long getTotalSales() {
|
||||
return totalSales;
|
||||
}
|
||||
|
||||
public void setTotalSales(Long totalSales) {
|
||||
this.totalSales = totalSales;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalRefunds() {
|
||||
return totalRefunds;
|
||||
}
|
||||
|
||||
public void setTotalRefunds(BigDecimal totalRefunds) {
|
||||
this.totalRefunds = totalRefunds;
|
||||
}
|
||||
|
||||
public Long getTotalRefundCount() {
|
||||
return totalRefundCount;
|
||||
}
|
||||
|
||||
public void setTotalRefundCount(Long totalRefundCount) {
|
||||
this.totalRefundCount = totalRefundCount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InventorySummary {
|
||||
private Long totalProducts;
|
||||
private Long lowStockProducts;
|
||||
private Long outOfStockProducts;
|
||||
|
||||
public InventorySummary() {
|
||||
}
|
||||
|
||||
public Long getTotalProducts() {
|
||||
return totalProducts;
|
||||
}
|
||||
|
||||
public void setTotalProducts(Long totalProducts) {
|
||||
this.totalProducts = totalProducts;
|
||||
}
|
||||
|
||||
public Long getLowStockProducts() {
|
||||
return lowStockProducts;
|
||||
}
|
||||
|
||||
public void setLowStockProducts(Long lowStockProducts) {
|
||||
this.lowStockProducts = lowStockProducts;
|
||||
}
|
||||
|
||||
public Long getOutOfStockProducts() {
|
||||
return outOfStockProducts;
|
||||
}
|
||||
|
||||
public void setOutOfStockProducts(Long outOfStockProducts) {
|
||||
this.outOfStockProducts = outOfStockProducts;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.example.petshopdesktop.api.dto.analytics;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class TopProduct {
|
||||
private Long productId;
|
||||
private String productName;
|
||||
private Long quantitySold;
|
||||
private BigDecimal revenue;
|
||||
|
||||
public TopProduct() {
|
||||
}
|
||||
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName) {
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public Long getQuantitySold() {
|
||||
return quantitySold;
|
||||
}
|
||||
|
||||
public void setQuantitySold(Long quantitySold) {
|
||||
this.quantitySold = quantitySold;
|
||||
}
|
||||
|
||||
public BigDecimal getRevenue() {
|
||||
return revenue;
|
||||
}
|
||||
|
||||
public void setRevenue(BigDecimal revenue) {
|
||||
this.revenue = revenue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.example.petshopdesktop.api.dto.appointment;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentRequest {
|
||||
private List<Long> petIds;
|
||||
private Long customerId;
|
||||
private Long storeId;
|
||||
private Long serviceId;
|
||||
private LocalDate appointmentDate;
|
||||
private LocalTime appointmentTime;
|
||||
private String appointmentStatus;
|
||||
|
||||
public AppointmentRequest() {
|
||||
}
|
||||
|
||||
public List<Long> getPetIds() {
|
||||
return petIds;
|
||||
}
|
||||
|
||||
public void setPetIds(List<Long> petIds) {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public Long getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(Long serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public LocalDate getAppointmentDate() {
|
||||
return appointmentDate;
|
||||
}
|
||||
|
||||
public void setAppointmentDate(LocalDate appointmentDate) {
|
||||
this.appointmentDate = appointmentDate;
|
||||
}
|
||||
|
||||
public LocalTime getAppointmentTime() {
|
||||
return appointmentTime;
|
||||
}
|
||||
|
||||
public void setAppointmentTime(LocalTime appointmentTime) {
|
||||
this.appointmentTime = appointmentTime;
|
||||
}
|
||||
|
||||
public String getAppointmentStatus() {
|
||||
return appointmentStatus;
|
||||
}
|
||||
|
||||
public void setAppointmentStatus(String appointmentStatus) {
|
||||
this.appointmentStatus = appointmentStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.example.petshopdesktop.api.dto.appointment;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public class AppointmentResponse {
|
||||
private Long appointmentId;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Long serviceId;
|
||||
private java.util.List<String> petNames;
|
||||
private java.util.List<Long> petIds;
|
||||
private String serviceName;
|
||||
private LocalDate appointmentDate;
|
||||
private LocalTime appointmentTime;
|
||||
private String appointmentStatus;
|
||||
|
||||
public AppointmentResponse() {
|
||||
}
|
||||
|
||||
public Long getAppointmentId() {
|
||||
return appointmentId;
|
||||
}
|
||||
|
||||
public void setAppointmentId(Long appointmentId) {
|
||||
this.appointmentId = appointmentId;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
public Long getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(Long serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public java.util.List<String> getPetNames() {
|
||||
return petNames;
|
||||
}
|
||||
|
||||
public void setPetNames(java.util.List<String> petNames) {
|
||||
this.petNames = petNames;
|
||||
}
|
||||
|
||||
public java.util.List<Long> getPetIds() {
|
||||
return petIds;
|
||||
}
|
||||
|
||||
public void setPetIds(java.util.List<Long> petIds) {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public LocalDate getAppointmentDate() {
|
||||
return appointmentDate;
|
||||
}
|
||||
|
||||
public void setAppointmentDate(LocalDate appointmentDate) {
|
||||
this.appointmentDate = appointmentDate;
|
||||
}
|
||||
|
||||
public LocalTime getAppointmentTime() {
|
||||
return appointmentTime;
|
||||
}
|
||||
|
||||
public void setAppointmentTime(LocalTime appointmentTime) {
|
||||
this.appointmentTime = appointmentTime;
|
||||
}
|
||||
|
||||
public String getAppointmentStatus() {
|
||||
return appointmentStatus;
|
||||
}
|
||||
|
||||
public void setAppointmentStatus(String appointmentStatus) {
|
||||
this.appointmentStatus = appointmentStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.example.petshopdesktop.api.dto.auth;
|
||||
|
||||
public class AvatarUploadResponse {
|
||||
private String avatarUrl;
|
||||
private String message;
|
||||
|
||||
public AvatarUploadResponse() {
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.example.petshopdesktop.api.dto.auth;
|
||||
|
||||
public class LoginRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public LoginRequest() {
|
||||
}
|
||||
|
||||
public LoginRequest(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.example.petshopdesktop.api.dto.auth;
|
||||
|
||||
public class LoginResponse {
|
||||
private String token;
|
||||
private String username;
|
||||
private String role;
|
||||
|
||||
public LoginResponse() {
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.example.petshopdesktop.api.dto.auth;
|
||||
|
||||
public class UserInfoResponse {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String email;
|
||||
private String fullName;
|
||||
private String phone;
|
||||
private String avatarUrl;
|
||||
private String role;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
|
||||
public UserInfoResponse() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.example.petshopdesktop.api.dto.chat;
|
||||
|
||||
public class ConversationRequest {
|
||||
private String message;
|
||||
|
||||
public ConversationRequest() {
|
||||
}
|
||||
|
||||
public ConversationRequest(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.example.petshopdesktop.api.dto.chat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class ConversationResponse {
|
||||
private Long id;
|
||||
private Long customerId;
|
||||
private Long staffId;
|
||||
private String status;
|
||||
private String mode;
|
||||
private String lastMessage;
|
||||
private LocalDateTime humanRequestedAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public ConversationResponse() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getStaffId() {
|
||||
return staffId;
|
||||
}
|
||||
|
||||
public void setStaffId(Long staffId) {
|
||||
this.staffId = staffId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getLastMessage() {
|
||||
return lastMessage;
|
||||
}
|
||||
|
||||
public void setLastMessage(String lastMessage) {
|
||||
this.lastMessage = lastMessage;
|
||||
}
|
||||
|
||||
public LocalDateTime getHumanRequestedAt() {
|
||||
return humanRequestedAt;
|
||||
}
|
||||
|
||||
public void setHumanRequestedAt(LocalDateTime humanRequestedAt) {
|
||||
this.humanRequestedAt = humanRequestedAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.example.petshopdesktop.api.dto.chat;
|
||||
|
||||
public class MessageRequest {
|
||||
private String content;
|
||||
|
||||
public MessageRequest() {
|
||||
}
|
||||
|
||||
public MessageRequest(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.example.petshopdesktop.api.dto.chat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class MessageResponse {
|
||||
private Long id;
|
||||
private Long conversationId;
|
||||
private Long senderId;
|
||||
private String content;
|
||||
private LocalDateTime timestamp;
|
||||
private Boolean isRead;
|
||||
|
||||
public MessageResponse() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getConversationId() {
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
public void setConversationId(Long conversationId) {
|
||||
this.conversationId = conversationId;
|
||||
}
|
||||
|
||||
public Long getSenderId() {
|
||||
return senderId;
|
||||
}
|
||||
|
||||
public void setSenderId(Long senderId) {
|
||||
this.senderId = senderId;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(LocalDateTime timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Boolean getIsRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.example.petshopdesktop.api.dto.common;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BulkDeleteRequest {
|
||||
private List<Long> ids;
|
||||
|
||||
public BulkDeleteRequest() {
|
||||
}
|
||||
|
||||
public BulkDeleteRequest(List<Long> ids) {
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
public List<Long> getIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public void setIds(List<Long> ids) {
|
||||
this.ids = ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.example.petshopdesktop.api.dto.common;
|
||||
|
||||
public class DropdownOption {
|
||||
private Long id;
|
||||
private String label;
|
||||
|
||||
public DropdownOption() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return label == null ? "" : label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.example.petshopdesktop.api.dto.common;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class PageResponse<T> {
|
||||
private List<T> content;
|
||||
|
||||
@JsonProperty("number")
|
||||
private int pageNumber;
|
||||
|
||||
@JsonProperty("size")
|
||||
private int pageSize;
|
||||
|
||||
private long totalElements;
|
||||
private int totalPages;
|
||||
private boolean last;
|
||||
|
||||
public PageResponse() {
|
||||
}
|
||||
|
||||
public List<T> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(List<T> content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public int getPageNumber() {
|
||||
return pageNumber;
|
||||
}
|
||||
|
||||
public void setPageNumber(int pageNumber) {
|
||||
this.pageNumber = pageNumber;
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(int pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
public long getTotalElements() {
|
||||
return totalElements;
|
||||
}
|
||||
|
||||
public void setTotalElements(long totalElements) {
|
||||
this.totalElements = totalElements;
|
||||
}
|
||||
|
||||
public int getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
public void setTotalPages(int totalPages) {
|
||||
this.totalPages = totalPages;
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.example.petshopdesktop.api.dto.employee;
|
||||
|
||||
public class EmployeeRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private Boolean active;
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
public String getFirstName() { return firstName; }
|
||||
public void setFirstName(String firstName) { this.firstName = firstName; }
|
||||
public String getLastName() { return lastName; }
|
||||
public void setLastName(String lastName) { this.lastName = lastName; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.example.petshopdesktop.api.dto.employee;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class EmployeeResponse {
|
||||
private Long employeeId;
|
||||
private Long userId;
|
||||
private String username;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private Boolean active;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Long getEmployeeId() { return employeeId; }
|
||||
public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public String getFirstName() { return firstName; }
|
||||
public void setFirstName(String firstName) { this.firstName = firstName; }
|
||||
public String getLastName() { return lastName; }
|
||||
public void setLastName(String lastName) { this.lastName = lastName; }
|
||||
public String getFullName() { return fullName; }
|
||||
public void setFullName(String fullName) { this.fullName = fullName; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.example.petshopdesktop.api.dto.inventory;
|
||||
|
||||
public class InventoryRequest {
|
||||
private Long prodId;
|
||||
private Integer quantity;
|
||||
|
||||
public InventoryRequest() {
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(Long prodId) {
|
||||
this.prodId = prodId;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.example.petshopdesktop.api.dto.inventory;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class InventoryResponse {
|
||||
private Long inventoryId;
|
||||
private Long prodId;
|
||||
private String productName;
|
||||
private String categoryName;
|
||||
private Integer quantity;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public InventoryResponse() {
|
||||
}
|
||||
|
||||
public Long getInventoryId() {
|
||||
return inventoryId;
|
||||
}
|
||||
|
||||
public void setInventoryId(Long inventoryId) {
|
||||
this.inventoryId = inventoryId;
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(Long prodId) {
|
||||
this.prodId = prodId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName) {
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getCategoryName() {
|
||||
return categoryName;
|
||||
}
|
||||
|
||||
public void setCategoryName(String categoryName) {
|
||||
this.categoryName = categoryName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.example.petshopdesktop.api.dto.pet;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class PetRequest {
|
||||
private String petName;
|
||||
private String petSpecies;
|
||||
private String petBreed;
|
||||
private Integer petAge;
|
||||
private String petStatus;
|
||||
private BigDecimal petPrice;
|
||||
|
||||
public PetRequest() {
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public String getPetSpecies() {
|
||||
return petSpecies;
|
||||
}
|
||||
|
||||
public void setPetSpecies(String petSpecies) {
|
||||
this.petSpecies = petSpecies;
|
||||
}
|
||||
|
||||
public String getPetBreed() {
|
||||
return petBreed;
|
||||
}
|
||||
|
||||
public void setPetBreed(String petBreed) {
|
||||
this.petBreed = petBreed;
|
||||
}
|
||||
|
||||
public Integer getPetAge() {
|
||||
return petAge;
|
||||
}
|
||||
|
||||
public void setPetAge(Integer petAge) {
|
||||
this.petAge = petAge;
|
||||
}
|
||||
|
||||
public String getPetStatus() {
|
||||
return petStatus;
|
||||
}
|
||||
|
||||
public void setPetStatus(String petStatus) {
|
||||
this.petStatus = petStatus;
|
||||
}
|
||||
|
||||
public BigDecimal getPetPrice() {
|
||||
return petPrice;
|
||||
}
|
||||
|
||||
public void setPetPrice(BigDecimal petPrice) {
|
||||
this.petPrice = petPrice;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.example.petshopdesktop.api.dto.pet;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class PetResponse {
|
||||
private Long petId;
|
||||
private String petName;
|
||||
private String petSpecies;
|
||||
private String petBreed;
|
||||
private Integer petAge;
|
||||
private String petStatus;
|
||||
private BigDecimal petPrice;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public PetResponse() {
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public void setPetId(Long petId) {
|
||||
this.petId = petId;
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public String getPetSpecies() {
|
||||
return petSpecies;
|
||||
}
|
||||
|
||||
public void setPetSpecies(String petSpecies) {
|
||||
this.petSpecies = petSpecies;
|
||||
}
|
||||
|
||||
public String getPetBreed() {
|
||||
return petBreed;
|
||||
}
|
||||
|
||||
public void setPetBreed(String petBreed) {
|
||||
this.petBreed = petBreed;
|
||||
}
|
||||
|
||||
public Integer getPetAge() {
|
||||
return petAge;
|
||||
}
|
||||
|
||||
public void setPetAge(Integer petAge) {
|
||||
this.petAge = petAge;
|
||||
}
|
||||
|
||||
public String getPetStatus() {
|
||||
return petStatus;
|
||||
}
|
||||
|
||||
public void setPetStatus(String petStatus) {
|
||||
this.petStatus = petStatus;
|
||||
}
|
||||
|
||||
public BigDecimal getPetPrice() {
|
||||
return petPrice;
|
||||
}
|
||||
|
||||
public void setPetPrice(BigDecimal petPrice) {
|
||||
this.petPrice = petPrice;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.example.petshopdesktop.api.dto.product;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ProductRequest {
|
||||
private String prodName;
|
||||
private Long categoryId;
|
||||
private BigDecimal prodPrice;
|
||||
private String prodDesc;
|
||||
|
||||
public ProductRequest() {
|
||||
}
|
||||
|
||||
public String getProdName() {
|
||||
return prodName;
|
||||
}
|
||||
|
||||
public void setProdName(String prodName) {
|
||||
this.prodName = prodName;
|
||||
}
|
||||
|
||||
public Long getCategoryId() {
|
||||
return categoryId;
|
||||
}
|
||||
|
||||
public void setCategoryId(Long categoryId) {
|
||||
this.categoryId = categoryId;
|
||||
}
|
||||
|
||||
public BigDecimal getProdPrice() {
|
||||
return prodPrice;
|
||||
}
|
||||
|
||||
public void setProdPrice(BigDecimal prodPrice) {
|
||||
this.prodPrice = prodPrice;
|
||||
}
|
||||
|
||||
public String getProdDesc() {
|
||||
return prodDesc;
|
||||
}
|
||||
|
||||
public void setProdDesc(String prodDesc) {
|
||||
this.prodDesc = prodDesc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.example.petshopdesktop.api.dto.product;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ProductResponse {
|
||||
private Long prodId;
|
||||
private String prodName;
|
||||
private String categoryName;
|
||||
private BigDecimal prodPrice;
|
||||
private String prodDesc;
|
||||
|
||||
public ProductResponse() {
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(Long prodId) {
|
||||
this.prodId = prodId;
|
||||
}
|
||||
|
||||
public String getProdName() {
|
||||
return prodName;
|
||||
}
|
||||
|
||||
public void setProdName(String prodName) {
|
||||
this.prodName = prodName;
|
||||
}
|
||||
|
||||
public String getCategoryName() {
|
||||
return categoryName;
|
||||
}
|
||||
|
||||
public void setCategoryName(String categoryName) {
|
||||
this.categoryName = categoryName;
|
||||
}
|
||||
|
||||
public BigDecimal getProdPrice() {
|
||||
return prodPrice;
|
||||
}
|
||||
|
||||
public void setProdPrice(BigDecimal prodPrice) {
|
||||
this.prodPrice = prodPrice;
|
||||
}
|
||||
|
||||
public String getProdDesc() {
|
||||
return prodDesc;
|
||||
}
|
||||
|
||||
public void setProdDesc(String prodDesc) {
|
||||
this.prodDesc = prodDesc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.example.petshopdesktop.api.dto.productsupplier;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ProductSupplierRequest {
|
||||
private Long productId;
|
||||
private Long supplierId;
|
||||
private BigDecimal cost;
|
||||
|
||||
public ProductSupplierRequest() {
|
||||
}
|
||||
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public Long getSupplierId() {
|
||||
return supplierId;
|
||||
}
|
||||
|
||||
public void setSupplierId(Long supplierId) {
|
||||
this.supplierId = supplierId;
|
||||
}
|
||||
|
||||
public BigDecimal getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(BigDecimal cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.example.petshopdesktop.api.dto.productsupplier;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ProductSupplierResponse {
|
||||
private Long productId;
|
||||
private Long supplierId;
|
||||
private String productName;
|
||||
private String supplierName;
|
||||
private BigDecimal cost;
|
||||
|
||||
public ProductSupplierResponse() {
|
||||
}
|
||||
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public Long getSupplierId() {
|
||||
return supplierId;
|
||||
}
|
||||
|
||||
public void setSupplierId(Long supplierId) {
|
||||
this.supplierId = supplierId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName) {
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getSupplierName() {
|
||||
return supplierName;
|
||||
}
|
||||
|
||||
public void setSupplierName(String supplierName) {
|
||||
this.supplierName = supplierName;
|
||||
}
|
||||
|
||||
public BigDecimal getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(BigDecimal cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.example.petshopdesktop.api.dto.purchaseorder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class PurchaseOrderResponse {
|
||||
private Long purchaseOrderId;
|
||||
private String supplierName;
|
||||
private LocalDate orderDate;
|
||||
private LocalDate expectedDeliveryDate;
|
||||
private String orderStatus;
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
public PurchaseOrderResponse() {
|
||||
}
|
||||
|
||||
public Long getPurchaseOrderId() {
|
||||
return purchaseOrderId;
|
||||
}
|
||||
|
||||
public void setPurchaseOrderId(Long purchaseOrderId) {
|
||||
this.purchaseOrderId = purchaseOrderId;
|
||||
}
|
||||
|
||||
public String getSupplierName() {
|
||||
return supplierName;
|
||||
}
|
||||
|
||||
public void setSupplierName(String supplierName) {
|
||||
this.supplierName = supplierName;
|
||||
}
|
||||
|
||||
public LocalDate getOrderDate() {
|
||||
return orderDate;
|
||||
}
|
||||
|
||||
public void setOrderDate(LocalDate orderDate) {
|
||||
this.orderDate = orderDate;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedDeliveryDate() {
|
||||
return expectedDeliveryDate;
|
||||
}
|
||||
|
||||
public void setExpectedDeliveryDate(LocalDate expectedDeliveryDate) {
|
||||
this.expectedDeliveryDate = expectedDeliveryDate;
|
||||
}
|
||||
|
||||
public String getOrderStatus() {
|
||||
return orderStatus;
|
||||
}
|
||||
|
||||
public void setOrderStatus(String orderStatus) {
|
||||
this.orderStatus = orderStatus;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAmount() {
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
public void setTotalAmount(BigDecimal totalAmount) {
|
||||
this.totalAmount = totalAmount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.example.petshopdesktop.api.dto.sale;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class SaleItemRequest {
|
||||
private Long prodId;
|
||||
private Integer quantity;
|
||||
|
||||
public SaleItemRequest() {
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(Long prodId) {
|
||||
this.prodId = prodId;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.example.petshopdesktop.api.dto.sale;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class SaleItemResponse {
|
||||
private Long saleItemId;
|
||||
private Long prodId;
|
||||
private String productName;
|
||||
private Integer quantity;
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
public SaleItemResponse() {
|
||||
}
|
||||
|
||||
public Long getSaleItemId() {
|
||||
return saleItemId;
|
||||
}
|
||||
|
||||
public void setSaleItemId(Long saleItemId) {
|
||||
this.saleItemId = saleItemId;
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(Long prodId) {
|
||||
this.prodId = prodId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName) {
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public BigDecimal getUnitPrice() {
|
||||
return unitPrice;
|
||||
}
|
||||
|
||||
public void setUnitPrice(BigDecimal unitPrice) {
|
||||
this.unitPrice = unitPrice;
|
||||
}
|
||||
|
||||
public BigDecimal getLineTotal() {
|
||||
if (unitPrice == null || quantity == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return unitPrice.multiply(BigDecimal.valueOf(quantity));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.example.petshopdesktop.api.dto.sale;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SaleRequest {
|
||||
private Long storeId;
|
||||
private String paymentMethod;
|
||||
private List<SaleItemRequest> items;
|
||||
private Boolean isRefund;
|
||||
private Long originalSaleId;
|
||||
|
||||
public SaleRequest() {
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
public void setPaymentMethod(String paymentMethod) {
|
||||
this.paymentMethod = paymentMethod;
|
||||
}
|
||||
|
||||
public List<SaleItemRequest> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<SaleItemRequest> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public Boolean getIsRefund() {
|
||||
return isRefund;
|
||||
}
|
||||
|
||||
public void setIsRefund(Boolean isRefund) {
|
||||
this.isRefund = isRefund;
|
||||
}
|
||||
|
||||
public Long getOriginalSaleId() {
|
||||
return originalSaleId;
|
||||
}
|
||||
|
||||
public void setOriginalSaleId(Long originalSaleId) {
|
||||
this.originalSaleId = originalSaleId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.example.petshopdesktop.api.dto.sale;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class SaleResponse {
|
||||
private Long saleId;
|
||||
private String employeeName;
|
||||
private String storeName;
|
||||
private LocalDateTime saleDate;
|
||||
private BigDecimal totalAmount;
|
||||
private String paymentMethod;
|
||||
private Boolean isRefund;
|
||||
private Long originalSaleId;
|
||||
private List<SaleItemResponse> items;
|
||||
|
||||
public SaleResponse() {
|
||||
}
|
||||
|
||||
public Long getSaleId() {
|
||||
return saleId;
|
||||
}
|
||||
|
||||
public void setSaleId(Long saleId) {
|
||||
this.saleId = saleId;
|
||||
}
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public void setEmployeeName(String employeeName) {
|
||||
this.employeeName = employeeName;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
public LocalDateTime getSaleDate() {
|
||||
return saleDate;
|
||||
}
|
||||
|
||||
public void setSaleDate(LocalDateTime saleDate) {
|
||||
this.saleDate = saleDate;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAmount() {
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
public void setTotalAmount(BigDecimal totalAmount) {
|
||||
this.totalAmount = totalAmount;
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
public void setPaymentMethod(String paymentMethod) {
|
||||
this.paymentMethod = paymentMethod;
|
||||
}
|
||||
|
||||
public Boolean getIsRefund() {
|
||||
return isRefund;
|
||||
}
|
||||
|
||||
public void setIsRefund(Boolean isRefund) {
|
||||
this.isRefund = isRefund;
|
||||
}
|
||||
|
||||
public Long getOriginalSaleId() {
|
||||
return originalSaleId;
|
||||
}
|
||||
|
||||
public void setOriginalSaleId(Long originalSaleId) {
|
||||
this.originalSaleId = originalSaleId;
|
||||
}
|
||||
|
||||
public List<SaleItemResponse> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<SaleItemResponse> items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.example.petshopdesktop.api.dto.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ServiceRequest {
|
||||
private String serviceName;
|
||||
private BigDecimal servicePrice;
|
||||
private String serviceDesc;
|
||||
private Integer serviceDuration;
|
||||
|
||||
public ServiceRequest() {
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public BigDecimal getServicePrice() {
|
||||
return servicePrice;
|
||||
}
|
||||
|
||||
public void setServicePrice(BigDecimal servicePrice) {
|
||||
this.servicePrice = servicePrice;
|
||||
}
|
||||
|
||||
public String getServiceDesc() {
|
||||
return serviceDesc;
|
||||
}
|
||||
|
||||
public void setServiceDesc(String serviceDesc) {
|
||||
this.serviceDesc = serviceDesc;
|
||||
}
|
||||
|
||||
public Integer getServiceDuration() {
|
||||
return serviceDuration;
|
||||
}
|
||||
|
||||
public void setServiceDuration(Integer serviceDuration) {
|
||||
this.serviceDuration = serviceDuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.example.petshopdesktop.api.dto.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ServiceResponse {
|
||||
private Long serviceId;
|
||||
private String serviceName;
|
||||
private BigDecimal servicePrice;
|
||||
private String serviceDesc;
|
||||
private Integer serviceDuration;
|
||||
|
||||
public ServiceResponse() {
|
||||
}
|
||||
|
||||
public Long getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(Long serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public BigDecimal getServicePrice() {
|
||||
return servicePrice;
|
||||
}
|
||||
|
||||
public void setServicePrice(BigDecimal servicePrice) {
|
||||
this.servicePrice = servicePrice;
|
||||
}
|
||||
|
||||
public String getServiceDesc() {
|
||||
return serviceDesc;
|
||||
}
|
||||
|
||||
public void setServiceDesc(String serviceDesc) {
|
||||
this.serviceDesc = serviceDesc;
|
||||
}
|
||||
|
||||
public Integer getServiceDuration() {
|
||||
return serviceDuration;
|
||||
}
|
||||
|
||||
public void setServiceDuration(Integer serviceDuration) {
|
||||
this.serviceDuration = serviceDuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.example.petshopdesktop.api.dto.supplier;
|
||||
|
||||
public class SupplierRequest {
|
||||
private String supCompany;
|
||||
private String supContactFirstName;
|
||||
private String supContactLastName;
|
||||
private String supPhone;
|
||||
private String supEmail;
|
||||
private String address;
|
||||
|
||||
public SupplierRequest() {
|
||||
}
|
||||
|
||||
public String getSupCompany() {
|
||||
return supCompany;
|
||||
}
|
||||
|
||||
public void setSupCompany(String supCompany) {
|
||||
this.supCompany = supCompany;
|
||||
}
|
||||
|
||||
public String getSupContactFirstName() {
|
||||
return supContactFirstName;
|
||||
}
|
||||
|
||||
public void setSupContactFirstName(String supContactFirstName) {
|
||||
this.supContactFirstName = supContactFirstName;
|
||||
}
|
||||
|
||||
public String getSupContactLastName() {
|
||||
return supContactLastName;
|
||||
}
|
||||
|
||||
public void setSupContactLastName(String supContactLastName) {
|
||||
this.supContactLastName = supContactLastName;
|
||||
}
|
||||
|
||||
public String getSupPhone() {
|
||||
return supPhone;
|
||||
}
|
||||
|
||||
public void setSupPhone(String supPhone) {
|
||||
this.supPhone = supPhone;
|
||||
}
|
||||
|
||||
public String getSupEmail() {
|
||||
return supEmail;
|
||||
}
|
||||
|
||||
public void setSupEmail(String supEmail) {
|
||||
this.supEmail = supEmail;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.example.petshopdesktop.api.dto.supplier;
|
||||
|
||||
public class SupplierResponse {
|
||||
private Long supId;
|
||||
private String supCompany;
|
||||
private String supContactFirstName;
|
||||
private String supContactLastName;
|
||||
private String supPhone;
|
||||
private String supEmail;
|
||||
private String address;
|
||||
|
||||
public SupplierResponse() {
|
||||
}
|
||||
|
||||
public Long getSupId() {
|
||||
return supId;
|
||||
}
|
||||
|
||||
public void setSupId(Long supId) {
|
||||
this.supId = supId;
|
||||
}
|
||||
|
||||
public String getSupCompany() {
|
||||
return supCompany;
|
||||
}
|
||||
|
||||
public void setSupCompany(String supCompany) {
|
||||
this.supCompany = supCompany;
|
||||
}
|
||||
|
||||
public String getSupContactFirstName() {
|
||||
return supContactFirstName;
|
||||
}
|
||||
|
||||
public void setSupContactFirstName(String supContactFirstName) {
|
||||
this.supContactFirstName = supContactFirstName;
|
||||
}
|
||||
|
||||
public String getSupContactLastName() {
|
||||
return supContactLastName;
|
||||
}
|
||||
|
||||
public void setSupContactLastName(String supContactLastName) {
|
||||
this.supContactLastName = supContactLastName;
|
||||
}
|
||||
|
||||
public String getSupPhone() {
|
||||
return supPhone;
|
||||
}
|
||||
|
||||
public void setSupPhone(String supPhone) {
|
||||
this.supPhone = supPhone;
|
||||
}
|
||||
|
||||
public String getSupEmail() {
|
||||
return supEmail;
|
||||
}
|
||||
|
||||
public void setSupEmail(String supEmail) {
|
||||
this.supEmail = supEmail;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.example.petshopdesktop.api.dto.user;
|
||||
|
||||
public class UserRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private Boolean active;
|
||||
|
||||
public UserRequest() {
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Boolean getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(Boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.example.petshopdesktop.api.dto.user;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class UserResponse {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private Boolean active;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public UserResponse() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Boolean getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(Boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
|
||||
import org.example.petshopdesktop.api.dto.adoption.AdoptionResponse;
|
||||
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class AdoptionApi {
|
||||
private static final AdoptionApi INSTANCE = new AdoptionApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private AdoptionApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static AdoptionApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<AdoptionResponse> listAdoptions(String query) throws Exception {
|
||||
String path = "/api/v1/adoptions?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<AdoptionResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<AdoptionResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from adoptions endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public AdoptionResponse createAdoption(AdoptionRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/adoptions", request, AdoptionResponse.class);
|
||||
}
|
||||
|
||||
public AdoptionResponse updateAdoption(Long id, AdoptionRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/adoptions/" + id, request, AdoptionResponse.class);
|
||||
}
|
||||
|
||||
public void deleteAdoptions(List<Long> ids) throws Exception {
|
||||
apiClient.deleteWithBody("/api/v1/adoptions", new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
|
||||
|
||||
public class AnalyticsApi {
|
||||
private static final AnalyticsApi INSTANCE = new AnalyticsApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private AnalyticsApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static AnalyticsApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public DashboardResponse getDashboard(int days, int top) throws Exception {
|
||||
String path = "/api/v1/analytics/dashboard?days=" + days + "&top=" + top;
|
||||
return apiClient.get(path, DashboardResponse.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.appointment.AppointmentRequest;
|
||||
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
|
||||
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentApi {
|
||||
private static final AppointmentApi INSTANCE = new AppointmentApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private AppointmentApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static AppointmentApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<AppointmentResponse> listAppointments(String query) throws Exception {
|
||||
String path = "/api/v1/appointments?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<AppointmentResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<AppointmentResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from appointments endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public AppointmentResponse createAppointment(AppointmentRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/appointments", request, AppointmentResponse.class);
|
||||
}
|
||||
|
||||
public AppointmentResponse updateAppointment(Long id, AppointmentRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/appointments/" + id, request, AppointmentResponse.class);
|
||||
}
|
||||
|
||||
public void deleteAppointments(List<Long> ids) throws Exception {
|
||||
apiClient.deleteWithBody("/api/v1/appointments", new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.auth.AvatarUploadResponse;
|
||||
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class AuthApi {
|
||||
private static final AuthApi INSTANCE = new AuthApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private AuthApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static AuthApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public UserInfoResponse getCurrentUser() throws Exception {
|
||||
return apiClient.get("/api/v1/auth/me", UserInfoResponse.class);
|
||||
}
|
||||
|
||||
public AvatarUploadResponse uploadAvatar(Path filePath) throws Exception {
|
||||
return apiClient.postMultipart("/api/v1/auth/me/avatar", "avatar", filePath, AvatarUploadResponse.class);
|
||||
}
|
||||
|
||||
public void deleteAvatar() throws Exception {
|
||||
apiClient.delete("/api/v1/auth/me/avatar");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.chat.ConversationRequest;
|
||||
import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChatApi {
|
||||
private static final ChatApi INSTANCE = new ChatApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private ChatApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static ChatApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<ConversationResponse> listConversations() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/chat/conversations");
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<ConversationResponse>>() {});
|
||||
}
|
||||
|
||||
public ConversationResponse createConversation(ConversationRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/chat/conversations", request, ConversationResponse.class);
|
||||
}
|
||||
|
||||
public ConversationResponse getConversation(Long id) throws Exception {
|
||||
return apiClient.get("/api/v1/chat/conversations/" + id, ConversationResponse.class);
|
||||
}
|
||||
|
||||
public List<MessageResponse> listMessages(Long conversationId) throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/chat/conversations/" + conversationId + "/messages");
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<MessageResponse>>() {});
|
||||
}
|
||||
|
||||
public MessageResponse sendMessage(Long conversationId, MessageRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/chat/conversations/" + conversationId + "/messages", request, MessageResponse.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DropdownApi {
|
||||
private static final DropdownApi INSTANCE = new DropdownApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private DropdownApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static DropdownApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<DropdownOption> getCategories() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/categories");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from categories endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getProducts() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/products");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from products endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getSuppliers() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/suppliers");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from suppliers endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getServices() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/services");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from services endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getCustomers() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from customers endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getPets() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/pets");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from pets endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getStores() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from stores endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class EmployeeApi {
|
||||
private static final EmployeeApi INSTANCE = new EmployeeApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private EmployeeApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static EmployeeApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<EmployeeResponse> listEmployees(String query) throws Exception {
|
||||
String path = "/api/v1/employees?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<EmployeeResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<EmployeeResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from employees endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public EmployeeResponse createEmployee(EmployeeRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/employees", request, EmployeeResponse.class);
|
||||
}
|
||||
|
||||
public EmployeeResponse updateEmployee(Long id, EmployeeRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/employees/" + id, request, EmployeeResponse.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.inventory.InventoryRequest;
|
||||
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class InventoryApi {
|
||||
private static final InventoryApi INSTANCE = new InventoryApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private InventoryApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static InventoryApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<InventoryResponse> listInventory(String query) throws Exception {
|
||||
String path = "/api/v1/inventory?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<InventoryResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<InventoryResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from inventory endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public InventoryResponse createInventory(InventoryRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/inventory", request, InventoryResponse.class);
|
||||
}
|
||||
|
||||
public InventoryResponse updateInventory(Long id, InventoryRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/inventory/" + id, request, InventoryResponse.class);
|
||||
}
|
||||
|
||||
public void deleteInventory(Long id) throws Exception {
|
||||
apiClient.delete("/api/v1/inventory/" + id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetRequest;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class PetApi {
|
||||
private static final PetApi INSTANCE = new PetApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private PetApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static PetApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<PetResponse> listPets(String query) throws Exception {
|
||||
String path = "/api/v1/pets?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<PetResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<PetResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from pets endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public PetResponse createPet(PetRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/pets", request, PetResponse.class);
|
||||
}
|
||||
|
||||
public PetResponse updatePet(Long id, PetRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/pets/" + id, request, PetResponse.class);
|
||||
}
|
||||
|
||||
public void deletePets(List<Long> ids) throws Exception {
|
||||
apiClient.deleteWithBody("/api/v1/pets", new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductRequest;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class ProductApi {
|
||||
private static final ProductApi INSTANCE = new ProductApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private ProductApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static ProductApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<ProductResponse> listProducts(String query) throws Exception {
|
||||
String path = "/api/v1/products?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<ProductResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<ProductResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from products endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public ProductResponse createProduct(ProductRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/products", request, ProductResponse.class);
|
||||
}
|
||||
|
||||
public ProductResponse updateProduct(Long id, ProductRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/products/" + id, request, ProductResponse.class);
|
||||
}
|
||||
|
||||
public void deleteProducts(List<Long> ids) throws Exception {
|
||||
apiClient.deleteWithBody("/api/v1/products", new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierRequest;
|
||||
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class ProductSupplierApi {
|
||||
private static final ProductSupplierApi INSTANCE = new ProductSupplierApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private ProductSupplierApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static ProductSupplierApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<ProductSupplierResponse> listProductSuppliers(String query) throws Exception {
|
||||
String path = "/api/v1/product-suppliers?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<ProductSupplierResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<ProductSupplierResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from product-suppliers endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public ProductSupplierResponse createProductSupplier(ProductSupplierRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/product-suppliers", request, ProductSupplierResponse.class);
|
||||
}
|
||||
|
||||
public ProductSupplierResponse updateProductSupplier(Long productId, Long supplierId, ProductSupplierRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/product-suppliers/" + productId + "/" + supplierId, request, ProductSupplierResponse.class);
|
||||
}
|
||||
|
||||
public void deleteProductSupplier(Long productId, Long supplierId) throws Exception {
|
||||
apiClient.delete("/api/v1/product-suppliers/" + productId + "/" + supplierId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.purchaseorder.PurchaseOrderResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class PurchaseOrderApi {
|
||||
private static final PurchaseOrderApi INSTANCE = new PurchaseOrderApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private PurchaseOrderApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static PurchaseOrderApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<PurchaseOrderResponse> listPurchaseOrders(String query) throws Exception {
|
||||
String path = "/api/v1/purchase-orders?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<PurchaseOrderResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<PurchaseOrderResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from purchase-orders endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class SaleApi {
|
||||
private static final SaleApi INSTANCE = new SaleApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private SaleApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static SaleApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<SaleResponse> listSales(int page, int size, String query) throws Exception {
|
||||
String path = "/api/v1/sales?page=" + page + "&size=" + size;
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<SaleResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<SaleResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from sales endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public SaleResponse getSale(Long id) throws Exception {
|
||||
return apiClient.get("/api/v1/sales/" + id, SaleResponse.class);
|
||||
}
|
||||
|
||||
public SaleResponse createSale(SaleRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/sales", request, SaleResponse.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.service.ServiceRequest;
|
||||
import org.example.petshopdesktop.api.dto.service.ServiceResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class ServiceApi {
|
||||
private static final ServiceApi INSTANCE = new ServiceApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private ServiceApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static ServiceApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<ServiceResponse> listServices(String query) throws Exception {
|
||||
String path = "/api/v1/services?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<ServiceResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<ServiceResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from services endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public ServiceResponse createService(ServiceRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/services", request, ServiceResponse.class);
|
||||
}
|
||||
|
||||
public ServiceResponse updateService(Long id, ServiceRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/services/" + id, request, ServiceResponse.class);
|
||||
}
|
||||
|
||||
public void deleteServices(List<Long> ids) throws Exception {
|
||||
apiClient.deleteWithBody("/api/v1/services", new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.supplier.SupplierRequest;
|
||||
import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class SupplierApi {
|
||||
private static final SupplierApi INSTANCE = new SupplierApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private SupplierApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static SupplierApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<SupplierResponse> listSuppliers(String query) throws Exception {
|
||||
String path = "/api/v1/suppliers?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<SupplierResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<SupplierResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from suppliers endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public SupplierResponse createSupplier(SupplierRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/suppliers", request, SupplierResponse.class);
|
||||
}
|
||||
|
||||
public SupplierResponse updateSupplier(Long id, SupplierRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/suppliers/" + id, request, SupplierResponse.class);
|
||||
}
|
||||
|
||||
public void deleteSuppliers(List<Long> ids) throws Exception {
|
||||
apiClient.deleteWithBody("/api/v1/suppliers", new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.common.PageResponse;
|
||||
import org.example.petshopdesktop.api.dto.user.UserRequest;
|
||||
import org.example.petshopdesktop.api.dto.user.UserResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class UserApi {
|
||||
private static final UserApi INSTANCE = new UserApi();
|
||||
private final ApiClient apiClient;
|
||||
|
||||
private UserApi() {
|
||||
this.apiClient = ApiClient.getInstance();
|
||||
}
|
||||
|
||||
public static UserApi getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<UserResponse> listUsers(String query) throws Exception {
|
||||
String path = "/api/v1/users?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<UserResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<UserResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from users endpoint");
|
||||
}
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public UserResponse createUser(UserRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/users", request, UserResponse.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.example.petshopdesktop.auth;
|
||||
|
||||
public enum Role {
|
||||
ADMIN,
|
||||
STAFF
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.example.petshopdesktop.auth;
|
||||
|
||||
public class UserSession {
|
||||
private static UserSession instance;
|
||||
|
||||
private Long userId;
|
||||
private Long employeeId;
|
||||
private String username;
|
||||
private String employeeName;
|
||||
private Role role;
|
||||
private String jwtToken;
|
||||
private Long storeId;
|
||||
private String avatarUrl;
|
||||
|
||||
private UserSession() {}
|
||||
|
||||
public static UserSession getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new UserSession();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void login(Long userId, String username, Role role, String jwtToken) {
|
||||
this.userId = userId;
|
||||
this.employeeId = userId;
|
||||
this.username = username;
|
||||
this.employeeName = username;
|
||||
this.role = role;
|
||||
this.jwtToken = jwtToken;
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
this.userId = null;
|
||||
this.employeeId = null;
|
||||
this.username = null;
|
||||
this.employeeName = null;
|
||||
this.role = null;
|
||||
this.jwtToken = null;
|
||||
this.storeId = null;
|
||||
this.avatarUrl = null;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public Long getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public String getJwtToken() {
|
||||
return jwtToken;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public void setEmployeeName(String employeeName) {
|
||||
this.employeeName = employeeName;
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return username != null && role != null;
|
||||
}
|
||||
|
||||
public boolean isAdmin() {
|
||||
return Role.ADMIN.equals(role);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.adoption.AdoptionResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.AdoptionDialogController;
|
||||
import org.example.petshopdesktop.models.Adoption;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AdoptionController {
|
||||
|
||||
@FXML
|
||||
private Button btnAdd;
|
||||
|
||||
@FXML
|
||||
private Button btnDelete;
|
||||
|
||||
@FXML
|
||||
private Button btnEdit;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, Integer> colAdoptionId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, String> colPetId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, String> colCustomerName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, String> colAdoptionDate;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, Double> colAdoptionFee;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, String> colAdoptionStatus;
|
||||
|
||||
@FXML
|
||||
private TableView<Adoption> tvAdoptions;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
private ObservableList<Adoption> data = FXCollections.observableArrayList();
|
||||
private String mode = null;
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
//Enable multiple selection
|
||||
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
|
||||
colPetId.setCellValueFactory(new PropertyValueFactory<>("petName"));
|
||||
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
|
||||
colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate"));
|
||||
colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee"));
|
||||
colAdoptionStatus.setCellValueFactory(new PropertyValueFactory<>("adoptionStatus"));
|
||||
|
||||
displayAdoptions();
|
||||
|
||||
tvAdoptions.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
});
|
||||
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredAdoptions(newValue);
|
||||
});
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvAdoptions.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvAdoptions.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
mode = "Add";
|
||||
openDialog(null, mode);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
//get selected adoptions
|
||||
var selectedAdoptions = tvAdoptions.getSelectionModel().getSelectedItems();
|
||||
if (selectedAdoptions.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedAdoptions.size() == 1
|
||||
? "Are you sure you want to delete this adoption record?"
|
||||
: "Are you sure you want to delete " + selectedAdoptions.size() + " adoption records?";
|
||||
question.setContentText(message);
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedAdoptions.stream()
|
||||
.map(a -> (long) a.getAdoptionId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
AdoptionApi.getInstance().deleteAdoptions(ids);
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted " + ids.size() + " adoption record(s)");
|
||||
alert.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting adoptions");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Failed");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display and reset inputs
|
||||
displayAdoptions();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
Adoption selectedAdoption = tvAdoptions.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedAdoption != null) {
|
||||
mode = "Edit";
|
||||
openDialog(selectedAdoption, mode);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFilteredAdoptions(String filter) {
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
|
||||
displayAdoptions();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter);
|
||||
List<Adoption> adoptionList = adoptions.stream()
|
||||
.map(this::mapToAdoption)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(adoptionList);
|
||||
tvAdoptions.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionController.displayFilteredAdoptions",
|
||||
e,
|
||||
"Filtering adoptions with filter: " + filter);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void displayAdoptions() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(null);
|
||||
List<Adoption> adoptionList = adoptions.stream()
|
||||
.map(this::mapToAdoption)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(adoptionList);
|
||||
tvAdoptions.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionController.displayAdoptions",
|
||||
e,
|
||||
"Fetching adoption data for table display");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openDialog(Adoption adoption, String mode) {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(
|
||||
"/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
try {
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionController.openDialog",
|
||||
e,
|
||||
"Loading adoption dialog in " + mode + " mode");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
AdoptionDialogController dialogController = fxmlLoader.getController();
|
||||
dialogController.setMode(mode);
|
||||
|
||||
if (mode.equals("Edit")) {
|
||||
dialogController.displayAdoptionDetails(adoption);
|
||||
}
|
||||
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL);
|
||||
dialogStage.setTitle(mode.equals("Add") ? "Add Adoption" : "Edit Adoption");
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
displayAdoptions();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
private Adoption mapToAdoption(AdoptionResponse response) {
|
||||
return new Adoption(
|
||||
response.getAdoptionId().intValue(),
|
||||
response.getPetId() != null ? response.getPetId().intValue() : 0,
|
||||
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
|
||||
response.getPetName(),
|
||||
response.getCustomerName(),
|
||||
response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "",
|
||||
response.getAdoptionFee() != null ? response.getAdoptionFee().doubleValue() : 0.0,
|
||||
response.getAdoptionStatus()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.chart.*;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DailySales;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
|
||||
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
|
||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AnalyticsController {
|
||||
|
||||
@FXML
|
||||
private Button btnRefresh;
|
||||
|
||||
@FXML
|
||||
private Label lblError;
|
||||
|
||||
@FXML
|
||||
private Label lblTotalRevenue;
|
||||
|
||||
@FXML
|
||||
private Label lblTotalTransactions;
|
||||
|
||||
@FXML
|
||||
private Label lblAvgTransaction;
|
||||
|
||||
@FXML
|
||||
private Label lblTotalItems;
|
||||
|
||||
@FXML
|
||||
private LineChart<Number, Number> chartSalesOverTime;
|
||||
|
||||
@FXML
|
||||
private NumberAxis axisSalesDate;
|
||||
|
||||
@FXML
|
||||
private BarChart<Number, String> chartTopRevenue;
|
||||
|
||||
@FXML
|
||||
private BarChart<Number, String> chartTopQuantity;
|
||||
|
||||
@FXML
|
||||
private PieChart chartPaymentMethods;
|
||||
|
||||
@FXML
|
||||
private BarChart<String, Number> chartEmployeePerformance;
|
||||
|
||||
private static final String SALES_COLOR = "#ff6b35";
|
||||
private static final String REVENUE_COLOR = "#4ecdc4";
|
||||
private static final String QUANTITY_COLOR = "#ff9f1c";
|
||||
private static final String EMPLOYEE_COLOR = "#1a759f";
|
||||
private static final List<String> PIE_COLORS = List.of("#ff6b35", "#4ecdc4", "#1a759f", "#ff9f1c", "#577590", "#90be6d");
|
||||
|
||||
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||
private final NumberFormat wholeNumber = NumberFormat.getIntegerInstance();
|
||||
private final DateTimeFormatter chartDateFormatter = DateTimeFormatter.ofPattern("MMM d", Locale.CANADA);
|
||||
private final List<String> salesDateLabels = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
configureCharts();
|
||||
loadAnalyticsData();
|
||||
}
|
||||
|
||||
private void disableAllCharts() {
|
||||
chartSalesOverTime.setVisible(false);
|
||||
chartTopRevenue.setVisible(false);
|
||||
chartTopQuantity.setVisible(false);
|
||||
chartPaymentMethods.setVisible(false);
|
||||
chartEmployeePerformance.setVisible(false);
|
||||
btnRefresh.setDisable(true);
|
||||
}
|
||||
|
||||
private void configureCharts() {
|
||||
chartSalesOverTime.setAnimated(false);
|
||||
axisSalesDate.setAnimated(false);
|
||||
axisSalesDate.setAutoRanging(false);
|
||||
axisSalesDate.setForceZeroInRange(false);
|
||||
axisSalesDate.setMinorTickVisible(false);
|
||||
axisSalesDate.setTickUnit(1);
|
||||
axisSalesDate.setTickLabelRotation(-45);
|
||||
axisSalesDate.setTickLabelFormatter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Number value) {
|
||||
int index = value.intValue();
|
||||
if (index < 0 || index >= salesDateLabels.size()) {
|
||||
return "";
|
||||
}
|
||||
int labelStep = Math.max(1, (salesDateLabels.size() + 14) / 15);
|
||||
int lastIndex = salesDateLabels.size() - 1;
|
||||
boolean nearEnd = index >= Math.max(0, lastIndex - 2);
|
||||
boolean showLabel = index == 0 || nearEnd || index % labelStep == 0;
|
||||
return showLabel ? salesDateLabels.get(index) : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
chartTopRevenue.setAnimated(true);
|
||||
chartTopQuantity.setAnimated(true);
|
||||
chartPaymentMethods.setAnimated(true);
|
||||
chartEmployeePerformance.setAnimated(true);
|
||||
}
|
||||
|
||||
private void loadAnalyticsData() {
|
||||
lblError.setVisible(false);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
loadSummaryData(dashboard);
|
||||
loadSalesOverTime(dashboard);
|
||||
loadTopProductsByRevenue(dashboard);
|
||||
loadTopProductsByQuantity(dashboard);
|
||||
loadPaymentMethodDistribution(sales);
|
||||
loadEmployeePerformance(sales);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
lblError.setVisible(true);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
lblError.setVisible(true);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadSummaryData(DashboardResponse dashboard) throws Exception {
|
||||
if (dashboard != null) {
|
||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||
Long totalSales = 0L;
|
||||
Long totalProducts = 0L;
|
||||
|
||||
if (dashboard.getSalesSummary() != null) {
|
||||
totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
|
||||
totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
|
||||
}
|
||||
|
||||
if (dashboard.getInventorySummary() != null) {
|
||||
totalProducts = dashboard.getInventorySummary().getTotalProducts() != null ? dashboard.getInventorySummary().getTotalProducts() : 0L;
|
||||
}
|
||||
|
||||
lblTotalRevenue.setText(currency.format(totalRevenue));
|
||||
lblTotalTransactions.setText(wholeNumber.format(totalSales));
|
||||
|
||||
BigDecimal avgTransaction = BigDecimal.ZERO;
|
||||
if (totalSales > 0) {
|
||||
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
lblAvgTransaction.setText(currency.format(avgTransaction));
|
||||
lblTotalItems.setText(wholeNumber.format(totalProducts));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSalesOverTime(DashboardResponse dashboard) throws Exception {
|
||||
List<DailySales> dailySales = dashboard.getDailySales() != null ? dashboard.getDailySales() : new ArrayList<>();
|
||||
XYChart.Series<Number, Number> series = new XYChart.Series<>();
|
||||
series.setName("Daily Revenue");
|
||||
|
||||
salesDateLabels.clear();
|
||||
for (int i = 0; i < dailySales.size(); i++) {
|
||||
DailySales dailySale = dailySales.get(i);
|
||||
salesDateLabels.add(formatChartDate(dailySale.getDate()));
|
||||
BigDecimal revenue = dailySale.getRevenue() != null ? dailySale.getRevenue() : BigDecimal.ZERO;
|
||||
series.getData().add(new XYChart.Data<>(i, revenue));
|
||||
}
|
||||
|
||||
int upperBound = Math.max(0, salesDateLabels.size() - 1);
|
||||
axisSalesDate.setLowerBound(0);
|
||||
axisSalesDate.setUpperBound(upperBound);
|
||||
chartSalesOverTime.getData().clear();
|
||||
chartSalesOverTime.getData().add(series);
|
||||
applyLineChartColor(chartSalesOverTime, SALES_COLOR);
|
||||
}
|
||||
|
||||
private String formatChartDate(String date) {
|
||||
if (date == null || date.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
return LocalDate.parse(date).format(chartDateFormatter);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadTopProductsByRevenue(DashboardResponse dashboard) throws Exception {
|
||||
List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
|
||||
XYChart.Series<Number, String> series = new XYChart.Series<>();
|
||||
series.setName("Revenue");
|
||||
|
||||
for (TopProduct product : topProducts) {
|
||||
BigDecimal revenue = product.getRevenue() != null ? product.getRevenue() : BigDecimal.ZERO;
|
||||
series.getData().add(new XYChart.Data<>(revenue, product.getProductName()));
|
||||
}
|
||||
|
||||
chartTopRevenue.getData().clear();
|
||||
chartTopRevenue.getData().add(series);
|
||||
applyBarChartColor(chartTopRevenue, REVENUE_COLOR);
|
||||
}
|
||||
|
||||
private void loadTopProductsByQuantity(DashboardResponse dashboard) throws Exception {
|
||||
List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
|
||||
XYChart.Series<Number, String> series = new XYChart.Series<>();
|
||||
series.setName("Quantity");
|
||||
|
||||
for (TopProduct product : topProducts) {
|
||||
Long quantitySold = product.getQuantitySold() != null ? product.getQuantitySold() : 0L;
|
||||
series.getData().add(new XYChart.Data<>(quantitySold, product.getProductName()));
|
||||
}
|
||||
|
||||
chartTopQuantity.getData().clear();
|
||||
chartTopQuantity.getData().add(series);
|
||||
applyBarChartColor(chartTopQuantity, QUANTITY_COLOR);
|
||||
}
|
||||
|
||||
private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception {
|
||||
Map<String, Long> paymentMethodCount = sales.stream()
|
||||
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
|
||||
.collect(Collectors.groupingBy(
|
||||
sale -> sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "Unknown",
|
||||
Collectors.counting()
|
||||
));
|
||||
|
||||
chartPaymentMethods.getData().clear();
|
||||
|
||||
List<Map.Entry<String, Long>> paymentEntries = paymentMethodCount.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
|
||||
for (Map.Entry<String, Long> entry : paymentEntries) {
|
||||
PieChart.Data slice = new PieChart.Data(
|
||||
entry.getKey() + " (" + entry.getValue() + ")",
|
||||
entry.getValue()
|
||||
);
|
||||
chartPaymentMethods.getData().add(slice);
|
||||
}
|
||||
|
||||
chartPaymentMethods.setLabelsVisible(false);
|
||||
applyPieChartColors();
|
||||
}
|
||||
|
||||
private void loadEmployeePerformance(List<SaleResponse> sales) throws Exception {
|
||||
Map<String, Double> employeeRevenue = sales.stream()
|
||||
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
|
||||
.filter(sale -> sale.getEmployeeName() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
SaleResponse::getEmployeeName,
|
||||
Collectors.summingDouble(sale -> sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0)
|
||||
));
|
||||
|
||||
XYChart.Series<String, Number> series = new XYChart.Series<>();
|
||||
series.setName("Revenue");
|
||||
|
||||
List<Map.Entry<String, Double>> employeeEntries = employeeRevenue.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
|
||||
for (Map.Entry<String, Double> entry : employeeEntries) {
|
||||
series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
chartEmployeePerformance.getData().clear();
|
||||
chartEmployeePerformance.getData().add(series);
|
||||
applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR);
|
||||
}
|
||||
|
||||
private void applyLineChartColor(LineChart<Number, Number> chart, String color) {
|
||||
Platform.runLater(() -> {
|
||||
for (XYChart.Series<Number, Number> series : chart.getData()) {
|
||||
if (series.getNode() != null) {
|
||||
series.getNode().setStyle("-fx-stroke: " + color + ";");
|
||||
}
|
||||
for (XYChart.Data<Number, Number> data : series.getData()) {
|
||||
if (data.getNode() != null) {
|
||||
data.getNode().setStyle("-fx-background-color: white, " + color + ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <X, Y> void applyBarChartColor(XYChart<X, Y> chart, String color) {
|
||||
Platform.runLater(() -> {
|
||||
for (XYChart.Series<X, Y> series : chart.getData()) {
|
||||
for (XYChart.Data<X, Y> data : series.getData()) {
|
||||
if (data.getNode() != null) {
|
||||
data.getNode().setStyle("-fx-bar-fill: " + color + ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void applyPieChartColors() {
|
||||
Platform.runLater(() -> {
|
||||
for (int i = 0; i < chartPaymentMethods.getData().size(); i++) {
|
||||
PieChart.Data slice = chartPaymentMethods.getData().get(i);
|
||||
if (slice.getNode() != null) {
|
||||
slice.getNode().setStyle("-fx-pie-color: " + PIE_COLORS.get(i % PIE_COLORS.size()) + ";");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
void handleRefresh(ActionEvent event) {
|
||||
loadAnalyticsData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.example.petshopdesktop.DTOs.AppointmentDTO;
|
||||
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AppointmentController {
|
||||
|
||||
@FXML private TableView<AppointmentDTO> tvAppointments;
|
||||
|
||||
@FXML private TableColumn<AppointmentDTO,Integer> colAppointmentId;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colPetName;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colServiceName;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colAppointmentDate;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colAppointmentTime;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colCustomerName;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colAppointmentStatus;
|
||||
|
||||
@FXML private Button btnAdd;
|
||||
@FXML private Button btnEdit;
|
||||
@FXML private Button btnDelete;
|
||||
|
||||
@FXML private TextField txtSearch;
|
||||
|
||||
private final ObservableList<AppointmentDTO> appointments = FXCollections.observableArrayList();
|
||||
private FilteredList<AppointmentDTO> filtered;
|
||||
|
||||
@FXML
|
||||
public void initialize(){
|
||||
//Enable multiple selection
|
||||
tvAppointments.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
|
||||
colPetName.setCellValueFactory(new PropertyValueFactory<>("petName"));
|
||||
colServiceName.setCellValueFactory(new PropertyValueFactory<>("serviceName"));
|
||||
colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate"));
|
||||
colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime"));
|
||||
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
|
||||
colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus"));
|
||||
|
||||
filtered = new FilteredList<>(appointments, a -> true);
|
||||
tvAppointments.setItems(filtered);
|
||||
|
||||
if (txtSearch != null) {
|
||||
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
||||
}
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvAppointments.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvAppointments.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loadAppointments();
|
||||
}
|
||||
|
||||
private void loadAppointments(){
|
||||
new Thread(() -> {
|
||||
try{
|
||||
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null);
|
||||
List<AppointmentDTO> appointmentDTOs = responses.stream()
|
||||
.map(this::mapToAppointmentDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
appointments.setAll(appointmentDTOs);
|
||||
});
|
||||
}catch(Exception e){
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentController.loadAppointments",
|
||||
e,
|
||||
"Loading appointments for table display");
|
||||
e.printStackTrace();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void applyFilter(String text) {
|
||||
String query = text == null || text.trim().isEmpty() ? null : text.trim();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query);
|
||||
List<AppointmentDTO> appointmentDTOs = responses.stream()
|
||||
.map(this::mapToAppointmentDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
appointments.setAll(appointmentDTOs);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentController.applyFilter",
|
||||
e,
|
||||
String.format("Filtering appointments with query: %s", query));
|
||||
e.printStackTrace();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event){
|
||||
openDialog(null, "Add");
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event){
|
||||
|
||||
AppointmentDTO selected =
|
||||
tvAppointments.getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected == null){
|
||||
showAlert("Select Appointment", "Please select appointment to edit.");
|
||||
return;
|
||||
}
|
||||
|
||||
openDialog(selected, "Edit");
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event){
|
||||
//get selected appointments
|
||||
var selectedAppointments = tvAppointments.getSelectionModel().getSelectedItems();
|
||||
if (selectedAppointments.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedAppointments.size() == 1
|
||||
? "Are you sure you want to delete this appointment?"
|
||||
: "Are you sure you want to delete " + selectedAppointments.size() + " appointments?";
|
||||
question.setContentText(message);
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
java.util.Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedAppointments.stream()
|
||||
.map(a -> (long) a.getAppointmentId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
AppointmentApi.getInstance().deleteAppointments(ids);
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted " + ids.size() + " appointment(s)");
|
||||
alert.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting appointments");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Failed");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display
|
||||
loadAppointments();
|
||||
}
|
||||
}
|
||||
|
||||
private void openDialog(AppointmentDTO appt, String mode){
|
||||
|
||||
try{
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource(
|
||||
"/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml"
|
||||
)
|
||||
);
|
||||
|
||||
Scene scene = new Scene(loader.load());
|
||||
|
||||
AppointmentDialogController controller =
|
||||
loader.getController();
|
||||
|
||||
controller.setMode(mode);
|
||||
|
||||
if(mode.equals("Edit")){
|
||||
controller.displayAppointmentDetails(appt);
|
||||
}
|
||||
|
||||
Stage stage = new Stage();
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.setScene(scene);
|
||||
stage.showAndWait();
|
||||
|
||||
loadAppointments();
|
||||
|
||||
}catch(Exception e){
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentController.openDialog",
|
||||
e,
|
||||
"Opening appointment dialog in " + mode + " mode");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void showAlert(String title, String msg){
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(msg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
|
||||
Long petId = response.getPetIds() != null && !response.getPetIds().isEmpty() ? response.getPetIds().get(0) : null;
|
||||
return new AppointmentDTO(
|
||||
response.getAppointmentId().intValue(),
|
||||
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
|
||||
response.getCustomerName(),
|
||||
petId != null ? petId.intValue() : 0,
|
||||
String.join(", ", response.getPetNames()),
|
||||
response.getServiceId() != null ? response.getServiceId().intValue() : 0,
|
||||
response.getServiceName(),
|
||||
response.getAppointmentDate().toString(),
|
||||
response.getAppointmentTime().toString(),
|
||||
response.getAppointmentStatus()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.example.petshopdesktop.api.ChatRealtimeClient;
|
||||
import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.endpoints.ChatApi;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ChatController {
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("MMM d, HH:mm");
|
||||
|
||||
@FXML
|
||||
private ListView<ConversationResponse> lvConversations;
|
||||
|
||||
@FXML
|
||||
private VBox vbMessages;
|
||||
|
||||
@FXML
|
||||
private ScrollPane spMessages;
|
||||
|
||||
@FXML
|
||||
private TextArea txtMessage;
|
||||
|
||||
@FXML
|
||||
private Button btnSend;
|
||||
|
||||
@FXML
|
||||
private Button btnRefresh;
|
||||
|
||||
@FXML
|
||||
private Label lblConversationTitle;
|
||||
|
||||
@FXML
|
||||
private Label lblChatStatus;
|
||||
|
||||
private final ObservableList<ConversationResponse> conversations = FXCollections.observableArrayList();
|
||||
private final Map<Long, String> customerLabels = new HashMap<>();
|
||||
private final ChatRealtimeClient realtimeClient = ChatRealtimeClient.getInstance();
|
||||
private ConversationResponse selectedConversation;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
lvConversations.setItems(conversations);
|
||||
lvConversations.setCellFactory(list -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(ConversationResponse item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Label title = new Label(getConversationTitle(item));
|
||||
title.setStyle("-fx-font-weight: bold; -fx-text-fill: #1f2937;");
|
||||
Label preview = new Label(item.getLastMessage() == null ? "" : item.getLastMessage());
|
||||
preview.setStyle("-fx-text-fill: #64748b;");
|
||||
preview.setWrapText(true);
|
||||
Label meta = new Label(buildConversationMeta(item));
|
||||
meta.setStyle("-fx-text-fill: #94a3b8; -fx-font-size: 11px;");
|
||||
VBox box = new VBox(4, title, preview, meta);
|
||||
setGraphic(box);
|
||||
}
|
||||
});
|
||||
|
||||
lvConversations.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
selectedConversation = newValue;
|
||||
lblConversationTitle.setText(getConversationTitle(newValue));
|
||||
loadMessages(newValue.getId());
|
||||
realtimeClient.subscribeToConversation(newValue.getId());
|
||||
}
|
||||
});
|
||||
|
||||
txtMessage.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == KeyCode.ENTER && event.isControlDown()) {
|
||||
btnSendClicked();
|
||||
}
|
||||
});
|
||||
|
||||
realtimeClient.setConversationListener(conversation -> Platform.runLater(() -> upsertConversation(conversation)));
|
||||
realtimeClient.setMessageListener(message -> Platform.runLater(() -> appendMessageIfSelected(message)));
|
||||
realtimeClient.setStatusListener(status -> Platform.runLater(() -> lblChatStatus.setText(status)));
|
||||
realtimeClient.subscribeToConversations();
|
||||
|
||||
loadCustomers();
|
||||
loadConversations();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRefreshClicked() {
|
||||
loadConversations();
|
||||
if (selectedConversation != null) {
|
||||
loadMessages(selectedConversation.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnSendClicked() {
|
||||
if (selectedConversation == null) {
|
||||
lblChatStatus.setText("Select a conversation");
|
||||
return;
|
||||
}
|
||||
|
||||
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
|
||||
if (content.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
txtMessage.clear();
|
||||
boolean sent = realtimeClient.sendMessage(selectedConversation.getId(), content);
|
||||
if (!sent) {
|
||||
sendMessageFallback(selectedConversation.getId(), content);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCustomers() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||
Map<Long, String> labels = new HashMap<>();
|
||||
for (DropdownOption option : customers) {
|
||||
labels.put(option.getId(), option.getLabel());
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
customerLabels.clear();
|
||||
customerLabels.putAll(labels);
|
||||
lvConversations.refresh();
|
||||
if (selectedConversation != null) {
|
||||
lblConversationTitle.setText(getConversationTitle(selectedConversation));
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> ActivityLogger.getInstance().logException(
|
||||
"ChatController.loadCustomers",
|
||||
e,
|
||||
"Loading customer labels for chat"));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadConversations() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ConversationResponse> response = ChatApi.getInstance().listConversations();
|
||||
response.sort(Comparator.comparing(ChatController::conversationSortTime, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
Platform.runLater(() -> {
|
||||
conversations.setAll(response);
|
||||
restoreSelection();
|
||||
if (selectedConversation == null && !conversations.isEmpty()) {
|
||||
lvConversations.getSelectionModel().selectFirst();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
lblChatStatus.setText("Chat unavailable");
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.loadConversations",
|
||||
e,
|
||||
"Loading conversations");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadMessages(Long conversationId) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<MessageResponse> messages = ChatApi.getInstance().listMessages(conversationId);
|
||||
Platform.runLater(() -> renderMessages(messages));
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> ActivityLogger.getInstance().logException(
|
||||
"ChatController.loadMessages",
|
||||
e,
|
||||
"Loading messages for conversation " + conversationId));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void sendMessageFallback(Long conversationId, String content) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
MessageResponse response = ChatApi.getInstance().sendMessage(conversationId, new MessageRequest(content));
|
||||
Platform.runLater(() -> {
|
||||
lblChatStatus.setText("Chat fallback active");
|
||||
appendMessageIfSelected(response);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> ActivityLogger.getInstance().logException(
|
||||
"ChatController.sendMessageFallback",
|
||||
e,
|
||||
"Sending chat message for conversation " + conversationId));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void renderMessages(List<MessageResponse> messages) {
|
||||
vbMessages.getChildren().clear();
|
||||
for (MessageResponse message : messages) {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
}
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
|
||||
private void appendMessageIfSelected(MessageResponse message) {
|
||||
upsertConversationForMessage(message);
|
||||
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
private void upsertConversation(ConversationResponse conversation) {
|
||||
Optional<ConversationResponse> existing = conversations.stream()
|
||||
.filter(item -> item.getId().equals(conversation.getId()))
|
||||
.findFirst();
|
||||
|
||||
if (existing.isPresent()) {
|
||||
ConversationResponse current = existing.get();
|
||||
int index = conversations.indexOf(current);
|
||||
conversations.set(index, conversation);
|
||||
} else {
|
||||
conversations.add(conversation);
|
||||
}
|
||||
|
||||
conversations.sort(Comparator.comparing(ChatController::conversationSortTime, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
restoreSelection();
|
||||
lvConversations.refresh();
|
||||
}
|
||||
|
||||
private void upsertConversationForMessage(MessageResponse message) {
|
||||
conversations.stream()
|
||||
.filter(conversation -> conversation.getId().equals(message.getConversationId()))
|
||||
.findFirst()
|
||||
.ifPresent(conversation -> {
|
||||
conversation.setLastMessage(message.getContent());
|
||||
conversation.setUpdatedAt(message.getTimestamp());
|
||||
conversations.sort(Comparator.comparing(ChatController::conversationSortTime, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
lvConversations.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
private void restoreSelection() {
|
||||
if (selectedConversation == null) {
|
||||
return;
|
||||
}
|
||||
conversations.stream()
|
||||
.filter(item -> item.getId().equals(selectedConversation.getId()))
|
||||
.findFirst()
|
||||
.ifPresent(match -> {
|
||||
selectedConversation = match;
|
||||
lvConversations.getSelectionModel().select(match);
|
||||
});
|
||||
}
|
||||
|
||||
private HBox createMessageBubble(MessageResponse message) {
|
||||
boolean mine = message.getSenderId() != null && message.getSenderId().equals(UserSession.getInstance().getUserId());
|
||||
Label author = new Label(resolveAuthorLabel(message));
|
||||
author.setStyle("-fx-font-weight: bold; -fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
|
||||
Label content = new Label(message.getContent());
|
||||
content.setWrapText(true);
|
||||
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
|
||||
String timestampText = message.getTimestamp() == null ? "" : TIME_FORMATTER.format(message.getTimestamp());
|
||||
Label timestamp = new Label(timestampText);
|
||||
timestamp.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#94a3b8") + "; -fx-font-size: 11px;");
|
||||
|
||||
VBox bubble = new VBox(4, author, content, timestamp);
|
||||
bubble.setMaxWidth(420);
|
||||
bubble.setStyle(mine
|
||||
? "-fx-background-color: #0f766e; -fx-background-radius: 14; -fx-padding: 12;"
|
||||
: "-fx-background-color: #e2e8f0; -fx-background-radius: 14; -fx-padding: 12;");
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
HBox container = new HBox(12);
|
||||
if (mine) {
|
||||
container.getChildren().addAll(spacer, bubble);
|
||||
} else {
|
||||
container.getChildren().addAll(bubble, spacer);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
private String resolveAuthorLabel(MessageResponse message) {
|
||||
Long currentUserId = UserSession.getInstance().getUserId();
|
||||
if (message.getSenderId() != null && message.getSenderId().equals(currentUserId)) {
|
||||
return "You";
|
||||
}
|
||||
if (selectedConversation != null && selectedConversation.getStaffId() != null && selectedConversation.getStaffId().equals(message.getSenderId())) {
|
||||
return "Staff";
|
||||
}
|
||||
return "Customer";
|
||||
}
|
||||
|
||||
private String getConversationTitle(ConversationResponse conversation) {
|
||||
String customerLabel = customerLabels.get(conversation.getCustomerId());
|
||||
return customerLabel != null ? customerLabel : "Customer #" + conversation.getCustomerId();
|
||||
}
|
||||
|
||||
private String buildConversationMeta(ConversationResponse conversation) {
|
||||
String assignee;
|
||||
if (conversation.getStaffId() != null) {
|
||||
assignee = "Assigned";
|
||||
} else if (conversation.getHumanRequestedAt() != null) {
|
||||
assignee = "Takeover requested";
|
||||
} else if ("AUTOMATED".equals(conversation.getMode())) {
|
||||
assignee = "Automated";
|
||||
} else {
|
||||
assignee = "Open";
|
||||
}
|
||||
String updated = conversation.getUpdatedAt() == null ? "" : TIME_FORMATTER.format(conversation.getUpdatedAt());
|
||||
return assignee + (updated.isBlank() ? "" : " · " + updated);
|
||||
}
|
||||
|
||||
private static java.time.LocalDateTime conversationSortTime(ConversationResponse conversation) {
|
||||
return conversation.getUpdatedAt() != null ? conversation.getUpdatedAt() : conversation.getCreatedAt();
|
||||
}
|
||||
|
||||
private void scrollMessagesToBottom() {
|
||||
Platform.runLater(() -> spMessages.setVvalue(1.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.InventoryApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.InventoryDialogController;
|
||||
import org.example.petshopdesktop.models.Inventory;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InventoryController {
|
||||
|
||||
//FXML elements
|
||||
@FXML
|
||||
private Button btnAdd;
|
||||
|
||||
@FXML
|
||||
private Button btnDelete;
|
||||
|
||||
@FXML
|
||||
private Button btnEdit;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Inventory, Integer> colInventoryId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Inventory, Integer> colProductId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Inventory, String> colProductName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Inventory, Integer> colQuantity;
|
||||
|
||||
@FXML
|
||||
private TableView<Inventory> tvInventory;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
private ObservableList<Inventory> data = FXCollections.observableArrayList();
|
||||
|
||||
//Determines if in add/edit mode
|
||||
private String mode = null;
|
||||
|
||||
//Loads upon view bootup
|
||||
@FXML
|
||||
void initialize() {
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
tvInventory.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.SINGLE);
|
||||
|
||||
colInventoryId.setCellValueFactory(new PropertyValueFactory<>("inventoryId"));
|
||||
colProductId.setCellValueFactory(new PropertyValueFactory<>("prodId"));
|
||||
colProductName.setCellValueFactory(new PropertyValueFactory<>("prodName"));
|
||||
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
|
||||
displayInventory();
|
||||
|
||||
tvInventory.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
});
|
||||
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredInventory(newValue);
|
||||
});
|
||||
|
||||
tvInventory.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvInventory.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Opens dialog in add mode
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
mode = "Add";
|
||||
openDialog(null, mode);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
Inventory selectedInventory = tvInventory.getSelectionModel().getSelectedItem();
|
||||
if (selectedInventory == null) return;
|
||||
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
question.setContentText("Are you sure you want to delete this inventory record?");
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
try {
|
||||
InventoryApi.getInstance().deleteInventory((long) selectedInventory.getInventoryId());
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted inventory record");
|
||||
alert.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"InventoryController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting inventory");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Failed");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
displayInventory();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
//Editing a record
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
Inventory selectedInventory = tvInventory.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedInventory != null) {
|
||||
mode = "Edit";
|
||||
openDialog(selectedInventory, mode);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFilteredInventory(String filter) {
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
|
||||
displayInventory();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(filter);
|
||||
List<Inventory> inventoryList = inventories.stream()
|
||||
.map(this::mapToInventory)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(inventoryList);
|
||||
tvInventory.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"InventoryController.displayFilteredInventory",
|
||||
e,
|
||||
String.format("Filtering inventory with keyword: %s", filter));
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void displayInventory() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(null);
|
||||
List<Inventory> inventoryList = inventories.stream()
|
||||
.map(this::mapToInventory)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(inventoryList);
|
||||
tvInventory.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"InventoryController.displayInventory",
|
||||
e,
|
||||
"Fetching inventory data for table display");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openDialog(Inventory inventory, String mode) {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/inventory-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
|
||||
try {
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"InventoryController.openDialog",
|
||||
e,
|
||||
String.format("Loading inventory dialog view in %s mode", mode));
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
InventoryDialogController dialogController = fxmlLoader.getController();
|
||||
dialogController.setMode(mode);
|
||||
|
||||
if (mode.equals("Edit")) {
|
||||
dialogController.displayInventoryDetails(inventory);
|
||||
}
|
||||
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL);
|
||||
dialogStage.setTitle(mode.equals("Add") ? "Add Inventory" : "Edit Inventory");
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
displayInventory();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
private Inventory mapToInventory(InventoryResponse response) {
|
||||
return new Inventory(
|
||||
response.getInventoryId().intValue(),
|
||||
response.getProdId() != null ? response.getProdId().intValue() : 0,
|
||||
response.getProductName(),
|
||||
response.getCategoryName() != null ? response.getCategoryName() : "",
|
||||
0,
|
||||
"N/A",
|
||||
response.getQuantity() != null ? response.getQuantity() : 0,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.auth.LoginRequest;
|
||||
import org.example.petshopdesktop.api.dto.auth.LoginResponse;
|
||||
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
|
||||
import org.example.petshopdesktop.auth.Role;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.ui.SvgWebViewFactory;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
public class LoginController {
|
||||
|
||||
@FXML
|
||||
private TextField txtUsername;
|
||||
|
||||
@FXML
|
||||
private PasswordField txtPassword;
|
||||
|
||||
@FXML
|
||||
private Label lblError;
|
||||
|
||||
@FXML
|
||||
private StackPane logoContainer;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
lblError.setText("");
|
||||
logoContainer.getChildren().setAll(SvgWebViewFactory.build("/org/example/petshopdesktop/images/leons-pet-store-badge-text-light.svg", 142));
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnLoginClicked(ActionEvent event) {
|
||||
String username = txtUsername.getText().trim();
|
||||
String password = txtPassword.getText();
|
||||
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
lblError.setText("Please enter username and password.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ApiClient apiClient = ApiClient.getInstance();
|
||||
|
||||
LoginRequest loginRequest = new LoginRequest(username, password);
|
||||
LoginResponse loginResponse = apiClient.post("/api/v1/auth/login", loginRequest, LoginResponse.class);
|
||||
|
||||
if (loginResponse == null) {
|
||||
throw new IllegalStateException("Login response is null");
|
||||
}
|
||||
|
||||
String token = loginResponse.getToken();
|
||||
String roleStr = loginResponse.getRole();
|
||||
if (token == null || roleStr == null) {
|
||||
throw new IllegalStateException("Token or role is null");
|
||||
}
|
||||
|
||||
if ("CUSTOMER".equalsIgnoreCase(roleStr)) {
|
||||
lblError.setText("Access Denied: Customer accounts cannot access the desktop application.");
|
||||
txtPassword.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
Role role = Role.valueOf(roleStr.toUpperCase());
|
||||
|
||||
UserSession.getInstance().login(null, username, role, token);
|
||||
|
||||
UserInfoResponse userInfo = apiClient.get("/api/v1/auth/me", UserInfoResponse.class);
|
||||
if (userInfo == null) {
|
||||
throw new IllegalStateException("User info is null");
|
||||
}
|
||||
UserSession.getInstance().login(userInfo.getId(), username, role, token);
|
||||
UserSession.getInstance().setStoreId(userInfo.getStoreId());
|
||||
UserSession.getInstance().setEmployeeName(userInfo.getFullName() == null || userInfo.getFullName().isBlank() ? username : userInfo.getFullName());
|
||||
UserSession.getInstance().setAvatarUrl(userInfo.getAvatarUrl());
|
||||
|
||||
openMainLayout();
|
||||
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"LoginController.btnLoginClicked",
|
||||
e,
|
||||
"Authentication attempt for username: " + username);
|
||||
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg != null && errorMsg.contains("Authentication failed")) {
|
||||
lblError.setText("Invalid username or password.");
|
||||
txtPassword.clear();
|
||||
} else if (e.getCause() instanceof java.net.ConnectException ||
|
||||
e instanceof java.net.http.HttpConnectTimeoutException) {
|
||||
lblError.setText("Backend is not reachable, check backend docker compose and port 8080.");
|
||||
} else {
|
||||
lblError.setText(errorMsg != null ? errorMsg : "Login failed. Please try again.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void openMainLayout() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml"));
|
||||
Scene scene = new Scene(loader.load());
|
||||
Stage stage = (Stage) txtUsername.getScene().getWindow();
|
||||
stage.setScene(scene);
|
||||
stage.setTitle("Pet Shop Manager");
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"LoginController.openMainLayout",
|
||||
e,
|
||||
"Loading main application layout after successful login");
|
||||
lblError.setText("Error loading application: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.ImagePattern;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.ApiConfig;
|
||||
import org.example.petshopdesktop.api.ChatRealtimeClient;
|
||||
import org.example.petshopdesktop.api.dto.auth.AvatarUploadResponse;
|
||||
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.AuthApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.ui.SvgWebViewFactory;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
public class MainLayoutController {
|
||||
|
||||
private static final String NAV_BASE_STYLE = "-fx-background-color: transparent; " +
|
||||
"-fx-text-fill: #cbd5e1; " +
|
||||
"-fx-background-radius: 10; " +
|
||||
"-fx-cursor: hand; " +
|
||||
"-fx-focus-color: transparent; " +
|
||||
"-fx-faint-focus-color: transparent;";
|
||||
|
||||
private static final String NAV_ACTIVE_STYLE = "-fx-background-color: #FF6B6B; " +
|
||||
"-fx-text-fill: white; " +
|
||||
"-fx-background-radius: 10; " +
|
||||
"-fx-cursor: hand; " +
|
||||
"-fx-focus-color: transparent; " +
|
||||
"-fx-faint-focus-color: transparent; " +
|
||||
"-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.22), 10, 0.15, 0, 2);";
|
||||
|
||||
@FXML
|
||||
private Button btnAdoptions;
|
||||
|
||||
@FXML
|
||||
private Button btnAppointments;
|
||||
|
||||
@FXML
|
||||
private Button btnInventory;
|
||||
|
||||
@FXML
|
||||
private Button btnLogout;
|
||||
|
||||
@FXML
|
||||
private Button btnPets;
|
||||
|
||||
@FXML
|
||||
private Button btnProductSuppliers;
|
||||
|
||||
@FXML
|
||||
private Button btnProducts;
|
||||
|
||||
@FXML
|
||||
private Button btnSalesHistory;
|
||||
|
||||
@FXML
|
||||
private Button btnPurchaseOrders;
|
||||
|
||||
@FXML
|
||||
private Button btnServices;
|
||||
|
||||
@FXML
|
||||
private Button btnSuppliers;
|
||||
|
||||
@FXML
|
||||
private Button btnStaffAccounts;
|
||||
|
||||
@FXML
|
||||
private Button btnAnalytics;
|
||||
|
||||
@FXML
|
||||
private Button btnChat;
|
||||
|
||||
@FXML
|
||||
private StackPane logoContainer;
|
||||
|
||||
@FXML
|
||||
private StackPane avatarPreview;
|
||||
|
||||
@FXML
|
||||
private Button btnChangeAvatar;
|
||||
|
||||
@FXML
|
||||
private Button btnRemoveAvatar;
|
||||
|
||||
@FXML
|
||||
private Label lblUsername;
|
||||
|
||||
@FXML
|
||||
private Label lblRole;
|
||||
|
||||
@FXML
|
||||
private Label lblAdminSection;
|
||||
|
||||
@FXML
|
||||
private Separator separatorAdmin;
|
||||
|
||||
@FXML
|
||||
private StackPane spContentArea;
|
||||
|
||||
@FXML
|
||||
void btnAdoptionsClicked(ActionEvent event) {
|
||||
loadView("adoption-view.fxml");
|
||||
updateButtons(btnAdoptions);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnAppointmentsClicked(ActionEvent event) {
|
||||
loadView("appointment-view.fxml");
|
||||
updateButtons(btnAppointments);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnInventoryClicked(ActionEvent event) {
|
||||
loadView("inventory-view.fxml");
|
||||
updateButtons(btnInventory);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnPetsClicked(ActionEvent event) {
|
||||
loadView("pet-view.fxml");
|
||||
updateButtons(btnPets);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnProductSuppliersClicked(ActionEvent event) {
|
||||
loadView("product-supplier-view.fxml");
|
||||
updateButtons(btnProductSuppliers);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnProductsClicked(ActionEvent event) {
|
||||
loadView("product-view.fxml");
|
||||
updateButtons(btnProducts);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnSalesHistoryClicked(ActionEvent event) {
|
||||
loadView("sale-view.fxml");
|
||||
updateButtons(btnSalesHistory);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnPurchaseOrdersClicked(ActionEvent event) {
|
||||
loadView("purchase-order-view.fxml");
|
||||
updateButtons(btnPurchaseOrders);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnStaffAccountsClicked(ActionEvent event) {
|
||||
loadView("staff-accounts-view.fxml");
|
||||
updateButtons(btnStaffAccounts);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnAnalyticsClicked(ActionEvent event) {
|
||||
loadView("analytics-view.fxml");
|
||||
updateButtons(btnAnalytics);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnServicesClicked(ActionEvent event) {
|
||||
loadView("service-view.fxml");
|
||||
updateButtons(btnServices);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnSuppliersClicked(ActionEvent event) {
|
||||
loadView("supplier-view.fxml");
|
||||
updateButtons(btnSuppliers);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnChatClicked(ActionEvent event) {
|
||||
loadView("chat-view.fxml");
|
||||
updateButtons(btnChat);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void logoClicked(MouseEvent event) {
|
||||
UserSession session = UserSession.getInstance();
|
||||
if (session.isAdmin()) {
|
||||
loadView("analytics-view.fxml");
|
||||
updateButtons(btnAnalytics);
|
||||
} else {
|
||||
loadView("sale-view.fxml");
|
||||
updateButtons(btnSalesHistory);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnChangeAvatarClicked(ActionEvent event) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Choose Profile Picture");
|
||||
chooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg", "*.gif")
|
||||
);
|
||||
java.io.File file = chooser.showOpenDialog(btnChangeAvatar.getScene().getWindow());
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
AvatarUploadResponse response = AuthApi.getInstance().uploadAvatar(file.toPath());
|
||||
UserSession.getInstance().setAvatarUrl(response.getAvatarUrl());
|
||||
renderAvatar(UserSession.getInstance().getEmployeeName(), response.getAvatarUrl());
|
||||
btnRemoveAvatar.setDisable(response.getAvatarUrl() == null || response.getAvatarUrl().isBlank());
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("MainLayoutController.btnChangeAvatarClicked", e, "Uploading avatar");
|
||||
showAvatarError(e.getMessage() != null ? e.getMessage() : "Could not upload profile picture.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRemoveAvatarClicked(ActionEvent event) {
|
||||
try {
|
||||
AuthApi.getInstance().deleteAvatar();
|
||||
UserSession.getInstance().setAvatarUrl(null);
|
||||
renderAvatar(UserSession.getInstance().getEmployeeName(), null);
|
||||
btnRemoveAvatar.setDisable(true);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("MainLayoutController.btnRemoveAvatarClicked", e, "Deleting avatar");
|
||||
showAvatarError(e.getMessage() != null ? e.getMessage() : "Could not remove profile picture.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnLogoutClicked(ActionEvent event) {
|
||||
ChatRealtimeClient.getInstance().disconnect();
|
||||
UserSession.getInstance().logout();
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/org/example/petshopdesktop/login-view.fxml"));
|
||||
Scene scene = new Scene(loader.load());
|
||||
Stage stage = (Stage) btnLogout.getScene().getWindow();
|
||||
stage.setScene(scene);
|
||||
stage.setTitle("Pet Shop Manager - Login");
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"MainLayoutController.btnLogoutClicked",
|
||||
e,
|
||||
"Loading login view after logout");
|
||||
System.err.println("Error loading login view: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
logoContainer.getChildren().setAll(SvgWebViewFactory.build("/org/example/petshopdesktop/images/leons-pet-store-badge-light.svg", 94));
|
||||
renderAvatar(UserSession.getInstance().getEmployeeName(), UserSession.getInstance().getAvatarUrl());
|
||||
btnRemoveAvatar.setDisable(UserSession.getInstance().getAvatarUrl() == null || UserSession.getInstance().getAvatarUrl().isBlank());
|
||||
refreshProfileHeader();
|
||||
applyRBAC();
|
||||
|
||||
UserSession session = UserSession.getInstance();
|
||||
if (session.isAdmin()) {
|
||||
loadView("analytics-view.fxml");
|
||||
updateButtons(btnAnalytics);
|
||||
} else {
|
||||
loadView("sale-view.fxml");
|
||||
updateButtons(btnSalesHistory);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshProfileHeader() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
UserInfoResponse userInfo = AuthApi.getInstance().getCurrentUser();
|
||||
String displayName = userInfo.getFullName() == null || userInfo.getFullName().isBlank()
|
||||
? UserSession.getInstance().getUsername()
|
||||
: userInfo.getFullName();
|
||||
Platform.runLater(() -> {
|
||||
UserSession.getInstance().setEmployeeName(displayName);
|
||||
UserSession.getInstance().setAvatarUrl(userInfo.getAvatarUrl());
|
||||
lblUsername.setText(displayName);
|
||||
renderAvatar(displayName, userInfo.getAvatarUrl());
|
||||
btnRemoveAvatar.setDisable(userInfo.getAvatarUrl() == null || userInfo.getAvatarUrl().isBlank());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> renderAvatar(UserSession.getInstance().getEmployeeName(), UserSession.getInstance().getAvatarUrl()));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void renderAvatar(String displayName, String avatarUrl) {
|
||||
Circle border = new Circle(29);
|
||||
border.setFill(Color.web("#dbe4ee"));
|
||||
|
||||
Circle circle = new Circle(26);
|
||||
Label initials = new Label(initials(displayName));
|
||||
initials.setStyle("-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16px;");
|
||||
|
||||
if (avatarUrl != null && !avatarUrl.isBlank()) {
|
||||
try {
|
||||
String resolvedUrl = avatarUrl.startsWith("http") ? avatarUrl : ApiConfig.getInstance().getBaseUrl() + avatarUrl;
|
||||
Image image = new Image(resolvedUrl, 52, 52, true, true, true);
|
||||
if (!image.isError()) {
|
||||
circle.setFill(new ImagePattern(image));
|
||||
initials.setVisible(false);
|
||||
} else {
|
||||
circle.setFill(Color.web("#4ECDC4"));
|
||||
initials.setVisible(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
circle.setFill(Color.web("#4ECDC4"));
|
||||
initials.setVisible(true);
|
||||
}
|
||||
} else {
|
||||
circle.setFill(Color.web("#4ECDC4"));
|
||||
initials.setVisible(true);
|
||||
}
|
||||
|
||||
avatarPreview.getChildren().setAll(border, circle, initials);
|
||||
}
|
||||
|
||||
private String initials(String displayName) {
|
||||
if (displayName == null || displayName.isBlank()) {
|
||||
return "?";
|
||||
}
|
||||
|
||||
String[] parts = displayName.trim().split("\\s+");
|
||||
if (parts.length == 1) {
|
||||
return parts[0].substring(0, 1).toUpperCase();
|
||||
}
|
||||
return (parts[0].substring(0, 1) + parts[parts.length - 1].substring(0, 1)).toUpperCase();
|
||||
}
|
||||
|
||||
private void showAvatarError(String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Profile Picture");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void applyRBAC() {
|
||||
UserSession session = UserSession.getInstance();
|
||||
|
||||
String displayName = session.getEmployeeName();
|
||||
if (displayName == null || displayName.isBlank()) {
|
||||
displayName = session.getUsername();
|
||||
}
|
||||
lblUsername.setText(displayName == null ? "" : displayName);
|
||||
lblRole.setText("Leon's Petstore");
|
||||
|
||||
boolean isAdmin = session.isAdmin();
|
||||
btnInventory.setVisible(isAdmin);
|
||||
btnInventory.setManaged(isAdmin);
|
||||
btnSuppliers.setVisible(isAdmin);
|
||||
btnSuppliers.setManaged(isAdmin);
|
||||
btnProductSuppliers.setVisible(isAdmin);
|
||||
btnProductSuppliers.setManaged(isAdmin);
|
||||
|
||||
btnPurchaseOrders.setVisible(isAdmin);
|
||||
btnPurchaseOrders.setManaged(isAdmin);
|
||||
|
||||
if (btnStaffAccounts != null) {
|
||||
btnStaffAccounts.setVisible(isAdmin);
|
||||
btnStaffAccounts.setManaged(isAdmin);
|
||||
}
|
||||
|
||||
if (lblAdminSection != null) {
|
||||
lblAdminSection.setVisible(isAdmin);
|
||||
lblAdminSection.setManaged(isAdmin);
|
||||
}
|
||||
|
||||
if (separatorAdmin != null) {
|
||||
separatorAdmin.setVisible(isAdmin);
|
||||
separatorAdmin.setManaged(isAdmin);
|
||||
}
|
||||
|
||||
if (btnAnalytics != null) {
|
||||
btnAnalytics.setVisible(isAdmin);
|
||||
btnAnalytics.setManaged(isAdmin);
|
||||
}
|
||||
|
||||
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void loadView(String fxmlFile) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/modelviews/" + fxmlFile));
|
||||
Parent view = loader.load();
|
||||
spContentArea.getChildren().clear();
|
||||
spContentArea.getChildren().add(view);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"MainLayoutController.loadView",
|
||||
e,
|
||||
"Loading view: " + fxmlFile);
|
||||
System.err.println("Error loading view: " + fxmlFile);
|
||||
e.printStackTrace();
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("View Load Error");
|
||||
alert.setHeaderText("Failed to load: " + fxmlFile);
|
||||
alert.setContentText("Error: " + e.getMessage() + "\n\nCheck console for details.");
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtons(Button activeButton) {
|
||||
Button[] buttons = {
|
||||
btnAdoptions,
|
||||
btnPets,
|
||||
btnAppointments,
|
||||
btnInventory,
|
||||
btnSalesHistory,
|
||||
btnServices,
|
||||
btnSuppliers,
|
||||
btnProductSuppliers,
|
||||
btnProducts,
|
||||
btnPurchaseOrders,
|
||||
btnStaffAccounts,
|
||||
btnAnalytics,
|
||||
btnChat
|
||||
};
|
||||
|
||||
for (Button button : buttons) {
|
||||
if (button != null) {
|
||||
button.setStyle(NAV_BASE_STYLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeButton != null) {
|
||||
activeButton.setStyle(NAV_ACTIVE_STYLE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.PetApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PetController {
|
||||
|
||||
@FXML
|
||||
private Button btnAdd;
|
||||
|
||||
@FXML
|
||||
private Button btnDelete;
|
||||
|
||||
@FXML
|
||||
private Button btnEdit;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, Integer> colPetAge;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colPetBreed;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, Integer> colPetId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colPetName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, Double> colPetPrice;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colPetSpecies;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colPetStatus;
|
||||
|
||||
@FXML
|
||||
private TableView<Pet> tvPets;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
mode = "Add";
|
||||
openDialog(null,mode);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
//get selected pets
|
||||
var selectedPets = tvPets.getSelectionModel().getSelectedItems();
|
||||
if (selectedPets.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedPets.size() == 1
|
||||
? "Are you sure you want to delete this pet?"
|
||||
: "Are you sure you want to delete " + selectedPets.size() + " pets?";
|
||||
question.setContentText(message);
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedPets.stream()
|
||||
.map(p -> (long) p.getPetId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
PetApi.getInstance().deletePets(ids);
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted " + ids.size() + " pet(s)");
|
||||
alert.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting pets");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Failed");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display and reset inputs
|
||||
displayPets();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
Pet selectedPet = tvPets.getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selectedPet != null){
|
||||
mode = "Edit";
|
||||
openDialog(selectedPet,mode);
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableList<Pet> data = FXCollections.observableArrayList();
|
||||
String mode = null;
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
//Enable multiple selection
|
||||
tvPets.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colPetId.setCellValueFactory(new PropertyValueFactory<Pet,Integer>("petId"));
|
||||
colPetName.setCellValueFactory(new PropertyValueFactory<Pet,String>("petName"));
|
||||
colPetSpecies.setCellValueFactory(new PropertyValueFactory<Pet,String>("petSpecies"));
|
||||
colPetBreed.setCellValueFactory(new PropertyValueFactory<Pet,String>("petBreed"));
|
||||
colPetAge.setCellValueFactory(new PropertyValueFactory<Pet,Integer>("petAge"));
|
||||
colPetStatus.setCellValueFactory(new PropertyValueFactory<Pet,String>("petStatus"));
|
||||
colPetPrice.setCellValueFactory(new PropertyValueFactory<Pet,Double>("petPrice"));
|
||||
|
||||
displayPets();
|
||||
|
||||
tvPets.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
});
|
||||
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredPet(newValue);
|
||||
});
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvPets.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvPets.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayFilteredPet(String filter) {
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
|
||||
displayPets();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<PetResponse> pets = PetApi.getInstance().listPets(filter);
|
||||
List<Pet> petList = pets.stream()
|
||||
.map(this::mapToPet)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(petList);
|
||||
tvPets.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetController.displayFilteredPet",
|
||||
e,
|
||||
String.format("Filtering pets with keyword: %s", filter));
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void displayPets() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<PetResponse> pets = PetApi.getInstance().listPets(null);
|
||||
List<Pet> petList = pets.stream()
|
||||
.map(this::mapToPet)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(petList);
|
||||
tvPets.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetController.displayPets",
|
||||
e,
|
||||
"Fetching pet data for table display");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openDialog(Pet pet, String mode){
|
||||
//Get new view
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
try{
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetController.openDialog",
|
||||
e,
|
||||
"Loading pet dialog in " + mode + " mode");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
PetDialogController dialogController = fxmlLoader.getController(); //controller associated with this view
|
||||
dialogController.setMode(mode);
|
||||
|
||||
//Open the dialog depending on the mode
|
||||
if(mode.equals("Edit")){
|
||||
//Make it display pet details in dialog
|
||||
dialogController.displayPetDetails(pet);
|
||||
}
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL); //make it modal
|
||||
if(mode.equals("Add")){
|
||||
dialogStage.setTitle("Add Pet");
|
||||
}
|
||||
else {
|
||||
dialogStage.setTitle("Edit Pet");
|
||||
}
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
//When dialog closes update table view and disable edit and delete buttons, and reset search bar
|
||||
displayPets();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
private Pet mapToPet(PetResponse response) {
|
||||
return new Pet(
|
||||
response.getPetId().intValue(),
|
||||
response.getPetName(),
|
||||
response.getPetSpecies(),
|
||||
response.getPetBreed(),
|
||||
response.getPetAge() != null ? response.getPetAge() : 0,
|
||||
response.getPetStatus(),
|
||||
response.getPetPrice().doubleValue()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.DTOs.ProductDTO;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The controller for any operations in the products view
|
||||
*/
|
||||
public class ProductController {
|
||||
|
||||
@FXML
|
||||
private Button btnAdd;
|
||||
|
||||
@FXML
|
||||
private Button btnDelete;
|
||||
|
||||
@FXML
|
||||
private Button btnEdit;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductDTO, String> colProductCategory;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductDTO, String> colProductDesc;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductDTO, Integer> colProductId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductDTO, String> colProductName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductDTO, Double> colProductPrice;
|
||||
|
||||
@FXML
|
||||
private TableView<ProductDTO> tvProducts;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
//data declaration
|
||||
private ObservableList<ProductDTO> data = FXCollections.observableArrayList(); //empty
|
||||
private String mode = null;
|
||||
|
||||
/**
|
||||
* Set up the table view for products and display it when starting up
|
||||
*/
|
||||
@FXML
|
||||
void initialize() {
|
||||
//Disable buttons until a row is selected
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
//Enable multiple selection
|
||||
tvProducts.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
//set up table columns
|
||||
colProductId.setCellValueFactory(new PropertyValueFactory<ProductDTO,Integer>("prodId"));
|
||||
colProductName.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("prodName"));
|
||||
colProductPrice.setCellValueFactory(new PropertyValueFactory<ProductDTO,Double>("prodPrice"));
|
||||
colProductCategory.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("categoryName"));
|
||||
colProductDesc.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("prodDesc"));
|
||||
|
||||
displayProduct();
|
||||
|
||||
//EventListener to Enable buttons when a row is selected
|
||||
tvProducts.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
}
|
||||
);
|
||||
|
||||
//EventListener to search when text is changed on searchbar
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredProduct(newValue);
|
||||
});
|
||||
|
||||
//EventListener for DELETE key press
|
||||
tvProducts.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvProducts.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the productDTO to table view
|
||||
*/
|
||||
private void displayProduct(){
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ProductResponse> products = ProductApi.getInstance().listProducts(null);
|
||||
List<ProductDTO> productDTOs = products.stream()
|
||||
.map(this::mapToProductDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(productDTOs);
|
||||
tvProducts.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductController.displayProduct",
|
||||
e,
|
||||
"Fetching product data for table display");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* open a new dialog for adding a product
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
mode = "Add";
|
||||
openDialog(null,mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete selected product(s) when delete is clicked
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
//get selected products
|
||||
var selectedProducts = tvProducts.getSelectionModel().getSelectedItems();
|
||||
if (selectedProducts.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedProducts.size() == 1
|
||||
? "Are you sure you want to delete this product?"
|
||||
: "Are you sure you want to delete " + selectedProducts.size() + " products?";
|
||||
question.setContentText(message);
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedProducts.stream()
|
||||
.map(p -> (long) p.getProdId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
ProductApi.getInstance().deleteProducts(ids);
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted " + ids.size() + " product(s)");
|
||||
alert.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting products");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Failed");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display and reset inputs
|
||||
displayProduct();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new dialog for editing a product
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
//set selected product
|
||||
ProductDTO selectedProduct = tvProducts.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedProduct != null) {
|
||||
mode = "Edit";
|
||||
openDialog(selectedProduct, mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the table given the string from the searchbar
|
||||
* @param filter word to filter table
|
||||
*/
|
||||
private void displayFilteredProduct(String filter){
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
|
||||
displayProduct();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ProductResponse> products = ProductApi.getInstance().listProducts(filter);
|
||||
List<ProductDTO> productDTOs = products.stream()
|
||||
.map(this::mapToProductDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(productDTOs);
|
||||
tvProducts.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductController.displayFilteredProduct",
|
||||
e,
|
||||
String.format("Filtering products with keyword: %s", filter));
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open the new Dialog for edit or adding
|
||||
* depending on the mode given
|
||||
* @param product the product entity for editing, null if adding
|
||||
* @param mode the mode the dialog should be in
|
||||
*/
|
||||
private void openDialog(ProductDTO product, String mode){
|
||||
//Get new view
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
try{
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductController.openDialog",
|
||||
e,
|
||||
String.format("Loading product dialog view in %s mode", mode));
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
ProductDialogController dialogController = fxmlLoader.getController(); //controller associated with this view
|
||||
dialogController.setMode(mode);
|
||||
|
||||
//Open the dialog depending on the mode
|
||||
if(mode.equals("Edit")){
|
||||
//Make it display suppliers details in dialog
|
||||
dialogController.displayProductDetails(product);
|
||||
}
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL); //make it modal
|
||||
if(mode.equals("Add")){
|
||||
dialogStage.setTitle("Add Product");
|
||||
}
|
||||
else {
|
||||
dialogStage.setTitle("Edit Product");
|
||||
}
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
//When dialog closes update table view and disable edit and delete buttons, and reset search bar
|
||||
displayProduct();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
private ProductDTO mapToProductDTO(ProductResponse response) {
|
||||
return new ProductDTO(
|
||||
response.getProdId().intValue(),
|
||||
response.getProdName(),
|
||||
response.getProdPrice().doubleValue(),
|
||||
0,
|
||||
response.getCategoryName(),
|
||||
response.getProdDesc()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
|
||||
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductSupplierApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductSupplierDialogController;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ProductSupplierController {
|
||||
|
||||
@FXML
|
||||
private Button btnAdd;
|
||||
|
||||
@FXML
|
||||
private Button btnDelete;
|
||||
|
||||
@FXML
|
||||
private Button btnEdit;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductSupplierDTO, Double> colCost;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductSupplierDTO, Integer> colProductId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductSupplierDTO, String> colProductName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductSupplierDTO, Integer> colSupplierId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductSupplierDTO, String> colSupplierName;
|
||||
|
||||
@FXML
|
||||
private TableView<ProductSupplierDTO> tvProductSuppliers;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
//data declaration
|
||||
private ObservableList<ProductSupplierDTO> data = FXCollections.observableArrayList();
|
||||
private String mode = null;
|
||||
|
||||
/**
|
||||
* Set up the table view for table and display it when starting up
|
||||
*/
|
||||
@FXML
|
||||
public void initialize() {
|
||||
//Disable buttons until a row is selected
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
//Enable multiple selection
|
||||
tvProductSuppliers.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
//set up table columns
|
||||
colProductId.setCellValueFactory(new PropertyValueFactory<ProductSupplierDTO,Integer>("prodId"));
|
||||
colProductName.setCellValueFactory(new PropertyValueFactory<ProductSupplierDTO,String>("prodName"));
|
||||
colSupplierId.setCellValueFactory(new PropertyValueFactory<ProductSupplierDTO,Integer>("supId"));
|
||||
colSupplierName.setCellValueFactory(new PropertyValueFactory<ProductSupplierDTO,String>("supCompany"));
|
||||
colCost.setCellValueFactory(new PropertyValueFactory<ProductSupplierDTO,Double>("cost"));
|
||||
|
||||
displayProductSupplier();
|
||||
|
||||
//EventListener to Enable buttons when a row is selected
|
||||
tvProductSuppliers.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
}
|
||||
);
|
||||
|
||||
//EventListener to search when text is changed on searchbar
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredProductSupplier(newValue);
|
||||
});
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvProductSuppliers.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvProductSuppliers.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the ProductSupplierDTO to table view
|
||||
*/
|
||||
private void displayProductSupplier() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(null);
|
||||
List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
|
||||
.map(this::mapToProductSupplierDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(productSupplierDTOs);
|
||||
tvProductSuppliers.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductSupplierController.displayProductSupplier",
|
||||
e,
|
||||
"Fetching product-supplier data for table display");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the table given the string from the searchbar
|
||||
* @param filter word to filter table
|
||||
*/
|
||||
private void displayFilteredProductSupplier(String filter){
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
|
||||
displayProductSupplier();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(filter);
|
||||
List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
|
||||
.map(this::mapToProductSupplierDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(productSupplierDTOs);
|
||||
tvProductSuppliers.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductSupplierController.displayFilteredProductSupplier",
|
||||
e,
|
||||
"Filtering product-supplier data with filter: " + filter);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* open a new dialog for adding a productSupplier
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
mode = "Add";
|
||||
openDialog(null,mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete selected product-supplier(s) when delete is clicked
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
//get selected product-suppliers
|
||||
var selectedProductSuppliers = tvProductSuppliers.getSelectionModel().getSelectedItems();
|
||||
if (selectedProductSuppliers.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedProductSuppliers.size() == 1
|
||||
? "Are you sure you want to delete this product-supplier?"
|
||||
: "Are you sure you want to delete " + selectedProductSuppliers.size() + " product-suppliers?";
|
||||
question.setContentText(message);
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
int deleteCount = 0;
|
||||
Exception lastException = null;
|
||||
|
||||
for (ProductSupplierDTO ps : selectedProductSuppliers) {
|
||||
try {
|
||||
ProductSupplierApi.getInstance().deleteProductSupplier(
|
||||
(long) ps.getProdId(),
|
||||
(long) ps.getSupId()
|
||||
);
|
||||
deleteCount++;
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductSupplierController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting product-supplier with productId=" + ps.getProdId() + ", supplierId=" + ps.getSupId());
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteCount > 0) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted " + deleteCount + " product-supplier(s)");
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
if (lastException != null && deleteCount < selectedProductSuppliers.size()) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Partially Failed");
|
||||
alert.setContentText("Deleted " + deleteCount + " of " + selectedProductSuppliers.size() + " product-supplier(s). Last error: " + lastException.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display and reset inputs
|
||||
displayProductSupplier();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
//set selected item
|
||||
ProductSupplierDTO selectedProductSupplier = tvProductSuppliers.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedProductSupplier != null) {
|
||||
mode = "Edit";
|
||||
openDialog(selectedProductSupplier,mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open the new Dialog for edit or adding
|
||||
* depending on the mode given
|
||||
* @param productSupplier the productSupplier entity for editing, null if adding
|
||||
* @param mode the mode the dialog should be in
|
||||
*/
|
||||
private void openDialog(ProductSupplierDTO productSupplier, String mode){
|
||||
//Get new view
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
try{
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductSupplierController.openDialog",
|
||||
e,
|
||||
"Loading product-supplier dialog in " + mode + " mode");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
ProductSupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view
|
||||
dialogController.setMode(mode);
|
||||
if (productSupplier != null) {
|
||||
dialogController.setSelectedIds(productSupplier.getSupId(), productSupplier.getProdId());
|
||||
}
|
||||
|
||||
//Open the dialog depending on the mode
|
||||
if(mode.equals("Edit")){
|
||||
//Make it display suppliers details in dialog
|
||||
dialogController.displayProductSupplierDetails(productSupplier);
|
||||
}
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL); //make it modal
|
||||
if(mode.equals("Add")){
|
||||
dialogStage.setTitle("Add Product-Supplier");
|
||||
}
|
||||
else {
|
||||
dialogStage.setTitle("Edit Product-Supplier");
|
||||
}
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
//When dialog closes update table view and disable edit and delete buttons, and reset search bar
|
||||
displayProductSupplier();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
private ProductSupplierDTO mapToProductSupplierDTO(ProductSupplierResponse response) {
|
||||
return new ProductSupplierDTO(
|
||||
response.getSupplierId().intValue(),
|
||||
response.getProductId().intValue(),
|
||||
response.getSupplierName(),
|
||||
response.getProductName(),
|
||||
response.getCost().doubleValue()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO;
|
||||
import org.example.petshopdesktop.api.dto.purchaseorder.PurchaseOrderResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.PurchaseOrderApi;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PurchaseOrderController {
|
||||
|
||||
@FXML private Button btnRefresh;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
@FXML private TableView<PurchaseOrderDTO> tvPurchaseOrders;
|
||||
|
||||
@FXML private TableColumn<PurchaseOrderDTO,Long> colOrderId;
|
||||
@FXML private TableColumn<PurchaseOrderDTO,String> colSupplier;
|
||||
@FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate;
|
||||
@FXML private TableColumn<PurchaseOrderDTO,String> colStatus;
|
||||
|
||||
private final ObservableList<PurchaseOrderDTO> purchaseOrders = FXCollections.observableArrayList();
|
||||
private FilteredList<PurchaseOrderDTO> filtered;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
|
||||
colOrderId.setCellValueFactory(
|
||||
new PropertyValueFactory<>("purchaseOrderId"));
|
||||
|
||||
colSupplier.setCellValueFactory(
|
||||
new PropertyValueFactory<>("supplierName"));
|
||||
|
||||
colOrderDate.setCellValueFactory(
|
||||
new PropertyValueFactory<>("orderDate"));
|
||||
|
||||
colStatus.setCellValueFactory(
|
||||
new PropertyValueFactory<>("status"));
|
||||
|
||||
filtered = new FilteredList<>(purchaseOrders, p -> true);
|
||||
tvPurchaseOrders.setItems(filtered);
|
||||
|
||||
if (txtSearch != null) {
|
||||
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
||||
}
|
||||
|
||||
loadPurchaseOrders();
|
||||
}
|
||||
|
||||
private void loadPurchaseOrders() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<PurchaseOrderResponse> responses = PurchaseOrderApi.getInstance().listPurchaseOrders(null);
|
||||
List<PurchaseOrderDTO> dtos = responses.stream()
|
||||
.map(this::mapToPurchaseOrderDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
purchaseOrders.setAll(dtos);
|
||||
tvPurchaseOrders.setItems(filtered);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PurchaseOrderController.loadPurchaseOrders",
|
||||
e,
|
||||
"Loading purchase orders for table display");
|
||||
new Alert(Alert.AlertType.ERROR,
|
||||
"Unable to load purchase orders").showAndWait();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void applyFilter(String text) {
|
||||
if (filtered == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String q = text == null ? "" : text.trim().toLowerCase();
|
||||
if (q.isEmpty()) {
|
||||
filtered.setPredicate(p -> true);
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.setPredicate(p ->
|
||||
String.valueOf(p.getPurchaseOrderId()).contains(q)
|
||||
|| safe(p.getSupplierName()).contains(q)
|
||||
|| safe(p.getOrderDate()).contains(q)
|
||||
|| safe(p.getStatus()).contains(q)
|
||||
);
|
||||
}
|
||||
|
||||
private static String safe(String v) {
|
||||
return v == null ? "" : v.toLowerCase();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRefresh() {
|
||||
loadPurchaseOrders();
|
||||
}
|
||||
|
||||
private PurchaseOrderDTO mapToPurchaseOrderDTO(PurchaseOrderResponse response) {
|
||||
return new PurchaseOrderDTO(
|
||||
response.getPurchaseOrderId(),
|
||||
response.getSupplierName(),
|
||||
response.getOrderDate() != null ? response.getOrderDate().toString() : "",
|
||||
response.getOrderStatus()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import javafx.concurrent.Task;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductApi;
|
||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductResponse;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||
import org.example.petshopdesktop.models.Product;
|
||||
import org.example.petshopdesktop.models.SaleCartItem;
|
||||
import org.example.petshopdesktop.models.SaleLineItem;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class SaleController {
|
||||
|
||||
@FXML
|
||||
private Button btnRefresh;
|
||||
|
||||
@FXML
|
||||
private Button btnRefund;
|
||||
|
||||
@FXML
|
||||
private Label lblModeNote;
|
||||
|
||||
@FXML
|
||||
private VBox vbCreateSale;
|
||||
|
||||
@FXML
|
||||
private ComboBox<Product> cbProduct;
|
||||
|
||||
@FXML
|
||||
private Spinner<Integer> spQuantity;
|
||||
|
||||
@FXML
|
||||
private Button btnAddToCart;
|
||||
|
||||
@FXML
|
||||
private Button btnRemoveSelected;
|
||||
|
||||
@FXML
|
||||
private TableView<SaleCartItem> tvCart;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleCartItem, String> colCartProduct;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleCartItem, Integer> colCartQty;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleCartItem, Double> colCartUnitPrice;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleCartItem, Double> colCartTotal;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbPaymentMethod;
|
||||
|
||||
@FXML
|
||||
private Label lblCartTotal;
|
||||
|
||||
@FXML
|
||||
private Button btnClearCart;
|
||||
|
||||
@FXML
|
||||
private Button btnSaveSale;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, Integer> colSaleId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, String> colSaleDate;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, String> colEmployeeName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, String> colServiceProduct;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, Integer> colSaleQuantity;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, Double> colSaleUnitPrice;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, Double> colSaleTotal;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleLineItem, String> colSalePaymentType;
|
||||
|
||||
@FXML
|
||||
private TableView<SaleLineItem> tvSales;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
private final ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
|
||||
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
|
||||
private FilteredList<SaleLineItem> filteredSales;
|
||||
|
||||
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
setupTables();
|
||||
setupCreateSale();
|
||||
applyRoleMode();
|
||||
|
||||
refreshSales();
|
||||
}
|
||||
|
||||
private void setupTables() {
|
||||
tvCart.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
tvCart.setFixedCellSize(34);
|
||||
colCartProduct.setCellValueFactory(new PropertyValueFactory<>("prodName"));
|
||||
colCartQty.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
colCartUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||
colCartTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||
tvCart.setItems(cartItems);
|
||||
tvCart.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
|
||||
tvSales.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
tvSales.setFixedCellSize(34);
|
||||
colSaleId.setCellValueFactory(new PropertyValueFactory<>("saleId"));
|
||||
colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate"));
|
||||
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
|
||||
colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("itemName"));
|
||||
colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||
colSaleTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||
colSalePaymentType.setCellValueFactory(new PropertyValueFactory<>("paymentMethod"));
|
||||
|
||||
filteredSales = new FilteredList<>(saleItems, s -> true);
|
||||
tvSales.setItems(filteredSales);
|
||||
|
||||
txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applySalesFilter(newVal));
|
||||
}
|
||||
|
||||
private void setupCreateSale() {
|
||||
spQuantity.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 999, 1));
|
||||
spQuantity.setEditable(true);
|
||||
|
||||
cbPaymentMethod.setItems(FXCollections.observableArrayList("Cash", "Card"));
|
||||
cbPaymentMethod.getSelectionModel().selectFirst();
|
||||
|
||||
updateCartTotal();
|
||||
|
||||
try {
|
||||
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
|
||||
ObservableList<Product> products = FXCollections.observableArrayList();
|
||||
for (ProductResponse pr : productResponses) {
|
||||
products.add(new Product(
|
||||
pr.getProdId().intValue(),
|
||||
pr.getProdName(),
|
||||
pr.getProdPrice().doubleValue(),
|
||||
0,
|
||||
pr.getProdDesc()
|
||||
));
|
||||
}
|
||||
cbProduct.setItems(products);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Loading products");
|
||||
}
|
||||
}
|
||||
|
||||
private void applyRoleMode() {
|
||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||
vbCreateSale.setVisible(!isAdmin);
|
||||
vbCreateSale.setManaged(!isAdmin);
|
||||
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
|
||||
}
|
||||
|
||||
private void refreshSales() {
|
||||
refreshSales(false);
|
||||
}
|
||||
|
||||
private void refreshSales(boolean showErrorDialog) {
|
||||
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
|
||||
@Override
|
||||
protected List<SaleLineItem> call() throws Exception {
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, 1000, null);
|
||||
List<SaleLineItem> lineItems = new ArrayList<>();
|
||||
|
||||
for (SaleResponse sale : sales) {
|
||||
String saleDate = sale.getSaleDate() != null
|
||||
? sale.getSaleDate().format(DATE_FORMATTER)
|
||||
: "";
|
||||
|
||||
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
|
||||
for (SaleItemResponse item : sale.getItems()) {
|
||||
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
|
||||
double lineTotal = unitPrice * item.getQuantity();
|
||||
lineItems.add(new SaleLineItem(
|
||||
sale.getSaleId().intValue(),
|
||||
saleDate,
|
||||
sale.getEmployeeName(),
|
||||
item.getProductName(),
|
||||
item.getQuantity(),
|
||||
unitPrice,
|
||||
lineTotal,
|
||||
sale.getPaymentMethod(),
|
||||
sale.getIsRefund() != null && sale.getIsRefund()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return lineItems;
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(event -> {
|
||||
saleItems.setAll(task.getValue());
|
||||
});
|
||||
|
||||
task.setOnFailed(event -> {
|
||||
Throwable e = task.getException();
|
||||
ActivityLogger.getInstance().logException("SaleController.refreshSales", (Exception) e, "Loading sales");
|
||||
if (showErrorDialog) {
|
||||
showError("Sales", "Could not load sales: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRefresh(ActionEvent event) {
|
||||
refreshSales(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnAddToCart(ActionEvent event) {
|
||||
Product product = cbProduct.getSelectionModel().getSelectedItem();
|
||||
if (product == null) {
|
||||
showError("Create Sale", "Select a product.");
|
||||
return;
|
||||
}
|
||||
|
||||
int requestedQty;
|
||||
try {
|
||||
requestedQty = spQuantity.getValue();
|
||||
} catch (Exception e) {
|
||||
showError("Create Sale", "Enter a valid quantity.");
|
||||
return;
|
||||
}
|
||||
if (requestedQty <= 0) {
|
||||
showError("Create Sale", "Quantity must be at least 1.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (SaleCartItem item : cartItems) {
|
||||
if (item.getProdId() == product.getProdId()) {
|
||||
item.setQuantity(item.getQuantity() + requestedQty);
|
||||
tvCart.refresh();
|
||||
updateCartTotal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cartItems.add(new SaleCartItem(product.getProdId(), product.getProdName(), requestedQty, product.getProdPrice()));
|
||||
updateCartTotal();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRemoveSelected(ActionEvent event) {
|
||||
SaleCartItem selected = tvCart.getSelectionModel().getSelectedItem();
|
||||
if (selected != null) {
|
||||
cartItems.remove(selected);
|
||||
updateCartTotal();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnClearCart(ActionEvent event) {
|
||||
cartItems.clear();
|
||||
updateCartTotal();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnSaveSale(ActionEvent event) {
|
||||
if (UserSession.getInstance().isAdmin()) {
|
||||
showError("Create Sale", "This action is restricted to staff.");
|
||||
return;
|
||||
}
|
||||
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
if (storeId == null || storeId <= 0) {
|
||||
showError("Create Sale", "Store is not set for this account.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cartItems.isEmpty()) {
|
||||
showError("Create Sale", "Add at least one item.");
|
||||
return;
|
||||
}
|
||||
|
||||
String payment = cbPaymentMethod.getSelectionModel().getSelectedItem();
|
||||
if (payment == null || payment.isBlank()) {
|
||||
showError("Create Sale", "Select a payment method.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SaleRequest request = new SaleRequest();
|
||||
request.setStoreId(storeId);
|
||||
request.setPaymentMethod(payment);
|
||||
|
||||
List<SaleItemRequest> itemRequests = new ArrayList<>();
|
||||
for (SaleCartItem cartItem : cartItems) {
|
||||
SaleItemRequest itemRequest = new SaleItemRequest();
|
||||
itemRequest.setProdId((long) cartItem.getProdId());
|
||||
itemRequest.setQuantity(cartItem.getQuantity());
|
||||
itemRequests.add(itemRequest);
|
||||
}
|
||||
request.setItems(itemRequests);
|
||||
|
||||
SaleResponse response = SaleApi.getInstance().createSale(request);
|
||||
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
|
||||
|
||||
cartItems.clear();
|
||||
updateCartTotal();
|
||||
|
||||
refreshSales(true);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
|
||||
showError("Create Sale", "Insufficient stock for one or more items.");
|
||||
} else {
|
||||
showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRefund(ActionEvent event) {
|
||||
openRefundDialog();
|
||||
}
|
||||
|
||||
private void openRefundDialog() {
|
||||
try {
|
||||
SaleLineItem selectedSale = tvSales.getSelectionModel().getSelectedItem();
|
||||
if (selectedSale != null && selectedSale.isRefund()) {
|
||||
showError("Refund", "Select an original sale, not an existing refund.");
|
||||
return;
|
||||
}
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/example/petshopdesktop/dialogviews/refund-dialog-view.fxml"));
|
||||
Stage dialog = new Stage();
|
||||
dialog.initOwner(btnRefund.getScene().getWindow());
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.setTitle("Process Refund");
|
||||
dialog.setScene(new Scene(loader.load()));
|
||||
if (selectedSale != null) {
|
||||
loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController()
|
||||
.prefillSale((long) selectedSale.getSaleId());
|
||||
}
|
||||
dialog.setResizable(false);
|
||||
dialog.showAndWait();
|
||||
|
||||
refreshSales(true);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("SaleController.openRefundDialog", e, "Opening refund dialog");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCartTotal() {
|
||||
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
|
||||
lblCartTotal.setText(currency.format(total));
|
||||
}
|
||||
|
||||
private void applySalesFilter(String filter) {
|
||||
String f = filter == null ? "" : filter.trim().toLowerCase();
|
||||
if (f.isEmpty()) {
|
||||
filteredSales.setPredicate(s -> true);
|
||||
return;
|
||||
}
|
||||
|
||||
filteredSales.setPredicate(s ->
|
||||
String.valueOf(s.getSaleId()).contains(f)
|
||||
|| safe(s.getSaleDate()).contains(f)
|
||||
|| safe(s.getEmployeeName()).contains(f)
|
||||
|| safe(s.getItemName()).contains(f)
|
||||
|| safe(s.getPaymentMethod()).contains(f)
|
||||
);
|
||||
}
|
||||
|
||||
private static String safe(String v) {
|
||||
return v == null ? "" : v.toLowerCase();
|
||||
}
|
||||
|
||||
private void showError(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showInfo(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.DTOs.ServiceDTO;
|
||||
import org.example.petshopdesktop.api.dto.service.ServiceResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.ServiceApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
import javafx.stage.Modality;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
public class ServiceController {
|
||||
|
||||
@FXML private Button btnAdd;
|
||||
@FXML private Button btnDelete;
|
||||
@FXML private Button btnEdit;
|
||||
|
||||
@FXML private TableColumn<ServiceDTO, Integer> colServiceId;
|
||||
@FXML private TableColumn<ServiceDTO, String> colServiceName;
|
||||
@FXML private TableColumn<ServiceDTO, String> colServiceDesc;
|
||||
@FXML private TableColumn<ServiceDTO, Integer> colServiceDuration;
|
||||
@FXML private TableColumn<ServiceDTO, Double> colServicePrice;
|
||||
|
||||
@FXML private TableView<ServiceDTO> tvServices;
|
||||
|
||||
@FXML private TextField txtSearch;
|
||||
|
||||
private ObservableList<ServiceDTO> data = FXCollections.observableArrayList();
|
||||
private String mode = null;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
tvServices.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colServiceId.setCellValueFactory(new PropertyValueFactory<>("serviceId"));
|
||||
colServiceName.setCellValueFactory(new PropertyValueFactory<>("serviceName"));
|
||||
colServiceDesc.setCellValueFactory(new PropertyValueFactory<>("serviceDesc"));
|
||||
colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration"));
|
||||
colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice"));
|
||||
|
||||
displayServices();
|
||||
|
||||
tvServices.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
}
|
||||
);
|
||||
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredServices(newValue);
|
||||
});
|
||||
|
||||
tvServices.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvServices.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayServices() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ServiceResponse> services = ServiceApi.getInstance().listServices(null);
|
||||
List<ServiceDTO> serviceDTOs = services.stream()
|
||||
.map(this::mapToServiceDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(serviceDTOs);
|
||||
tvServices.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ServiceController.displayServices",
|
||||
e,
|
||||
"Fetching service data for table display");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void displayFilteredServices(String filter) {
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
|
||||
displayServices();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ServiceResponse> services = ServiceApi.getInstance().listServices(filter);
|
||||
List<ServiceDTO> serviceDTOs = services.stream()
|
||||
.map(this::mapToServiceDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(serviceDTOs);
|
||||
tvServices.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ServiceController.displayFilteredServices",
|
||||
e,
|
||||
String.format("Filtering services with keyword: %s", filter));
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
mode = "Add";
|
||||
openDialog(null, mode);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
ServiceDTO selected = tvServices.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selected != null) {
|
||||
mode = "Edit";
|
||||
openDialog(selected, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
var selectedServices = tvServices.getSelectionModel().getSelectedItems();
|
||||
if (selectedServices.isEmpty()) return;
|
||||
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedServices.size() == 1
|
||||
? "Are you sure you want to delete this service?"
|
||||
: "Are you sure you want to delete " + selectedServices.size() + " services?";
|
||||
question.setContentText(message);
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedServices.stream()
|
||||
.map(s -> (long) s.getServiceId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
ServiceApi.getInstance().deleteServices(ids);
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted " + ids.size() + " service(s)");
|
||||
alert.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ServiceController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting services");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Failed");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
displayServices();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
private void openDialog(ServiceDTO service, String mode) {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
try {
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ServiceController.openDialog",
|
||||
e,
|
||||
String.format("Loading service dialog view in %s mode", mode));
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
ServiceDialogController dialogController = fxmlLoader.getController();
|
||||
dialogController.setMode(mode);
|
||||
|
||||
if (mode.equals("Edit")) {
|
||||
dialogController.setService(service);
|
||||
}
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL);
|
||||
if (mode.equals("Add")) {
|
||||
dialogStage.setTitle("Add Service");
|
||||
} else {
|
||||
dialogStage.setTitle("Edit Service");
|
||||
}
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
displayServices();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
private ServiceDTO mapToServiceDTO(ServiceResponse response) {
|
||||
return new ServiceDTO(
|
||||
response.getServiceId().intValue(),
|
||||
response.getServiceName(),
|
||||
response.getServiceDesc(),
|
||||
response.getServiceDuration() != null ? response.getServiceDuration() : 0,
|
||||
response.getServicePrice().doubleValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.models.StaffAccount;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StaffAccountsController {
|
||||
|
||||
@FXML
|
||||
private TableView<StaffAccount> tvStaff;
|
||||
|
||||
@FXML
|
||||
private TableColumn<StaffAccount, String> colUsername;
|
||||
|
||||
@FXML
|
||||
private TableColumn<StaffAccount, String> colName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<StaffAccount, String> colEmail;
|
||||
|
||||
@FXML
|
||||
private TableColumn<StaffAccount, String> colPhone;
|
||||
|
||||
@FXML
|
||||
private TableColumn<StaffAccount, String> colStatus;
|
||||
|
||||
@FXML
|
||||
private TableColumn<StaffAccount, java.sql.Timestamp> colCreated;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
@FXML
|
||||
private Label lblError;
|
||||
|
||||
@FXML
|
||||
private Button btnCreateAccount;
|
||||
|
||||
@FXML
|
||||
private Button btnEditAccount;
|
||||
|
||||
private final ObservableList<StaffAccount> staffAccounts = FXCollections.observableArrayList();
|
||||
private FilteredList<StaffAccount> filtered;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
colUsername.setCellValueFactory(new PropertyValueFactory<>("username"));
|
||||
colName.setCellValueFactory(new PropertyValueFactory<>("fullName"));
|
||||
colEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
|
||||
colPhone.setCellValueFactory(new PropertyValueFactory<>("phone"));
|
||||
colStatus.setCellValueFactory(new PropertyValueFactory<>("status"));
|
||||
colCreated.setCellValueFactory(new PropertyValueFactory<>("createdAt"));
|
||||
|
||||
filtered = new FilteredList<>(staffAccounts, a -> true);
|
||||
tvStaff.setItems(filtered);
|
||||
|
||||
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
||||
|
||||
tvStaff.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
|
||||
if (btnEditAccount != null) {
|
||||
btnEditAccount.setDisable(newValue == null);
|
||||
}
|
||||
});
|
||||
|
||||
if (!UserSession.getInstance().isAdmin()) {
|
||||
lblError.setText("Access restricted.");
|
||||
tvStaff.setDisable(true);
|
||||
btnCreateAccount.setDisable(true);
|
||||
if (btnEditAccount != null) {
|
||||
btnEditAccount.setDisable(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (btnEditAccount != null) {
|
||||
btnEditAccount.setDisable(true);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRefreshClicked(ActionEvent event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnCreateAccountClicked(ActionEvent event) {
|
||||
lblError.setText("");
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-register-dialog-view.fxml"));
|
||||
Stage dialog = new Stage();
|
||||
dialog.initOwner(tvStaff.getScene().getWindow());
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.setTitle("Create Staff Account");
|
||||
dialog.setScene(new Scene(loader.load()));
|
||||
dialog.setResizable(false);
|
||||
dialog.showAndWait();
|
||||
refresh();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("StaffAccountsController.btnCreateAccountClicked", e, "Opening staff register dialog");
|
||||
lblError.setText("Could not open staff account creation.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnEditAccountClicked(ActionEvent event) {
|
||||
lblError.setText("");
|
||||
StaffAccount selected = tvStaff.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) {
|
||||
lblError.setText("Select a staff account to edit.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml"));
|
||||
Stage dialog = new Stage();
|
||||
dialog.initOwner(tvStaff.getScene().getWindow());
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.setTitle("Edit Staff Account");
|
||||
dialog.setScene(new Scene(loader.load()));
|
||||
dialog.setResizable(false);
|
||||
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
|
||||
controller.setStaffAccount(selected);
|
||||
dialog.showAndWait();
|
||||
refresh();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("StaffAccountsController.btnEditAccountClicked", e, "Opening staff edit dialog");
|
||||
lblError.setText("Could not open staff account editor.");
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
lblError.setText("");
|
||||
tvStaff.setDisable(true);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<EmployeeResponse> employees = EmployeeApi.getInstance().listEmployees(null);
|
||||
List<StaffAccount> accounts = employees.stream()
|
||||
.map(this::mapToStaffAccount)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
staffAccounts.setAll(accounts);
|
||||
tvStaff.setDisable(false);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
|
||||
Platform.runLater(() -> {
|
||||
String message = e.getMessage();
|
||||
lblError.setText(message == null || message.isBlank()
|
||||
? "Could not load staff accounts."
|
||||
: "Could not load staff accounts: " + message);
|
||||
tvStaff.setDisable(false);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private StaffAccount mapToStaffAccount(EmployeeResponse employee) {
|
||||
long userId = employee.getUserId() != null ? employee.getUserId() : 0L;
|
||||
long employeeId = employee.getEmployeeId() != null ? employee.getEmployeeId() : 0L;
|
||||
String username = employee.getUsername();
|
||||
String fullName = employee.getFullName() != null ? employee.getFullName() : "";
|
||||
String[] names = splitFullName(fullName);
|
||||
String firstName = names[0];
|
||||
String lastName = names[1];
|
||||
String email = employee.getEmail() != null ? employee.getEmail() : "";
|
||||
String phone = employee.getPhone() != null ? employee.getPhone() : "";
|
||||
String role = employee.getRole() != null ? employee.getRole() : "STAFF";
|
||||
boolean active = employee.getActive() != null ? employee.getActive() : false;
|
||||
Timestamp createdAt = employee.getCreatedAt() != null
|
||||
? Timestamp.from(employee.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())
|
||||
: null;
|
||||
|
||||
return new StaffAccount(userId, employeeId, username, firstName, lastName, email, phone, role, active, createdAt);
|
||||
}
|
||||
|
||||
private String[] splitFullName(String fullName) {
|
||||
if (fullName == null || fullName.trim().isEmpty()) {
|
||||
return new String[]{"", ""};
|
||||
}
|
||||
String[] parts = fullName.trim().split("\\s+", 2);
|
||||
String firstName = parts.length > 0 ? parts[0] : "";
|
||||
String lastName = parts.length > 1 ? parts[1] : "";
|
||||
return new String[]{firstName, lastName};
|
||||
}
|
||||
|
||||
private void applyFilter(String text) {
|
||||
String q = text == null ? "" : text.trim().toLowerCase();
|
||||
if (q.isEmpty()) {
|
||||
filtered.setPredicate(a -> true);
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.setPredicate(a ->
|
||||
safe(a.getUsername()).contains(q)
|
||||
|| safe(a.getFullName()).contains(q)
|
||||
|| safe(a.getEmail()).contains(q)
|
||||
|| safe(a.getPhone()).contains(q)
|
||||
|| safe(a.getStatus()).contains(q)
|
||||
);
|
||||
}
|
||||
|
||||
private static String safe(String v) {
|
||||
return v == null ? "" : v.toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.SupplierApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.SupplierDialogController;
|
||||
import org.example.petshopdesktop.models.Supplier;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The controller for any operations in the supplier view
|
||||
*/
|
||||
public class SupplierController {
|
||||
|
||||
@FXML
|
||||
private Button btnAdd;
|
||||
|
||||
@FXML
|
||||
private Button btnDelete;
|
||||
|
||||
@FXML
|
||||
private Button btnEdit;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Supplier, String> colContactPerson;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Supplier, String> colSupplierEmail;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Supplier, Integer> colSupplierId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Supplier, String> colSupplierName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Supplier, String> colSupplierPhone;
|
||||
|
||||
@FXML
|
||||
private TableView<Supplier> tvSuppliers;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
private ObservableList<Supplier> data = FXCollections.observableArrayList();
|
||||
private String mode = null;
|
||||
|
||||
/**
|
||||
* Set up the table view for suppliers and display it when starting up
|
||||
*/
|
||||
@FXML
|
||||
void initialize(){
|
||||
//Disable buttons until a row is selected
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
//Enable multiple selection
|
||||
tvSuppliers.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
//set columns for table view
|
||||
colSupplierId.setCellValueFactory(new PropertyValueFactory<Supplier, Integer>("supId"));
|
||||
colSupplierName.setCellValueFactory(new PropertyValueFactory<Supplier, String>("supCompany"));
|
||||
colContactPerson.setCellValueFactory(new PropertyValueFactory<Supplier, String>("supFullName"));
|
||||
colSupplierEmail.setCellValueFactory(new PropertyValueFactory<Supplier, String>("supEmail"));
|
||||
colSupplierPhone.setCellValueFactory(new PropertyValueFactory<Supplier, String>("supPhone"));
|
||||
|
||||
displaySupplier();
|
||||
|
||||
//EventListener to Enable buttons when a row is selected
|
||||
tvSuppliers.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
}
|
||||
);
|
||||
|
||||
//EventListener to search when text is changed on searchbar
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredSupplier(newValue);
|
||||
});
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvSuppliers.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvSuppliers.getSelectionModel().getSelectedItem() != null) {
|
||||
btnDeleteClicked(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the suppliers to table view
|
||||
*/
|
||||
private void displaySupplier(){
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<SupplierResponse> suppliers = SupplierApi.getInstance().listSuppliers(null);
|
||||
List<Supplier> supplierList = suppliers.stream()
|
||||
.map(this::mapToSupplier)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(supplierList);
|
||||
tvSuppliers.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"SupplierController.displaySupplier",
|
||||
e,
|
||||
"Fetching supplier data for table display");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the table given the string from the searchbar
|
||||
* @param filter word to filter table
|
||||
*/
|
||||
private void displayFilteredSupplier(String filter){
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
|
||||
displaySupplier();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<SupplierResponse> suppliers = SupplierApi.getInstance().listSuppliers(filter);
|
||||
List<Supplier> supplierList = suppliers.stream()
|
||||
.map(this::mapToSupplier)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
data.setAll(supplierList);
|
||||
tvSuppliers.setItems(data);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
ActivityLogger.getInstance().logException(
|
||||
"SupplierController.displayFilteredSupplier",
|
||||
e,
|
||||
"Filtering suppliers with filter: " + filter);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* open a new dialog for adding a supplier
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
mode = "Add";
|
||||
openDialog(null,mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete selected supplier(s) when delete is clicked
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
//get selected suppliers
|
||||
var selectedSuppliers = tvSuppliers.getSelectionModel().getSelectedItems();
|
||||
if (selectedSuppliers.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedSuppliers.size() == 1
|
||||
? "Are you sure you want to delete this supplier?"
|
||||
: "Are you sure you want to delete " + selectedSuppliers.size() + " suppliers?";
|
||||
question.setContentText(message);
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedSuppliers.stream()
|
||||
.map(s -> (long) s.getSupId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
SupplierApi.getInstance().deleteSuppliers(ids);
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Successfully deleted " + ids.size() + " supplier(s)");
|
||||
alert.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"SupplierController.btnDeleteClicked",
|
||||
e,
|
||||
"Deleting suppliers");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Delete Operation Failed");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display and reset inputs
|
||||
displaySupplier();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new dialog for editing a supplier
|
||||
* @param event click event for button
|
||||
*/
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
//set selected supplier
|
||||
Supplier selectedSupplier = tvSuppliers.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedSupplier != null) {
|
||||
mode = "Edit";
|
||||
openDialog(selectedSupplier, mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open the new Dialog for edit or adding
|
||||
* depending on the mode given
|
||||
* @param supplier the supplier entity for editing, null if adding
|
||||
* @param mode the mode the dialog should be in
|
||||
*/
|
||||
private void openDialog(Supplier supplier, String mode){
|
||||
//Get new view
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
try{
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"SupplierController.openDialog",
|
||||
e,
|
||||
"Loading supplier dialog in " + mode + " mode");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
SupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view
|
||||
dialogController.setMode(mode);
|
||||
|
||||
//Open the dialog depending on the mode
|
||||
if(mode.equals("Edit")){
|
||||
//Make it display suppliers details in dialog
|
||||
dialogController.displaySupplierDetails(supplier);
|
||||
}
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL); //make it modal
|
||||
if(mode.equals("Add")){
|
||||
dialogStage.setTitle("Add Supplier");
|
||||
}
|
||||
else {
|
||||
dialogStage.setTitle("Edit Supplier");
|
||||
}
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
//When dialog closes update table view and disable edit and delete buttons, and reset search bar
|
||||
displaySupplier();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
private Supplier mapToSupplier(SupplierResponse response) {
|
||||
String contactPerson = response.getSupContactFirstName() + " " + response.getSupContactLastName() != null ? response.getSupContactFirstName() + " " + response.getSupContactLastName() : "";
|
||||
String[] nameParts = contactPerson.split(" ", 2);
|
||||
String firstName = nameParts.length > 0 ? nameParts[0] : "";
|
||||
String lastName = nameParts.length > 1 ? nameParts[1] : "";
|
||||
|
||||
return new Supplier(
|
||||
response.getSupId().intValue(),
|
||||
response.getSupCompany(),
|
||||
firstName,
|
||||
lastName,
|
||||
response.getSupEmail(),
|
||||
response.getSupPhone()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.models.Adoption;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public class AdoptionDialogController {
|
||||
|
||||
//FXML elements
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@FXML
|
||||
private Button btnSave;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbAdoptionStatus;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbCustomer;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbPet;
|
||||
|
||||
@FXML
|
||||
private DatePicker dpAdoptionDate;
|
||||
|
||||
@FXML
|
||||
private Label lblAdoptionId;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
//Stores if the dialog view is in add/edit mode
|
||||
private String mode = null;
|
||||
|
||||
//Adoption statuses
|
||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||
"Pending", "Completed", "Cancelled"
|
||||
);
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
|
||||
cbAdoptionStatus.setItems(statusList);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
|
||||
Platform.runLater(() -> {
|
||||
if (pets != null) {
|
||||
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
|
||||
cbPet.setItems(petsObs);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionDialogController.initialize",
|
||||
e,
|
||||
"Loading pets for combo box");
|
||||
System.out.println("Error loading pets: " + e.getMessage());
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||
Platform.runLater(() -> {
|
||||
if (customers != null) {
|
||||
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
|
||||
cbCustomer.setItems(customersObs);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionDialogController.initialize",
|
||||
e,
|
||||
"Loading customers for combo box");
|
||||
System.out.println("Error loading customers: " + e.getMessage());
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
buttonSaveClicked(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
String errorMsg = "";
|
||||
|
||||
if (cbPet.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Pet is required.\n";
|
||||
}
|
||||
|
||||
if (cbCustomer.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Customer is required.\n";
|
||||
}
|
||||
|
||||
if (dpAdoptionDate.getValue() == null) {
|
||||
errorMsg += "Adoption Date is required.\n";
|
||||
}
|
||||
|
||||
if (cbAdoptionStatus.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Status is required.\n";
|
||||
}
|
||||
|
||||
if (errorMsg.isEmpty()) {
|
||||
try {
|
||||
AdoptionRequest request = new AdoptionRequest();
|
||||
request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId());
|
||||
request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId());
|
||||
request.setAdoptionDate(dpAdoptionDate.getValue());
|
||||
request.setAdoptionStatus(cbAdoptionStatus.getValue());
|
||||
|
||||
if (mode.equals("Add")) {
|
||||
AdoptionApi.getInstance().createAdoption(request);
|
||||
} else {
|
||||
String[] parts = lblAdoptionId.getText().split(": ");
|
||||
if (parts.length < 2) {
|
||||
throw new IllegalStateException("Invalid adoption ID format");
|
||||
}
|
||||
Long adoptionId = Long.parseLong(parts[1]);
|
||||
AdoptionApi.getInstance().updateAdoption(adoptionId, request);
|
||||
}
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Saved");
|
||||
alert.setContentText(mode + " succeeded");
|
||||
alert.showAndWait();
|
||||
closeStage(mouseEvent);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionDialogController.buttonSaveClicked",
|
||||
e,
|
||||
mode + " adoption");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Database Operation Error");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
} else {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Input Error");
|
||||
alert.setContentText(errorMsg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
public void displayAdoptionDetails(Adoption adoption) {
|
||||
if (adoption != null) {
|
||||
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
|
||||
|
||||
for (DropdownOption pet : cbPet.getItems()) {
|
||||
if (pet.getLabel().equals(adoption.getPetName())) {
|
||||
cbPet.getSelectionModel().select(pet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (DropdownOption customer : cbCustomer.getItems()) {
|
||||
if (customer.getLabel().equals(adoption.getCustomerName())) {
|
||||
cbCustomer.getSelectionModel().select(customer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
|
||||
try {
|
||||
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionDialogController.displayAdoptionDetails",
|
||||
e,
|
||||
"Parsing adoption date");
|
||||
}
|
||||
}
|
||||
|
||||
for (String status : cbAdoptionStatus.getItems()) {
|
||||
if (status.equals(adoption.getAdoptionStatus())) {
|
||||
cbAdoptionStatus.getSelectionModel().select(status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Adoption");
|
||||
lblAdoptionId.setVisible(mode.equals("Edit"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.scene.control.ListCell;
|
||||
|
||||
import org.example.petshopdesktop.DTOs.AppointmentDTO;
|
||||
import org.example.petshopdesktop.api.dto.appointment.AppointmentRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentDialogController {
|
||||
|
||||
// ============================
|
||||
// FXML
|
||||
// ============================
|
||||
|
||||
@FXML private Button btnCancel;
|
||||
@FXML private Button btnSave;
|
||||
|
||||
@FXML private ComboBox<DropdownOption> cbService;
|
||||
@FXML private ComboBox<DropdownOption> cbCustomer;
|
||||
@FXML private ComboBox<DropdownOption> cbPet;
|
||||
|
||||
@FXML private ComboBox<Integer> cbHour;
|
||||
@FXML private ComboBox<Integer> cbMinute;
|
||||
|
||||
@FXML private ComboBox<String> cbAppointmentStatus;
|
||||
@FXML private DatePicker dpAppointmentDate;
|
||||
|
||||
@FXML private Label lblAppointmentId;
|
||||
@FXML private Label lblMode;
|
||||
|
||||
// ============================
|
||||
// DATA
|
||||
// ============================
|
||||
|
||||
private String mode = null; // Add | Edit
|
||||
private AppointmentDTO selectedAppointment = null;
|
||||
|
||||
private ObservableList<String> statusList =
|
||||
FXCollections.observableArrayList(
|
||||
"Booked", "Completed", "Cancelled"
|
||||
);
|
||||
|
||||
//
|
||||
// MODE
|
||||
//
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Appointment");
|
||||
lblAppointmentId.setVisible(!mode.equals("Add"));
|
||||
}
|
||||
|
||||
//
|
||||
// INITIALIZE
|
||||
//
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> services = DropdownApi.getInstance().getServices();
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (services != null) {
|
||||
cbService.setItems(FXCollections.observableArrayList(services));
|
||||
}
|
||||
if (customers != null) {
|
||||
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||
}
|
||||
if (pets != null) {
|
||||
cbPet.setItems(FXCollections.observableArrayList(pets));
|
||||
}
|
||||
syncSelectedAppointment();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.initialize",
|
||||
e,
|
||||
"Loading combo box data for services, customers, and pets");
|
||||
e.printStackTrace();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
cbAppointmentStatus.setItems(statusList);
|
||||
|
||||
// Hours 9 AM - 5 PM
|
||||
for (int i = 9; i <= 17; i++) {
|
||||
cbHour.getItems().add(i);
|
||||
}
|
||||
|
||||
cbMinute.getItems().addAll(0, 15, 30, 45);
|
||||
|
||||
// Show dropdown labels
|
||||
cbService.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
cbService.setButtonCell(new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
cbCustomer.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
cbCustomer.setButtonCell(new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
cbPet.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
cbPet.setButtonCell(new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
btnSave.setOnMouseClicked(this::buttonSaveClicked);
|
||||
btnCancel.setOnMouseClicked(this::closeStage);
|
||||
}
|
||||
|
||||
//
|
||||
// DISPLAY FOR EDIT
|
||||
//
|
||||
|
||||
public void displayAppointmentDetails(AppointmentDTO appt) {
|
||||
|
||||
selectedAppointment = appt;
|
||||
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
|
||||
|
||||
try {
|
||||
dpAppointmentDate.setValue(
|
||||
java.time.LocalDate.parse(appt.getAppointmentDate())
|
||||
);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.displayAppointmentDetails",
|
||||
e,
|
||||
"Parsing appointment date");
|
||||
}
|
||||
|
||||
cbAppointmentStatus.setValue(appt.getAppointmentStatus());
|
||||
|
||||
try {
|
||||
LocalTime time = LocalTime.parse(appt.getAppointmentTime());
|
||||
cbHour.setValue(time.getHour());
|
||||
cbMinute.setValue(time.getMinute());
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.displayAppointmentDetails",
|
||||
e,
|
||||
"Parsing appointment time");
|
||||
}
|
||||
|
||||
cbService.getItems().forEach(s -> {
|
||||
if (s.getId() != null && s.getId().longValue() == appt.getServiceId()) cbService.setValue(s);
|
||||
});
|
||||
|
||||
cbCustomer.getItems().forEach(c -> {
|
||||
if (c.getId() != null && c.getId().longValue() == appt.getCustomerId()) cbCustomer.setValue(c);
|
||||
});
|
||||
|
||||
cbPet.getItems().forEach(p -> {
|
||||
if (p.getId() != null && p.getId().longValue() == appt.getPetId()) cbPet.setValue(p);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// SAVE
|
||||
//
|
||||
|
||||
private void buttonSaveClicked(MouseEvent e) {
|
||||
|
||||
if (cbService.getValue() == null ||
|
||||
cbCustomer.getValue() == null ||
|
||||
cbPet.getValue() == null ||
|
||||
dpAppointmentDate.getValue() == null ||
|
||||
cbHour.getValue() == null ||
|
||||
cbMinute.getValue() == null ||
|
||||
cbAppointmentStatus.getValue() == null) {
|
||||
|
||||
showError("All fields are required");
|
||||
return;
|
||||
}
|
||||
|
||||
LocalTime appointmentTime = LocalTime.of(cbHour.getValue(), cbMinute.getValue());
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
if (storeId == null || storeId <= 0) {
|
||||
showError("Store is not set for this account");
|
||||
return;
|
||||
}
|
||||
|
||||
AppointmentRequest request = new AppointmentRequest();
|
||||
request.setPetIds(Collections.singletonList(cbPet.getValue().getId()));
|
||||
request.setCustomerId(cbCustomer.getValue().getId());
|
||||
request.setStoreId(storeId);
|
||||
request.setServiceId(cbService.getValue().getId());
|
||||
request.setAppointmentDate(dpAppointmentDate.getValue());
|
||||
request.setAppointmentTime(appointmentTime);
|
||||
request.setAppointmentStatus(cbAppointmentStatus.getValue());
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (mode.equals("Add")) {
|
||||
AppointmentApi.getInstance().createAppointment(request);
|
||||
} else {
|
||||
AppointmentApi.getInstance().updateAppointment(
|
||||
(long) selectedAppointment.getAppointmentId(),
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
Platform.runLater(() -> closeStage(e));
|
||||
|
||||
} catch (Exception ex) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.buttonSaveClicked",
|
||||
ex,
|
||||
"Saving appointment in " + mode + " mode");
|
||||
ex.printStackTrace();
|
||||
showError("Error saving appointment: " + ex.getMessage());
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
//
|
||||
// UTIL
|
||||
//
|
||||
|
||||
private void closeStage(MouseEvent e) {
|
||||
Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
private void showError(String msg) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Input Error");
|
||||
alert.setContentText(msg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void syncSelectedAppointment() {
|
||||
if (selectedAppointment != null) {
|
||||
displayAppointmentDetails(selectedAppointment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
import org.example.petshopdesktop.models.Inventory;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.inventory.InventoryRequest;
|
||||
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.InventoryApi;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductApi;
|
||||
import org.example.petshopdesktop.models.Product;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InventoryDialogController {
|
||||
|
||||
//FXML elements
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@FXML
|
||||
private Button btnSave;
|
||||
|
||||
@FXML
|
||||
private ComboBox<Product> cbProduct;
|
||||
|
||||
@FXML
|
||||
private Label lblInventoryId;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
@FXML
|
||||
private TextField txtQuantity;
|
||||
|
||||
//Determines if the mode is add or edit
|
||||
private String mode = null;
|
||||
|
||||
//Loads upon .FXML boot
|
||||
@FXML
|
||||
void initialize() {
|
||||
cbProduct.setConverter(new StringConverter<Product>() {
|
||||
|
||||
//Displays product in combobox (prodID + name)
|
||||
@Override
|
||||
public String toString(Product product) {
|
||||
return product == null ? "" : product.getProdId() + ": " + product.getProdName();
|
||||
}
|
||||
|
||||
//Not needed
|
||||
@Override
|
||||
public Product fromString(String string) { return null; }
|
||||
});
|
||||
|
||||
//Load product list from API into combobox
|
||||
try {
|
||||
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
|
||||
if (productResponses != null) {
|
||||
ObservableList<Product> products = FXCollections.observableArrayList();
|
||||
for (ProductResponse pr : productResponses) {
|
||||
products.add(new Product(
|
||||
pr.getProdId().intValue(),
|
||||
pr.getProdName(),
|
||||
pr.getProdPrice().doubleValue(),
|
||||
0,
|
||||
pr.getProdDesc()
|
||||
));
|
||||
}
|
||||
cbProduct.setItems(products);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"InventoryDialogController.initialize",
|
||||
e,
|
||||
"Loading products for combo box");
|
||||
System.out.println("Error loading products: " + e.getMessage());
|
||||
}
|
||||
|
||||
//Save button handler
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
buttonSaveClicked(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
//Cancel button handler
|
||||
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Handles save button click event
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
int numRow = 0;
|
||||
String errorMsg = "";
|
||||
|
||||
if (cbProduct.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Product is required.\n";
|
||||
}
|
||||
|
||||
//Validate inputs
|
||||
errorMsg += Validator.isPresent(txtQuantity.getText(), "Quantity");
|
||||
errorMsg += Validator.isLessThanVarChars(txtQuantity.getText(), "Quantity", 11);
|
||||
errorMsg += Validator.isNonNegativeInteger(txtQuantity.getText(), "Quantity");
|
||||
|
||||
//Operation only occurs if there are no errors
|
||||
if (errorMsg.isEmpty()) {
|
||||
try {
|
||||
InventoryRequest request = new InventoryRequest();
|
||||
Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem();
|
||||
request.setProdId((long) selectedProduct.getProdId());
|
||||
int quantity;
|
||||
try {
|
||||
quantity = Integer.parseInt(txtQuantity.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid quantity format");
|
||||
}
|
||||
request.setQuantity(quantity);
|
||||
|
||||
if (mode.equals("Add")) {
|
||||
InventoryApi.getInstance().createInventory(request);
|
||||
} else {
|
||||
String[] parts = lblInventoryId.getText().split(": ");
|
||||
if (parts.length < 2) {
|
||||
throw new IllegalStateException("Invalid inventory ID format");
|
||||
}
|
||||
Long inventoryId = Long.parseLong(parts[1]);
|
||||
InventoryApi.getInstance().updateInventory(inventoryId, request);
|
||||
}
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Saved");
|
||||
alert.setContentText(mode + " succeeded");
|
||||
alert.showAndWait();
|
||||
closeStage(mouseEvent);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"InventoryDialogController.buttonSaveClicked",
|
||||
e,
|
||||
mode + " inventory");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Database Operation Error");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
//Displays validation errors
|
||||
else {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Input Error");
|
||||
alert.setContentText(errorMsg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
//Close dialog view
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
//Editing
|
||||
//Displays fields with existing inventory data
|
||||
public void displayInventoryDetails(Inventory inventory) {
|
||||
if (inventory != null) {
|
||||
|
||||
//Displays inventory ID
|
||||
lblInventoryId.setText("ID: " + inventory.getInventoryId());
|
||||
|
||||
//Selecting matching product in combobox
|
||||
for (Product product : cbProduct.getItems()) {
|
||||
if (product.getProdId() == inventory.getProdId()) {
|
||||
cbProduct.getSelectionModel().select(product);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
txtQuantity.setText(String.valueOf(inventory.getQuantity()));
|
||||
}
|
||||
}
|
||||
|
||||
//Sets dialog view to add/edit. Updates UI labels
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Inventory");
|
||||
lblInventoryId.setVisible(mode.equals("Edit"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetRequest;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.PetApi;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
public class PetDialogController {
|
||||
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@FXML
|
||||
private Button btnSave;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbPetStatus;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
@FXML
|
||||
private Label lblPetId;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetAge;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetBreed;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetName;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetPrice;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetSpecies;
|
||||
|
||||
private String mode = null;
|
||||
|
||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||
"Available", "Adopted"
|
||||
);
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
|
||||
cbPetStatus.setItems(statusList); //set status combobox
|
||||
|
||||
//Set up mouse handlers for buttons
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
buttonSaveClicked(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
String errorMsg = "";
|
||||
|
||||
//Check validation (input required)
|
||||
errorMsg += Validator.isPresent(txtPetName.getText(), "Pet Name");
|
||||
errorMsg += Validator.isPresent(txtPetAge.getText(), "Age");
|
||||
errorMsg += Validator.isPresent(txtPetBreed.getText(), "Breed");
|
||||
errorMsg += Validator.isPresent(txtPetSpecies.getText(), "Species");
|
||||
errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price");
|
||||
if (cbPetStatus.getSelectionModel().getSelectedItem() == null){
|
||||
errorMsg += "Status is required";
|
||||
}
|
||||
|
||||
//Check validation (length size)
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetSpecies.getText(), "Species", 50);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetBreed.getText(), "Breed", 50);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11);
|
||||
|
||||
//Check validation (format)
|
||||
errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price");
|
||||
errorMsg += Validator.isNonNegativeInteger(txtPetAge.getText(), "Age");
|
||||
|
||||
if(errorMsg.isEmpty()){
|
||||
PetRequest request = buildPetRequest();
|
||||
try {
|
||||
if(mode.equals("Add")) {
|
||||
PetApi.getInstance().createPet(request);
|
||||
} else {
|
||||
String[] parts = lblPetId.getText().split(": ");
|
||||
if (parts.length < 2) {
|
||||
throw new IllegalStateException("Invalid pet ID format");
|
||||
}
|
||||
Long petId = Long.parseLong(parts[1]);
|
||||
PetApi.getInstance().updatePet(petId, request);
|
||||
}
|
||||
|
||||
//tell the user operation was successful
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Saved");
|
||||
alert.setContentText(mode + " succeeded");
|
||||
alert.showAndWait();
|
||||
closeStage(mouseEvent);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetDialogController.buttonSaveClicked",
|
||||
e,
|
||||
mode + " pet record");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Operation Error");
|
||||
alert.setContentText(mode + " failed: " + e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
else{
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Input Error");
|
||||
alert.setContentText(errorMsg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
private PetRequest buildPetRequest() {
|
||||
PetRequest request = new PetRequest();
|
||||
request.setPetName(txtPetName.getText());
|
||||
request.setPetSpecies(txtPetSpecies.getText());
|
||||
request.setPetBreed(txtPetBreed.getText());
|
||||
request.setPetStatus(cbPetStatus.getValue());
|
||||
try {
|
||||
request.setPetPrice(new BigDecimal(txtPetPrice.getText()));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid price format");
|
||||
}
|
||||
|
||||
int age;
|
||||
try {
|
||||
age = Integer.parseInt(txtPetAge.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid age format");
|
||||
}
|
||||
request.setPetAge(age);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
public void displayPetDetails(Pet pet){
|
||||
if (pet!=null){
|
||||
lblPetId.setText("ID: " + pet.getPetId());
|
||||
txtPetName.setText(pet.getPetName());
|
||||
txtPetSpecies.setText(pet.getPetSpecies());
|
||||
txtPetBreed.setText(pet.getPetBreed());
|
||||
txtPetAge.setText(pet.getPetAge() + "");
|
||||
txtPetPrice.setText(pet.getPetPrice() + "");
|
||||
|
||||
//get the right combobox selection
|
||||
for (String status : cbPetStatus.getItems()) {
|
||||
if(status.equals(pet.getPetStatus())){
|
||||
cbPetStatus.getSelectionModel().select(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Pet");
|
||||
if(mode.equals("Add")) {
|
||||
lblPetId.setVisible(false);
|
||||
}
|
||||
else if(mode.equals("Edit")) {
|
||||
lblPetId.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.DTOs.ProductDTO;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductRequest;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductApi;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class ProductDialogController {
|
||||
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@FXML
|
||||
private Button btnSave;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbProdCategory;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
@FXML
|
||||
private Label lblProdId;
|
||||
|
||||
@FXML
|
||||
private TextField txtProdDesc;
|
||||
|
||||
@FXML
|
||||
private TextField txtProdName;
|
||||
|
||||
@FXML
|
||||
private TextField txtProdPrice;
|
||||
|
||||
private String mode = null;
|
||||
|
||||
/**
|
||||
* Add event listeners to buttons when dialog loads
|
||||
*/
|
||||
@FXML
|
||||
void initialize() {
|
||||
//Set up mouse handlers for buttons
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
buttonSaveClicked(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
//Set up combobox for selecting category
|
||||
try {
|
||||
List<DropdownOption> categories = DropdownApi.getInstance().getCategories();
|
||||
if (categories != null) {
|
||||
ObservableList<DropdownOption> categoriesObs = FXCollections.observableArrayList(categories);
|
||||
cbProdCategory.setItems(categoriesObs);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductDialogController.initialize",
|
||||
e,
|
||||
"Loading categories for combo box");
|
||||
System.out.println("Error loading categories: " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the inputs, then add or update the database depending
|
||||
* on the mode
|
||||
* @param mouseEvent click event for save button
|
||||
*/
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
int numRow = 0; //how many rows affected
|
||||
String errorMsg = ""; //error message for validation
|
||||
|
||||
//Check Validation (input required)
|
||||
errorMsg += Validator.isPresent(txtProdName.getText(), "Product Name");
|
||||
errorMsg += Validator.isPresent(txtProdPrice.getText(), "Product Price");
|
||||
if (cbProdCategory.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Category is required \n";
|
||||
}
|
||||
|
||||
//Check validation (length size)
|
||||
errorMsg += Validator.isLessThanVarChars(txtProdName.getText(), "Product Name", 100);
|
||||
errorMsg += Validator.isLessThanVarChars(txtProdDesc.getText(), "Description", 100);
|
||||
errorMsg += Validator.isLessThanVarChars(txtProdPrice.getText(), "Product Price", 12);
|
||||
|
||||
//Check Validation (format)
|
||||
errorMsg += Validator.isNonNegativeDouble(txtProdPrice.getText(), "Product Price");
|
||||
|
||||
if (errorMsg.isEmpty()) {
|
||||
try {
|
||||
ProductRequest request = new ProductRequest();
|
||||
request.setProdName(txtProdName.getText());
|
||||
BigDecimal price;
|
||||
try {
|
||||
price = new BigDecimal(txtProdPrice.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid price format");
|
||||
}
|
||||
request.setProdPrice(price);
|
||||
request.setCategoryId(cbProdCategory.getSelectionModel().getSelectedItem().getId());
|
||||
request.setProdDesc(txtProdDesc.getText());
|
||||
|
||||
if (mode.equals("Add")) {
|
||||
ProductApi.getInstance().createProduct(request);
|
||||
} else {
|
||||
String[] parts = lblProdId.getText().split(": ");
|
||||
if (parts.length < 2) {
|
||||
throw new IllegalStateException("Invalid product ID format");
|
||||
}
|
||||
Long productId = Long.parseLong(parts[1]);
|
||||
ProductApi.getInstance().updateProduct(productId, request);
|
||||
}
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Saved");
|
||||
alert.setContentText(mode + " succeeded");
|
||||
alert.showAndWait();
|
||||
closeStage(mouseEvent);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductDialogController.buttonSaveClicked",
|
||||
e,
|
||||
mode + " product");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Database Operation Error");
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
else{ //Display validation errors
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Input Error");
|
||||
alert.setContentText(errorMsg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the product data in text fields and combobox
|
||||
* @param product the product entity containing data to display
|
||||
*/
|
||||
public void displayProductDetails(ProductDTO product){
|
||||
if (product!=null){
|
||||
lblProdId.setText("ID: " + product.getProdId());
|
||||
txtProdName.setText(product.getProdName());
|
||||
txtProdDesc.setText(product.getProdDesc());
|
||||
txtProdPrice.setText(product.getProdPrice() + "");
|
||||
|
||||
for (DropdownOption category : cbProdCategory.getItems()) {
|
||||
if(category.getLabel().equals(product.getCategoryName())){
|
||||
cbProdCategory.getSelectionModel().select(category);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the window
|
||||
* @param mouseEvent mouse event to close
|
||||
*/
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode of the dialog
|
||||
* @param mode the mode to for the dialog
|
||||
*/
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Product");
|
||||
if(mode.equals("Add")) {
|
||||
lblProdId.setVisible(false);
|
||||
}
|
||||
else if(mode.equals("Edit")) {
|
||||
lblProdId.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierRequest;
|
||||
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductSupplierApi;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ProductSupplierDialogController {
|
||||
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@FXML
|
||||
private Button btnSave;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbProduct;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbSupplier;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
@FXML
|
||||
private Label lblProductSupplierId;
|
||||
|
||||
@FXML
|
||||
private TextField txtCost;
|
||||
|
||||
private String mode = null;
|
||||
private int selectedSupId = -1;
|
||||
private int selectedProdId = -1;
|
||||
|
||||
/**
|
||||
* add event listeners to buttons and set up combobox
|
||||
*/
|
||||
@FXML
|
||||
void initialize() { //Set up mouse handlers for buttons
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
buttonSaveClicked(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
cbSupplier.setButtonCell(new ListCell<DropdownOption>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getLabel());
|
||||
}
|
||||
}
|
||||
});
|
||||
cbSupplier.setCellFactory(lv -> new ListCell<DropdownOption>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getLabel());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cbProduct.setButtonCell(new ListCell<DropdownOption>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getLabel());
|
||||
}
|
||||
}
|
||||
});
|
||||
cbProduct.setCellFactory(lv -> new ListCell<DropdownOption>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getLabel());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
var suppliers = DropdownApi.getInstance().getSuppliers();
|
||||
var products = DropdownApi.getInstance().getProducts();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (suppliers != null) {
|
||||
cbSupplier.setItems(FXCollections.observableArrayList(suppliers));
|
||||
}
|
||||
if (products != null) {
|
||||
cbProduct.setItems(FXCollections.observableArrayList(products));
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductSupplierDialogController.initialize",
|
||||
e,
|
||||
"Loading suppliers and products for combo boxes");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Initialization Error");
|
||||
alert.setContentText("Failed to load dropdown data: " + e.getMessage());
|
||||
alert.showAndWait();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the inputs, then add or update the database depending
|
||||
* on the mode
|
||||
* @param mouseEvent click event for save button
|
||||
*/
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
String errorMsg = "";
|
||||
|
||||
errorMsg += Validator.isPresent(txtCost.getText(), "Cost");
|
||||
if (cbProduct.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Product is required \n";
|
||||
}
|
||||
if (cbSupplier.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Supplier is required \n";
|
||||
}
|
||||
|
||||
errorMsg += Validator.isLessThanVarChars(txtCost.getText(), "Cost", 12);
|
||||
errorMsg += Validator.isNonNegativeDouble(txtCost.getText(), "Cost");
|
||||
|
||||
if(errorMsg.isEmpty()){
|
||||
ProductSupplierRequest request = collectProductSupplierRequest();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (mode.equals("Add")) {
|
||||
ProductSupplierApi.getInstance().createProductSupplier(request);
|
||||
} else {
|
||||
ProductSupplierApi.getInstance().updateProductSupplier((long) selectedProdId, (long) selectedSupId, request);
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setHeaderText("Saved");
|
||||
alert.setContentText(mode + " succeeded");
|
||||
alert.showAndWait();
|
||||
closeStage(mouseEvent);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ProductSupplierDialogController.buttonSaveClicked",
|
||||
e,
|
||||
mode + " product-supplier");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Database Operation Error");
|
||||
alert.setContentText(mode + " failed: " + e.getMessage());
|
||||
alert.showAndWait();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Input Error");
|
||||
alert.setContentText(errorMsg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* collect the data for new/updated productSupplier
|
||||
* @return productSupplier request with data
|
||||
*/
|
||||
private ProductSupplierRequest collectProductSupplierRequest() {
|
||||
ProductSupplierRequest request = new ProductSupplierRequest();
|
||||
request.setSupplierId(cbSupplier.getSelectionModel().getSelectedItem().getId());
|
||||
request.setProductId(cbProduct.getSelectionModel().getSelectedItem().getId());
|
||||
try {
|
||||
request.setCost(new BigDecimal(txtCost.getText()));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid cost format");
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the productsupplier data in text fields and combobox
|
||||
* @param productSupplier
|
||||
*/
|
||||
public void displayProductSupplierDetails(ProductSupplierDTO productSupplier){
|
||||
if(productSupplier != null){
|
||||
txtCost.setText(productSupplier.getCost() + "");
|
||||
}
|
||||
|
||||
for (DropdownOption product : cbProduct.getItems()) {
|
||||
if(product.getId() == productSupplier.getProdId()){
|
||||
cbProduct.getSelectionModel().select(product);
|
||||
}
|
||||
}
|
||||
|
||||
for (DropdownOption supplier : cbSupplier.getItems()) {
|
||||
if (supplier.getId() == productSupplier.getSupId()) {
|
||||
cbSupplier.getSelectionModel().select(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the window
|
||||
* @param mouseEvent mouse event to close
|
||||
*/
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode of the dialog
|
||||
* @param mode the mode for the dialog
|
||||
*/
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Product");
|
||||
lblProductSupplierId.setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the current supplier and productId for (needed for update since compound primary key)
|
||||
* @param supId supplier id
|
||||
* @param prodId product id
|
||||
*/
|
||||
public void setSelectedIds(int supId, int prodId){
|
||||
this.selectedSupId = supId;
|
||||
this.selectedProdId = prodId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RefundDialogController {
|
||||
|
||||
@FXML
|
||||
private TextField txtSaleId;
|
||||
|
||||
@FXML
|
||||
private Button btnLoadSale;
|
||||
|
||||
@FXML
|
||||
private Label lblSaleInfo;
|
||||
|
||||
@FXML
|
||||
private TableView<SaleItemResponse> tvOriginalItems;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleItemResponse, String> colOriginalProduct;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleItemResponse, Integer> colOriginalQuantity;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleItemResponse, BigDecimal> colOriginalUnitPrice;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SaleItemResponse, BigDecimal> colOriginalTotal;
|
||||
|
||||
@FXML
|
||||
private Button btnAddToRefund;
|
||||
|
||||
@FXML
|
||||
private TableView<RefundItem> tvRefundItems;
|
||||
|
||||
@FXML
|
||||
private TableColumn<RefundItem, String> colRefundProduct;
|
||||
|
||||
@FXML
|
||||
private TableColumn<RefundItem, Integer> colRefundQuantity;
|
||||
|
||||
@FXML
|
||||
private TableColumn<RefundItem, Double> colRefundUnitPrice;
|
||||
|
||||
@FXML
|
||||
private TableColumn<RefundItem, Double> colRefundTotal;
|
||||
|
||||
@FXML
|
||||
private Button btnRemoveFromRefund;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbPaymentMethod;
|
||||
|
||||
@FXML
|
||||
private Label lblRefundTotal;
|
||||
|
||||
@FXML
|
||||
private Button btnProcessRefund;
|
||||
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
private SaleResponse currentSale;
|
||||
private final List<SaleItemResponse> baseOriginalItems = new ArrayList<>();
|
||||
private final ObservableList<SaleItemResponse> originalItems = FXCollections.observableArrayList();
|
||||
private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
|
||||
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
setupTables();
|
||||
cbPaymentMethod.setItems(FXCollections.observableArrayList("Cash", "Card", "Debit"));
|
||||
cbPaymentMethod.getSelectionModel().selectFirst();
|
||||
updateRefundTotal();
|
||||
}
|
||||
|
||||
private void setupTables() {
|
||||
colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
|
||||
colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("lineTotal"));
|
||||
tvOriginalItems.setItems(originalItems);
|
||||
tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
|
||||
colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
|
||||
colRefundQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
colRefundUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||
colRefundTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||
tvRefundItems.setItems(refundItems);
|
||||
tvRefundItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnLoadSaleClicked(ActionEvent event) {
|
||||
loadSale();
|
||||
}
|
||||
|
||||
public void prefillSale(Long saleId) {
|
||||
if (saleId == null) {
|
||||
return;
|
||||
}
|
||||
txtSaleId.setText(String.valueOf(saleId));
|
||||
loadSale();
|
||||
}
|
||||
|
||||
private void loadSale() {
|
||||
String saleIdText = txtSaleId.getText().trim();
|
||||
if (saleIdText.isEmpty()) {
|
||||
showError("Load Sale", "Enter a transaction ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
Long saleId;
|
||||
try {
|
||||
saleId = Long.parseLong(saleIdText);
|
||||
} catch (NumberFormatException e) {
|
||||
showError("Load Sale", "Invalid transaction ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<SaleResponse> allSales = SaleApi.getInstance().listSales(0, 1000, null);
|
||||
currentSale = SaleApi.getInstance().getSale(saleId);
|
||||
if (Boolean.TRUE.equals(currentSale.getIsRefund())) {
|
||||
clearLoadedSale();
|
||||
showError("Load Sale", "Select an original sale, not a refund record.");
|
||||
return;
|
||||
}
|
||||
List<SaleResponse> previousRefunds = allSales.stream()
|
||||
.filter(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
|
||||
currentSale.getSaleDate().format(formatter),
|
||||
currentSale.getEmployeeName(),
|
||||
currency.format(currentSale.getTotalAmount()),
|
||||
currentSale.getPaymentMethod());
|
||||
lblSaleInfo.setText(saleInfo);
|
||||
|
||||
List<SaleItemResponse> refundableItems = buildRefundableItems(currentSale, previousRefunds);
|
||||
if (refundableItems.isEmpty()) {
|
||||
showError("Load Sale", "This sale has no remaining refundable items.");
|
||||
return;
|
||||
}
|
||||
|
||||
baseOriginalItems.clear();
|
||||
baseOriginalItems.addAll(copySaleItems(refundableItems));
|
||||
originalItems.setAll(copySaleItems(refundableItems));
|
||||
cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
|
||||
|
||||
refundItems.clear();
|
||||
updateOriginalItemAvailability();
|
||||
updateRefundTotal();
|
||||
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale");
|
||||
showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnAddToRefundClicked(ActionEvent event) {
|
||||
if (currentSale == null) {
|
||||
showError("Add to Refund", "Load a sale first.");
|
||||
return;
|
||||
}
|
||||
|
||||
SaleItemResponse selected = tvOriginalItems.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) {
|
||||
showError("Add to Refund", "Select an item from the original sale.");
|
||||
return;
|
||||
}
|
||||
|
||||
int alreadyRefunded = refundItems.stream()
|
||||
.filter(r -> r.getProdId() == selected.getProdId().intValue())
|
||||
.mapToInt(RefundItem::getQuantity)
|
||||
.sum();
|
||||
|
||||
int available = selected.getQuantity() - alreadyRefunded;
|
||||
if (available <= 0) {
|
||||
showError("Add to Refund", "All items of this product are already in the refund list.");
|
||||
return;
|
||||
}
|
||||
|
||||
TextInputDialog dialog = new TextInputDialog(String.valueOf(available));
|
||||
dialog.setTitle("Refund Quantity");
|
||||
dialog.setHeaderText("Product: " + selected.getProductName());
|
||||
dialog.setContentText("Enter quantity to refund (max " + available + "):");
|
||||
|
||||
Optional<String> result = dialog.showAndWait();
|
||||
if (result.isPresent()) {
|
||||
try {
|
||||
int quantity = Integer.parseInt(result.get().trim());
|
||||
if (quantity <= 0) {
|
||||
showError("Add to Refund", "Quantity must be at least 1.");
|
||||
return;
|
||||
}
|
||||
if (quantity > available) {
|
||||
showError("Add to Refund", "Cannot refund more than " + available + " items.");
|
||||
return;
|
||||
}
|
||||
|
||||
addOrMergeRefundItem(selected, quantity);
|
||||
updateOriginalItemAvailability();
|
||||
updateRefundTotal();
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
showError("Add to Refund", "Invalid quantity.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnRemoveFromRefundClicked(ActionEvent event) {
|
||||
RefundItem selected = tvRefundItems.getSelectionModel().getSelectedItem();
|
||||
if (selected != null) {
|
||||
refundItems.remove(selected);
|
||||
updateOriginalItemAvailability();
|
||||
updateRefundTotal();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnProcessRefundClicked(ActionEvent event) {
|
||||
if (currentSale == null) {
|
||||
showError("Process Refund", "Load a sale first.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (refundItems.isEmpty()) {
|
||||
showError("Process Refund", "Add at least one item to refund.");
|
||||
return;
|
||||
}
|
||||
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
if (storeId == null || storeId <= 0) {
|
||||
showError("Process Refund", "Store is not set for this account.");
|
||||
return;
|
||||
}
|
||||
|
||||
String payment = cbPaymentMethod.getSelectionModel().getSelectedItem();
|
||||
if (payment == null || payment.isBlank()) {
|
||||
showError("Process Refund", "Select a payment method.");
|
||||
return;
|
||||
}
|
||||
|
||||
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
confirm.setTitle("Confirm Refund");
|
||||
confirm.setHeaderText("Process refund for sale ID " + currentSale.getSaleId() + "?");
|
||||
confirm.setContentText("Refund amount: " + lblRefundTotal.getText());
|
||||
|
||||
Optional<ButtonType> confirmResult = confirm.showAndWait();
|
||||
if (confirmResult.isEmpty() || confirmResult.get() != ButtonType.OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SaleRequest request = new SaleRequest();
|
||||
request.setStoreId(storeId);
|
||||
request.setPaymentMethod(payment);
|
||||
request.setIsRefund(true);
|
||||
request.setOriginalSaleId(currentSale.getSaleId());
|
||||
|
||||
List<SaleItemRequest> items = new ArrayList<>();
|
||||
for (RefundItem item : refundItems) {
|
||||
SaleItemRequest saleItem = new SaleItemRequest();
|
||||
saleItem.setProdId((long) item.getProdId());
|
||||
saleItem.setQuantity(-item.getQuantity());
|
||||
items.add(saleItem);
|
||||
}
|
||||
request.setItems(items);
|
||||
|
||||
SaleResponse refundResponse = SaleApi.getInstance().createSale(request);
|
||||
|
||||
Alert success = new Alert(Alert.AlertType.INFORMATION);
|
||||
success.setTitle("Refund Processed");
|
||||
success.setHeaderText(null);
|
||||
success.setContentText("Refund ID " + refundResponse.getSaleId() + " was created successfully.");
|
||||
success.showAndWait();
|
||||
|
||||
closeDialog();
|
||||
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund");
|
||||
showError("Process Refund", e.getMessage() != null ? e.getMessage() : "Could not process refund.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnCancelClicked(ActionEvent event) {
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
private void clearLoadedSale() {
|
||||
currentSale = null;
|
||||
lblSaleInfo.setText("");
|
||||
baseOriginalItems.clear();
|
||||
originalItems.clear();
|
||||
refundItems.clear();
|
||||
updateRefundTotal();
|
||||
}
|
||||
|
||||
private void addOrMergeRefundItem(SaleItemResponse selected, int quantity) {
|
||||
for (int i = 0; i < refundItems.size(); i++) {
|
||||
RefundItem existing = refundItems.get(i);
|
||||
if (existing.getProdId() == selected.getProdId().intValue()) {
|
||||
refundItems.set(i, new RefundItem(
|
||||
existing.getProdId(),
|
||||
existing.getProductName(),
|
||||
existing.getQuantity() + quantity,
|
||||
existing.getUnitPrice()
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
refundItems.add(new RefundItem(
|
||||
selected.getProdId().intValue(),
|
||||
selected.getProductName(),
|
||||
quantity,
|
||||
selected.getUnitPrice().doubleValue()
|
||||
));
|
||||
}
|
||||
|
||||
private void updateOriginalItemAvailability() {
|
||||
if (currentSale == null) {
|
||||
baseOriginalItems.clear();
|
||||
originalItems.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, Integer> pendingRefunds = new HashMap<>();
|
||||
for (RefundItem refundItem : refundItems) {
|
||||
pendingRefunds.merge((long) refundItem.getProdId(), refundItem.getQuantity(), Integer::sum);
|
||||
}
|
||||
|
||||
List<SaleItemResponse> refreshedItems = new ArrayList<>();
|
||||
for (SaleItemResponse originalItem : baseOriginalItems) {
|
||||
SaleItemResponse refreshedItem = copySaleItem(originalItem);
|
||||
int pending = pendingRefunds.getOrDefault(refreshedItem.getProdId(), 0);
|
||||
refreshedItem.setQuantity(Math.max(0, refreshedItem.getQuantity() - pending));
|
||||
if (refreshedItem.getQuantity() > 0) {
|
||||
refreshedItems.add(refreshedItem);
|
||||
}
|
||||
}
|
||||
|
||||
originalItems.setAll(refreshedItems);
|
||||
tvOriginalItems.getSelectionModel().clearSelection();
|
||||
tvOriginalItems.refresh();
|
||||
tvRefundItems.refresh();
|
||||
}
|
||||
|
||||
private List<SaleItemResponse> copySaleItems(List<SaleItemResponse> items) {
|
||||
List<SaleItemResponse> copies = new ArrayList<>();
|
||||
for (SaleItemResponse item : items) {
|
||||
copies.add(copySaleItem(item));
|
||||
}
|
||||
return copies;
|
||||
}
|
||||
|
||||
private SaleItemResponse copySaleItem(SaleItemResponse source) {
|
||||
SaleItemResponse copy = new SaleItemResponse();
|
||||
copy.setSaleItemId(source.getSaleItemId());
|
||||
copy.setProdId(source.getProdId());
|
||||
copy.setProductName(source.getProductName());
|
||||
copy.setQuantity(source.getQuantity());
|
||||
copy.setUnitPrice(source.getUnitPrice());
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void updateRefundTotal() {
|
||||
double total = refundItems.stream().mapToDouble(RefundItem::getTotal).sum();
|
||||
lblRefundTotal.setText(currency.format(total));
|
||||
}
|
||||
|
||||
private void closeDialog() {
|
||||
Stage stage = (Stage) btnCancel.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
private List<SaleItemResponse> buildRefundableItems(SaleResponse sale, List<SaleResponse> previousRefunds) {
|
||||
Map<Long, Integer> refundedByProduct = new HashMap<>();
|
||||
for (SaleResponse refund : previousRefunds) {
|
||||
if (refund.getItems() == null) {
|
||||
continue;
|
||||
}
|
||||
for (SaleItemResponse refundItem : refund.getItems()) {
|
||||
if (refundItem.getProdId() == null || refundItem.getQuantity() == null) {
|
||||
continue;
|
||||
}
|
||||
refundedByProduct.merge(refundItem.getProdId(), Math.abs(refundItem.getQuantity()), Integer::sum);
|
||||
}
|
||||
}
|
||||
|
||||
List<SaleItemResponse> refundableItems = new ArrayList<>();
|
||||
if (sale.getItems() == null) {
|
||||
return refundableItems;
|
||||
}
|
||||
|
||||
for (SaleItemResponse originalItem : sale.getItems()) {
|
||||
if (originalItem.getProdId() == null || originalItem.getQuantity() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int remainingQuantity = originalItem.getQuantity() - refundedByProduct.getOrDefault(originalItem.getProdId(), 0);
|
||||
if (remainingQuantity <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SaleItemResponse refundableItem = new SaleItemResponse();
|
||||
refundableItem.setSaleItemId(originalItem.getSaleItemId());
|
||||
refundableItem.setProdId(originalItem.getProdId());
|
||||
refundableItem.setProductName(originalItem.getProductName());
|
||||
refundableItem.setQuantity(remainingQuantity);
|
||||
refundableItem.setUnitPrice(originalItem.getUnitPrice());
|
||||
refundableItems.add(refundableItem);
|
||||
}
|
||||
|
||||
return refundableItems;
|
||||
}
|
||||
|
||||
private void showError(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
public static class RefundItem {
|
||||
private final int prodId;
|
||||
private final String productName;
|
||||
private final int quantity;
|
||||
private final double unitPrice;
|
||||
|
||||
public RefundItem(int prodId, String productName, int quantity, double unitPrice) {
|
||||
this.prodId = prodId;
|
||||
this.productName = productName;
|
||||
this.quantity = quantity;
|
||||
this.unitPrice = unitPrice;
|
||||
}
|
||||
|
||||
public int getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public double getUnitPrice() {
|
||||
return unitPrice;
|
||||
}
|
||||
|
||||
public double getTotal() {
|
||||
return quantity * unitPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user