Add BudgetAmounts
Add CategorizerController Add ReportController
This commit is contained in:
parent
373708f991
commit
09c3d932c2
58 changed files with 1640 additions and 91 deletions
23
.classpath
23
.classpath
|
|
@ -24,14 +24,6 @@
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" path="target/generated-sources/annotations">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="ignore_optional_problems" value="true"/>
|
|
||||||
<attribute name="optional" value="true"/>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
<attribute name="m2e-apt" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="test" value="true"/>
|
<attribute name="test" value="true"/>
|
||||||
|
|
@ -39,12 +31,21 @@
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" path="target/generated-sources/annotations">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="test" value="true"/>
|
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
|
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|
|
||||||
146
.factorypath
146
.factorypath
|
|
@ -1,74 +1,80 @@
|
||||||
<factorypath>
|
<factorypath>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-web/2.1.12.RELEASE/spring-boot-starter-web-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-thymeleaf/3.4.7/spring-boot-starter-thymeleaf-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter/2.1.12.RELEASE/spring-boot-starter-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter/3.4.7/spring-boot-starter-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot/2.1.12.RELEASE/spring-boot-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot/3.4.7/spring-boot-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-autoconfigure/2.1.12.RELEASE/spring-boot-autoconfigure-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-autoconfigure/3.4.7/spring-boot-autoconfigure-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-logging/2.1.12.RELEASE/spring-boot-starter-logging-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-logging/3.4.7/spring-boot-starter-logging-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-classic/1.5.18/logback-classic-1.5.18.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-to-slf4j/2.11.2/log4j-to-slf4j-2.11.2.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-to-slf4j/2.24.3/log4j-to-slf4j-2.24.3.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-api/2.11.2/log4j-api-2.11.2.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-api/2.24.3/log4j-api-2.24.3.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/jul-to-slf4j/2.0.17/jul-to-slf4j-2.0.17.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-core/5.1.13.RELEASE/spring-core-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-core/6.2.8/spring-core-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jcl/5.1.13.RELEASE/spring-jcl-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jcl/6.2.8/spring-jcl-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/yaml/snakeyaml/2.3/snakeyaml-2.3.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-json/2.1.12.RELEASE/spring-boot-starter-json-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/thymeleaf/thymeleaf-spring6/3.1.3.RELEASE/thymeleaf-spring6-3.1.3.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.10/jackson-datatype-jdk8-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/thymeleaf/thymeleaf/3.1.3.RELEASE/thymeleaf-3.1.3.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.10/jackson-datatype-jsr310-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/attoparser/attoparser/2.0.7.RELEASE/attoparser-2.0.7.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.10/jackson-module-parameter-names-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/unbescape/unbescape/1.1.6.RELEASE/unbescape-1.1.6.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-tomcat/2.1.12.RELEASE/spring-boot-starter-tomcat-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-web/3.4.7/spring-boot-starter-web-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-core/9.0.30/tomcat-embed-core-9.0.30.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-json/3.4.7/spring-boot-starter-json-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-el/9.0.30/tomcat-embed-el-9.0.30.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.18.4/jackson-datatype-jdk8-2.18.4.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.30/tomcat-embed-websocket-9.0.30.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.18.4/jackson-datatype-jsr310-2.18.4.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/validator/hibernate-validator/6.0.18.Final/hibernate-validator-6.0.18.Final.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/module/jackson-module-parameter-names/2.18.4/jackson-module-parameter-names-2.18.4.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-tomcat/3.4.7/spring-boot-starter-tomcat-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/jboss/logging/jboss-logging/3.3.3.Final/jboss-logging-3.3.3.Final.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-core/10.1.42/tomcat-embed-core-10.1.42.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-el/10.1.42/tomcat-embed-el-10.1.42.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-web/5.1.13.RELEASE/spring-web-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.42/tomcat-embed-websocket-10.1.42.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-beans/5.1.13.RELEASE/spring-beans-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-web/6.2.8/spring-web-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-webmvc/5.1.13.RELEASE/spring-webmvc-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-beans/6.2.8/spring-beans-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-context/5.1.13.RELEASE/spring-context-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/io/micrometer/micrometer-observation/1.14.8/micrometer-observation-1.14.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-expression/5.1.13.RELEASE/spring-expression-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/io/micrometer/micrometer-commons/1.14.8/micrometer-commons-1.14.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-data-jpa/2.1.12.RELEASE/spring-boot-starter-data-jpa-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-webmvc/6.2.8/spring-webmvc-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-aop/2.1.12.RELEASE/spring-boot-starter-aop-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-context/6.2.8/spring-context-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-expression/6.2.8/spring-expression-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/transaction/javax.transaction-api/1.3/javax.transaction-api-1.3.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-data-jpa/3.4.7/spring-boot-starter-data-jpa-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/orm/hibernate-core/6.6.18.Final/hibernate-core-6.6.18.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/persistence/jakarta.persistence-api/3.1.0/jakarta.persistence-api-3.1.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/hibernate-core/5.3.15.Final/hibernate-core-5.3.15.Final.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/transaction/jakarta.transaction-api/2.0.1/jakarta.transaction-api-2.0.1.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/persistence/javax.persistence-api/2.2/javax.persistence-api-2.2.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/jboss/logging/jboss-logging/3.6.1.Final/jboss-logging-3.6.1.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/javassist/javassist/3.23.2-GA/javassist-3.23.2-GA.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/common/hibernate-commons-annotations/7.0.3.Final/hibernate-commons-annotations-7.0.3.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/net/bytebuddy/byte-buddy/1.9.16/byte-buddy-1.9.16.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/io/smallrye/jandex/3.2.0/jandex-3.2.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/antlr/antlr/2.7.7/antlr-2.7.7.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/classmate/1.7.0/classmate-1.7.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/jboss/jandex/2.0.5.Final/jandex-2.0.5.Final.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/net/bytebuddy/byte-buddy/1.15.11/byte-buddy-1.15.11.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/dom4j/dom4j/2.1.1/dom4j-2.1.1.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/xml/bind/jakarta.xml.bind-api/4.0.2/jakarta.xml.bind-api-4.0.2.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/common/hibernate-commons-annotations/5.0.4.Final/hibernate-commons-annotations-5.0.4.Final.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/activation/jakarta.activation-api/2.1.3/jakarta.activation-api-2.1.3.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/jaxb-runtime/2.3.1/jaxb-runtime-2.3.1.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/jaxb-runtime/4.0.5/jaxb-runtime-4.0.5.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/txw2/2.3.1/txw2-2.3.1.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/jaxb-core/4.0.5/jaxb-core-4.0.5.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/sun/istack/istack-commons-runtime/3.0.7/istack-commons-runtime-3.0.7.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/eclipse/angus/angus-activation/2.0.2/angus-activation-2.0.2.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/jvnet/staxex/stax-ex/1.8/stax-ex-1.8.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/txw2/4.0.5/txw2-4.0.5.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/sun/xml/fastinfoset/FastInfoset/1.2.15/FastInfoset-1.2.15.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/sun/istack/istack-commons-runtime/4.1.2/istack-commons-runtime-4.1.2.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-jpa/2.1.15.RELEASE/spring-data-jpa-2.1.15.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-commons/2.1.15.RELEASE/spring-data-commons-2.1.15.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/antlr/antlr4-runtime/4.13.0/antlr4-runtime-4.13.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-orm/5.1.13.RELEASE/spring-orm-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-jpa/3.4.7/spring-data-jpa-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-tx/5.1.13.RELEASE/spring-tx-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-commons/3.4.7/spring-data-commons-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-orm/6.2.8/spring-orm-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aspects/5.1.13.RELEASE/spring-aspects-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-tx/6.2.8/spring-tx-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-jdbc/2.1.12.RELEASE/spring-boot-starter-jdbc-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aspects/6.2.8/spring-aspects-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/zaxxer/HikariCP/3.2.0/HikariCP-3.2.0.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/aspectj/aspectjweaver/1.9.24/aspectjweaver-1.9.24.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jdbc/5.1.13.RELEASE/spring-jdbc-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-jdbc/3.4.7/spring-boot-starter-jdbc-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-security/2.1.12.RELEASE/spring-boot-starter-security-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/zaxxer/HikariCP/5.1.0/HikariCP-5.1.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aop/5.1.13.RELEASE/spring-aop-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jdbc/6.2.8/spring-jdbc-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-config/5.1.7.RELEASE/spring-security-config-5.1.7.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-security/3.4.7/spring-boot-starter-security-3.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-core/5.1.7.RELEASE/spring-security-core-5.1.7.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aop/6.2.8/spring-aop-6.2.8.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-web/5.1.7.RELEASE/spring-security-web-5.1.7.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-config/6.4.7/spring-security-config-6.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-core/6.4.7/spring-security-core-6.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-crypto/6.4.7/spring-security-crypto-6.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-web/6.4.7/spring-security-web-6.4.7.jar" enabled="true" runInBatchMode="false"/>
|
||||||
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/thymeleaf/extras/thymeleaf-extras-springsecurity6/3.1.1.RELEASE/thymeleaf-extras-springsecurity6-3.1.1.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||||
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar" enabled="true" runInBatchMode="false"/>
|
||||||
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.38/lombok-1.18.38.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/io/jsonwebtoken/jjwt/0.9.0/jjwt-0.9.0.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/io/jsonwebtoken/jjwt/0.9.0/jjwt-0.9.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-databind/2.9.10.2/jackson-databind-2.9.10.2.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-databind/2.18.4/jackson-databind-2.18.4.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-annotations/2.9.10/jackson-annotations-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-annotations/2.18.4/jackson-annotations-2.18.4.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.9.10/jackson-core-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.18.4.1/jackson-core-2.18.4.1.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/mysql/mysql-connector-java/8.0.19/mysql-connector-java-8.0.19.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/com/mysql/mysql-connector-j/9.1.0/mysql-connector-j-9.1.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/joda-time/joda-time/2.10.5/joda-time-2.10.5.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/joda-time/joda-time/2.10.9/joda-time-2.10.9.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-csv/1.5/commons-csv-1.5.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-csv/1.5/commons-csv-1.5.jar" enabled="true" runInBatchMode="false"/>
|
||||||
|
<factorypathentry kind="VARJAR" id="M2_REPO/javax/xml/bind/jaxb-api/2.3.0/jaxb-api-2.3.0.jar" enabled="true" runInBatchMode="false"/>
|
||||||
</factorypath>
|
</factorypath>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
encoding//src/main/java=UTF-8
|
encoding//src/main/java=UTF-8
|
||||||
encoding//src/main/resources=UTF-8
|
encoding//src/main/resources=UTF-8
|
||||||
|
encoding//src/test/java=UTF-8
|
||||||
|
encoding//src/test/resources=UTF-8
|
||||||
encoding/<project>=UTF-8
|
encoding/<project>=UTF-8
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,5 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||||
org.eclipse.jdt.core.compiler.processAnnotations=enabled
|
org.eclipse.jdt.core.compiler.processAnnotations=enabled
|
||||||
org.eclipse.jdt.core.compiler.release=disabled
|
org.eclipse.jdt.core.compiler.release=enabled
|
||||||
org.eclipse.jdt.core.compiler.source=17
|
org.eclipse.jdt.core.compiler.source=17
|
||||||
|
|
|
||||||
5
pom.xml
5
pom.xml
|
|
@ -80,6 +80,11 @@
|
||||||
<artifactId>jaxb-api</artifactId>
|
<artifactId>jaxb-api</artifactId>
|
||||||
<version>2.3.0</version>
|
<version>2.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.8.5</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|
|
||||||
23
src/main/java/com/stephenschafer/budget/BudgetAmounts.java
Normal file
23
src/main/java/com/stephenschafer/budget/BudgetAmounts.java
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class BudgetAmounts {
|
||||||
|
private String year = null;
|
||||||
|
private Integer categoryId;
|
||||||
|
private BigDecimal yearBudget;
|
||||||
|
private Map<Integer, BigDecimal> monthBudgets = new HashMap<>();
|
||||||
|
|
||||||
|
public void setMonthBudget(final int monthNum, final BigDecimal amount) {
|
||||||
|
monthBudgets.put(Integer.valueOf(monthNum), amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
175
src/main/java/com/stephenschafer/budget/CatRegex.java
Normal file
175
src/main/java/com/stephenschafer/budget/CatRegex.java
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class CatRegex {
|
||||||
|
private final Pattern pattern;
|
||||||
|
private final String category;
|
||||||
|
private final String source;
|
||||||
|
private final int priority;
|
||||||
|
private final String extraDescription;
|
||||||
|
private final Integer year;
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
public CatRegex(final int id, final Pattern pattern, final String category, final String source,
|
||||||
|
final int priority, final String extraDescription, final Integer year) {
|
||||||
|
this.id = id;
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.category = category;
|
||||||
|
this.source = source;
|
||||||
|
this.priority = priority;
|
||||||
|
this.extraDescription = extraDescription;
|
||||||
|
this.year = year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CatRegex(final String line) {
|
||||||
|
final String[] parts = line.split(",");
|
||||||
|
final String category = parts[0].trim();
|
||||||
|
final String regex = parts[1].trim();
|
||||||
|
String flagsString = "";
|
||||||
|
String source = "";
|
||||||
|
int priority = 0;
|
||||||
|
String extraDescription = "";
|
||||||
|
Integer year = null;
|
||||||
|
if (parts.length > 2) {
|
||||||
|
flagsString = parts[2].trim();
|
||||||
|
if (parts.length > 3) {
|
||||||
|
source = parts[3].trim();
|
||||||
|
if (parts.length > 4) {
|
||||||
|
final String priorityString = parts[4].trim();
|
||||||
|
if (priorityString.length() > 0) {
|
||||||
|
try {
|
||||||
|
priority = Integer.parseInt(priorityString);
|
||||||
|
}
|
||||||
|
catch (final NumberFormatException e) {
|
||||||
|
System.out.println(
|
||||||
|
"Invalid priority in reegex line: " + line + "<br/>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parts.length > 5) {
|
||||||
|
extraDescription = parts[5].trim();
|
||||||
|
if (parts.length > 6) {
|
||||||
|
final String yearString = parts[6].trim();
|
||||||
|
if (yearString.length() > 0) {
|
||||||
|
try {
|
||||||
|
year = Integer.valueOf(yearString);
|
||||||
|
}
|
||||||
|
catch (final NumberFormatException e) {
|
||||||
|
System.out.println(
|
||||||
|
"Invalid year in reegex line: " + line + "<br/>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parts.length > 7) {
|
||||||
|
System.out.println("Too many parts in " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int flags = 0;
|
||||||
|
if (flagsString.indexOf("i") >= 0) {
|
||||||
|
flags |= Pattern.CASE_INSENSITIVE;
|
||||||
|
}
|
||||||
|
if (flagsString.indexOf("m") >= 0) {
|
||||||
|
flags |= Pattern.MULTILINE;
|
||||||
|
}
|
||||||
|
pattern = Pattern.compile(regex, flags);
|
||||||
|
this.category = category;
|
||||||
|
this.source = source.length() == 0 ? null : source;
|
||||||
|
this.priority = priority;
|
||||||
|
this.extraDescription = extraDescription;
|
||||||
|
this.year = year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toLine() {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(category);
|
||||||
|
sb.append(", ");
|
||||||
|
sb.append(pattern.pattern());
|
||||||
|
sb.append(", ");
|
||||||
|
String flags = "";
|
||||||
|
if ((pattern.flags() & Pattern.CASE_INSENSITIVE) != 0) {
|
||||||
|
flags += "i";
|
||||||
|
}
|
||||||
|
if ((pattern.flags() & Pattern.MULTILINE) != 0) {
|
||||||
|
flags += "m";
|
||||||
|
}
|
||||||
|
sb.append(flags);
|
||||||
|
sb.append(", ");
|
||||||
|
sb.append(source == null ? "" : source);
|
||||||
|
sb.append(", ");
|
||||||
|
sb.append(priority == 0 ? "" : String.valueOf(priority));
|
||||||
|
sb.append(", ");
|
||||||
|
sb.append(extraDescription);
|
||||||
|
sb.append(", ");
|
||||||
|
sb.append(year == null ? "" : year.toString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern getPattern() {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hashCode = 0;
|
||||||
|
if (source != null) {
|
||||||
|
hashCode += source.hashCode();
|
||||||
|
}
|
||||||
|
return hashCode + pattern.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (obj instanceof CatRegex) {
|
||||||
|
final CatRegex that = (CatRegex) obj;
|
||||||
|
if (!source.equals(that.source) || !pattern.equals(that.pattern)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(category);
|
||||||
|
sb.append(": ");
|
||||||
|
sb.append(pattern.toString());
|
||||||
|
if (source != null && source.trim().length() > 0) {
|
||||||
|
sb.append(" (" + source + ")");
|
||||||
|
}
|
||||||
|
sb.append(", ");
|
||||||
|
sb.append(priority);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtraDescription() {
|
||||||
|
return extraDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(final int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getYear() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class CategorizationResponse {
|
||||||
|
String year = null;
|
||||||
|
List<MultiplyAssignedTransaction> multiplyAssignedTransactions = new ArrayList<>();
|
||||||
|
List<UnresolvedItem> unassignedTransactions = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,362 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
import org.springframework.jdbc.core.StatementCallback;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
public class CategorizerController {
|
||||||
|
@Autowired
|
||||||
|
TransactionDao transactionDao;
|
||||||
|
@Autowired
|
||||||
|
UserService userService;
|
||||||
|
@Autowired
|
||||||
|
JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@GetMapping("/problems")
|
||||||
|
@ResponseBody
|
||||||
|
private ApiResponse<Map<String, CategorizationResponse>> getProblems(
|
||||||
|
final HttpServletRequest request)
|
||||||
|
throws SQLException, UnsupportedEncodingException, FileNotFoundException, IOException {
|
||||||
|
log.debug("GET /problems");
|
||||||
|
if (!userService.isAuthorized(request)) {
|
||||||
|
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
"You are not authorized to do this", null);
|
||||||
|
}
|
||||||
|
final Map<String, CategorizationResponse> response = new HashMap<>();
|
||||||
|
final Set<String> years = getYears();
|
||||||
|
for (final String year : years) {
|
||||||
|
final CategorizationResponse yearResponse = new CategorizationResponse();
|
||||||
|
yearResponse.year = year;
|
||||||
|
response.put(year, yearResponse);
|
||||||
|
final String uatSql = Util.getResourceAsString("getTransactionDescriptions.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year);
|
||||||
|
jdbcTemplate.execute((StatementCallback<Void>) stmt -> {
|
||||||
|
try (ResultSet rs = stmt.executeQuery(uatSql)) {
|
||||||
|
while (rs.next()) {
|
||||||
|
rs.getInt(1);
|
||||||
|
final java.sql.Date date = rs.getDate(2);
|
||||||
|
final String source = rs.getString(3);
|
||||||
|
final String description = rs.getString(4);
|
||||||
|
final BigDecimal tmpAmount = rs.getBigDecimal(5);
|
||||||
|
final BigDecimal amount = tmpAmount == null ? new BigDecimal(0) : tmpAmount;
|
||||||
|
final UnresolvedItem unresolvedItem = new UnresolvedItem(
|
||||||
|
Integer.parseInt(year), source, description, date, amount);
|
||||||
|
yearResponse.unassignedTransactions.add(unresolvedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
final String matSql = Util.getResourceAsString("getMultiplyAssignedTransactions.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year);
|
||||||
|
jdbcTemplate.execute((StatementCallback<Void>) stmt -> {
|
||||||
|
try (ResultSet rs = stmt.executeQuery(matSql)) {
|
||||||
|
final List<RegexDisplay> regexes = new ArrayList<>();
|
||||||
|
Detail detail = null;
|
||||||
|
while (rs.next()) {
|
||||||
|
final Detail lastDetail = detail;
|
||||||
|
int i = 0;
|
||||||
|
final int transId = rs.getInt(++i);
|
||||||
|
final java.sql.Date date = rs.getDate(++i);
|
||||||
|
final String source = rs.getString(++i);
|
||||||
|
final String description = rs.getString(++i);
|
||||||
|
final BigDecimal tmpAmount = rs.getBigDecimal(++i);
|
||||||
|
final BigDecimal amount = tmpAmount == null ? new BigDecimal(0) : tmpAmount;
|
||||||
|
final int regexId = rs.getInt(++i);
|
||||||
|
final int categoryId = rs.getInt(++i);
|
||||||
|
final String expression = rs.getString(++i);
|
||||||
|
final int flags = rs.getInt(++i);
|
||||||
|
final String requiredSource = rs.getString(++i);
|
||||||
|
final int priority = rs.getInt(++i);
|
||||||
|
final String extraDescription = rs.getString(++i);
|
||||||
|
final int tmpRequiredYear = rs.getInt(++i);
|
||||||
|
final Integer requiredYear = rs.wasNull() ? null
|
||||||
|
: Integer.valueOf(tmpRequiredYear);
|
||||||
|
final String fqCategoryName = null;
|
||||||
|
final Regex regex = new Regex(regexId, categoryId, expression, flags,
|
||||||
|
requiredSource, priority, extraDescription, requiredYear);
|
||||||
|
final RegexDisplay regexDisplay = new RegexDisplay(regex, fqCategoryName);
|
||||||
|
regexes.add(regexDisplay);
|
||||||
|
detail = new Detail(transId, source, description, date, amount);
|
||||||
|
if (lastDetail != null
|
||||||
|
&& lastDetail.getTransactionId() != detail.getTransactionId()) {
|
||||||
|
final MultiplyAssignedTransaction mat = new MultiplyAssignedTransaction();
|
||||||
|
mat.setRegexes(regexes);
|
||||||
|
mat.setTransaction(lastDetail);
|
||||||
|
yearResponse.multiplyAssignedTransactions.add(mat);
|
||||||
|
regexes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (detail != null) {
|
||||||
|
final MultiplyAssignedTransaction mat = new MultiplyAssignedTransaction();
|
||||||
|
mat.setRegexes(regexes);
|
||||||
|
mat.setTransaction(detail);
|
||||||
|
yearResponse.multiplyAssignedTransactions.add(mat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new ApiResponse<>(HttpStatus.OK.value(), "Problems fetched successfully", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/categorize")
|
||||||
|
@ResponseBody
|
||||||
|
private ApiResponse<Map<String, CategorizationResponse>> categorize(
|
||||||
|
final HttpServletRequest request)
|
||||||
|
throws SQLException, UnsupportedEncodingException, FileNotFoundException, IOException {
|
||||||
|
log.info("GET /categorize");
|
||||||
|
if (!userService.isAuthorized(request)) {
|
||||||
|
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
"You are not authorized to do this", null);
|
||||||
|
}
|
||||||
|
final Map<String, CategorizationResponse> response = new HashMap<>();
|
||||||
|
final Map<String, List<Detail>> categoryMap = new HashMap<>();
|
||||||
|
final Set<String> years = getYears();
|
||||||
|
final List<CatRegex> catRegexes = getCatRegexes();
|
||||||
|
final Set<UnresolvedItem> unresolvedSet = new HashSet<>();
|
||||||
|
for (final String year : years) {
|
||||||
|
final CategorizationResponse yearResponse = new CategorizationResponse();
|
||||||
|
yearResponse.year = year;
|
||||||
|
response.put(year, yearResponse);
|
||||||
|
final Map<Integer, String> extraDescriptionMap = new HashMap<>();
|
||||||
|
final Map<Integer, Integer> linkedRegexes = new HashMap<>();
|
||||||
|
jdbcTemplate.execute(Util.getResourceAsString("clearExtraDescriptions.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year));
|
||||||
|
jdbcTemplate.execute("drop table if exists ${databaseName}.transaction_regex_mtm" //
|
||||||
|
.replace("${databaseName}", "budget_" + year));
|
||||||
|
jdbcTemplate.execute(Util.getResourceAsString("createTransactionRegexTable.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year));
|
||||||
|
final String sql = Util.getResourceAsString("getTransactionDescriptions.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year);
|
||||||
|
final String insertSql = Util.getResourceAsString("insertTransactionRegex.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year);
|
||||||
|
jdbcTemplate.execute((StatementCallback<Void>) stmt -> {
|
||||||
|
try (ResultSet rs = stmt.executeQuery(sql)) {
|
||||||
|
while (rs.next()) {
|
||||||
|
final int transactionId = rs.getInt(1);
|
||||||
|
final java.sql.Date date = rs.getDate(2);
|
||||||
|
final String source = rs.getString(3);
|
||||||
|
final String description = rs.getString(4);
|
||||||
|
final BigDecimal tmpAmount = rs.getBigDecimal(5);
|
||||||
|
final BigDecimal amount = tmpAmount == null ? new BigDecimal(0) : tmpAmount;
|
||||||
|
final Detail detail = new Detail(transactionId, source, description, date,
|
||||||
|
amount);
|
||||||
|
if (description != null) {
|
||||||
|
int maxPriority = Integer.MIN_VALUE;
|
||||||
|
final Map<String, Set<CatRegex>> matchesFound = new HashMap<>();
|
||||||
|
for (final CatRegex catRegex : catRegexes) {
|
||||||
|
final String requiredSource = catRegex.getSource();
|
||||||
|
if (requiredSource == null || requiredSource.equals(source)) {
|
||||||
|
final Integer requiredYear = catRegex.getYear();
|
||||||
|
if (requiredYear == null
|
||||||
|
|| requiredYear.equals(Integer.valueOf(year))) {
|
||||||
|
final Pattern pattern = catRegex.getPattern();
|
||||||
|
final Matcher matcher = pattern.matcher(description);
|
||||||
|
final String category = catRegex.getCategory();
|
||||||
|
if (matcher.matches()) {
|
||||||
|
final int priority = catRegex.getPriority();
|
||||||
|
if (priority > maxPriority) {
|
||||||
|
maxPriority = priority;
|
||||||
|
final int regexId = catRegex.getId();
|
||||||
|
linkedRegexes.put(transactionId, regexId);
|
||||||
|
}
|
||||||
|
Set<CatRegex> set = matchesFound.get(category);
|
||||||
|
if (set == null) {
|
||||||
|
set = new HashSet<>();
|
||||||
|
matchesFound.put(category, set);
|
||||||
|
}
|
||||||
|
set.add(catRegex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchesFound.isEmpty()) {
|
||||||
|
final Calendar cal = new GregorianCalendar();
|
||||||
|
cal.setTime(date);
|
||||||
|
if (cal.get(Calendar.YEAR) > 2023) {
|
||||||
|
final UnresolvedItem unresolvedItem = new UnresolvedItem(
|
||||||
|
Integer.parseInt(year), source, description, date,
|
||||||
|
amount);
|
||||||
|
unresolvedSet.add(unresolvedItem);
|
||||||
|
yearResponse.unassignedTransactions.add(unresolvedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (matchesFound.size() > 1) {
|
||||||
|
// more than one category matches this description
|
||||||
|
// find the one(s) with the highest priority
|
||||||
|
final Map<String, Set<CatRegex>> actualMatchesFound = new HashMap<>();
|
||||||
|
for (final String category : matchesFound.keySet()) {
|
||||||
|
final Set<CatRegex> set = matchesFound.get(category);
|
||||||
|
for (final CatRegex catRegex : set) {
|
||||||
|
if (catRegex.getPriority() == maxPriority) {
|
||||||
|
Set<CatRegex> newSet = actualMatchesFound.get(category);
|
||||||
|
if (newSet == null) {
|
||||||
|
newSet = new HashSet<>();
|
||||||
|
actualMatchesFound.put(category, newSet);
|
||||||
|
}
|
||||||
|
newSet.add(catRegex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (actualMatchesFound.size() > 1) {
|
||||||
|
// more than one category matches this description and also has the max priority
|
||||||
|
// print to HTML so user can make the regex more precise
|
||||||
|
final MultiplyAssignedTransaction multiplyAssignedTransaction = new MultiplyAssignedTransaction();
|
||||||
|
multiplyAssignedTransaction.transaction = detail;
|
||||||
|
yearResponse.multiplyAssignedTransactions.add(
|
||||||
|
multiplyAssignedTransaction);
|
||||||
|
for (final String category : actualMatchesFound.keySet()) {
|
||||||
|
final Set<CatRegex> newSet = actualMatchesFound.get(
|
||||||
|
category);
|
||||||
|
for (final CatRegex catRegex : newSet) {
|
||||||
|
multiplyAssignedTransaction.regexes.add(
|
||||||
|
new RegexDisplay(catRegex));
|
||||||
|
jdbcTemplate.update(insertSql, catRegex.getId(),
|
||||||
|
transactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addTransactionToCategory(year, actualMatchesFound, categoryMap,
|
||||||
|
extraDescriptionMap, detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addTransactionToCategory(year, matchesFound, categoryMap,
|
||||||
|
extraDescriptionMap, detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
for (final Integer transactionId : extraDescriptionMap.keySet()) {
|
||||||
|
final String extraDescription = extraDescriptionMap.get(transactionId);
|
||||||
|
final String setSql = Util.getResourceAsString("setExtraDescription.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year);
|
||||||
|
jdbcTemplate.update(setSql, extraDescription, transactionId);
|
||||||
|
}
|
||||||
|
for (final Integer transactionId : linkedRegexes.keySet()) {
|
||||||
|
final Integer regexId = linkedRegexes.get(transactionId);
|
||||||
|
final String linkRegexSql = Util.getResourceAsString("updateRegexLink.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year);
|
||||||
|
try {
|
||||||
|
System.out.println("updating year " + year + ", trans " + transactionId
|
||||||
|
+ " with regex id " + regexId);
|
||||||
|
jdbcTemplate.update(linkRegexSql, regexId, transactionId);
|
||||||
|
}
|
||||||
|
catch (final Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ApiResponse<>(HttpStatus.OK.value(), "Categorization fetched successfully",
|
||||||
|
response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addTransactionToCategory(final String year, final Map<String, Set<CatRegex>> matchesFound,
|
||||||
|
final Map<String, List<Detail>> categoryMap,
|
||||||
|
final Map<Integer, String> extraDescriptionMap, final Detail detail) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
String sep = "";
|
||||||
|
for (final String category : matchesFound.keySet()) {
|
||||||
|
final Set<CatRegex> regexSet = matchesFound.get(category);
|
||||||
|
List<Detail> details = categoryMap.get(category);
|
||||||
|
if (details == null) {
|
||||||
|
details = new ArrayList<>();
|
||||||
|
categoryMap.put(category, details);
|
||||||
|
}
|
||||||
|
details.add(detail);
|
||||||
|
for (final CatRegex regex : regexSet) {
|
||||||
|
sb.append(sep);
|
||||||
|
sep = ", ";
|
||||||
|
sb.append(regex.getExtraDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extraDescriptionMap.put(detail.transactionId, sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> getYears() throws IOException, SQLException {
|
||||||
|
final String sql = Util.getResourceAsString("getYears.sql") //
|
||||||
|
.replace("${databaseName}", "budget");
|
||||||
|
final List<Integer> list = jdbcTemplate.queryForList(sql, Integer.class);
|
||||||
|
final Set<String> result = new HashSet<>();
|
||||||
|
for (final Integer year : list) {
|
||||||
|
result.add(year.toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, Category> loadCategories() throws IOException, SQLException {
|
||||||
|
final Map<Integer, Category> categories = new HashMap<>();
|
||||||
|
final String sql = Util.getResourceAsString("getCategories.sql") //
|
||||||
|
.replace("${databaseName}", "budget");
|
||||||
|
final List<Category> categoryList = jdbcTemplate.query(sql,
|
||||||
|
(RowMapper<Category>) (rs, rowNum) -> {
|
||||||
|
final int id = rs.getInt(1);
|
||||||
|
final int parentId = rs.getInt(2);
|
||||||
|
final String name = rs.getString(3);
|
||||||
|
return new Category(id, parentId, name);
|
||||||
|
});
|
||||||
|
for (final Category category : categoryList) {
|
||||||
|
categories.put(Integer.valueOf(category.id), category);
|
||||||
|
}
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CatRegex> getCatRegexes() throws IOException, SQLException {
|
||||||
|
final Map<Integer, Category> categories = loadCategories();
|
||||||
|
final String sql = Util.getResourceAsString("getRegexes.sql") //
|
||||||
|
.replace("${databaseName}", "budget");
|
||||||
|
final List<CatRegex> catRegexes = jdbcTemplate.query(sql,
|
||||||
|
(RowMapper<CatRegex>) (resultSet, rowNum) -> {
|
||||||
|
int i = 0;
|
||||||
|
final int id = resultSet.getInt(++i);
|
||||||
|
final int categoryId = resultSet.getInt(++i);
|
||||||
|
final String regex = resultSet.getString(++i);
|
||||||
|
final int flags = resultSet.getInt(++i);
|
||||||
|
final String source = resultSet.getString(++i);
|
||||||
|
final int priority = resultSet.getInt(++i);
|
||||||
|
final String description = resultSet.getString(++i);
|
||||||
|
final int yearInt = resultSet.getInt(++i);
|
||||||
|
final Integer year = resultSet.wasNull() ? null : Integer.valueOf(yearInt);
|
||||||
|
final Pattern pattern = Pattern.compile(regex, flags);
|
||||||
|
final Category category = categories.get(categoryId);
|
||||||
|
final String categoryName = category == null ? null
|
||||||
|
: category.getFullName(categories);
|
||||||
|
final CatRegex catRegex = new CatRegex(id, pattern, categoryName, source, priority,
|
||||||
|
description, year);
|
||||||
|
return catRegex;
|
||||||
|
});
|
||||||
|
return catRegexes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.stephenschafer.budget;
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
@ -17,6 +19,19 @@ public class Category {
|
||||||
this(intValue, category.getParentCategoryId(), category.getName());
|
this(intValue, category.getParentCategoryId(), category.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getFullName(final Map<Integer, Category> categories) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
if (parentCategoryId != null) {
|
||||||
|
final Category parentCategory = categories.get(parentCategoryId);
|
||||||
|
if (parentCategory != null) {
|
||||||
|
sb.append(parentCategory.getFullName(categories));
|
||||||
|
sb.append(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(name);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
Integer id;
|
Integer id;
|
||||||
Integer parentCategoryId;
|
Integer parentCategoryId;
|
||||||
String name;
|
String name;
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ public class CategoryDaoImpl implements CategoryDao {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Category> getCategoryAncestry(final int categoryId) {
|
public List<Category> getCategoryAncestry(final int categoryId) {
|
||||||
return getCategoryAncestry(null, categoryId);
|
return getCategoryAncestry(new ArrayList<>(), categoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Category> getCategoryAncestry(final List<Category> ancestry,
|
private List<Category> getCategoryAncestry(final List<Category> ancestry,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ public class CustomCorsConfiguration implements CorsConfigurationSource {
|
||||||
@Override
|
@Override
|
||||||
public CorsConfiguration getCorsConfiguration(final HttpServletRequest request) {
|
public CorsConfiguration getCorsConfiguration(final HttpServletRequest request) {
|
||||||
final CorsConfiguration config = new CorsConfiguration();
|
final CorsConfiguration config = new CorsConfiguration();
|
||||||
config.setAllowedOrigins(List.of("http://localhost:3000"));
|
config.setAllowedOrigins(
|
||||||
|
List.of("http://localhost:3000", "http://localhost:3001", "http://kirk:3001"));
|
||||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||||
config.setAllowedHeaders(List.of("*"));
|
config.setAllowedHeaders(List.of("*"));
|
||||||
return config;
|
return config;
|
||||||
|
|
|
||||||
21
src/main/java/com/stephenschafer/budget/Detail.java
Normal file
21
src/main/java/com/stephenschafer/budget/Detail.java
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
class Detail {
|
||||||
|
int transactionId;
|
||||||
|
String source;
|
||||||
|
String description;
|
||||||
|
Date date;
|
||||||
|
BigDecimal amount;
|
||||||
|
}
|
||||||
|
|
@ -19,8 +19,8 @@ import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class JwtTokenUtil implements Serializable {
|
public class JwtTokenUtil implements Serializable {
|
||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
public String getUsernameFromToken(final String token) {
|
public String getUsernameFromToken(final String token) {
|
||||||
return getClaimFromToken(token, Claims::getSubject);
|
return getClaimFromToken(token, Claims::getSubject);
|
||||||
|
|
@ -51,11 +51,13 @@ public class JwtTokenUtil implements Serializable {
|
||||||
private String doGenerateToken(final String subject) {
|
private String doGenerateToken(final String subject) {
|
||||||
final Claims claims = Jwts.claims().setSubject(subject);
|
final Claims claims = Jwts.claims().setSubject(subject);
|
||||||
claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
|
claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
|
||||||
return Jwts.builder().setClaims(claims).setIssuer("http://stephenschafer.com").setIssuedAt(
|
return Jwts.builder() //
|
||||||
new Date(System.currentTimeMillis())).setExpiration(
|
.setClaims(claims) //
|
||||||
new Date(System.currentTimeMillis()
|
.setIssuer("http://stephenschafer.com") //
|
||||||
+ ACCESS_TOKEN_VALIDITY_SECONDS * 1000L)).signWith(SignatureAlgorithm.HS256,
|
.setIssuedAt(new Date(System.currentTimeMillis())) //
|
||||||
SIGNING_KEY).compact();
|
.setExpiration(
|
||||||
|
new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS * 1000L)) //
|
||||||
|
.signWith(SignatureAlgorithm.HS256, SIGNING_KEY).compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean validateToken(final String token, final UserDetails userDetails) {
|
public Boolean validateToken(final String token, final UserDetails userDetails) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class MultiplyAssignedTransaction {
|
||||||
|
Detail transaction = null;
|
||||||
|
List<RegexDisplay> regexes = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import java.util.Optional;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
@ -90,6 +91,27 @@ public class RegexController {
|
||||||
return new ApiResponse<>(HttpStatus.OK.value(), "Regex retrieved successfully", regex);
|
return new ApiResponse<>(HttpStatus.OK.value(), "Regex retrieved successfully", regex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/regex/{regexId}")
|
||||||
|
@ResponseBody
|
||||||
|
public ApiResponse<Regex> deleteRegex(@PathVariable(required = true) final Integer regexId,
|
||||||
|
final HttpServletRequest request) {
|
||||||
|
log.info("DELETE /regex/" + regexId);
|
||||||
|
if (!userService.isAuthorized(request)) {
|
||||||
|
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
"You are not authorized to do this", null);
|
||||||
|
}
|
||||||
|
if (regexId == null) {
|
||||||
|
return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Regex ID not specified",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
final Optional<Regex> optionalRegex = regexDao.getById(regexId.intValue());
|
||||||
|
if (!optionalRegex.isPresent()) {
|
||||||
|
return new ApiResponse<>(HttpStatus.NOT_FOUND.value(), "Regex ID not found", null);
|
||||||
|
}
|
||||||
|
regexDao.deleteById(regexId.intValue());
|
||||||
|
return new ApiResponse<>(HttpStatus.OK.value(), "Regex deleted successfully", null);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/regexes/category/{categoryName}")
|
@GetMapping("/regexes/category/{categoryName}")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public ApiResponse<List<Regex>> getRegexesByCategory(
|
public ApiResponse<List<Regex>> getRegexesByCategory(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,13 @@ public class RegexDisplay {
|
||||||
regex.priority, regex.description, regex.year);
|
regex.priority, regex.description, regex.year);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RegexDisplay(final CatRegex catRegex) {
|
||||||
|
this(catRegex.getId(), (Integer) null, catRegex.getCategory(),
|
||||||
|
catRegex.getPattern().pattern(), catRegex.getPattern().flags(),
|
||||||
|
catRegex.getSource(), catRegex.getPriority(), catRegex.getExtraDescription(),
|
||||||
|
catRegex.getYear());
|
||||||
|
}
|
||||||
|
|
||||||
Integer id;
|
Integer id;
|
||||||
Integer categoryId;
|
Integer categoryId;
|
||||||
String fqCategoryName;
|
String fqCategoryName;
|
||||||
|
|
|
||||||
16
src/main/java/com/stephenschafer/budget/Report.java
Normal file
16
src/main/java/com/stephenschafer/budget/Report.java
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class Report {
|
||||||
|
String year = null;
|
||||||
|
int monthCount = 0;
|
||||||
|
ReportCategory rootCategory = null;
|
||||||
|
}
|
||||||
232
src/main/java/com/stephenschafer/budget/ReportCategory.java
Normal file
232
src/main/java/com/stephenschafer/budget/ReportCategory.java
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class ReportCategory {
|
||||||
|
private final Integer id;
|
||||||
|
private final Integer parentId;
|
||||||
|
private final String name;
|
||||||
|
@JsonIgnore
|
||||||
|
private ReportCategory parent;
|
||||||
|
private boolean included;
|
||||||
|
private List<ReportDetail> details = new ArrayList<>();
|
||||||
|
private Map<Integer, BigDecimal> monthTotals = null;
|
||||||
|
private Map<Integer, BigDecimal> monthGrandTotals = null;
|
||||||
|
@JsonIgnore
|
||||||
|
private final Map<Integer, ReportCategory> children = new HashMap<>();
|
||||||
|
private final int monthCount;
|
||||||
|
private final BudgetAmounts budgetAmounts = new BudgetAmounts();
|
||||||
|
|
||||||
|
public ReportCategory(final Integer id, final Integer parentId, final String name,
|
||||||
|
final int monthCount) {
|
||||||
|
this.id = id;
|
||||||
|
this.parentId = parentId;
|
||||||
|
this.name = name;
|
||||||
|
this.monthCount = monthCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateParent(final Map<Integer, ReportCategory> categories) {
|
||||||
|
if (parentId == null) {
|
||||||
|
parent = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parent = categories.get(parentId);
|
||||||
|
parent.addChild(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChild(final ReportCategory category) {
|
||||||
|
children.put(category.id, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public ReportCategory getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(final ReportCategory parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getParentId() {
|
||||||
|
return parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQualifiedName() {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
if (parent != null) {
|
||||||
|
sb.append(parent.getQualifiedName());
|
||||||
|
sb.append(".");
|
||||||
|
}
|
||||||
|
sb.append(name);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ReportDetail> getDetails() {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDetails(final List<ReportDetail> details) {
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDetail(final ReportDetail detail) {
|
||||||
|
details.add(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, ReportCategory> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getDetailTotal() {
|
||||||
|
var amount = new BigDecimal(0);
|
||||||
|
for (final ReportDetail detail : details) {
|
||||||
|
amount = amount.add(detail.amount);
|
||||||
|
}
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMonthTotal(final int month) {
|
||||||
|
var amount = new BigDecimal(0);
|
||||||
|
for (final ReportDetail detail : details) {
|
||||||
|
final Calendar cal = new GregorianCalendar();
|
||||||
|
cal.setTime(detail.date);
|
||||||
|
if (month == cal.get(Calendar.MONTH)) {
|
||||||
|
amount = amount.add(detail.amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, BigDecimal> getMonthTotals() {
|
||||||
|
Map<Integer, BigDecimal> monthTotals = this.monthTotals;
|
||||||
|
if (monthTotals == null) {
|
||||||
|
monthTotals = new HashMap<>();
|
||||||
|
for (final ReportDetail detail : details) {
|
||||||
|
final Calendar cal = new GregorianCalendar();
|
||||||
|
cal.setTime(detail.date);
|
||||||
|
final int month = cal.get(Calendar.MONTH);
|
||||||
|
BigDecimal monthTotal = monthTotals.get(month);
|
||||||
|
if (monthTotal == null) {
|
||||||
|
monthTotal = new BigDecimal(0);
|
||||||
|
}
|
||||||
|
monthTotals.put(month, monthTotal.add(detail.amount));
|
||||||
|
}
|
||||||
|
this.monthTotals = monthTotals;
|
||||||
|
}
|
||||||
|
return monthTotals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getGrandTotal() {
|
||||||
|
var total = getDetailTotal();
|
||||||
|
for (final Integer categoryId : children.keySet()) {
|
||||||
|
final var category = children.get(categoryId);
|
||||||
|
total = total.add(category.getGrandTotal());
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getGrandAverage() {
|
||||||
|
final BigDecimal grandTotal = getGrandTotal();
|
||||||
|
if (monthCount == 0) {
|
||||||
|
return new BigDecimal(0);
|
||||||
|
}
|
||||||
|
return grandTotal.divide(new BigDecimal(monthCount), RoundingMode.HALF_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, BigDecimal> getMonthGrandTotals() {
|
||||||
|
Map<Integer, BigDecimal> monthGrandTotals = this.monthGrandTotals;
|
||||||
|
if (monthGrandTotals == null) {
|
||||||
|
monthGrandTotals = this.getMonthTotals();
|
||||||
|
for (final Integer categoryId : children.keySet()) {
|
||||||
|
final var childCategory = children.get(categoryId);
|
||||||
|
final Map<Integer, BigDecimal> childMonthGrandTotals = childCategory.getMonthGrandTotals();
|
||||||
|
for (final Integer monthNum : childMonthGrandTotals.keySet()) {
|
||||||
|
final BigDecimal childMonthGrandTotal = childMonthGrandTotals.get(monthNum);
|
||||||
|
if (childMonthGrandTotal != null) {
|
||||||
|
final BigDecimal previousValue = monthGrandTotals.containsKey(monthNum)
|
||||||
|
? monthGrandTotals.get(monthNum)
|
||||||
|
: new BigDecimal(0);
|
||||||
|
monthGrandTotals.put(monthNum, previousValue.add(childMonthGrandTotal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.monthGrandTotals = monthGrandTotals;
|
||||||
|
}
|
||||||
|
return monthGrandTotals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLargestMonth() {
|
||||||
|
final Map<Integer, BigDecimal> monthGrandTotals = this.getMonthGrandTotals();
|
||||||
|
BigDecimal max = new BigDecimal(0);
|
||||||
|
int maxMonth = -1;
|
||||||
|
int maxCount = 0;
|
||||||
|
for (int month = 0; month < 12; month++) {
|
||||||
|
BigDecimal monthAmount = monthGrandTotals.get(month);
|
||||||
|
if (monthAmount == null) {
|
||||||
|
monthAmount = new BigDecimal(0);
|
||||||
|
}
|
||||||
|
if (monthAmount.compareTo(max) == 0) {
|
||||||
|
maxCount++;
|
||||||
|
}
|
||||||
|
else if (monthAmount.compareTo(max) > 0) {
|
||||||
|
max = monthAmount;
|
||||||
|
maxMonth = month;
|
||||||
|
maxCount = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxCount > 1 ? -1 : maxMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ReportCategory> getChildCategories() {
|
||||||
|
final List<ReportCategory> categories = new ArrayList<>();
|
||||||
|
for (final Integer categoryId : children.keySet()) {
|
||||||
|
final var category = children.get(categoryId);
|
||||||
|
categories.add(category);
|
||||||
|
}
|
||||||
|
categories.sort((arg0, arg1) -> {
|
||||||
|
final var name0 = arg0.getName();
|
||||||
|
final var name1 = arg1.getName();
|
||||||
|
final var comparison = name0.compareTo(name1);
|
||||||
|
if (comparison != 0) {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
final var id0 = arg0.getId();
|
||||||
|
final var id1 = arg1.getId();
|
||||||
|
return id0.compareTo(id1);
|
||||||
|
});
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIncluded() {
|
||||||
|
return included;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIncluded(final boolean include) {
|
||||||
|
this.included = include;
|
||||||
|
}
|
||||||
|
}
|
||||||
272
src/main/java/com/stephenschafer/budget/ReportController.java
Normal file
272
src/main/java/com/stephenschafer/budget/ReportController.java
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
public class ReportController {
|
||||||
|
@Autowired
|
||||||
|
TransactionDao transactionDao;
|
||||||
|
@Autowired
|
||||||
|
UserService userService;
|
||||||
|
@Autowired
|
||||||
|
JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@PutMapping("/budget")
|
||||||
|
@ResponseBody
|
||||||
|
public ApiResponse<Report> putBudget(@RequestBody final Map<String, Report> reports,
|
||||||
|
final HttpServletRequest request) {
|
||||||
|
if (!userService.isAuthorized(request)) {
|
||||||
|
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
"You are not authorized to do this", null);
|
||||||
|
}
|
||||||
|
categoryDao.update(report);
|
||||||
|
return new ApiResponse<>(HttpStatus.OK.value(), "Category updated successfully", report);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/report")
|
||||||
|
@ResponseBody
|
||||||
|
private ApiResponse<Map<String, Report>> report(
|
||||||
|
@RequestParam(required = false) List<String> years,
|
||||||
|
@RequestParam(required = false, name = "startDate") final String startDateString,
|
||||||
|
@RequestParam(required = false, name = "endDate") final String endDateString,
|
||||||
|
@RequestParam(required = false) final List<String> includes,
|
||||||
|
final HttpServletRequest request)
|
||||||
|
throws SQLException, UnsupportedEncodingException, FileNotFoundException, IOException {
|
||||||
|
log.info("GET /report");
|
||||||
|
if (!userService.isAuthorized(request)) {
|
||||||
|
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
"You are not authorized to do this", null);
|
||||||
|
}
|
||||||
|
if (years == null) {
|
||||||
|
years = getYears();
|
||||||
|
}
|
||||||
|
var tmpIncludeExpenses = true;
|
||||||
|
var tmpIncludeIncome = true;
|
||||||
|
var tmpIncludePayments = false;
|
||||||
|
var tmpIncludeInvestments = false;
|
||||||
|
if (includes != null && !includes.isEmpty()) {
|
||||||
|
tmpIncludeExpenses = false;
|
||||||
|
tmpIncludeIncome = false;
|
||||||
|
tmpIncludePayments = false;
|
||||||
|
tmpIncludeInvestments = false;
|
||||||
|
for (final String include : includes) {
|
||||||
|
if ("expenses".equalsIgnoreCase(include)) {
|
||||||
|
tmpIncludeExpenses = true;
|
||||||
|
}
|
||||||
|
else if ("income".equalsIgnoreCase(include)) {
|
||||||
|
tmpIncludeIncome = true;
|
||||||
|
}
|
||||||
|
else if ("payments".equalsIgnoreCase(include)) {
|
||||||
|
tmpIncludePayments = true;
|
||||||
|
}
|
||||||
|
else if ("investments".equalsIgnoreCase(include)) {
|
||||||
|
tmpIncludeInvestments = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final var includeExpenses = tmpIncludeExpenses;
|
||||||
|
final var includeIncome = tmpIncludeIncome;
|
||||||
|
final var includePayments = tmpIncludePayments;
|
||||||
|
final var includeInvestments = tmpIncludeInvestments;
|
||||||
|
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
final Date startDate;
|
||||||
|
final Date endDate;
|
||||||
|
try {
|
||||||
|
startDate = startDateString == null ? null : df.parse(startDateString);
|
||||||
|
endDate = endDateString == null ? null : df.parse(endDateString);
|
||||||
|
}
|
||||||
|
catch (final ParseException e) {
|
||||||
|
return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Invalid date", null);
|
||||||
|
}
|
||||||
|
final int startMonth;
|
||||||
|
if (startDate != null) {
|
||||||
|
final Calendar startCal = new GregorianCalendar();
|
||||||
|
startCal.setTime(startDate);
|
||||||
|
startMonth = startCal.get(Calendar.MONTH);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
startMonth = 0;
|
||||||
|
}
|
||||||
|
final int endMonth;
|
||||||
|
if (endDate != null) {
|
||||||
|
final Calendar endCal = new GregorianCalendar();
|
||||||
|
endCal.setTime(endDate);
|
||||||
|
endMonth = endCal.get(Calendar.MONTH);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
endMonth = 11;
|
||||||
|
}
|
||||||
|
final int monthCount = endMonth - startMonth + 1;
|
||||||
|
final Map<String, Report> response = new HashMap<>();
|
||||||
|
for (final String year : years) {
|
||||||
|
final var categoryMap = loadCategories(year, includeIncome, includePayments,
|
||||||
|
includeInvestments, includeExpenses, monthCount);
|
||||||
|
final String createBudgetTableSql = Util.getResourceAsString(
|
||||||
|
"createIfNotExistBudgetAmount.sql");
|
||||||
|
jdbcTemplate.update(createBudgetTableSql);
|
||||||
|
final String getBudgetAmountsSql = Util.getResourceAsString("getBudgetAmounts.sql");
|
||||||
|
jdbcTemplate.query(getBudgetAmountsSql, new Object[] {}, new int[] {},
|
||||||
|
(RowCallbackHandler) rs -> {
|
||||||
|
var i = 0;
|
||||||
|
final int categoryId = rs.getInt(++i);
|
||||||
|
final var category = categoryMap.get(categoryId);
|
||||||
|
if (category != null) {
|
||||||
|
final BudgetAmounts budgetAmounts = category.getBudgetAmounts();
|
||||||
|
budgetAmounts.setCategoryId(categoryId);
|
||||||
|
budgetAmounts.setYear(year);
|
||||||
|
budgetAmounts.setYearBudget(rs.getBigDecimal(++i));
|
||||||
|
for (int monthIndex = 0; monthIndex < 12; monthIndex++) {
|
||||||
|
budgetAmounts.setMonthBudget(monthIndex, rs.getBigDecimal(++i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final String sql = Util.getResourceAsString("getTransactionDetail.sql") //
|
||||||
|
.replace("${databaseName}", "budget_" + year) //
|
||||||
|
.replace("${regexDatabaseName}", "budget");
|
||||||
|
jdbcTemplate.query(sql, new Object[] {}, new int[] {}, (RowCallbackHandler) rs -> {
|
||||||
|
final var detail = new ReportDetail();
|
||||||
|
var i = 0;
|
||||||
|
detail.transactionId = rs.getInt(++i);
|
||||||
|
detail.date = rs.getDate(++i);
|
||||||
|
detail.source = rs.getString(++i);
|
||||||
|
detail.description = rs.getString(++i);
|
||||||
|
detail.amount = rs.getBigDecimal(++i);
|
||||||
|
if (rs.wasNull()) {
|
||||||
|
detail.amount = new BigDecimal(0);
|
||||||
|
}
|
||||||
|
final var categoryId = rs.getInt(++i);
|
||||||
|
detail.regex = rs.getString(++i);
|
||||||
|
detail.flags = rs.getInt(++i);
|
||||||
|
detail.requiredSource = rs.getString(++i);
|
||||||
|
detail.extraDescription = rs.getString(++i);
|
||||||
|
final var category = categoryMap.get(categoryId);
|
||||||
|
if (category != null && //
|
||||||
|
detail.isAfter(startDate) && //
|
||||||
|
detail.isBefore(endDate) && //
|
||||||
|
category.isIncluded()) {
|
||||||
|
category.addDetail(detail);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final var rootCategory = new ReportCategory(-1, null, "total", monthCount);
|
||||||
|
for (final Integer categoryId : categoryMap.keySet()) {
|
||||||
|
final var category = categoryMap.get(categoryId);
|
||||||
|
if ((category != null) && (category.getParent() == null)) {
|
||||||
|
rootCategory.addChild(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Report report = new Report(year, monthCount, rootCategory);
|
||||||
|
response.put(year, report);
|
||||||
|
}
|
||||||
|
return new ApiResponse<>(HttpStatus.OK.value(), "Report fetched successfully", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getYears() throws IOException, SQLException {
|
||||||
|
final String sql = Util.getResourceAsString("getYears.sql") //
|
||||||
|
.replace("${databaseName}", "budget");
|
||||||
|
final List<Integer> list = jdbcTemplate.queryForList(sql, Integer.class);
|
||||||
|
final List<String> result = new ArrayList<>();
|
||||||
|
for (final Integer year : list) {
|
||||||
|
result.add(year.toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryFilter {
|
||||||
|
boolean include(ReportCategory category);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, ReportCategory> loadCategories(final String year,
|
||||||
|
final boolean includeIncome, final boolean includePayments,
|
||||||
|
final boolean includeInvestments, final boolean includeExpenses, final int monthCount)
|
||||||
|
throws SQLException, IOException {
|
||||||
|
ReportCategory unknownCategory = null;
|
||||||
|
final Map<Integer, ReportCategory> categories = new HashMap<>();
|
||||||
|
final var sql = Util.getResourceAsString("getCategories.sql").replace("${databaseName}",
|
||||||
|
"budget");
|
||||||
|
final List<ReportCategory> list = jdbcTemplate.query(sql,
|
||||||
|
(RowMapper<ReportCategory>) (rs, rowNum) -> {
|
||||||
|
final Integer id = rs.getInt(1);
|
||||||
|
Integer parentId = rs.getInt(2);
|
||||||
|
if (rs.wasNull()) {
|
||||||
|
parentId = null;
|
||||||
|
}
|
||||||
|
final var name = rs.getString(3);
|
||||||
|
final var category = new ReportCategory(id, parentId, name, monthCount);
|
||||||
|
return category;
|
||||||
|
});
|
||||||
|
for (final ReportCategory category : list) {
|
||||||
|
categories.put(category.getId(), category);
|
||||||
|
if ("unknown".equals(category.getName()) && category.getParentId() == null) {
|
||||||
|
unknownCategory = category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final Integer categoryId : categories.keySet()) {
|
||||||
|
final var category = categories.get(categoryId);
|
||||||
|
category.updateParent(categories);
|
||||||
|
}
|
||||||
|
if (unknownCategory == null) {
|
||||||
|
unknownCategory = new ReportCategory(-1, null, "unknown", monthCount);
|
||||||
|
categories.put(unknownCategory.getId(), unknownCategory);
|
||||||
|
}
|
||||||
|
for (final Integer categoryId : categories.keySet()) {
|
||||||
|
final var category = categories.get(categoryId);
|
||||||
|
final CategoryFilter filter = category1 -> {
|
||||||
|
final String categoryName = category1.getName();
|
||||||
|
if ("income".equals(categoryName) || "refunds".equals(categoryName)) {
|
||||||
|
if (!includeIncome) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("payment".equals(categoryName)) {
|
||||||
|
if (!includePayments) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("investment".equals(categoryName)) {
|
||||||
|
if (!includeInvestments) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!includeExpenses) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
final boolean included = filter.include(category);
|
||||||
|
category.setIncluded(included);
|
||||||
|
}
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/main/java/com/stephenschafer/budget/ReportDetail.java
Normal file
80
src/main/java/com/stephenschafer/budget/ReportDetail.java
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
class ReportDetail implements Comparable<ReportDetail> {
|
||||||
|
int transactionId;
|
||||||
|
String source;
|
||||||
|
String description;
|
||||||
|
Date date;
|
||||||
|
BigDecimal amount;
|
||||||
|
String regex;
|
||||||
|
int flags;
|
||||||
|
String requiredSource;
|
||||||
|
String extraDescription;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(final ReportDetail arg1) {
|
||||||
|
final var arg0 = this;
|
||||||
|
int comparison;
|
||||||
|
if (arg0.date == null) {
|
||||||
|
if (arg1.date != null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg1.date == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
comparison = arg0.date.compareTo(arg1.date);
|
||||||
|
if (comparison != 0) {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arg0.source == null) {
|
||||||
|
if (arg1.source != null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg1.source == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
comparison = arg0.source.compareTo(arg1.source);
|
||||||
|
if (comparison != 0) {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arg0.description == null) {
|
||||||
|
if (arg1.description != null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg1.description == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
comparison = arg0.description.compareTo(arg1.description);
|
||||||
|
if (comparison != 0) {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAfter(final Date startDate) {
|
||||||
|
return startDate == null || startDate.getTime() <= this.date.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBefore(final Date endDate) {
|
||||||
|
return endDate == null || endDate.getTime() > this.date.getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/main/java/com/stephenschafer/budget/UnresolvedItem.java
Normal file
61
src/main/java/com/stephenschafer/budget/UnresolvedItem.java
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
class UnresolvedItem implements Comparable<UnresolvedItem> {
|
||||||
|
private final int year;
|
||||||
|
private final String source;
|
||||||
|
private final String description;
|
||||||
|
private final java.sql.Date date;
|
||||||
|
private final BigDecimal amount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(final UnresolvedItem that) {
|
||||||
|
if (this.year < that.year) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (this.year > that.year) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
String thisSource = source;
|
||||||
|
if (thisSource == null) {
|
||||||
|
thisSource = "";
|
||||||
|
}
|
||||||
|
String thatSource = that.source;
|
||||||
|
if (thatSource == null) {
|
||||||
|
thatSource = "";
|
||||||
|
}
|
||||||
|
final int comparison = thisSource.compareTo(thatSource);
|
||||||
|
if (comparison != 0) {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
String thisdescription = description;
|
||||||
|
if (thisdescription == null) {
|
||||||
|
thisdescription = "";
|
||||||
|
}
|
||||||
|
String thatdescription = that.description;
|
||||||
|
if (thatdescription == null) {
|
||||||
|
thatdescription = "";
|
||||||
|
}
|
||||||
|
return thisdescription.compareTo(thatdescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String replace(final String string) {
|
||||||
|
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
return string.replace("${year}", String.valueOf(year)).replace("${source}", source).replace(
|
||||||
|
"${description}", description).replace("${date}", df.format(date)).replace("${amount}",
|
||||||
|
amount.setScale(2, RoundingMode.HALF_UP).toPlainString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,22 @@
|
||||||
package com.stephenschafer.budget;
|
package com.stephenschafer.budget;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
public class Util {
|
public class Util {
|
||||||
|
public static String getResourceAsString(final String resourceName) throws IOException {
|
||||||
|
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
try (final Reader reader = new InputStreamReader(
|
||||||
|
classLoader.getResourceAsStream(resourceName))) {
|
||||||
|
final char[] buffer = new char[0x1000];
|
||||||
|
int charsRead = reader.read(buffer);
|
||||||
|
while (charsRead >= 0) {
|
||||||
|
sb.append(buffer, 0, charsRead);
|
||||||
|
charsRead = reader.read(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
select category_id, year_amount, jan_amount, feb_amount, mar_amount, apr_amount, may_amount, jun_amount,
|
||||||
|
jul_amount, aug_amount, sep_amount, oct_amount, nov_amount, dec_amount
|
||||||
|
from ${databaseName}.budget_amount
|
||||||
|
|
@ -18,3 +18,6 @@ spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.Ph
|
||||||
#server.ssl.key-alias=timesheet
|
#server.ssl.key-alias=timesheet
|
||||||
#server.ssl.key-password=foobar
|
#server.ssl.key-password=foobar
|
||||||
#server.ssl.enabled=true
|
#server.ssl.enabled=true
|
||||||
|
|
||||||
|
# this is necessary to see sql exceptions
|
||||||
|
logging.level.com.stephenschafer.budget=DEBUG
|
||||||
1
src/main/resources/clearExtraDescriptions.sql
Normal file
1
src/main/resources/clearExtraDescriptions.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
update ${databaseName}.transaction set extra_description = '', regex_id = null
|
||||||
6
src/main/resources/createCategoryTable.sql
Normal file
6
src/main/resources/createCategoryTable.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
create table ${databaseName}.category (
|
||||||
|
id int not null primary key auto_increment,
|
||||||
|
parent_category_id int,
|
||||||
|
name text not null,
|
||||||
|
constraint unique key (parent_category_id, name)
|
||||||
|
)
|
||||||
16
src/main/resources/createIfNotExistBudgetAmount.sql
Normal file
16
src/main/resources/createIfNotExistBudgetAmount.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
create table if not exists ${databaseName}.budget_amount (
|
||||||
|
category_id int not null primary key,
|
||||||
|
year_amount decimal(10,2),
|
||||||
|
jan_amount decimal(10,2),
|
||||||
|
feb_amount decimal(10,2),
|
||||||
|
mar_amount decimal(10,2),
|
||||||
|
apr_amount decimal(10,2),
|
||||||
|
may_amount decimal(10,2),
|
||||||
|
jun_amount decimal(10,2),
|
||||||
|
jul_amount decimal(10,2),
|
||||||
|
aug_amount decimal(10,2),
|
||||||
|
sep_amount decimal(10,2),
|
||||||
|
oct_amount decimal(10,2),
|
||||||
|
nov_amount decimal(10,2),
|
||||||
|
dec_amount decimal(10,2)
|
||||||
|
)
|
||||||
10
src/main/resources/createRegexTable.sql
Normal file
10
src/main/resources/createRegexTable.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
create table ${databaseName}.regex (
|
||||||
|
id int not null primary key auto_increment,
|
||||||
|
category_id int,
|
||||||
|
regex text not null,
|
||||||
|
flags int,
|
||||||
|
source varchar(32),
|
||||||
|
priority int,
|
||||||
|
description text,
|
||||||
|
year int
|
||||||
|
)
|
||||||
5
src/main/resources/createTransactionRegexTable.sql
Normal file
5
src/main/resources/createTransactionRegexTable.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
create table ${databaseName}.transaction_regex_mtm (
|
||||||
|
regex_id int not null,
|
||||||
|
transaction_id int not null,
|
||||||
|
primary key (transaction_id, regex_id)
|
||||||
|
)
|
||||||
12
src/main/resources/createTransactionTable.sql
Normal file
12
src/main/resources/createTransactionTable.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
create table ${databaseName}.transaction (
|
||||||
|
id int not null primary key auto_increment,
|
||||||
|
source varchar(32),
|
||||||
|
unique_identifier varchar(64),
|
||||||
|
type varchar(64),
|
||||||
|
description text,
|
||||||
|
extra_description text,
|
||||||
|
date date,
|
||||||
|
amount decimal(10,2),
|
||||||
|
optional int default 0,
|
||||||
|
regex_id int
|
||||||
|
)
|
||||||
3
src/main/resources/createYearsTable.sql
Normal file
3
src/main/resources/createYearsTable.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
create table ${databaseName}.years (
|
||||||
|
year int not null primary key
|
||||||
|
)
|
||||||
1
src/main/resources/findTable.sql
Normal file
1
src/main/resources/findTable.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
select table_rows from information_schema.tables where table_schema = ? and table_name = ?
|
||||||
5
src/main/resources/getCategories.sql
Normal file
5
src/main/resources/getCategories.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
select
|
||||||
|
id,
|
||||||
|
parent_category_id,
|
||||||
|
name
|
||||||
|
from ${databaseName}.category
|
||||||
8
src/main/resources/getChase.sql
Normal file
8
src/main/resources/getChase.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
select
|
||||||
|
transaction_date,
|
||||||
|
description,
|
||||||
|
category,
|
||||||
|
type,
|
||||||
|
amount
|
||||||
|
from ${databaseName}.chase
|
||||||
|
|
||||||
1
src/main/resources/getChildCategoryId.sql
Normal file
1
src/main/resources/getChildCategoryId.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
select id from ${databaseName}.category where name = ? and parent_category_id = ?
|
||||||
7
src/main/resources/getCiti.sql
Normal file
7
src/main/resources/getCiti.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
select
|
||||||
|
status,
|
||||||
|
date,
|
||||||
|
description,
|
||||||
|
debit,
|
||||||
|
credit
|
||||||
|
from ${databaseName}.citi
|
||||||
15
src/main/resources/getDigitalOrders.sql
Normal file
15
src/main/resources/getDigitalOrders.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
select
|
||||||
|
m.monetary_component_type_code as type,
|
||||||
|
i.${productNameCol} as name,
|
||||||
|
i.digital_order_item_id as order_id,
|
||||||
|
i.order_date as date,
|
||||||
|
sum(m.transaction_amount) as amount
|
||||||
|
from ${databaseName}.${digOrdItems} i
|
||||||
|
left outer join ${databaseName}.${digOrders} o on o.order_id = i.order_id
|
||||||
|
left outer join ${databaseName}.${digOrdersMonetary} m on m.digital_order_item_id = i.digital_order_item_id
|
||||||
|
group by
|
||||||
|
i.${productNameCol},
|
||||||
|
i.digital_order_item_id,
|
||||||
|
i.order_date,
|
||||||
|
m.monetary_component_type_code
|
||||||
|
|
||||||
14
src/main/resources/getDigitalReturns.sql
Normal file
14
src/main/resources/getDigitalReturns.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
select
|
||||||
|
m.monetary_component_type as type,
|
||||||
|
i.${productNameCol} as name,
|
||||||
|
i.digital_order_item_id as order_id,
|
||||||
|
i.order_date as date,
|
||||||
|
-sum(m.transaction_amount) as amount
|
||||||
|
from ${databaseName}.${digOrdItems} i
|
||||||
|
inner join ${databaseName}.${digOrdReturnsMonetary} m on m.digital_order_item_id = i.digital_order_item_id
|
||||||
|
group by
|
||||||
|
i.${productNameCol},
|
||||||
|
i.digital_order_item_id,
|
||||||
|
i.order_date,
|
||||||
|
m.monetary_component_type
|
||||||
|
|
||||||
7
src/main/resources/getDiscover.sql
Normal file
7
src/main/resources/getDiscover.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
select
|
||||||
|
trans_date,
|
||||||
|
post_date,
|
||||||
|
description,
|
||||||
|
amount,
|
||||||
|
category
|
||||||
|
from ${databaseName}.discover
|
||||||
6
src/main/resources/getFirstBank.sql
Normal file
6
src/main/resources/getFirstBank.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
select
|
||||||
|
date,
|
||||||
|
description,
|
||||||
|
type,
|
||||||
|
amount
|
||||||
|
from ${databaseName}.first_bank
|
||||||
5
src/main/resources/getMultiplyAssignedTransactions.sql
Normal file
5
src/main/resources/getMultiplyAssignedTransactions.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
select t.id, t.date, t.source, t.description, t.amount,
|
||||||
|
r.id, r.category_id, r.regex, r.flags, r.source, r.priority, r.description, r.year
|
||||||
|
from ${databaseName}.transaction t
|
||||||
|
inner join ${databaseName}.transaction_regex_mtm tr on tr.transaction_id = t.id
|
||||||
|
inner join budget.regex r on r.id = tr.regex_id
|
||||||
13
src/main/resources/getPaypal.sql
Normal file
13
src/main/resources/getPaypal.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
select
|
||||||
|
date,
|
||||||
|
time,
|
||||||
|
time_zone,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
status,
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
receipt_id,
|
||||||
|
balance
|
||||||
|
from ${databaseName}.paypal
|
||||||
|
|
||||||
10
src/main/resources/getRegexes.sql
Normal file
10
src/main/resources/getRegexes.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
select
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
regex,
|
||||||
|
flags,
|
||||||
|
source,
|
||||||
|
priority,
|
||||||
|
description,
|
||||||
|
year
|
||||||
|
from ${databaseName}.regex
|
||||||
8
src/main/resources/getRetailOrders.sql
Normal file
8
src/main/resources/getRetailOrders.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
select
|
||||||
|
website as website,
|
||||||
|
product_name as name,
|
||||||
|
order_id as order_id,
|
||||||
|
date(order_date) as date,
|
||||||
|
total_owed as amount
|
||||||
|
from ${databaseName}.${retailOrders} o
|
||||||
|
|
||||||
1
src/main/resources/getRootCategoryId.sql
Normal file
1
src/main/resources/getRootCategoryId.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
select id from ${databaseName}.category where name = ? and parent_category_id is null
|
||||||
3
src/main/resources/getTransactionDescriptions.sql
Normal file
3
src/main/resources/getTransactionDescriptions.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
select t.id, t.date, t.source, t.description, t.amount
|
||||||
|
from ${databaseName}.transaction t
|
||||||
|
where t.regex_id is null
|
||||||
3
src/main/resources/getTransactionDetail.sql
Normal file
3
src/main/resources/getTransactionDetail.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
select t.id, t.date, t.source, t.description, t.amount, r.category_id, r.regex, r.flags, r.source, r.description
|
||||||
|
from ${databaseName}.transaction t
|
||||||
|
inner join ${regexDatabaseName}.regex r on r.id = t.regex_id
|
||||||
1
src/main/resources/getTransactionRegex.sql
Normal file
1
src/main/resources/getTransactionRegex.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
select regex_id from ${databaseName}.transaction_regex_mtm where transaction_id = ?
|
||||||
3
src/main/resources/getYears.sql
Normal file
3
src/main/resources/getYears.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
select
|
||||||
|
year
|
||||||
|
from ${databaseName}.years
|
||||||
1
src/main/resources/insertChildCategory.sql
Normal file
1
src/main/resources/insertChildCategory.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
insert into ${databaseName}.category (name, parent_category_id) values (?, ?)
|
||||||
9
src/main/resources/insertRegex.sql
Normal file
9
src/main/resources/insertRegex.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
insert into ${databaseName}.regex (
|
||||||
|
category_id,
|
||||||
|
regex,
|
||||||
|
flags,
|
||||||
|
source,
|
||||||
|
priority,
|
||||||
|
description,
|
||||||
|
year
|
||||||
|
) values (?, ?, ?, ?, ?, ?, ?)
|
||||||
1
src/main/resources/insertRootCategory.sql
Normal file
1
src/main/resources/insertRootCategory.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
insert into ${databaseName}.category (name) values (?)
|
||||||
8
src/main/resources/insertTransaction.sql
Normal file
8
src/main/resources/insertTransaction.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
insert into ${databaseName}.transaction (
|
||||||
|
source,
|
||||||
|
unique_identifier,
|
||||||
|
type,
|
||||||
|
description,
|
||||||
|
date,
|
||||||
|
amount
|
||||||
|
) values (?, ?, ?, ?, ?, ?)
|
||||||
1
src/main/resources/insertTransactionRegex.sql
Normal file
1
src/main/resources/insertTransactionRegex.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
insert into ${databaseName}.transaction_regex_mtm (regex_id, transaction_id) values (?, ?)
|
||||||
3
src/main/resources/insertYear.sql
Normal file
3
src/main/resources/insertYear.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
insert into ${databaseName}.years (
|
||||||
|
year
|
||||||
|
) values (?)
|
||||||
1
src/main/resources/setExtraDescription.sql
Normal file
1
src/main/resources/setExtraDescription.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
update ${databaseName}.transaction set extra_description = ? where id = ?
|
||||||
1
src/main/resources/updateRegexLink.sql
Normal file
1
src/main/resources/updateRegexLink.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
update ${databaseName}.transaction set regex_id = ? where id = ?
|
||||||
Loading…
Reference in a new issue