initial commit

This commit is contained in:
Armin Wolf 2024-03-03 13:08:48 +01:00
parent 7e8fb52862
commit 1a54ae9661
69 changed files with 4230 additions and 186 deletions

215
.gitignore vendored
View File

@ -1,190 +1,33 @@
# ---> Java
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# ---> Linux
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# ---> macOS
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# ---> Gradle
.gradle
**/build/
!src/**/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# ---> Maven
HELP.md
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
# Eclipse m2e generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1,18 @@
# 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.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

316
mvnw vendored Normal file
View 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
mvnw.cmd vendored Normal file
View 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%

120
pom.xml Normal file
View File

@ -0,0 +1,120 @@
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>de.arminwolf</groupId>
<artifactId>finance-analyzer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>finance-analyzer</name>
<description>App to Analyze Excel Exports from Finanzguru</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.13.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -0,0 +1,14 @@
package de.arminwolf.financeanalyzer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FinanceAnalyzerApplication {
public static void main(String[] args) {
SpringApplication.run(FinanceAnalyzerApplication.class, args);
}
}

View File

@ -0,0 +1,61 @@
package de.arminwolf.financeanalyzer.conf;
import java.io.Serializable;
import java.util.Objects;
public class BankAccount implements Serializable, Comparable<BankAccount> {
private String iban;
private String bankAccountName;
public BankAccount() {
}
public BankAccount(final String iban, final String bankAccountName) {
this.iban = iban;
this.bankAccountName = bankAccountName;
}
@Override
public int compareTo(final BankAccount o) {
return bankAccountName.compareTo(o.bankAccountName);
}
public String getIban() {
return iban;
}
public void setIban(final String iban) {
this.iban = iban;
}
public String getBankAccountName() {
return bankAccountName;
}
public void setBankAccountName(final String bankAccountName) {
this.bankAccountName = bankAccountName;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final BankAccount that = (BankAccount) o;
return Objects.equals(iban, that.iban) && Objects.equals(bankAccountName, that.bankAccountName);
}
@Override
public int hashCode() {
return Objects.hash(iban, bankAccountName);
}
}

View File

@ -0,0 +1,36 @@
package de.arminwolf.financeanalyzer.conf;
import java.util.HashSet;
import java.util.Set;
public class Configuration {
private Set<BankAccount> bankAccounts;
private int numberOfMonths;
public Configuration() {
this.numberOfMonths = 1;
this.bankAccounts = new HashSet<>();
}
public Set<BankAccount> getBankAccounts() {
return bankAccounts;
}
public void setBankAccounts(final Set<BankAccount> bankAccounts) {
this.bankAccounts = bankAccounts;
}
public int getNumberOfMonths() {
return numberOfMonths;
}
public void setNumberOfMonths(final int numberOfMonths) {
this.numberOfMonths = numberOfMonths;
}
}

View File

@ -0,0 +1,15 @@
package de.arminwolf.financeanalyzer.conf;
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfiguration {
@Bean
public LayoutDialect layoutDialect() {
return new LayoutDialect();
}
}

View File

@ -0,0 +1,111 @@
package de.arminwolf.financeanalyzer.controller;
import de.arminwolf.financeanalyzer.conf.BankAccount;
import de.arminwolf.financeanalyzer.conf.Configuration;
import de.arminwolf.financeanalyzer.service.CacheService;
import de.arminwolf.financeanalyzer.service.ConverterService;
import de.arminwolf.financeanalyzer.service.FileService;
import de.arminwolf.financeanalyzer.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@Controller
@RequestMapping("/data")
public class ConverterController {
@Autowired
ConverterService service;
@Autowired
CacheService cacheService;
@Autowired
FileService fileService;
@PostMapping("/convert")
public String convert(RedirectAttributes redirectAttributes, @RequestParam("file") MultipartFile file) {
try (InputStream in = file.getInputStream()) {
Optional<String> json = service.convert(in);
if (json.isPresent()) {
File workingDirectory = fileService.getOrCreateWorkingDirectory();
Path tempFile = Files.createFile(Paths.get(workingDirectory.getPath(), "finance-analyzer.json"));
Path path = Files.writeString(tempFile, json.get(), StandardCharsets.UTF_8);
cacheService.put("currentJsonFile", path.toAbsolutePath().toFile().toString());
Configuration configuration = new Configuration();
Set<BankAccount> bankAccounts =
Arrays.stream(fileService.getJsonFile())
.map(t -> new BankAccount(t.getReferenzkonto(), t.getNameReferenzkonto()))
.collect(Collectors.toSet());
configuration.setBankAccounts(bankAccounts);
fileService.saveConfiguration(configuration);
redirectAttributes.addFlashAttribute("result", "Converted successfully!");
return "redirect:/data/success";
} else {
redirectAttributes.addFlashAttribute("result", "Error while converting");
return "redirect:/data/error";
}
} catch (IOException e) {
redirectAttributes.addFlashAttribute("result", "Error while converting");
return "redirect:/data/error";
}
}
@PostMapping("/load")
public String convert(RedirectAttributes redirectAttributes,
@RequestParam("account") String account,
@RequestParam("month") String month) {
redirectAttributes.addFlashAttribute("result", "Converted successfully!");
redirectAttributes.addFlashAttribute("account", account);
redirectAttributes.addFlashAttribute("month", month);
System.out.println("[ Load Method ] Account: " + account);
System.out.println("[ Load Method ] Month: " + month);
return "redirect:/data/success";
}
@GetMapping("/templates/error")
public String handleError(@ModelAttribute("result") String result, Model model) {
model.addAttribute("result", result);
return Constants.ERROR;
}
@GetMapping("/success")
public String handleSuccess(RedirectAttributes redirectAttributes,
@ModelAttribute("result") String result,
@ModelAttribute("account") String account,
@ModelAttribute("month") String month,
Model model) {
redirectAttributes.addFlashAttribute("result", "Converted successfully!");
redirectAttributes.addFlashAttribute("account", account);
redirectAttributes.addFlashAttribute("month", month);
redirectAttributes.addFlashAttribute("result", result);
return "redirect:/".concat(Constants.REPORTS).concat("/");
}
}

View File

@ -0,0 +1,31 @@
package de.arminwolf.financeanalyzer.controller;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
System.out.println(request.getRequestURL());
return "/error/404";
}
else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "/error/500";
}
}
return "/error";
}
}

View File

@ -0,0 +1,38 @@
package de.arminwolf.financeanalyzer.controller;
import de.arminwolf.financeanalyzer.service.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;
@RestController
@RequestMapping("/file")
public class FileController {
@Autowired
CacheService cacheService;
@RequestMapping(value = "/current", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<String> get() throws IOException {
if (Objects.nonNull(cacheService.get("currentJsonFile"))) {
String jsonFile = Files.readString(Paths.get(cacheService.get("currentJsonFile").toString()));
if (Objects.nonNull(jsonFile) && !jsonFile.isEmpty()) {
return ResponseEntity.ok(jsonFile);
}
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("No persistent JSON found. Upload a new one.");
}
}

View File

@ -0,0 +1,55 @@
package de.arminwolf.financeanalyzer.controller;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import de.arminwolf.financeanalyzer.util.UrlUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.util.Objects;
import java.util.Optional;
import static de.arminwolf.financeanalyzer.util.Constants.INDEX;
@Controller
public class MainController {
@GetMapping("/")
public String get(Model model, HttpServletRequest request) {
RestTemplate restTemplate = new RestTemplate();
try {
final Optional<TransactionDAO[]> response = getTransactions(request, restTemplate);
if (response.isEmpty()) {
return "redirect:/upload/";
} else {
// Verarbeiten Sie hier die Antwort
model.addAttribute("data", response.get());
return INDEX;
}
} catch (Exception e) {
e.printStackTrace(System.err);
return "redirect:/upload";
}
}
private static Optional<TransactionDAO[]> getTransactions(final HttpServletRequest request, final RestTemplate restTemplate) {
try {
TransactionDAO[] forObject = restTemplate.getForObject(UrlUtil.getRelativeUrl(request, "/file/current"), TransactionDAO[].class);
if (Objects.isNull(forObject)) {
return Optional.empty();
} else {
return Optional.of(forObject);
}
} catch (RestClientException e) {
return Optional.empty();
}
}
}

View File

@ -0,0 +1,57 @@
package de.arminwolf.financeanalyzer.controller;
import de.arminwolf.financeanalyzer.conf.Configuration;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import de.arminwolf.financeanalyzer.dao.ReportDAO;
import de.arminwolf.financeanalyzer.dao.TableDataDAO;
import de.arminwolf.financeanalyzer.service.CacheService;
import de.arminwolf.financeanalyzer.service.ReportService;
import de.arminwolf.financeanalyzer.util.Constants;
import de.arminwolf.financeanalyzer.util.DateUtil;
import de.arminwolf.financeanalyzer.util.UrlUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@Controller
@RequestMapping("/reports")
public class ReportController {
@Autowired
private ReportService reportService;
@GetMapping("/")
public String reports(@ModelAttribute("account") String iban,
@ModelAttribute("month") String month,
Model model, HttpServletRequest request) {
RestTemplate restTemplate = new RestTemplate();
if (Objects.isNull(iban) || iban.isEmpty()) {
return "redirect:/upload/";
}
try {
final String fileURL = UrlUtil.getRelativeUrl(request, "/file/current");
final TransactionDAO[] response = restTemplate.getForObject(fileURL, TransactionDAO[].class);
if (Objects.isNull(response)) {
return "redirect:/upload";
} else {
reportService.createReport(response, month, iban, model);
return Constants.REPORTS;
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
return "redirect:/upload/";
}
}
}

View File

@ -0,0 +1,38 @@
package de.arminwolf.financeanalyzer.controller;
import de.arminwolf.financeanalyzer.conf.Configuration;
import de.arminwolf.financeanalyzer.service.CacheService;
import de.arminwolf.financeanalyzer.service.FileService;
import de.arminwolf.financeanalyzer.service.ReportService;
import de.arminwolf.financeanalyzer.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap;
@Controller
@RequestMapping("/upload")
public class UploadController {
@Autowired
FileService fileService;
@Autowired
CacheService cacheService;
@GetMapping("/")
public String upload(Model model) {
final RedirectAttributes redirectAttributes = new RedirectAttributesModelMap();
fileService.getJsonFileName().ifPresent(jsonFileName -> {
cacheService.put("currentJsonFile", jsonFileName);
cacheService.put("configuration", fileService.getConfiguration());
model.addAttribute("foundJsonFile", jsonFileName);
model.addAttribute("accounts", ((Configuration) cacheService.get("configuration")).getBankAccounts());
});
return Constants.INDEX;
}
}

View File

@ -0,0 +1,151 @@
package de.arminwolf.financeanalyzer.dao;
import de.arminwolf.financeanalyzer.dao.charts.model.HighChartDAO;
import de.arminwolf.financeanalyzer.dao.charts.model.Series;
import de.arminwolf.financeanalyzer.dao.charts.factories.BubbleChartFactory;
import de.arminwolf.financeanalyzer.dao.charts.factories.ColumnChartFactory;
import de.arminwolf.financeanalyzer.dao.charts.factories.LineChartFactory;
import de.arminwolf.financeanalyzer.dao.model.OutputData;
import de.arminwolf.financeanalyzer.util.NumberUtil;
import de.arminwolf.financeanalyzer.util.OrderDateStringComparator;
import org.apache.commons.math3.util.Pair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class ReportDAO {
private HighChartDAO cashFlowChart;
private HighChartDAO accountBalanceLineChart;
private HighChartDAO transactionCategoriesBubbleChart;
private Set<OutputData> standingOutputData;
private Set<OutputData> notStandingOutputData;
private List<OutputData> incomeList;
public ReportDAO() {
this.standingOutputData = new HashSet<>();
this.notStandingOutputData = new HashSet<>();
this.incomeList = new ArrayList<>();
}
public Set<OutputData> getStandingOrders() {
return this.standingOutputData;
}
public List<OutputData> getSortedStandingOrders() {
return this.standingOutputData.stream().sorted(new OrderDateStringComparator()).collect(Collectors.toList());
}
public Float getStandingOrdersAmount() {
return NumberUtil.round(getStandingOrders().stream().map(OutputData::getAmountAsFloat).reduce(0f, Float::sum));
}
public Float getNotStandingOrdersAmount() {
return NumberUtil.round(getNotStandingOrders().stream().map(OutputData::getAmountAsFloat).reduce(0f, Float::sum));
}
public HighChartDAO getTransactionCategoriesBubbleChart() {
return transactionCategoriesBubbleChart;
}
public void setIncomeList(final List<TransactionDAO> incomes) {
this.incomeList = incomes.stream()
.map(t -> new OutputData(t.getVerwendungszweck(), t))
.sorted(new OrderDateStringComparator())
.collect(Collectors.toList());
}
public List<OutputData> getIncomeList() {
return incomeList;
}
public void setNotStandingOrders(final List<TransactionDAO> notStandingOrders) {
Set<OutputData> list = new HashSet<>();
notStandingOrders.forEach(order -> list.add(new OutputData(order)));
this.notStandingOutputData = list;
}
public final Set<OutputData> getNotStandingOrders() {
return notStandingOutputData;
}
public final List<OutputData> getSortedNotStandingOrders() {
return notStandingOutputData.stream().sorted(new OrderDateStringComparator()).collect(Collectors.toList());
}
public Float getTotalExpense() {
return getStandingOrdersAmount() + getNotStandingOrdersAmount();
}
public void setTransactionCategoriesBubbleChart(final List<TransactionDAO> transactions) {
this.transactionCategoriesBubbleChart = BubbleChartFactory.createBubbleChart("Kateogrien der Transaktionen", transactions);
}
public void setAccountBalanceLineChart(final List<Pair<String, String>> pairList) {
String title = "Verlauf des Kontostandes";
String chartTitle = "Kontostand in EUR";
List<Series> series = new ArrayList<>();
Series data = new Series();
data.setData(pairList.stream().map(e -> Float.parseFloat(e.getValue())).collect(Collectors.toList()));
series.add(data);
this.accountBalanceLineChart = LineChartFactory.createLineChart(title, chartTitle, pairList.stream().map(Pair::getKey).collect(Collectors.toList()), series);
}
public HighChartDAO getAccountBalanceLineChart() {
return accountBalanceLineChart;
}
public void setCashFlowChart(final Map<String, List<Float>> ausgaben, final Map<String, List<Float>> einnahmen) {
this.cashFlowChart =
ColumnChartFactory.createColumnChart("Cashflow",
"Einnahmen und Ausgaben",
ausgaben, "Ausgaben",
einnahmen, "Einnahmen");
}
public HighChartDAO getCashFlowChart() {
return cashFlowChart;
}
public Float getIncome() {
return this.incomeList.stream().map(OutputData::getAmountAsFloat).reduce(0f, Float::sum);
}
public Float getDiff() {
return NumberUtil.round(Math.abs(getIncome()) - Math.abs(getTotalExpense()));
}
public void setStandingOrders(final Collection<TransactionDAO> orders) {
Set<OutputData> list = new HashSet<>();
orders.forEach(order -> list.add(new OutputData(order)));
this.standingOutputData = list;
}
}

View File

@ -0,0 +1,37 @@
package de.arminwolf.financeanalyzer.dao;
import java.util.List;
public class TableDataDAO {
private List<String> headers;
private List<List<String>> rows;
public List<String> getHeaders() {
return headers;
}
public void setHeaders(final List<String> headers) {
this.headers = headers;
}
public List<List<String>> getRows() {
return rows;
}
public void setRows(final List<List<String>> rows) {
this.rows = rows;
}
public static class TableDataFactory {
public static TableDataDAO createTableDataDTO() {
return new TableDataDAO();
}
}
}

View File

@ -0,0 +1,555 @@
package de.arminwolf.financeanalyzer.dao;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"mandatsreferenz",
"analyse-vom-frei-verfuegbaren-einkommen-ausgeschlossen",
"analyse-vertrag",
"e-ref",
"analyse-hauptkategorie",
"analyse-umsatzart",
"analyse-woche",
"analyse-monat",
"analyse-quartal",
"kontostand",
"analyse-vertrags-id",
"waehrung",
"verwendungszweck",
"buchungstag",
"analyse-vertragsturnus",
"analyse-betrag",
"betrag",
"name-referenzkonto",
"beguenstigter/auftraggeber",
"analyse-umbuchung",
"iban-beguenstigter/auftraggeber",
"referenzkonto",
"analyse-unterkategorie",
"analyse-jahr",
"glaeubiger-id"
})
public class TransactionDAO {
@JsonProperty("mandatsreferenz")
private String mandatsreferenz;
@JsonProperty("analyse-vom-frei-verfuegbaren-einkommen-ausgeschlossen")
private String analyseVomFreiVerfuegbarenEinkommenAusgeschlossen;
@JsonProperty("analyse-vertrag")
private String analyseVertrag;
@JsonProperty("e-ref")
private String eRef;
@JsonProperty("analyse-hauptkategorie")
private String analyseHauptkategorie;
@JsonProperty("analyse-umsatzart")
private String analyseUmsatzart;
@JsonProperty("analyse-woche")
private String analyseWoche;
@JsonProperty("analyse-monat")
private String analyseMonat;
@JsonProperty("analyse-quartal")
private String analyseQuartal;
@JsonProperty("kontostand")
private String kontostand;
@JsonProperty("analyse-vertrags-id")
private String analyseVertragsId;
@JsonProperty("waehrung")
private String waehrung;
@JsonProperty("verwendungszweck")
private String verwendungszweck;
@JsonProperty("buchungstag")
private String buchungstag;
@JsonProperty("analyse-vertragsturnus")
private String analyseVertragsturnus;
@JsonProperty("analyse-betrag")
private String analyseBetrag;
@JsonProperty("betrag")
private String betrag;
@JsonProperty("name-referenzkonto")
private String nameReferenzkonto;
@JsonProperty("beguenstigter/auftraggeber")
private String beguenstigterAuftraggeber;
@JsonProperty("analyse-umbuchung")
private String analyseUmbuchung;
@JsonProperty("iban-beguenstigter/auftraggeber")
private String ibanBeguenstigterAuftraggeber;
@JsonProperty("referenzkonto")
private String referenzkonto;
@JsonProperty("analyse-unterkategorie")
private String analyseUnterkategorie;
@JsonProperty("analyse-jahr")
private String analyseJahr;
@JsonProperty("glaeubiger-id")
private String glaeubigerId;
@JsonIgnore
private final Map<String, Object> additionalProperties = new LinkedHashMap<String, Object>();
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
@JsonProperty("analyse-betrag")
public String getAnalyseBetrag() {
return analyseBetrag;
}
@JsonProperty("analyse-hauptkategorie")
public String getAnalyseHauptkategorie() {
return analyseHauptkategorie;
}
@JsonProperty("analyse-jahr")
public String getAnalyseJahr() {
return analyseJahr;
}
@JsonProperty("analyse-monat")
public String getAnalyseMonat() {
return analyseMonat;
}
@JsonProperty("analyse-quartal")
public String getAnalyseQuartal() {
return analyseQuartal;
}
@JsonProperty("analyse-umbuchung")
public String getAnalyseUmbuchung() {
return analyseUmbuchung;
}
@JsonProperty("analyse-umsatzart")
public String getAnalyseUmsatzart() {
return analyseUmsatzart;
}
@JsonProperty("analyse-unterkategorie")
public String getAnalyseUnterkategorie() {
return analyseUnterkategorie;
}
@JsonProperty("analyse-vertrag")
public String getAnalyseVertrag() {
return analyseVertrag;
}
@JsonProperty("analyse-vertrags-id")
public String getAnalyseVertragsId() {
return analyseVertragsId;
}
@JsonProperty("analyse-vertragsturnus")
public String getAnalyseVertragsturnus() {
return analyseVertragsturnus;
}
@JsonProperty("analyse-vom-frei-verfuegbaren-einkommen-ausgeschlossen")
public String getAnalyseVomFreiVerfuegbarenEinkommenAusgeschlossen() {
return analyseVomFreiVerfuegbarenEinkommenAusgeschlossen;
}
@JsonProperty("analyse-woche")
public String getAnalyseWoche() {
return analyseWoche;
}
@JsonProperty("beguenstigter/auftraggeber")
public String getBeguenstigterAuftraggeber() {
return beguenstigterAuftraggeber;
}
@JsonProperty("betrag")
public String getBetrag() {
return betrag;
}
public float getBetragAsFloat() {
return Float.parseFloat(betrag);
}
@JsonProperty("buchungstag")
public String getBuchungstag() {
return buchungstag;
}
@JsonProperty("glaeubiger-id")
public String getGlaeubigerId() {
return glaeubigerId;
}
@JsonProperty("iban-beguenstigter/auftraggeber")
public String getIbanBeguenstigterAuftraggeber() {
return ibanBeguenstigterAuftraggeber;
}
@JsonProperty("kontostand")
public String getKontostand() {
return kontostand;
}
@JsonProperty("mandatsreferenz")
public String getMandatsreferenz() {
return mandatsreferenz;
}
@JsonProperty("name-referenzkonto")
public String getNameReferenzkonto() {
return nameReferenzkonto;
}
@JsonProperty("referenzkonto")
public String getReferenzkonto() {
return referenzkonto;
}
@JsonProperty("verwendungszweck")
public String getVerwendungszweck() {
return verwendungszweck;
}
@JsonProperty("waehrung")
public String getWaehrung() {
return waehrung;
}
@JsonProperty("e-ref")
public String geteRef() {
return eRef;
}
@JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
@JsonProperty("analyse-betrag")
public void setAnalyseBetrag(String analyseBetrag) {
this.analyseBetrag = analyseBetrag;
}
@JsonProperty("analyse-hauptkategorie")
public void setAnalyseHauptkategorie(String analyseHauptkategorie) {
this.analyseHauptkategorie = analyseHauptkategorie;
}
@JsonProperty("analyse-jahr")
public void setAnalyseJahr(String analyseJahr) {
this.analyseJahr = analyseJahr;
}
@JsonProperty("analyse-monat")
public void setAnalyseMonat(String analyseMonat) {
this.analyseMonat = analyseMonat;
}
@JsonProperty("analyse-quartal")
public void setAnalyseQuartal(String analyseQuartal) {
this.analyseQuartal = analyseQuartal;
}
@JsonProperty("analyse-umbuchung")
public void setAnalyseUmbuchung(String analyseUmbuchung) {
this.analyseUmbuchung = analyseUmbuchung;
}
@JsonProperty("analyse-umsatzart")
public void setAnalyseUmsatzart(String analyseUmsatzart) {
this.analyseUmsatzart = analyseUmsatzart;
}
@JsonProperty("analyse-unterkategorie")
public void setAnalyseUnterkategorie(String analyseUnterkategorie) {
this.analyseUnterkategorie = analyseUnterkategorie;
}
@JsonProperty("analyse-vertrag")
public void setAnalyseVertrag(String analyseVertrag) {
this.analyseVertrag = analyseVertrag;
}
@JsonProperty("analyse-vertrags-id")
public void setAnalyseVertragsId(String analyseVertragsId) {
this.analyseVertragsId = analyseVertragsId;
}
@JsonProperty("analyse-vertragsturnus")
public void setAnalyseVertragsturnus(String analyseVertragsturnus) {
this.analyseVertragsturnus = analyseVertragsturnus;
}
@JsonProperty("analyse-vom-frei-verfuegbaren-einkommen-ausgeschlossen")
public void setAnalyseVomFreiVerfuegbarenEinkommenAusgeschlossen(String analyseVomFreiVerfuegbarenEinkommenAusgeschlossen) {
this.analyseVomFreiVerfuegbarenEinkommenAusgeschlossen = analyseVomFreiVerfuegbarenEinkommenAusgeschlossen;
}
@JsonProperty("analyse-woche")
public void setAnalyseWoche(String analyseWoche) {
this.analyseWoche = analyseWoche;
}
@JsonProperty("beguenstigter/auftraggeber")
public void setBeguenstigterAuftraggeber(String beguenstigterAuftraggeber) {
if (beguenstigterAuftraggeber.contains("Abbuchung")) {
this.beguenstigterAuftraggeber = "Sparkasse Münsterland Ost";
} else {
this.beguenstigterAuftraggeber = beguenstigterAuftraggeber;
}
}
@JsonProperty("betrag")
public void setBetrag(String betrag) {
this.betrag = betrag;
}
@JsonProperty("buchungstag")
public void setBuchungstag(String buchungstag) {
this.buchungstag = buchungstag;
}
@JsonProperty("glaeubiger-id")
public void setGlaeubigerId(String glaeubigerId) {
this.glaeubigerId = glaeubigerId;
}
@JsonProperty("iban-beguenstigter/auftraggeber")
public void setIbanBeguenstigterAuftraggeber(String ibanBeguenstigterAuftraggeber) {
this.ibanBeguenstigterAuftraggeber = ibanBeguenstigterAuftraggeber;
}
@JsonProperty("kontostand")
public void setKontostand(String kontostand) {
this.kontostand = kontostand;
}
@JsonProperty("mandatsreferenz")
public void setMandatsreferenz(String mandatsreferenz) {
this.mandatsreferenz = mandatsreferenz;
}
@JsonProperty("name-referenzkonto")
public void setNameReferenzkonto(String nameReferenzkonto) {
this.nameReferenzkonto = nameReferenzkonto;
}
@JsonProperty("referenzkonto")
public void setReferenzkonto(String referenzkonto) {
this.referenzkonto = referenzkonto;
}
@JsonProperty("verwendungszweck")
public void setVerwendungszweck(String verwendungszweck) {
this.verwendungszweck = verwendungszweck;
}
@JsonProperty("waehrung")
public void setWaehrung(String waehrung) {
this.waehrung = waehrung;
}
@JsonProperty("e-ref")
public void seteRef(String eRef) {
this.eRef = eRef;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(TransactionDAO.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('[');
sb.append("mandatsreferenz");
sb.append('=');
sb.append(((this.mandatsreferenz == null) ? "<null>" : this.mandatsreferenz));
sb.append(',');
sb.append("analyseVomFreiVerfuegbarenEinkommenAusgeschlossen");
sb.append('=');
sb.append(((this.analyseVomFreiVerfuegbarenEinkommenAusgeschlossen == null) ? "<null>" : this.analyseVomFreiVerfuegbarenEinkommenAusgeschlossen));
sb.append(',');
sb.append("analyseVertrag");
sb.append('=');
sb.append(((this.analyseVertrag == null) ? "<null>" : this.analyseVertrag));
sb.append(',');
sb.append("eRef");
sb.append('=');
sb.append(((this.eRef == null) ? "<null>" : this.eRef));
sb.append(',');
sb.append("analyseHauptkategorie");
sb.append('=');
sb.append(((this.analyseHauptkategorie == null) ? "<null>" : this.analyseHauptkategorie));
sb.append(',');
sb.append("analyseUmsatzart");
sb.append('=');
sb.append(((this.analyseUmsatzart == null) ? "<null>" : this.analyseUmsatzart));
sb.append(',');
sb.append("analyseWoche");
sb.append('=');
sb.append(((this.analyseWoche == null) ? "<null>" : this.analyseWoche));
sb.append(',');
sb.append("analyseMonat");
sb.append('=');
sb.append(((this.analyseMonat == null) ? "<null>" : this.analyseMonat));
sb.append(',');
sb.append("analyseQuartal");
sb.append('=');
sb.append(((this.analyseQuartal == null) ? "<null>" : this.analyseQuartal));
sb.append(',');
sb.append("kontostand");
sb.append('=');
sb.append(((this.kontostand == null) ? "<null>" : this.kontostand));
sb.append(',');
sb.append("analyseVertragsId");
sb.append('=');
sb.append(((this.analyseVertragsId == null) ? "<null>" : this.analyseVertragsId));
sb.append(',');
sb.append("waehrung");
sb.append('=');
sb.append(((this.waehrung == null) ? "<null>" : this.waehrung));
sb.append(',');
sb.append("verwendungszweck");
sb.append('=');
sb.append(((this.verwendungszweck == null) ? "<null>" : this.verwendungszweck));
sb.append(',');
sb.append("buchungstag");
sb.append('=');
sb.append(((this.buchungstag == null) ? "<null>" : this.buchungstag));
sb.append(',');
sb.append("analyseVertragsturnus");
sb.append('=');
sb.append(((this.analyseVertragsturnus == null) ? "<null>" : this.analyseVertragsturnus));
sb.append(',');
sb.append("analyseBetrag");
sb.append('=');
sb.append(((this.analyseBetrag == null) ? "<null>" : this.analyseBetrag));
sb.append(',');
sb.append("betrag");
sb.append('=');
sb.append(((this.betrag == null) ? "<null>" : this.betrag));
sb.append(',');
sb.append("nameReferenzkonto");
sb.append('=');
sb.append(((this.nameReferenzkonto == null) ? "<null>" : this.nameReferenzkonto));
sb.append(',');
sb.append("beguenstigterAuftraggeber");
sb.append('=');
sb.append(((this.beguenstigterAuftraggeber == null) ? "<null>" : this.beguenstigterAuftraggeber));
sb.append(',');
sb.append("analyseUmbuchung");
sb.append('=');
sb.append(((this.analyseUmbuchung == null) ? "<null>" : this.analyseUmbuchung));
sb.append(',');
sb.append("ibanBeguenstigterAuftraggeber");
sb.append('=');
sb.append(((this.ibanBeguenstigterAuftraggeber == null) ? "<null>" : this.ibanBeguenstigterAuftraggeber));
sb.append(',');
sb.append("referenzkonto");
sb.append('=');
sb.append(((this.referenzkonto == null) ? "<null>" : this.referenzkonto));
sb.append(',');
sb.append("analyseUnterkategorie");
sb.append('=');
sb.append(((this.analyseUnterkategorie == null) ? "<null>" : this.analyseUnterkategorie));
sb.append(',');
sb.append("analyseJahr");
sb.append('=');
sb.append(((this.analyseJahr == null) ? "<null>" : this.analyseJahr));
sb.append(',');
sb.append("glaeubigerId");
sb.append('=');
sb.append(((this.glaeubigerId == null) ? "<null>" : this.glaeubigerId));
sb.append(',');
sb.append("additionalProperties");
sb.append('=');
sb.append(((this.additionalProperties == null) ? "<null>" : this.additionalProperties));
sb.append(',');
if (sb.charAt((sb.length() - 1)) == ',') {
sb.setCharAt((sb.length() - 1), ']');
} else {
sb.append(']');
}
return sb.toString();
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final TransactionDAO that = (TransactionDAO) o;
return Objects.equals(referenzkonto, that.referenzkonto)
&& Objects.equals(analyseBetrag, that.analyseBetrag)
&& Objects.equals(verwendungszweck, that.verwendungszweck)
&& Objects.equals(betrag, that.betrag)
&& Objects.equals(ibanBeguenstigterAuftraggeber, that.ibanBeguenstigterAuftraggeber);
}
@Override
public int hashCode() {
return Objects.hash(referenzkonto, verwendungszweck, analyseBetrag, betrag, ibanBeguenstigterAuftraggeber);
}
}

View File

@ -0,0 +1,60 @@
package de.arminwolf.financeanalyzer.dao.charts.factories;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import de.arminwolf.financeanalyzer.dao.charts.model.Chart;
import de.arminwolf.financeanalyzer.dao.charts.model.HighChartDAO;
import de.arminwolf.financeanalyzer.dao.charts.model.Packedbubble;
import de.arminwolf.financeanalyzer.dao.charts.model.PlotOptions;
import de.arminwolf.financeanalyzer.dao.charts.model.Series;
import de.arminwolf.financeanalyzer.dao.charts.model.SeriesElement;
import de.arminwolf.financeanalyzer.dao.charts.model.Title;
import de.arminwolf.financeanalyzer.dao.charts.model.ToolTip;
import de.arminwolf.financeanalyzer.util.RandomColorUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class BubbleChartFactory {
public static HighChartDAO createBubbleChart(
final String pTitle,
final List<TransactionDAO> transactions) {
HighChartDAO highChartDAO = new HighChartDAO();
highChartDAO.setTitle(new Title(pTitle));
highChartDAO.setChart(new Chart("packedbubble", "75%"));
List<Series> series = new ArrayList<>();
List<String> colors = new ArrayList<>();
AtomicInteger min = new AtomicInteger(0);
AtomicInteger max = new AtomicInteger(0);
transactions.stream()
.collect(Collectors.groupingBy(TransactionDAO::getAnalyseHauptkategorie))
.forEach((key, value) -> {
Series data = new Series();
data.setName(key);
Float reduce = value.stream().reduce(0f, (a, b) -> a + (-1.0f * b.getBetragAsFloat()), Float::sum);
min.set(Math.min(min.get(), reduce.intValue()));
max.set(Math.max(max.get(), reduce.intValue()));
data.setComplexData(value.stream().map(b -> new SeriesElement(b.getBeguenstigterAuftraggeber(), -1.0f * b.getBetragAsFloat())).collect(Collectors.toList()));
series.add(data);
colors.add(RandomColorUtil.getRandomColor());
});
PlotOptions plotOptions = new PlotOptions();
plotOptions.setPackedbubble(new Packedbubble());
plotOptions.getPackedbubble().setzMax(max.get());
plotOptions.getPackedbubble().setzMin(min.get());
plotOptions.getPackedbubble().getDataLabels().getFilter().setValue(Double.valueOf(0.2 * max.get()).intValue());
highChartDAO.setPlotOptions(plotOptions);
highChartDAO.setTooltip(new ToolTip("<b>{point.name}:</b> <u>{point.value} €</u>", true));
highChartDAO.setColors(colors);
highChartDAO.setSeries(series);
return highChartDAO;
}
}

View File

@ -0,0 +1,74 @@
package de.arminwolf.financeanalyzer.dao.charts.factories;
import de.arminwolf.financeanalyzer.dao.charts.model.Chart;
import de.arminwolf.financeanalyzer.dao.charts.model.Column;
import de.arminwolf.financeanalyzer.dao.charts.model.HighChartDAO;
import de.arminwolf.financeanalyzer.dao.charts.model.PlotOptions;
import de.arminwolf.financeanalyzer.dao.charts.model.Series;
import de.arminwolf.financeanalyzer.dao.charts.model.Title;
import de.arminwolf.financeanalyzer.dao.charts.model.XAxis;
import de.arminwolf.financeanalyzer.dao.charts.model.YAxis;
import de.arminwolf.financeanalyzer.util.RandomColorUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ColumnChartFactory {
public static HighChartDAO createColumnChart(
final String pTitle,
final String pChartTitle,
final Map<String, List<Float>> ausgaben,
final String pAusgabenTitle,
final Map<String, List<Float>> einnahmen,
final String pEinnahmenTitle) {
HighChartDAO chart = new HighChartDAO();
PlotOptions plotOptions = new PlotOptions();
Column column = new Column();
//column.setStacking("normal");
plotOptions.setColumn(column);
chart.setPlotOptions(plotOptions);
chart.setTitle(new Title(pTitle));
XAxis xAxis = new XAxis();
xAxis.setCategories(List.of("Einname", "Ausgabe"));
chart.setXAxis(xAxis);
YAxis yAxis = new YAxis();
yAxis.setMin(0);
yAxis.setTitle(new Title(pChartTitle));
chart.setYAxis(yAxis);
chart.setChart(new Chart("column"));
Series einnahmenSeries = new Series();
einnahmenSeries.setStack(1);
einnahmenSeries.setName(pEinnahmenTitle);
einnahmenSeries.setData(einnahmen.values()
.stream()
.map(floats -> floats.stream().reduce(0.0f, Float::sum))
.collect(Collectors.toList()));
Series ausgabenSeries = new Series();
ausgabenSeries.setStack(0);
ausgabenSeries.setName(pAusgabenTitle);
ausgabenSeries.setData(ausgaben.values()
.stream()
.map(floats -> floats.stream().map(Math::abs).reduce(0.0f, Float::sum))
.collect(Collectors.toList()));
chart.setColors(List.of(RandomColorUtil.getRandomColor(), RandomColorUtil.getRandomColor()));
List<Series> series = new ArrayList<>();
series.add(einnahmenSeries);
series.add(ausgabenSeries);
chart.setSeries(series);
return chart;
}
}

View File

@ -0,0 +1,42 @@
package de.arminwolf.financeanalyzer.dao.charts.factories;
import de.arminwolf.financeanalyzer.dao.charts.model.Chart;
import de.arminwolf.financeanalyzer.dao.charts.model.HighChartDAO;
import de.arminwolf.financeanalyzer.dao.charts.model.Series;
import de.arminwolf.financeanalyzer.dao.charts.model.Title;
import de.arminwolf.financeanalyzer.dao.charts.model.XAxis;
import de.arminwolf.financeanalyzer.dao.charts.model.YAxis;
import de.arminwolf.financeanalyzer.util.RandomColorUtil;
import java.util.List;
public class LineChartFactory {
public static HighChartDAO createLineChart(final String pTitle, final String pChartTitle, final List<String> categories, final List<Series> pData) {
HighChartDAO lineChart = new HighChartDAO();
Title title = new Title();
title.setText(pTitle);
lineChart.setTitle(title);
XAxis xAxis = new XAxis();
xAxis.setCategories(categories);
YAxis yAxis = new YAxis();
Title chartTitle = new Title();
chartTitle.setText(pChartTitle);
yAxis.setTitle(chartTitle);
lineChart.setXAxis(xAxis);
lineChart.setYAxis(yAxis);
Chart chart = new Chart();
chart.setType("line");
lineChart.setChart(chart);
lineChart.setColors(List.of(RandomColorUtil.getRandomColor()));
lineChart.setSeries(pData);
return lineChart;
}
}

View File

@ -0,0 +1,46 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import java.io.Serializable;
public class Chart implements Serializable {
private String type;
private String height;
public Chart() {
this.type = "";
}
public Chart(final String type) {
this.type = type;
}
public Chart(final String type, final String height) {
this.type = type;
this.height = height;
}
public String getHeight() {
return height;
}
public void setHeight(final String height) {
this.height = height;
}
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@ -0,0 +1,20 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import java.io.Serializable;
public class Column implements Serializable {
private String stacking;
public String getStacking() {
return this.stacking;
}
public void setStacking(String stacking) {
this.stacking = stacking;
}
}

View File

@ -0,0 +1,59 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
public class DataLabels {
private boolean enabled;
private String format;
private Filter filter;
private Style style;
public DataLabels(boolean enabled, String format, Filter filter, Style style) {
this.enabled = enabled;
this.format = format;
this.filter = filter;
this.style = style;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
public String getFormat() {
return format;
}
public void setFormat(final String format) {
this.format = format;
}
public Filter getFilter() {
return filter;
}
public void setFilter(final Filter filter) {
this.filter = filter;
}
public Style getStyle() {
return style;
}
public void setStyle(final Style style) {
this.style = style;
}
}

View File

@ -0,0 +1,45 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
public class Filter {
private String property;
private String operator;
private int value;
public Filter(String property, String operator, int value) {
this.property = property;
this.operator = operator;
this.value = value;
}
public String getProperty() {
return property;
}
public void setProperty(final String property) {
this.property = property;
}
public String getOperator() {
return operator;
}
public void setOperator(final String operator) {
this.operator = operator;
}
public int getValue() {
return value;
}
public void setValue(final int value) {
this.value = value;
}
}

View File

@ -0,0 +1,125 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class HighChartDAO implements Serializable {
private PlotOptions plotOptions;
private ToolTip tooltip;
private YAxis yAxis;
private XAxis xAxis;
private List<? extends Series> series;
private Title title;
private Chart chart;
private List<String> colors;
@JsonProperty("tooltip")
public ToolTip getTooltip() {
return tooltip;
}
public void setTooltip(final ToolTip tooltip) {
this.tooltip = tooltip;
}
public YAxis getyAxis() {
return yAxis;
}
public void setyAxis(final YAxis yAxis) {
this.yAxis = yAxis;
}
public XAxis getxAxis() {
return xAxis;
}
public void setxAxis(final XAxis xAxis) {
this.xAxis = xAxis;
}
public List<String> getColors() {
return colors;
}
public void setColors(final List<String> colors) {
this.colors = colors;
}
public PlotOptions getPlotOptions() {
return this.plotOptions;
}
public void setPlotOptions(PlotOptions plotOptions) {
this.plotOptions = plotOptions;
}
@JsonProperty("yAxis")
public YAxis getYAxis() {
return this.yAxis;
}
public void setYAxis(YAxis yAxis) {
this.yAxis = yAxis;
}
@JsonProperty("xAxis")
public XAxis getXAxis() {
return this.xAxis;
}
public void setXAxis(XAxis xAxis) {
this.xAxis = xAxis;
}
public List<? extends Series> getSeries() {
return this.series;
}
public void setSeries(List<? extends Series> series) {
this.series = series;
}
public Title getTitle() {
return this.title;
}
public void setTitle(Title title) {
this.title = title;
}
public Chart getChart() {
return this.chart;
}
public void setChart(Chart chart) {
this.chart = chart;
}
}

View File

@ -0,0 +1,76 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
public class LayoutAlgorithm {
private double gravitationalConstant;
private boolean splitSeries;
private boolean seriesInteraction;
private boolean dragBetweenSeries;
private boolean parentNodeLimit;
public LayoutAlgorithm(
final double gravitationalConstant,
final boolean splitSeries,
final boolean seriesInteraction,
final boolean dragBetweenSeries,
final boolean parentNodeLimit
) {
this.gravitationalConstant = gravitationalConstant;
this.splitSeries = splitSeries;
this.seriesInteraction = seriesInteraction;
this.dragBetweenSeries = dragBetweenSeries;
this.parentNodeLimit = parentNodeLimit;
}
public double getGravitationalConstant() {
return gravitationalConstant;
}
public void setGravitationalConstant(final double gravitationalConstant) {
this.gravitationalConstant = gravitationalConstant;
}
public boolean isSplitSeries() {
return splitSeries;
}
public void setSplitSeries(final boolean splitSeries) {
this.splitSeries = splitSeries;
}
public boolean isSeriesInteraction() {
return seriesInteraction;
}
public void setSeriesInteraction(final boolean seriesInteraction) {
this.seriesInteraction = seriesInteraction;
}
public boolean isDragBetweenSeries() {
return dragBetweenSeries;
}
public void setDragBetweenSeries(final boolean dragBetweenSeries) {
this.dragBetweenSeries = dragBetweenSeries;
}
public boolean isParentNodeLimit() {
return parentNodeLimit;
}
public void setParentNodeLimit(final boolean parentNodeLimit) {
this.parentNodeLimit = parentNodeLimit;
}
}

View File

@ -0,0 +1,95 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
public class Packedbubble {
private String minSize;
private String maxSize;
private int zMin;
private int zMax;
private LayoutAlgorithm layoutAlgorithm;
private DataLabels dataLabels;
public Packedbubble(String minSize, String maxSize, int zMin, int zMax, LayoutAlgorithm layoutAlgorithm, DataLabels dataLabels) {
this.minSize = minSize;
this.maxSize = maxSize;
this.zMin = zMin;
this.zMax = zMax;
this.layoutAlgorithm = layoutAlgorithm;
this.dataLabels = dataLabels;
}
public Packedbubble() {
this.minSize = "20%";
this.maxSize = "100%";
this.zMin = 0;
this.zMax = 1000;
this.layoutAlgorithm = new LayoutAlgorithm(0.02, true, false, true, true);
this.dataLabels = new DataLabels(
true, "{point.name}",
new Filter("y", ">", 0),
new Style("black", "none", "normal")
);
}
public String getMinSize() {
return minSize;
}
public void setMinSize(final String minSize) {
this.minSize = minSize;
}
public String getMaxSize() {
return maxSize;
}
public void setMaxSize(final String maxSize) {
this.maxSize = maxSize;
}
public int getzMin() {
return zMin;
}
public void setzMin(final int zMin) {
this.zMin = zMin;
}
public int getzMax() {
return zMax;
}
public void setzMax(final int zMax) {
this.zMax = zMax;
}
public LayoutAlgorithm getLayoutAlgorithm() {
return layoutAlgorithm;
}
public void setLayoutAlgorithm(final LayoutAlgorithm layoutAlgorithm) {
this.layoutAlgorithm = layoutAlgorithm;
}
public DataLabels getDataLabels() {
return dataLabels;
}
public void setDataLabels(final DataLabels dataLabels) {
this.dataLabels = dataLabels;
}
}

View File

@ -0,0 +1,28 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
public class PlotOptions {
private Packedbubble packedbubble;
private Column column;
public Column getColumn() {
return this.column;
}
public void setColumn(Column column) {
this.column = column;
}
public Packedbubble getPackedbubble() {
return packedbubble;
}
public void setPackedbubble(final Packedbubble packedbubble) {
this.packedbubble = packedbubble;
}
}

View File

@ -0,0 +1,62 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class Series implements Serializable {
private Set<? extends SeriesElement> complexData;
private List<Float> data;
private int stack = 0;
private String name;
public void setComplexData(Collection<? extends SeriesElement> data) {
this.complexData = new HashSet<>(data);
}
public String getName() {
return this.name;
}
public int getStack() {
return stack;
}
public void setName(String name) {
this.name = name;
}
public void setData(final List<Float> data) {
this.data = data;
}
@JsonProperty("data")
public List<?> getData() {
if (Objects.isNull(data)) {
return this.complexData.stream().toList();
} else {
return this.data;
}
}
public void setStack(final int i) {
this.stack = i;
}
}

View File

@ -0,0 +1,50 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import java.util.Objects;
public class SeriesElement {
private String name;
private Float value;
public SeriesElement(final String name, final Float value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public Float getValue() {
return value;
}
public void setValue(final Float value) {
this.value = value;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final SeriesElement that = (SeriesElement) o;
return Objects.equals(name, that.name) && Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(name, value);
}
}

View File

@ -0,0 +1,46 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
public class Style {
private String color;
private String textOutline;
private String fontWeight;
public Style(String color, String textOutline, String fontWeight) {
this.color = color;
this.textOutline = textOutline;
this.fontWeight = fontWeight;
}
public String getColor() {
return color;
}
public void setColor(final String color) {
this.color = color;
}
public String getTextOutline() {
return textOutline;
}
public void setTextOutline(final String textOutline) {
this.textOutline = textOutline;
}
public String getFontWeight() {
return fontWeight;
}
public void setFontWeight(final String fontWeight) {
this.fontWeight = fontWeight;
}
}

View File

@ -0,0 +1,28 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import java.io.Serializable;
public class Title implements Serializable {
private String text;
public Title() {
this.text = "";
}
public Title(final String text) {
this.text = text;
}
public String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
}

View File

@ -0,0 +1,39 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
public class ToolTip {
private String pointFormat;
private boolean useHTML;
public ToolTip(String pointFormat, boolean useHTML) {
this.pointFormat = pointFormat;
this.useHTML = useHTML;
}
public ToolTip() {
this.pointFormat = "<b>{point.name}:</b> {point.value}";
this.useHTML = true;
}
public String getPointFormat() {
return pointFormat;
}
public void setPointFormat(final String pointFormat) {
this.pointFormat = pointFormat;
}
public boolean isUseHTML() {
return useHTML;
}
public void setUseHTML(final boolean useHTML) {
this.useHTML = useHTML;
}
}

View File

@ -0,0 +1,20 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import java.io.Serializable;
import java.util.Collection;
public class XAxis implements Serializable {
private Collection<? extends String> categories;
public Collection<? extends String> getCategories() {
return this.categories;
}
public void setCategories(Collection<? extends String> categories) {
this.categories = categories;
}
}

View File

@ -0,0 +1,32 @@
package de.arminwolf.financeanalyzer.dao.charts.model;
import java.io.Serializable;
public class YAxis implements Serializable {
private Title title;
private int min;
public Title getTitle() {
return this.title;
}
public void setMin(final int i) {
this.min=i;
}
public int getMin() {
return min;
}
public void setTitle(Title title) {
this.title = title;
}
}

View File

@ -0,0 +1,147 @@
package de.arminwolf.financeanalyzer.dao.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import de.arminwolf.financeanalyzer.util.DateUtil;
import de.arminwolf.financeanalyzer.util.NumberUtil;
import java.time.LocalDate;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
public class OutputData implements Comparable<OutputData> {
@JsonProperty("id")
public String id;
@JsonProperty("amount")
public String amount;
@JsonProperty("amountAsFloat")
public Float amountAsFloat;
@JsonProperty("contractTurnaround")
public String contractTurnaround;
@JsonProperty("contractTurnaround")
public String date;
@JsonProperty("recipient")
public String recipient;
@JsonProperty("purpose")
public String purpose;
public OutputData(final Map.Entry<String, TransactionDAO> order) {
this(order.getKey(), order.getValue());
}
public OutputData(final String id, final TransactionDAO transaction) {
this.id = id;
this.amount = transaction.getBetrag().concat(" ").concat(transaction.getWaehrung());
this.contractTurnaround = transaction.getAnalyseVertragsturnus().isBlank() ? "monatlich" : transaction.getAnalyseVertragsturnus();
this.amountAsFloat = Float.parseFloat(transaction.getBetrag());
this.date = DateUtil.prettyPrint(DateUtil.parse(transaction.getBuchungstag()));
this.purpose = transaction.getVerwendungszweck().trim().isEmpty() ?
"Überweisung an " + transaction.getBeguenstigterAuftraggeber() :
transaction.getVerwendungszweck();
this.recipient = transaction.getBeguenstigterAuftraggeber();
}
public OutputData(final TransactionDAO t) {
this(LocalDate.now().toString(), t);
}
public String getDate() {
return date;
}
public void setDate(final String date) {
this.date = date;
}
@Override
public int compareTo(final OutputData o) {
return getAmountAsFloat().compareTo(o.getAmountAsFloat());
}
public Float getAmountAsFloat() {
return NumberUtil.round(amountAsFloat);
}
public void setAmountAsFloat(final Float amountAsFloat) {
this.amountAsFloat = amountAsFloat;
}
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getAmount() {
return amount;
}
public void setAmount(final String amount) {
this.amount = amount;
}
public String getContractTurnaround() {
return contractTurnaround;
}
public void setContractTurnaround(final String contractTurnaround) {
this.contractTurnaround = contractTurnaround;
}
public String getPurpose() {
return purpose;
}
public void setPurpose(final String purpose) {
this.purpose = purpose;
}
public String getRecipient() {
return recipient;
}
public void setRecipient(final String recipient) {
this.recipient = recipient;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final OutputData outputData = (OutputData) o;
return Objects.equals(amount, outputData.amount) && Objects.equals(contractTurnaround, outputData.contractTurnaround) && Objects.equals(date, outputData.date) && Objects.equals(recipient, outputData.recipient) && Objects.equals(purpose, outputData.purpose);
}
@Override
public int hashCode() {
return Objects.hash(amount, contractTurnaround, date, recipient, purpose);
}
}

View File

@ -0,0 +1,24 @@
package de.arminwolf.financeanalyzer.service;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class CacheService {
private final Map<String, Object> cache = new HashMap<>();
public void put(final String key, final Object value) {
System.out.printf("Added to Cache. Key: [%s], Value: [%s]\n", key, value);
cache.put(key, value);
}
public Object get(final String key) {
return cache.get(key);
}
}

View File

@ -0,0 +1,16 @@
package de.arminwolf.financeanalyzer.service;
import de.arminwolf.financeanalyzer.util.XlsxToJsonConverter;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.util.Optional;
@Service
public class ConverterService {
public Optional<String> convert(InputStream in) {
return XlsxToJsonConverter.writeJson(XlsxToJsonConverter.readXlsx(in));
}
}

View File

@ -0,0 +1,134 @@
package de.arminwolf.financeanalyzer.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.arminwolf.financeanalyzer.conf.Configuration;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
@Service
public class FileService {
public static final String CONFIGURATION_JSON = "configuration.json";
@Autowired
private CacheService cacheService;
public Configuration getConfiguration() {
File workingDirectory = getOrCreateWorkingDirectory();
Path configurationFilePath = Paths.get(workingDirectory.toPath().toString(), CONFIGURATION_JSON);
System.out.printf("Loading '%s'\n", configurationFilePath);
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(configurationFilePath.toFile(), Configuration.class);
} catch (IOException e) {
System.out.printf("Error while accesing '%s'\n", configurationFilePath);
}
return new Configuration();
}
public Optional<String> getJsonFileName() {
File workingDirectory = getOrCreateWorkingDirectory();
final Path jsonFilePath = Paths.get(workingDirectory.toPath().toString(), "finance-analyzer.json");
if (!jsonFilePath.toFile().exists()) {
return Optional.empty();
} else {
String absolutePath = jsonFilePath.toAbsolutePath().toString();
System.out.println("Absolute Path of json file: " + absolutePath);
return Optional.of(absolutePath);
}
}
public boolean isJsonPresent() {
File orCreateWorkingDirectory = getOrCreateWorkingDirectory();
return getJsonFiles(orCreateWorkingDirectory).findFirst().isPresent();
}
private Stream<String> getJsonFiles(final File orCreateWorkingDirectory) {
return Arrays.stream(Objects.requireNonNull(orCreateWorkingDirectory.list((dir, name) -> name.endsWith(".json"))));
}
public TransactionDAO[] getJsonFile() {
File workingDirectory = getOrCreateWorkingDirectory();
final Path jsonFilePath = Paths.get(workingDirectory.toPath().toString(), "finance-analyzer.json");
if (!jsonFilePath.toFile().exists()) {
throw new IllegalStateException("Could not load json file.");
} else {
System.out.printf("Loading '%s'\n", jsonFilePath);
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(jsonFilePath.toFile(), new TypeReference<TransactionDAO[]>() {
});
} catch (IOException e) {
System.out.printf("Error while accesing '%s'\n", jsonFilePath);
}
}
return new TransactionDAO[0];
}
public File getOrCreateWorkingDirectory() {
if (Objects.isNull(cacheService.get("workingDirectory"))) {
final String userHomeAsString = System.getProperty("user.home");
System.out.printf("Looking for working Directory '.finance-analyser' in user home (%s).\n", userHomeAsString);
Path path = Paths.get(userHomeAsString.concat(File.separator).concat(".finance-analyser"));
boolean exists = Files.exists(path);
if (exists) {
return path.toFile();
} else {
Path directory = null;
try {
directory = Files.createDirectory(path);
cacheService.put("workingDirectory", directory.toFile());
return directory.toFile();
} catch (IOException e) {
System.err.println("Could not create directory in user home.. Permission issue?" + e.getMessage());
}
throw new IllegalStateException("Could not create working directory. Please fix the permissions.");
}
} else {
return (File) cacheService.get("workingDirectory");
}
}
public void saveConfiguration(final Configuration configuration) {
File workingDirectory = getOrCreateWorkingDirectory();
Path path = Paths.get(workingDirectory.getPath(), CONFIGURATION_JSON);
if (path.toFile().exists()) {
System.out.println("Configuration file already exists. Overwriting it.");
try {
Files.delete(path);
} catch (IOException e) {
System.out.println("Could not delete existing configuration file." + e.getMessage());
}
}
ObjectMapper objectMapper = new ObjectMapper();
try {
objectMapper.writeValue(path.toFile(), configuration);
} catch (IOException e) {
System.err.println("Could not save configuration file." + e.getMessage());
}
}
}

View File

@ -0,0 +1,181 @@
package de.arminwolf.financeanalyzer.service;
import de.arminwolf.financeanalyzer.conf.Configuration;
import de.arminwolf.financeanalyzer.dao.ReportDAO;
import de.arminwolf.financeanalyzer.dao.TableDataDAO;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import de.arminwolf.financeanalyzer.service.usecases.CashFlowService;
import de.arminwolf.financeanalyzer.service.usecases.IncomeService;
import de.arminwolf.financeanalyzer.service.usecases.OtherTransactionService;
import de.arminwolf.financeanalyzer.service.usecases.StandingOrderService;
import de.arminwolf.financeanalyzer.util.DateUtil;
import de.arminwolf.financeanalyzer.util.TransactionDateStringComparator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static de.arminwolf.financeanalyzer.util.Constants.AUSGABEN;
import static de.arminwolf.financeanalyzer.util.Constants.BEGUENSTIGTER;
import static de.arminwolf.financeanalyzer.util.Constants.BETRAG;
import static de.arminwolf.financeanalyzer.util.Constants.DATUM;
import static de.arminwolf.financeanalyzer.util.Constants.EINNAHMEN;
import static de.arminwolf.financeanalyzer.util.Constants.TURNUS;
import static de.arminwolf.financeanalyzer.util.Constants.VERWENDUNGSZWECK;
@Service
public class ReportService {
public static final String EUR = " EUR";
@Autowired
private IncomeService incomeService;
@Autowired
private CashFlowService cashFlowService;
@Autowired
private StandingOrderService standingOrderService;
@Autowired
private OtherTransactionService otherTransactionService;
@Autowired
private CacheService cacheService;
private ReportDAO report;
public ReportDAO createReport(final TransactionDAO[] response, String month, String iban, final Model model) {
this.report = new ReportDAO();
System.out.println("LocalDate : " + DateUtil.parseMonth(month));
int numberOfMonths = DateUtil.monthsBetween(DateUtil.parseMonth(month), LocalDate.now());
final List<TransactionDAO> filteredIncomeTransactions = getFilteredIncomeTransactions(response, numberOfMonths, iban);
final List<TransactionDAO> filteredExpenseTransactions = getFilteredExpenseTransactions(response, numberOfMonths, iban);
// Verarbeiten Sie hier die Antwort
((Configuration) cacheService.get("configuration")).getBankAccounts().stream()
.filter(bankAccount -> bankAccount.getIban().equals(iban)).findFirst()
.ifPresent(bankAccount -> {
model.addAttribute("accountName", bankAccount.getBankAccountName());
model.addAttribute("iban", iban);
});
// Daueraufträge
standingOrderService.setStandingOrder(report, filteredExpenseTransactions);
// Sonstige Transaktionen
otherTransactionService.setOtherTransaction(report, filteredExpenseTransactions);
// Cashflow
cashFlowService.setCashFlowChart(report, filteredIncomeTransactions, filteredExpenseTransactions);
// Einkommen
incomeService.setIncome(report, filteredIncomeTransactions);
LocalDate localDate = LocalDate.now().minusMonths(numberOfMonths);
final LocalDate start = DateUtil.getFirstDayOfMonth(localDate);
final LocalDate end = DateUtil.getLastDayOfMonth(localDate);
model.addAttribute("date", String.format("%s - %s", DateUtil.format(start), DateUtil.format(end)));
model.addAttribute("numberOfMonths", numberOfMonths);
model.addAttribute("cashFlow", getCashFlowData());
model.addAttribute("income", getIncomeData());
model.addAttribute("standingOrders", getStandingOrdersData());
model.addAttribute("notStandingOrders", getNotStandingOrdersData());
model.addAttribute("incomeExpenseColumnReport", report.getCashFlowChart());
model.addAttribute("accountBalanceLineChart", report.getAccountBalanceLineChart());
model.addAttribute("transactionCategories", report.getTransactionCategoriesBubbleChart());
return report;
}
private TableDataDAO getCashFlowData() {
TableDataDAO cashFlowData = TableDataDAO.TableDataFactory.createTableDataDTO();
cashFlowData.setHeaders(Arrays.asList("Einnahmen", "Verträge", "Ausgaben (ohne Veträge)", "Ausgaben insgesammt", "Differenz"));
cashFlowData.setRows(List.of(
Arrays.asList(
String.valueOf(report.getIncome()).concat(EUR),
String.valueOf(report.getStandingOrdersAmount()).concat(EUR),
String.valueOf(report.getNotStandingOrdersAmount()).concat(EUR),
String.valueOf(report.getTotalExpense()).concat(EUR),
String.valueOf(report.getDiff()).concat(EUR)))
);
return cashFlowData;
}
private List<TransactionDAO> getFilteredIncomeTransactions(final TransactionDAO[] response, final int numberOfMonths, final String iban) {
return getFilteredTransactions(response, EINNAHMEN, numberOfMonths, iban);
}
private List<TransactionDAO> getFilteredExpenseTransactions(final TransactionDAO[] response, final int numberOfMonths, final String iban) {
return getFilteredTransactions(response, AUSGABEN, numberOfMonths, iban);
}
private List<TransactionDAO> getFilteredTransactions(final TransactionDAO[] response, final String type, int numberOfMonths, String iban) {
final LocalDate localDate = LocalDate.now().minusMonths(numberOfMonths);
final LocalDate start = LocalDate.of(localDate.getYear(), localDate.getMonth(), 1);
final LocalDate end = YearMonth.of(localDate.getYear(), localDate.getMonth()).atEndOfMonth();
return Arrays.stream(response)
.filter(t -> DateUtil.isBetween(t.getBuchungstag(), start, end))
.filter(t -> t.getReferenzkonto().equals(iban))
.filter(e -> e.getAnalyseBetrag().equals(type))
.sorted(new TransactionDateStringComparator())
.collect(Collectors.toList());
}
private TableDataDAO getIncomeData() {
TableDataDAO incomeData = TableDataDAO.TableDataFactory.createTableDataDTO();
incomeData.setHeaders(Arrays.asList(BETRAG, DATUM, BEGUENSTIGTER, VERWENDUNGSZWECK));
incomeData.setRows(report.getIncomeList().stream()
.map(income -> Arrays.asList(
income.getAmount(),
income.getDate(),
income.getRecipient(),
income.getPurpose())).toList());
return incomeData;
}
private TableDataDAO getNotStandingOrdersData() {
TableDataDAO notStandingOrders = TableDataDAO.TableDataFactory.createTableDataDTO();
notStandingOrders.setHeaders(Arrays.asList(BETRAG, DATUM, TURNUS, BEGUENSTIGTER, VERWENDUNGSZWECK));
notStandingOrders.setRows(report.getSortedNotStandingOrders().stream()
.map(income -> Arrays.asList(
income.getAmount(),
income.getDate(),
income.getContractTurnaround(),
income.getRecipient(),
income.getPurpose())).toList());
return notStandingOrders;
}
private TableDataDAO getStandingOrdersData() {
TableDataDAO standingOrders = TableDataDAO.TableDataFactory.createTableDataDTO();
standingOrders.setHeaders(Arrays.asList(BETRAG, DATUM, TURNUS, BEGUENSTIGTER, VERWENDUNGSZWECK));
standingOrders.setRows(report.getSortedStandingOrders().stream()
.map(income -> Arrays.asList(
income.getAmount(),
income.getDate(),
income.getContractTurnaround(),
income.getRecipient(),
income.getPurpose())).toList());
return standingOrders;
}
}

View File

@ -0,0 +1,67 @@
package de.arminwolf.financeanalyzer.service.usecases;
import de.arminwolf.financeanalyzer.dao.ReportDAO;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import org.apache.commons.math3.util.Pair;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import static de.arminwolf.financeanalyzer.util.Constants.AUSGABEN;
import static de.arminwolf.financeanalyzer.util.Constants.EINNAHMEN;
@Service
public class CashFlowService {
public void setCashFlowChart(final ReportDAO reportDAO,
final List<TransactionDAO> filteredIncomeTransactions,
final List<TransactionDAO> filteredExpenseTransactions) {
Map<String, List<Float>> expenses = new TreeMap<>();
Map<String, List<Float>> income = new TreeMap<>();
filteredIncomeTransactions.stream()
.map(TransactionDAO::getAnalyseMonat)
.forEach(month -> {
income.put(month, new ArrayList<>());
filteredIncomeTransactions.stream()
.filter(t -> t.getAnalyseMonat().equals(month))
.filter(t -> t.getAnalyseBetrag().equals(EINNAHMEN))
.forEach(t -> income.get(month).add(t.getBetragAsFloat()));
});
filteredExpenseTransactions.stream()
.map(TransactionDAO::getAnalyseMonat)
.forEach(month -> {
expenses.put(month, new ArrayList<>());
filteredExpenseTransactions.stream()
.filter(t -> t.getAnalyseMonat().equals(month))
.filter(t -> t.getAnalyseBetrag().equals(AUSGABEN))
.forEach(t -> expenses.get(month).add(t.getBetragAsFloat()));
});
System.out.println("Number of Dataseries. Expenses: " + expenses.size() + ", Income: " + income.size());
income.values().forEach(System.out::println);
System.out.println("-------------------");
reportDAO.setCashFlowChart(expenses, income);
final List<TransactionDAO> allTransaction = new ArrayList<>();
allTransaction.addAll(filteredIncomeTransactions);
allTransaction.addAll(filteredExpenseTransactions);
// Line Chart
reportDAO.setAccountBalanceLineChart(allTransaction.stream()
.map(t -> Pair.create(t.getBuchungstag(), t.getKontostand()))
.collect(Collectors.toList()));
// Bubble Chart
reportDAO.setTransactionCategoriesBubbleChart(filteredExpenseTransactions);
}
}

View File

@ -0,0 +1,21 @@
package de.arminwolf.financeanalyzer.service.usecases;
import de.arminwolf.financeanalyzer.dao.ReportDAO;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
import static de.arminwolf.financeanalyzer.util.Constants.EINNAHMEN;
@Service
public class IncomeService {
public void setIncome(final ReportDAO reportDAO, final List<TransactionDAO> filteredTransactions) {
reportDAO.setIncomeList(filteredTransactions.stream()
.filter(e -> e.getAnalyseBetrag().equals(EINNAHMEN))
.collect(Collectors.toList()));
}
}

View File

@ -0,0 +1,23 @@
package de.arminwolf.financeanalyzer.service.usecases;
import de.arminwolf.financeanalyzer.dao.ReportDAO;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import de.arminwolf.financeanalyzer.dao.model.OutputData;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class OtherTransactionService {
public void setOtherTransaction(final ReportDAO reportDAO,
final List<TransactionDAO> filteredTransactions) {
reportDAO.setNotStandingOrders(filteredTransactions.stream()
.filter(t -> !reportDAO.getStandingOrders().contains(new OutputData(t)))
.distinct()
.collect(Collectors.toList()));
}
}

View File

@ -0,0 +1,29 @@
package de.arminwolf.financeanalyzer.service.usecases;
import de.arminwolf.financeanalyzer.dao.ReportDAO;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import de.arminwolf.financeanalyzer.util.StreamUtil;
import de.arminwolf.financeanalyzer.util.TransactionDateStringReverseComparator;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StandingOrderService {
public static final String JA = "ja";
public void setStandingOrder(final ReportDAO reportDAO, final List<TransactionDAO> filteredTransactions) {
List<TransactionDAO> transactions = filteredTransactions.stream()
.filter(e -> e.getAnalyseVertrag().equals(JA)
|| e.getAnalyseUmsatzart().equals("Dauerauftrag")
|| e.getAnalyseUmsatzart().equals("Lastschrift"))
.sorted(new TransactionDateStringReverseComparator())
.toList();
List<TransactionDAO> standingOrderTransactions = StreamUtil.findDuplicateInStream(transactions);
reportDAO.setStandingOrders(standingOrderTransactions);
}
}

View File

@ -0,0 +1,18 @@
package de.arminwolf.financeanalyzer.util;
public class Constants {
public static final String INDEX = "upload";
public static final String ERROR = "templates/error";
public static final String REPORTS = "reports";
public static final String EINNAHMEN = "Einnahmen";
public static final String AUSGABEN = "Ausgaben";
public static final String GEHALT = "Gehalt";
public static final String TURNUS = "Turnus";
public static final String VERWENDUNGSZWECK = "Verwendungszweck";
public static final String BEGUENSTIGTER = "Begünstigter";
public static final String DATUM = "Datum";
public static final String BETRAG = "Betrag";
}

View File

@ -0,0 +1,9 @@
package de.arminwolf.financeanalyzer.util;
import de.arminwolf.financeanalyzer.dao.model.OutputData;
import java.util.Comparator;
public interface DateFormatStringComparatorForOrder extends Comparator<OutputData> {
}

View File

@ -0,0 +1,113 @@
package de.arminwolf.financeanalyzer.util;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class DateUtil {
private final static DateFormat df = new SimpleDateFormat("dd-MMM-yyyy", Locale.GERMAN);
private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
private final static DateTimeFormatter originalFormatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy");
private final static Calendar startCalendar = Calendar.getInstance();
public static String format(final LocalDate start) {
return start.format(formatter);
}
public static LocalDate getFirstDayOfMonth(final LocalDate localDate) {
return LocalDate.of(localDate.getYear(), localDate.getMonth(), 1);
}
public static LocalDate getLastDayOfMonth(final LocalDate localDate) {
return YearMonth.of(localDate.getYear(), localDate.getMonth()).atEndOfMonth();
}
public static boolean isBefore(String date, LocalDate end) {
try {
Date parse = df.parse(date);
Instant instant = parse.toInstant();
LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
return localDate.isBefore(end);
} catch (ParseException e) {
System.out.println("ERROR: " + date);
return false;
}
}
public static boolean isAfter(String date, LocalDate start) {
try {
Date parse = df.parse(date);
Instant instant = parse.toInstant();
LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
return localDate.isAfter(start);
} catch (ParseException e) {
System.out.println("ERROR: " + date);
e.printStackTrace(System.err);
return false;
}
}
public static boolean isBetween(String date, LocalDate start, LocalDate end) {
try {
Date parse = df.parse(date);
Instant instant = parse.toInstant();
LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
return (localDate.isEqual(end) || localDate.isBefore(end)) && (localDate.isEqual(start) || localDate.isAfter(start));
} catch (ParseException e) {
System.out.println("ERROR: " + date);
e.printStackTrace(System.err);
return false;
}
}
public static LocalDate parse(final String buchungstag) {
return LocalDate.parse(buchungstag, originalFormatter);
}
public static LocalDate parseMonth(final String month) {
String[] split = month.split("-");
return LocalDate.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]), 1);
}
public static String prettyPrint(final LocalDate date) {
return date.format(formatter);
}
public static int monthsBetween(LocalDate startDate, LocalDate endDate) {
if (startDate == null || endDate == null) {
throw new IllegalArgumentException("Both startDate and endDate must be provided");
}
startCalendar.setTime(Date.from(startDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
int startDateTotalMonths = 12 * startCalendar.get(Calendar.YEAR) + startCalendar.get(Calendar.MONTH);
Calendar endCalendar = Calendar.getInstance();
endCalendar.setTime(Date.from(endDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
int endDateTotalMonths = 12 * endCalendar.get(Calendar.YEAR)
+ endCalendar.get(Calendar.MONTH);
return endDateTotalMonths - startDateTotalMonths;
}
}

View File

@ -0,0 +1,16 @@
package de.arminwolf.financeanalyzer.util;
import java.util.Objects;
public class NumberUtil {
public static Float round(final Float value) {
return round(value, 2);
}
public static Float round(final Float value, final int precision) {
return Math.round((Objects.isNull(value) ? 0f : value) * Math.pow(10, precision)) / (float) Math.pow(10, precision);
}
}

View File

@ -0,0 +1,20 @@
package de.arminwolf.financeanalyzer.util;
import de.arminwolf.financeanalyzer.dao.model.OutputData;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Comparator;
public class OrderDateStringComparator implements Comparator<OutputData> {
@Override
public int compare(final OutputData o1, final OutputData o2) {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy");
try {
return -1 * formatter.parse(o1.getDate()).compareTo(formatter.parse(o2.getDate()));
} catch (ParseException e) {
return -1;
}
}
}

View File

@ -0,0 +1,17 @@
package de.arminwolf.financeanalyzer.util;
import java.util.Random;
public class RandomColorUtil {
private static final Random random = new Random();
public static String getRandomColor() {
// create a big random number - maximum is ffffff (hex) = 16777215 (dez)
int nextInt = random.nextInt(0xffffff + 1);
// format it as hexadecimal string (with hashtag and leading zeros)
return String.format("#%06x", nextInt);
}
}

View File

@ -0,0 +1,29 @@
package de.arminwolf.financeanalyzer.util;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class StreamUtil<T> {
public static <T> List<T> findDuplicateInStream(List<T> list) {
// Set to store the duplicate elements
Set<T> items = new HashSet<>();
// Return the set of duplicate elements
return list.stream()
// Set.add() returns false
// if the element was
// already present in the set.
// Hence filter such elements
.filter(items::add)
// Collect duplicate elements
// in the set
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,22 @@
package de.arminwolf.financeanalyzer.util;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Comparator;
public class TransactionDateStringComparator implements Comparator<TransactionDAO> {
@Override
public int compare(final TransactionDAO o1, final TransactionDAO o2) {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy");
try {
return formatter.parse(o1.getBuchungstag()).compareTo(formatter.parse(o2.getBuchungstag()));
} catch (ParseException e) {
return -1;
}
}
}

View File

@ -0,0 +1,22 @@
package de.arminwolf.financeanalyzer.util;
import de.arminwolf.financeanalyzer.dao.TransactionDAO;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Comparator;
public class TransactionDateStringReverseComparator implements Comparator<TransactionDAO> {
@Override
public int compare(final TransactionDAO o1, final TransactionDAO o2) {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy");
try {
return -1 * formatter.parse(o1.getBuchungstag()).compareTo(formatter.parse(o2.getBuchungstag()));
} catch (ParseException e) {
return -1;
}
}
}

View File

@ -0,0 +1,12 @@
package de.arminwolf.financeanalyzer.util;
import jakarta.servlet.http.HttpServletRequest;
public class UrlUtil {
public static String getRelativeUrl(final HttpServletRequest request, final String path) {
String requestURL = request.getRequestURL().toString();
String servletPath = request.getServletPath();
return requestURL.substring(0, requestURL.lastIndexOf(servletPath)).concat(path);
}
}

View File

@ -0,0 +1,68 @@
package de.arminwolf.financeanalyzer.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public class XlsxToJsonConverter {
public static final String JSON_EXTENSION = ".json";
public static List<Map<String, String>> readXlsx(InputStream inputFile) {
List<Map<String, String>> data = new ArrayList<>();
ZipSecureFile.setMinInflateRatio(0.001);
try (XSSFWorkbook workbook = new XSSFWorkbook(inputFile)) {
Sheet sheet = workbook.getSheetAt(0);
Row headerRow = sheet.getRow(0);
int lastColumnNum = headerRow.getLastCellNum();
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
Map<String, String> rowData = new HashMap<>();
for (int j = 0; j < lastColumnNum; j++) {
Cell cell = row.getCell(j);
String cellValue = "";
if (Objects.nonNull(cell)) {
cellValue = cell.toString();
}
rowData.put(convert2jsonKey(headerRow.getCell(j).toString()), cellValue);
}
data.add(rowData);
}
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
private static String convert2jsonKey(final String string) {
return string.toLowerCase(Locale.GERMANY).replaceAll(" ", "-");
}
public static Optional<String> writeJson(List<Map<String, String>> data) {
try (ByteArrayOutputStream fos = new ByteArrayOutputStream()) {
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(fos, data);
return Optional.of(fos.toString());
} catch (Exception e) {
return Optional.empty();
}
}
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,137 @@
:root {
line-height: 1.6;
font-size: 0.9rem;
}
body {
font-family: Courier, monospace;
width: auto;
color: #666; /* Helle Textfarbe */
background-color: #fff;
margin: 0;
padding: 0;
transition: background-color 0.3s ease; /* Beispiel für Übergänge */
}
.card-header {
border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;
background-color: #343a40;
}
.card-header p {
color: #333;
}
/* Überschriften */
h1, h2, h3, h4, h5, h6 {
font-weight: bold;
color: #fff; /* Helle Textfarbe für Links und Schaltflächen */
font-family: Courier, monospace;
}
a, .btn {
color: #fff; /* Helle Textfarbe für Links und Schaltflächen */
}
a:hover, .btn:hover {
background-color: #444; /* Dunklere Hintergrundfarbe beim Hovern über Links und Schaltflächen */
}
/* Texte */
p, span, label {
line-height: 1.25;
font-family: Courier, monospace;
}
/* Links */
a {
color: #007bff;
}
.red {
color: red;
}
.green {
color: green;
}
/* Fehlermeldungen */
.error {
color: #dc3545;
font-size: 14px;
}
.content-section {
width: 85%;
margin: 0 auto;
padding: 5em;
}
hr {
margin-top: 1rem;
margin-bottom: 1rem;
border: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
/* Container */
.container {
margin: 2em auto;
padding: 2em;
background-color: #fff;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3);
font-family: Courier, monospace;
}
.flex-container {
display: flex;
justify-content: space-between;
}
ul {
list-style: none;
margin: 0;
padding: 0;
font-family: Courier, monospace;
}
.scrollable-table {
overflow: auto; /* Scrollen aktivieren, wenn der Inhalt überläuft */
}
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
th, td {
padding: 5px;
text-align: right;
}
th {
background-color: #f2f2f2;
color: #333;
font-weight: bold;
text-align: right;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f5f5f5;
}
/* Definiert die Trennlinie für die Zeile */
tr.border-row td {
border-top: 1px solid darkgrey;
}
[data-analysis-value^="-"] {
color: red;
}
[data-analysis-value$="EUR"]:not([data-analysis-value^="-"]) {
color: green;
}

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error 404 - Page not found</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="../../static/css/styles.css" th:href="@{/css/style.css}"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"/>
</head>
<body>
<main class="container">
<div class="row">
<div class="col-md-12 text-center">
<h1>Error - Page not found</h1>
<hr>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error 404 - Page not found</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="../../static/css/styles.css" th:href="@{/css/style.css}"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"/>
</head>
<body>
<main class="container">
<div class="row">
<div class="col-md-12 text-center">
<h1>Internal Server Error</h1>
<hr>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,7 @@
<div layout:fragment="scripts(id, data)">
<script th:inline="javascript">
/*<![CDATA[*/
Highcharts.chart( /*[[${id}]]*/, /*[[${data}]]*/);
/*]]>*/
</script>
</div>

View File

@ -0,0 +1,19 @@
<head xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${title}">Dashboard</title>
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"/>
<link rel="stylesheet" th:href="@{/css/styles.css}"/>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
<script src="https://code.highcharts.com/highcharts-more.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</head>

View File

@ -0,0 +1,16 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="/upload/"><i style='font-size:24px' class='fas'>&#xf201;</i></a>
<button class="navbar-toggler" type="button"
data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="/upload/">Upload</a>
</li>
</ul>
</div>
</nav>

View File

@ -0,0 +1,3 @@
<div class="page-header" xmlns:th="http://www.thymeleaf.org">
<h1 th:text="${title}">Title</h1>
</div>

View File

@ -0,0 +1,15 @@
<table layout:fragment="table(data)">
<thead>
<tr>
<th th:each="header : ${data.getHeaders()}" th:text="${header}">Betrag</th>
</tr>
</thead>
<tbody>
<tr th:each="row : ${data.getRows()}">
<td th:each="column : ${row}"
th:text="${column}"
th:attr="data-analysis-value=${column}"
></td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<!-- Header Fragment -->
<th:block layout:insert="~{fragments/html_head}"/>
<body>
<th:block layout:replace="~{fragments/navigation}"/>
<div class="container-fluid mt-3">
<th:block layout:insert="~{fragments/page_title}"/>
<th:block layout:fragment="content"/>
</div>
</body>
</html>

View File

@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:with="http://www.thymeleaf.org/extras/with"
layout:decorate="~{layout}"
with:title="Reports">
<th:block layout:fragment="content">
<div class="container-fluid">
<div class="row justify-content-center mb-3">
<div class="col-md-12">
<div class="card ">
<div class="card-header bg-dark align-items-center">
<span class="md-12 display-5 text-light">Kontodaten</span>
</div>
<div class="card-body">
<p class="md-12 display-5" th:text="'Name: ' + ${accountName}"></p>
<p class="md-12 display-5" th:text="'IBAN: ' + ${iban}"></p>
<p class="md-12 display-5" th:text="'Datum: ' + ${date}"></p>
</div>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark align-items-center">
<span class="md-12 display-5 text-light">Cashflow Analyse</span>
</div>
<div class="card-body scrollable-table">
<div layout:replace="~{fragments/table :: table(data=${cashFlow})}"></div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<!-- Chart -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-dark align-items-center">
<span class="md-12 display-5 text-light">Kontostand Verlauf</span>
</div>
<div class="card-body">
<div id="line-chart-container"></div>
</div>
</div>
</div>
<!-- Chart End -->
<!-- Chart -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-dark align-items-center">
<span class="md-12 display-5 text-light">Verteilung Einnahmen und Ausgaben</span>
</div>
<div class="card-body">
<div id="chart-container"></div>
</div>
</div>
</div>
<!-- Chart End -->,
</div>
<div class="row justify-content-center mb-3">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark align-items-center">
<span class="md-12 display-5 text-light">Einkommen</span>
</div>
<div class="card-body scrollable-table">
<div layout:replace="~{fragments/table :: table(data=${income})}"></div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark align-items-center"><span class="md-12 display-5 text-light">Verträge</span></div>
<div class="card-body scrollable-table">
<div layout:replace="~{fragments/table :: table(data=${standingOrders})}"></div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark align-items-center"><span class="md-12 display-5 text-light">Keine Verträge</span></div>
<div class="card-body scrollable-table">
<div layout:replace="~{fragments/table :: table(data=${notStandingOrders})}"></div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<!-- Chart -->
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark align-items-center"><span class="md-12 display-5 text-light">Kategorien der Ausgaben</span></div>
<div class="card-body">
<div id="bubble-chart-container"></div>
</div>
</div>
</div>
<!-- Chart End -->
</div>
</div>
<div layout:replace="~{fragments/body_end_scripts :: scripts(id=${'bubble-chart-container'}, data=${transactionCategories})}"/>
<div layout:replace="~{fragments/body_end_scripts :: scripts(id=${'chart-container'}, data=${incomeExpenseColumnReport})}"/>
<div layout:replace="~{fragments/body_end_scripts :: scripts(id=${'line-chart-container'}, data=${accountBalanceLineChart})}"/>
</th:block>

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:with="http://www.thymeleaf.org/extras/with"
layout:decorate="~{layout}"
with:title="Upload">
<th:block layout:fragment="content">
<div class="container-fluid">
<div class="row justify-content-center" th:if="${foundJsonFile != null}">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark align-items-center">
<p class="md-12 display-5 text-light">Bestehnde Daten gefunden: </p>
</div>
<div class="card-body">
<p class="md-12 display-5" th:text="${foundJsonFile}"></p>
<form action="#" th:action="@{/data/load}" th:method="post">
<div style="display: flex; flex-direction: row; justify-content: flex-start; gap: 1em;">
<select name="account">
<option th:each="account : ${accounts}" th:value="${account.iban}"
th:text="${account.bankAccountName}">
</option>
</select>
<input th:value="${#dates.format(#dates.createNow(), 'yyyy-MM')}" type="month" id="month" name="month"/>
<input type="submit"/>
</div>
</form>
</div>
</div>
</div>
</div>
</br>
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark align-items-center">
<span class="md-12 display-5 text-light">Upload</span>
</div>
<div class="card-body">
<form style="display: flex; justify-content: space-between;" action="#"
th:action="@{/data/convert/}" th:method="post" enctype="multipart/form-data">
<input type="file" name="file" required/>
<input type="submit" value="Upload"/>
</form>
</div>
</div>
</div>
</div>
</div>
</th:block>