├── .gitignore
├── .travis.yml
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── java
│ └── com
│ │ └── javadevzone
│ │ └── cotas
│ │ ├── CotasApplication.java
│ │ ├── controllers
│ │ ├── AssetController.java
│ │ ├── InvestmentController.java
│ │ ├── QuotaController.java
│ │ ├── QuotaHolderController.java
│ │ └── WalletController.java
│ │ ├── dto
│ │ └── QuotaHistory.java
│ │ ├── entity
│ │ ├── Asset.java
│ │ ├── AssetHistory.java
│ │ ├── Investment.java
│ │ ├── QuotaHolder.java
│ │ ├── Wallet.java
│ │ ├── WalletHistory.java
│ │ └── enums
│ │ │ └── AssetType.java
│ │ ├── exceptions
│ │ ├── AssetHistoryNotFoundException.java
│ │ ├── AssetNotFoundException.java
│ │ ├── RequiredAttributeException.java
│ │ ├── ValoresDeFechamentoInvalidoException.java
│ │ └── WalletNotFoundException.java
│ │ ├── repository
│ │ ├── AssetHistoryRepository.java
│ │ ├── AssetRepository.java
│ │ ├── InvestmentRepository.java
│ │ ├── QuotaHolderRepository.java
│ │ ├── WalletHistoryRepository.java
│ │ └── WalletRepository.java
│ │ └── services
│ │ ├── InvestmentService.java
│ │ ├── QuotaService.java
│ │ └── WalletService.java
└── resources
│ ├── application.properties
│ └── db
│ └── migration
│ ├── V1__create-project-tables.sql
│ ├── V2__insert_assethistory_data.sql
│ └── V3__create-missing-table-columns.sql
└── test
├── java
└── com
│ └── javadevzone
│ └── cotas
│ ├── CotasApplicationTests.java
│ ├── controllers
│ ├── QuotaHolderControllerTest.java
│ └── WalletControllerTest.java
│ └── services
│ ├── InvestmentServiceTest.java
│ └── QuotaServiceTest.java
└── resources
├── application.properties
├── jhsf-asset-history.json
├── jhsf-investments.json
└── mock-investments.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | HELP.md
25 | .gradle
26 | build/
27 | !gradle/wrapper/gradle-wrapper.jar
28 | !**/src/main/**
29 | !**/src/test/**
30 |
31 | ### STS ###
32 | .apt_generated
33 | .classpath
34 | .factorypath
35 | .project
36 | .settings
37 | .springBeans
38 | .sts4-cache
39 |
40 | ### IntelliJ IDEA ###
41 | .idea
42 | *.iws
43 | *.iml
44 | *.ipr
45 | out/
46 |
47 | ### NetBeans ###
48 | /nbproject/private/
49 | /nbbuild/
50 | /dist/
51 | /nbdist/
52 | /.nb-gradle/
53 |
54 | ### VS Code ###
55 | .vscode/
56 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | script:
2 | - docker run -d --rm --name mysql-server -v cotas-mysql:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=cotas mysql:8.0
3 | - gradle check
4 | before_install:
5 | - chmod +x gradlew
6 | language: java
7 | jdk:
8 | - openjdk11
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Backend for Quotas System
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | # Descrição
24 | Calcular o lucro dos investimentos de cada participante do fundo/clube
25 |
26 | ## Rodar o Docker do Mysql
27 |
28 | * Primeiro, devemos criar o volume lógico do docker para manter os dados do sistema;
29 |
30 | ```
31 | docker volume create cotas-mysql
32 | ```
33 |
34 | * Em seguida, devemos executar o container, utilizando o volume criado como referência;
35 | * O comando abaixo, executa o container docker na porta **3306** e com a senha de *root* = **123456**;
36 |
37 | ```
38 | docker run -d --rm --name mysql-server -v cotas-mysql:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=cotas mysql:8.0
39 | ```
40 |
41 | ## API Calls Examples
42 |
43 | [Postman Documentation](https://documenter.getpostman.com/view/984544/SWTG6bCs)
44 |
45 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.springframework.boot' version '2.2.1.RELEASE'
3 | id 'io.spring.dependency-management' version '1.0.8.RELEASE'
4 | id 'org.asciidoctor.convert' version '1.5.8'
5 | id 'java'
6 | id "org.flywaydb.flyway" version "6.1.0"
7 | }
8 |
9 | group = 'com.javadevzone'
10 | version = '0.0.1-SNAPSHOT'
11 | sourceCompatibility = '11'
12 |
13 | configurations {
14 | compileOnly {
15 | extendsFrom annotationProcessor
16 | }
17 | }
18 |
19 | flyway {
20 | url = 'jdbc:mysql://localhost:3306/cotas'
21 | user = 'root'
22 | password = '123456'
23 | }
24 |
25 | repositories {
26 | mavenCentral()
27 | }
28 |
29 | ext {
30 | set('snippetsDir', file("build/generated-snippets"))
31 | }
32 |
33 | dependencies {
34 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
35 | implementation 'org.springframework.boot:spring-boot-starter-mail'
36 | implementation 'org.springframework.boot:spring-boot-starter-web'
37 | implementation 'org.flywaydb:flyway-core'
38 | compileOnly 'org.projectlombok:lombok'
39 | runtimeOnly 'mysql:mysql-connector-java'
40 | annotationProcessor 'org.projectlombok:lombok'
41 | testImplementation('org.springframework.boot:spring-boot-starter-test') {
42 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
43 | }
44 | testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
45 | testCompile group: 'com.github.tomakehurst', name: 'wiremock', version: '2.25.1', ext: 'pom'
46 | testCompile group: 'com.h2database', name: 'h2', version: '1.4.200'
47 | compile group: 'org.springframework.boot', name: 'spring-boot-devtools'
48 | compile group: 'com.zaxxer', name: 'HikariCP', version: '3.4.1'
49 | }
50 |
51 | test {
52 | outputs.dir snippetsDir
53 | useJUnitPlatform()
54 | }
55 |
56 | asciidoctor {
57 | inputs.dir snippetsDir
58 | dependsOn test
59 | }
60 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Java-DevZone/cotas-backend/31d352eb08d0df7eb430c6d37c3d1d4380dfe8f1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'cotas'
2 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/CotasApplication.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.web.servlet.config.annotation.CorsRegistry;
6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7 |
8 | @SpringBootApplication
9 | public class CotasApplication implements WebMvcConfigurer {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(CotasApplication.class, args);
13 | }
14 |
15 | @Override
16 | public void addCorsMappings(CorsRegistry registry) {
17 | registry.addMapping("/**");
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/controllers/AssetController.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.controllers;
2 |
3 | import com.javadevzone.cotas.entity.Asset;
4 | import com.javadevzone.cotas.repository.AssetRepository;
5 | import lombok.RequiredArgsConstructor;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.web.bind.annotation.*;
9 |
10 | @Slf4j
11 | @RestController
12 | @RequiredArgsConstructor
13 | @RequestMapping("/assets")
14 | public class AssetController {
15 |
16 | private final AssetRepository assetRepository;
17 |
18 | @PostMapping
19 | @ResponseStatus(HttpStatus.CREATED)
20 | public Asset create(@RequestBody final Asset asset) {
21 | Asset savedAsset = assetRepository.save(asset);
22 | log.info("Salvando Ativo {}", savedAsset);
23 |
24 | return savedAsset;
25 | }
26 |
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/controllers/InvestmentController.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.controllers;
2 |
3 | import com.javadevzone.cotas.entity.Investment;
4 | import com.javadevzone.cotas.entity.Wallet;
5 | import com.javadevzone.cotas.repository.InvestmentRepository;
6 | import lombok.RequiredArgsConstructor;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.http.HttpStatus;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.web.bind.annotation.*;
11 | import org.springframework.web.server.ResponseStatusException;
12 |
13 | import java.time.LocalDateTime;
14 | import java.util.List;
15 | import java.util.Optional;
16 |
17 | import static java.lang.String.format;
18 | import static org.springframework.http.HttpStatus.*;
19 |
20 | @Slf4j
21 | @RestController
22 | @RequiredArgsConstructor
23 | @RequestMapping("/investments")
24 | public class InvestmentController {
25 |
26 | private final InvestmentRepository investmentRepository;
27 |
28 | @PostMapping
29 | @ResponseStatus(CREATED)
30 | public Investment create(@RequestBody final Investment investment) {
31 | investment.setCreatedAt(LocalDateTime.now());
32 |
33 | return investmentRepository.save(investment);
34 | }
35 |
36 | @PutMapping
37 | @ResponseStatus(OK)
38 | public Investment update(@RequestBody final Investment investment) {
39 | investment.setUpdatedAt(LocalDateTime.now());
40 |
41 | return investmentRepository.save(investment);
42 | }
43 |
44 | @GetMapping("/{investment.id}")
45 | public ResponseEntity get(@PathVariable("investment.id") Long investmentId) {
46 | return investmentRepository
47 | .findById(investmentId)
48 | .map(investment -> ResponseEntity.status(HttpStatus.OK).body(investment))
49 | .orElseThrow(() ->
50 | new ResponseStatusException(NOT_FOUND, format("Investment com ID [%s] não foi encontrado.", investmentId)));
51 | }
52 |
53 | @GetMapping
54 | public ResponseEntity> getAllByWallet(Long walletId) {
55 | log.info("Wallet ID {}", walletId);
56 | return Optional.ofNullable(investmentRepository
57 | .findAllByWalletOrderByDateAsc(Wallet.builder().id(walletId).build()))
58 | .map(investments -> ResponseEntity.status(HttpStatus.OK).body(investments))
59 | .orElseThrow(() ->
60 | new ResponseStatusException(NOT_FOUND, format("Investments com Wallet ID [%s] não foram encontrados.", walletId)));
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/controllers/QuotaController.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.controllers;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import com.javadevzone.cotas.entity.Wallet;
5 | import com.javadevzone.cotas.entity.WalletHistory;
6 | import com.javadevzone.cotas.exceptions.AssetNotFoundException;
7 | import com.javadevzone.cotas.repository.WalletHistoryRepository;
8 | import com.javadevzone.cotas.services.QuotaService;
9 | import lombok.Getter;
10 | import lombok.RequiredArgsConstructor;
11 | import lombok.Setter;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.apache.tomcat.jni.Local;
14 | import org.springframework.web.bind.annotation.*;
15 | import org.springframework.web.server.ResponseStatusException;
16 |
17 | import java.time.LocalDate;
18 |
19 | import static org.springframework.http.HttpStatus.NOT_FOUND;
20 | import static org.springframework.http.HttpStatus.OK;
21 |
22 | @Slf4j
23 | @RestController
24 | @RequiredArgsConstructor
25 | @RequestMapping("/quota")
26 | public class QuotaController {
27 |
28 | private final QuotaService quotaService;
29 | private final WalletHistoryRepository walletHistoryRepository;
30 |
31 | @ResponseStatus(OK)
32 | @PostMapping("/{walletId}/recalculate")
33 | public WalletHistory recalculateQuotaForDate(@PathVariable Long walletId,
34 | @RequestBody QuotaForDate quotaForDate) {
35 | try {
36 | log.info("Data recebida: {}", quotaForDate.getDate());
37 | WalletHistory walletHistory = quotaService.calculateQuotaValue(walletId, quotaForDate.getDate());
38 | return walletHistoryRepository.save(walletHistory);
39 | } catch (AssetNotFoundException assetNotFound) {
40 | throw new ResponseStatusException(NOT_FOUND, assetNotFound.getMessage(), assetNotFound);
41 | }
42 | }
43 |
44 | @Getter @Setter
45 | static class QuotaForDate {
46 |
47 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
48 | private LocalDate date;
49 |
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/controllers/QuotaHolderController.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.controllers;
2 |
3 | import com.javadevzone.cotas.entity.QuotaHolder;
4 | import com.javadevzone.cotas.exceptions.RequiredAttributeException;
5 | import com.javadevzone.cotas.repository.QuotaHolderRepository;
6 | import lombok.AllArgsConstructor;
7 | import org.springframework.http.ResponseEntity;
8 | import org.springframework.web.bind.annotation.*;
9 |
10 | import java.net.URI;
11 | import java.util.Objects;
12 |
13 | import static org.springframework.http.HttpStatus.CREATED;
14 |
15 | @RestController
16 | @RequestMapping("/quotaHolder")
17 | @AllArgsConstructor
18 | public class QuotaHolderController {
19 |
20 | private final QuotaHolderRepository quotaHolderRepository;
21 |
22 | @PostMapping
23 | @ResponseStatus(CREATED)
24 | public QuotaHolder create(@RequestBody QuotaHolder quotaHolder) {
25 | if (Objects.isNull(quotaHolder.getName()))
26 | throw new RequiredAttributeException("O nome do Investidor é obrigatório");
27 |
28 | return quotaHolderRepository.save(quotaHolder);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/controllers/WalletController.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.controllers;
2 |
3 | import com.javadevzone.cotas.dto.QuotaHistory;
4 | import com.javadevzone.cotas.entity.Wallet;
5 | import com.javadevzone.cotas.repository.WalletRepository;
6 | import com.javadevzone.cotas.services.WalletService;
7 | import lombok.RequiredArgsConstructor;
8 | import org.springframework.dao.EmptyResultDataAccessException;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.MediaType;
11 | import org.springframework.http.ResponseEntity;
12 | import org.springframework.web.bind.annotation.*;
13 | import org.springframework.web.server.ResponseStatusException;
14 |
15 | import java.math.BigDecimal;
16 | import java.time.LocalDate;
17 | import java.time.LocalDateTime;
18 | import java.time.LocalTime;
19 | import java.util.List;
20 |
21 | import static java.lang.String.format;
22 | import static org.springframework.http.HttpStatus.*;
23 |
24 | @RestController
25 | @RequiredArgsConstructor
26 | @RequestMapping("/wallet")
27 | public class WalletController {
28 |
29 | private final WalletRepository walletRepository;
30 | private final WalletService walletService;
31 |
32 | @PostMapping
33 | @ResponseStatus(CREATED)
34 | public Wallet create(@RequestBody final Wallet wallet) {
35 | wallet.setQuota(BigDecimal.ONE);
36 | wallet.setCreatedAt(LocalDateTime.now());
37 |
38 | return walletRepository.save(wallet);
39 | }
40 |
41 | @PutMapping
42 | @ResponseStatus(OK)
43 | public Wallet update(@RequestBody final Wallet wallet) {
44 | wallet.setUpdatedAt(LocalDateTime.now());
45 | return walletRepository.save(wallet);
46 | }
47 |
48 | @GetMapping("/{wallet.id}")
49 | public ResponseEntity get(@PathVariable("wallet.id") Long walletId) {
50 | return walletRepository
51 | .findById(walletId)
52 | .map(wallet -> ResponseEntity.status(HttpStatus.OK).body(wallet))
53 | .orElseThrow(() ->
54 | new ResponseStatusException(NOT_FOUND, format("Wallet com ID [%s] não foi encontrada.", walletId)));
55 | }
56 |
57 | @DeleteMapping("/{walletId}")
58 | @ResponseStatus(NO_CONTENT)
59 | public void delete(@PathVariable Long walletId) {
60 | try {
61 | walletRepository.deleteById(walletId);
62 | }catch (EmptyResultDataAccessException e) {
63 | throw new ResponseStatusException(NOT_FOUND, format("Wallet com ID [%s] não foi encontrada.", walletId));
64 | }
65 | }
66 |
67 | @GetMapping("/{walletId}/quotaHistory")
68 | public List consolidar(@PathVariable Long walletId) {
69 | return walletService.calculateQuotaValueFrom(walletId, LocalDateTime.of(LocalDate.now().plusDays(-2), LocalTime.now()));
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/dto/QuotaHistory.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.dto;
2 |
3 | import lombok.*;
4 |
5 | import java.math.BigDecimal;
6 | import java.time.LocalDate;
7 | import java.util.List;
8 |
9 | @Data
10 | @AllArgsConstructor
11 | public class QuotaHistory {
12 |
13 | private String ticket;
14 | private BigDecimal quotaTotal;
15 | private Long quantity;
16 | private List dataList;
17 |
18 | private QuotaHistoryData lastHistoryData;
19 |
20 | public QuotaHistory() {
21 | this.quantity = 0L;
22 | this.quotaTotal = BigDecimal.ZERO;
23 | }
24 |
25 | public QuotaHistory(String ticket, List dataList) {
26 | this();
27 | this.ticket = ticket;
28 | this.dataList = dataList;
29 | }
30 |
31 | public void addQuantity(Long quantity) {
32 | this.quantity += quantity;
33 | }
34 |
35 | public void addQuotaTotal(BigDecimal newQuotas) {
36 | this.quotaTotal = this.quotaTotal.add(newQuotas);
37 | }
38 |
39 | @Value
40 | @Builder(toBuilder = true)
41 | @AllArgsConstructor
42 | public static class QuotaHistoryData {
43 | private LocalDate date;
44 | private BigDecimal quota;
45 | private BigDecimal value;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/entity/Asset.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.entity;
2 |
3 | import com.javadevzone.cotas.entity.enums.AssetType;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Builder;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import javax.persistence.Entity;
10 | import javax.persistence.EnumType;
11 | import javax.persistence.Enumerated;
12 | import javax.persistence.Id;
13 |
14 | @Data
15 | @AllArgsConstructor
16 | @NoArgsConstructor
17 | @Builder
18 | @Entity
19 | public class Asset {
20 |
21 | @Id
22 | private String ticket;
23 |
24 | @Enumerated(EnumType.STRING)
25 | private AssetType type;
26 |
27 | public Asset(String ticket) {
28 | this.ticket = ticket;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/entity/AssetHistory.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.entity;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import lombok.*;
5 |
6 | import javax.persistence.Entity;
7 | import javax.persistence.Id;
8 | import javax.persistence.OneToOne;
9 | import java.math.BigDecimal;
10 | import java.time.LocalDate;
11 |
12 | @Data
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | @Builder
16 | @Entity
17 | @ToString
18 | public class AssetHistory {
19 |
20 | @Id
21 | private Long id;
22 | private BigDecimal value;
23 |
24 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
25 | private LocalDate date;
26 |
27 | @OneToOne
28 | private Asset asset;
29 |
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/entity/Investment.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.entity;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import com.fasterxml.jackson.annotation.JsonManagedReference;
5 | import lombok.*;
6 |
7 | import javax.persistence.*;
8 | import javax.validation.constraints.NotNull;
9 | import java.math.BigDecimal;
10 | import java.time.LocalDate;
11 | import java.time.LocalDateTime;
12 |
13 |
14 | @Entity
15 | @Builder(toBuilder = true)
16 | @NoArgsConstructor
17 | @AllArgsConstructor
18 | @Data
19 | public class Investment {
20 |
21 | @Id
22 | @GeneratedValue(strategy = GenerationType.IDENTITY)
23 | private Long id;
24 |
25 | private BigDecimal value;
26 | private Long quantity;
27 |
28 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
29 | private LocalDate date;
30 | private LocalDateTime createdAt;
31 | private LocalDateTime updatedAt;
32 |
33 | @OneToOne
34 | @NotNull
35 | private Asset asset;
36 |
37 | @ManyToOne(fetch = FetchType.LAZY)
38 | @JoinColumn(name = "wallet_id")
39 | private Wallet wallet;
40 |
41 | @Transient
42 | public BigDecimal getInvestmentTotal() {
43 | return this.value.multiply(new BigDecimal(quantity));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/entity/QuotaHolder.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.entity;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | import javax.persistence.*;
9 | import java.time.LocalDate;
10 |
11 | @Data
12 | @Entity
13 | @Builder
14 | @NoArgsConstructor
15 | @AllArgsConstructor
16 | public class QuotaHolder {
17 |
18 | @Id
19 | @GeneratedValue(strategy = GenerationType.IDENTITY)
20 | private Long id;
21 | private String name;
22 | private LocalDate optInAt;
23 | private LocalDate optOutAt;
24 |
25 | @ManyToOne
26 | private Wallet wallet;
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/entity/Wallet.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.entity;
2 |
3 | import com.fasterxml.jackson.annotation.JsonBackReference;
4 | import com.fasterxml.jackson.annotation.JsonIgnore;
5 | import lombok.*;
6 |
7 | import javax.persistence.*;
8 | import java.math.BigDecimal;
9 | import java.time.LocalDateTime;
10 | import java.util.Set;
11 |
12 |
13 | @Entity
14 | @Builder
15 | @AllArgsConstructor
16 | @NoArgsConstructor
17 | @Getter @Setter
18 | @ToString(exclude = {"investments", "quotaHolders"})
19 | public class Wallet {
20 |
21 | @Id
22 | @GeneratedValue(strategy = GenerationType.IDENTITY)
23 | private Long id;
24 |
25 | private String name;
26 | private BigDecimal totalValue;
27 | private BigDecimal quota;
28 | private LocalDateTime quotaUpdatedAt;
29 | private LocalDateTime createdAt;
30 | private LocalDateTime updatedAt;
31 |
32 | @JsonIgnore
33 | @OneToMany(mappedBy = "wallet")
34 | private Set investments;
35 |
36 | @JsonIgnore
37 | @OneToMany(mappedBy = "wallet")
38 | private Set quotaHolders;
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/entity/WalletHistory.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.entity;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | import javax.persistence.*;
9 | import java.io.Serializable;
10 | import java.math.BigDecimal;
11 | import java.math.RoundingMode;
12 | import java.time.LocalDate;
13 |
14 | import static java.util.Objects.isNull;
15 |
16 | @Data
17 | @Entity
18 | @Builder(toBuilder = true)
19 | @NoArgsConstructor
20 | @AllArgsConstructor
21 | public class WalletHistory implements Serializable {
22 |
23 | @Id
24 | @GeneratedValue(strategy = GenerationType.IDENTITY)
25 | private Long id;
26 |
27 | @ManyToOne
28 | @JoinColumn(name = "wallet_id")
29 | private Wallet wallet;
30 |
31 | private BigDecimal quota;
32 | private BigDecimal totalQuotas;
33 |
34 | private LocalDate registerDate;
35 |
36 | @Transient
37 | public BigDecimal getWalletValue() {
38 | return this.quota.multiply(this.totalQuotas).setScale(6, RoundingMode.CEILING);
39 | }
40 |
41 | public void addTotalQuotas(BigDecimal acquiredQuotas) {
42 | if (isNull(this.totalQuotas))
43 | this.totalQuotas = BigDecimal.ZERO;
44 |
45 | this.totalQuotas = this.totalQuotas.add(acquiredQuotas);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/entity/enums/AssetType.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.entity.enums;
2 |
3 | public enum AssetType {
4 |
5 | ACAO,
6 | FUNDO,
7 | TESOURODIRETO,
8 | CDB,
9 | DEBENTURES;
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/exceptions/AssetHistoryNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.exceptions;
2 |
3 | import com.javadevzone.cotas.entity.Asset;
4 |
5 | import javax.validation.constraints.NotNull;
6 | import java.time.LocalDate;
7 |
8 | import static java.lang.String.format;
9 |
10 | public class AssetHistoryNotFoundException extends RuntimeException {
11 |
12 | public AssetHistoryNotFoundException(@NotNull Asset asset) {
13 | super(format("Não foi possível encontra um Asset History para o Ativo %s", asset));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/exceptions/AssetNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.exceptions;
2 |
3 | import com.javadevzone.cotas.entity.Asset;
4 |
5 | import javax.validation.constraints.NotNull;
6 |
7 | import static java.lang.String.format;
8 |
9 | public class AssetNotFoundException extends RuntimeException {
10 |
11 | public AssetNotFoundException(@NotNull Asset asset) {
12 | super(format("Não foi possível encontra a Asset com Ticket %s", asset.getTicket()));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/exceptions/RequiredAttributeException.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.exceptions;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(HttpStatus.BAD_REQUEST)
7 | public class RequiredAttributeException extends RuntimeException {
8 |
9 | public RequiredAttributeException(String mensagem) {
10 | super(mensagem);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/exceptions/ValoresDeFechamentoInvalidoException.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.exceptions;
2 |
3 | import java.math.BigDecimal;
4 |
5 | public class ValoresDeFechamentoInvalidoException extends RuntimeException {
6 |
7 | public ValoresDeFechamentoInvalidoException(ArithmeticException e, BigDecimal hoje, BigDecimal ontem) {
8 | super(String.format("Não foi possível calcular a variação para os fechamentos %s e %s", hoje, ontem), e);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/exceptions/WalletNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.exceptions;
2 |
3 | public class WalletNotFoundException extends RuntimeException {
4 |
5 | public WalletNotFoundException() {
6 | super("A Wallet não foi encontrada");
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/repository/AssetHistoryRepository.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.repository;
2 |
3 | import com.javadevzone.cotas.entity.Asset;
4 | import com.javadevzone.cotas.entity.AssetHistory;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.data.repository.CrudRepository;
7 | import org.springframework.stereotype.Repository;
8 |
9 | import java.time.LocalDate;
10 | import java.util.List;
11 | import java.util.Optional;
12 |
13 | @Repository
14 | public interface AssetHistoryRepository extends JpaRepository, CrudRepository {
15 |
16 | Optional findByAssetAndDate(Asset asset, LocalDate date);
17 |
18 | Optional findFirstByAssetOrderByDateDesc(Asset asset);
19 |
20 | Optional findFirstByAssetAndDate(Asset asset, LocalDate date);
21 |
22 | Optional> findAllByAssetAndDateAfterOrderByDateAsc(Asset asset, LocalDate dateTime);
23 |
24 | Optional findFirstByAssetAndDateOrderByDateDesc(Asset asset, LocalDate date);
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/repository/AssetRepository.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.repository;
2 |
3 | import com.javadevzone.cotas.entity.Asset;
4 | import com.javadevzone.cotas.entity.Wallet;
5 | import org.springframework.data.jpa.repository.Query;
6 | import org.springframework.data.repository.CrudRepository;
7 | import org.springframework.stereotype.Repository;
8 |
9 | import java.time.LocalDate;
10 | import java.util.List;
11 | import java.util.Optional;
12 |
13 | @Repository
14 | public interface AssetRepository extends CrudRepository {
15 |
16 | @Query("select distinct asset from Wallet wallet " +
17 | "join wallet.investments investment " +
18 | "join investment.asset asset " +
19 | "where wallet = :wallet")
20 | Optional> findAssetsByWallet(Wallet wallet);
21 |
22 | @Query("select distinct asset from Wallet wallet " +
23 | "join wallet.investments investment " +
24 | "join investment.asset asset " +
25 | "where wallet = :wallet " +
26 | "and investment.date <= :date ")
27 | Optional> findInvestedAssetsByDate(Wallet wallet, LocalDate date);
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/repository/InvestmentRepository.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.repository;
2 |
3 | import com.javadevzone.cotas.entity.Asset;
4 | import com.javadevzone.cotas.entity.Investment;
5 | import com.javadevzone.cotas.entity.Wallet;
6 | import org.springframework.data.jpa.repository.Query;
7 | import org.springframework.data.repository.CrudRepository;
8 | import org.springframework.stereotype.Repository;
9 |
10 | import java.math.BigDecimal;
11 | import java.time.LocalDate;
12 | import java.util.List;
13 | import java.util.Optional;
14 |
15 | @Repository
16 | public interface InvestmentRepository extends CrudRepository {
17 |
18 | Optional> findAllByAssetOrderByDateAsc(Asset asset);
19 |
20 | List findAllByWalletOrderByDateAsc(Wallet wallet);
21 |
22 | List findAllByWalletAndDate(Wallet wallet, LocalDate date);
23 |
24 | List findAllByWalletAndDateBefore(Wallet wallet, LocalDate date);
25 |
26 | @Query("select sum(i.quantity) from Investment i where i.wallet = :wallet and i.asset = :asset and i.date <= :date")
27 | Optional getQuantityByWalletAndAssetAndDateBefore(Wallet wallet, Asset asset, LocalDate date);
28 |
29 | @Query("select sum(i.quantity) from Investment i where i.wallet = :wallet and i.asset = :asset")
30 | Optional getQuantityByWalletAndAsset(Wallet wallet, Asset asset);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/repository/QuotaHolderRepository.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.repository;
2 |
3 | import com.javadevzone.cotas.entity.QuotaHolder;
4 | import org.springframework.data.repository.CrudRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | @Repository
8 | public interface QuotaHolderRepository extends CrudRepository {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/repository/WalletHistoryRepository.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.repository;
2 |
3 | import com.javadevzone.cotas.entity.Wallet;
4 | import com.javadevzone.cotas.entity.WalletHistory;
5 | import org.springframework.data.jpa.repository.Query;
6 | import org.springframework.data.repository.CrudRepository;
7 | import org.springframework.stereotype.Repository;
8 |
9 | import java.time.LocalDate;
10 | import java.util.Optional;
11 |
12 | @Repository
13 | public interface WalletHistoryRepository extends CrudRepository {
14 |
15 | Optional findFirstByWalletAndRegisterDateIsBeforeOrderByRegisterDateDesc(Wallet wallet, LocalDate registerDate);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/repository/WalletRepository.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.repository;
2 |
3 | import com.javadevzone.cotas.entity.Wallet;
4 | import org.springframework.data.repository.CrudRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | @Repository
8 | public interface WalletRepository extends CrudRepository {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/services/InvestmentService.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.services;
2 |
3 | import com.javadevzone.cotas.entity.*;
4 | import com.javadevzone.cotas.exceptions.WalletNotFoundException;
5 | import com.javadevzone.cotas.repository.AssetHistoryRepository;
6 | import com.javadevzone.cotas.repository.AssetRepository;
7 | import com.javadevzone.cotas.repository.InvestmentRepository;
8 | import lombok.RequiredArgsConstructor;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.springframework.stereotype.Service;
11 |
12 | import java.math.BigDecimal;
13 | import java.time.LocalDate;
14 | import java.util.Collections;
15 | import java.util.List;
16 | import java.util.Objects;
17 |
18 | import static java.math.BigDecimal.ZERO;
19 | import static java.math.RoundingMode.CEILING;
20 | import static java.util.Collections.emptyList;
21 |
22 | @Slf4j
23 | @Service
24 | @RequiredArgsConstructor
25 | public class InvestmentService {
26 |
27 | private final AssetRepository assetRepository;
28 | private final InvestmentRepository investmentRepository;
29 | private final AssetHistoryRepository assetHistoryRepository;
30 |
31 | public BigDecimal calculateInvestmentsProfitability(WalletHistory walletHistory, LocalDate date) {
32 | BigDecimal totalWalletValue = assetRepository.findInvestedAssetsByDate(walletHistory.getWallet(), date)
33 | .orElse(emptyList())
34 | .stream()
35 | .map(asset -> {
36 | Long quantity = investmentRepository.getQuantityByWalletAndAssetAndDateBefore(walletHistory.getWallet(), asset, date)
37 | .orElse(0L);
38 |
39 | AssetHistory assetHistory = assetHistoryRepository.findByAssetAndDate(asset, date)
40 | .orElse(AssetHistory.builder().asset(asset).value(ZERO).build());
41 | return assetHistory.getValue().multiply(new BigDecimal(quantity));
42 | })
43 | .reduce(BigDecimal::add)
44 | .orElse(ZERO);
45 |
46 | BigDecimal previousWalletValue = walletHistory.getWalletValue();
47 |
48 | if (previousWalletValue.compareTo(ZERO) == 0) {
49 | previousWalletValue = investmentRepository.findAllByWalletAndDateBefore(walletHistory.getWallet(), date)
50 | .stream()
51 | .map(Investment::getInvestmentTotal)
52 | .reduce(BigDecimal::add)
53 | .orElse(ZERO);
54 | }
55 |
56 | return calculatePercentage(previousWalletValue, totalWalletValue);
57 | }
58 |
59 | private BigDecimal calculatePercentage(BigDecimal firstValue, BigDecimal actualValue) {
60 | BigDecimal percentage = actualValue.subtract(firstValue).divide(firstValue, 8, CEILING);
61 | log.info("Percentage founded: {}", percentage);
62 |
63 | return percentage;
64 | }
65 |
66 | public Investment addInvestment(Investment investment) {
67 | if (Objects.isNull(investment.getWallet())) throw new WalletNotFoundException();
68 |
69 | investment.setDate(LocalDate.now());
70 |
71 | return investmentRepository.save(investment);
72 | }
73 |
74 |
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/services/QuotaService.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.services;
2 |
3 | import com.javadevzone.cotas.entity.Wallet;
4 | import com.javadevzone.cotas.entity.WalletHistory;
5 | import com.javadevzone.cotas.repository.WalletHistoryRepository;
6 | import com.javadevzone.cotas.repository.WalletRepository;
7 | import lombok.RequiredArgsConstructor;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.math.BigDecimal;
12 | import java.time.LocalDate;
13 | import java.util.Optional;
14 |
15 | import static java.math.BigDecimal.ONE;
16 | import static java.math.BigDecimal.ZERO;
17 | import static java.math.RoundingMode.CEILING;
18 |
19 | @Slf4j
20 | @Service
21 | @RequiredArgsConstructor
22 | public class QuotaService {
23 |
24 | private final WalletRepository walletRepository;
25 | private final WalletHistoryRepository walletHistoryRepository;
26 | private final InvestmentService investmentService;
27 |
28 | public WalletHistory calculateQuotaValue(Long walletId, LocalDate date) {
29 | Wallet wallet = walletRepository.findById(walletId)
30 | .orElse(Wallet.builder().id(walletId).build());
31 |
32 | WalletHistory walletHistory = walletHistoryRepository.findFirstByWalletAndRegisterDateIsBeforeOrderByRegisterDateDesc(wallet, date)
33 | .orElse(WalletHistory.builder().wallet(wallet).quota(ONE).totalQuotas(ZERO).build());
34 |
35 | BigDecimal profitability = investmentService.calculateInvestmentsProfitability(walletHistory, date);
36 | BigDecimal newQuotaValue = calculateQuota(walletHistory.getQuota(), profitability);
37 |
38 | return new WalletHistory(null, wallet, newQuotaValue, walletHistory.getTotalQuotas(), date);
39 | }
40 |
41 | private BigDecimal calculateQuota(BigDecimal actualQuota, BigDecimal profitability) {
42 | return actualQuota.add(actualQuota.multiply(profitability).setScale(6, CEILING));
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/javadevzone/cotas/services/WalletService.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.services;
2 |
3 | import com.javadevzone.cotas.dto.QuotaHistory;
4 | import com.javadevzone.cotas.dto.QuotaHistory.QuotaHistoryData;
5 | import com.javadevzone.cotas.entity.AssetHistory;
6 | import com.javadevzone.cotas.entity.Wallet;
7 | import com.javadevzone.cotas.exceptions.ValoresDeFechamentoInvalidoException;
8 | import com.javadevzone.cotas.repository.AssetHistoryRepository;
9 | import com.javadevzone.cotas.repository.InvestmentRepository;
10 | import com.javadevzone.cotas.repository.WalletRepository;
11 | import lombok.RequiredArgsConstructor;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.springframework.stereotype.Service;
14 |
15 | import java.math.BigDecimal;
16 | import java.math.MathContext;
17 | import java.math.RoundingMode;
18 | import java.time.LocalDate;
19 | import java.time.LocalDateTime;
20 | import java.util.ArrayList;
21 | import java.util.Collections;
22 | import java.util.List;
23 | import java.util.Optional;
24 | import java.util.stream.Collectors;
25 |
26 | import static java.util.Objects.isNull;
27 |
28 | @Service
29 | @RequiredArgsConstructor
30 | @Slf4j
31 | public class WalletService {
32 |
33 | private final AssetHistoryRepository assetHistoryRepository;
34 | private final InvestmentRepository investmentRepository;
35 | private final WalletRepository walletRepository;
36 |
37 | private final static MathContext MATH_CONTEXT = new MathContext(6, RoundingMode.HALF_UP);
38 |
39 | public Wallet consolidar(Wallet wallet) {
40 | BigDecimal resultadoFinanceiroHoje = wallet.getInvestments()
41 | .stream()
42 | .map(investment -> assetHistoryRepository.findByAssetAndDate(investment.getAsset(), LocalDate.now())
43 | .flatMap(assetHistory -> Optional.of(assetHistory.getValue().multiply(new BigDecimal(investment.getQuantity()), MATH_CONTEXT)))
44 | .orElse(BigDecimal.ZERO))
45 | .reduce(BigDecimal::add)
46 | .orElse(BigDecimal.ZERO);
47 |
48 | BigDecimal novaCota = calculaCota(wallet, resultadoFinanceiroHoje);
49 |
50 | wallet.setQuota(novaCota);
51 | wallet.setTotalValue(resultadoFinanceiroHoje);
52 | wallet.setQuotaUpdatedAt(LocalDateTime.now());
53 | log.info("{}", wallet);
54 |
55 | return wallet;
56 | }
57 |
58 | private BigDecimal calculaCota(final Wallet wallet, final BigDecimal financeiroHoje) {
59 | BigDecimal financeiroOntem = wallet.getTotalValue();
60 |
61 | try {
62 | BigDecimal variacaoCota = financeiroHoje.subtract(financeiroOntem)
63 | .divide(financeiroOntem, 6, RoundingMode.HALF_UP);
64 | BigDecimal resultado = wallet.getQuota().add(wallet.getQuota().multiply(variacaoCota)).setScale(6, RoundingMode.HALF_UP);
65 | log.info("Novo valor da cota é {}", resultado);
66 |
67 | return resultado;
68 | } catch(ArithmeticException e) {
69 | throw new ValoresDeFechamentoInvalidoException(e, financeiroHoje, financeiroOntem);
70 | }
71 | }
72 |
73 | public List calculateQuotaValueFrom(Long walletId, LocalDateTime date) {
74 | Wallet wallet = walletRepository.findById(walletId)
75 | .orElse(Wallet.builder().id(walletId).build());
76 |
77 | return investmentRepository.findAllByWalletOrderByDateAsc(wallet)
78 | .stream()
79 | .map(investment -> {
80 | List quotaHistoryData = assetHistoryRepository
81 | .findAllByAssetAndDateAfterOrderByDateAsc(investment.getAsset(), investment.getCreatedAt().toLocalDate())
82 | .flatMap(this::calculateQuotaHistory)
83 | .orElse(Collections.emptyList());
84 | return new QuotaHistory(investment.getAsset().getTicket(), quotaHistoryData);
85 | })
86 | .collect(Collectors.toList());
87 | }
88 |
89 | private Optional> calculateQuotaHistory(List assetHistories) {
90 | List dataList = new ArrayList<>();
91 | QuotaHistoryData lastValue = null;
92 |
93 | for (AssetHistory history : assetHistories) {
94 | if (isNull(lastValue)) {
95 | lastValue = new QuotaHistoryData(history.getDate(), BigDecimal.ONE, history.getValue());
96 | } else {
97 | BigDecimal newQuotaValue = calculateQuota(lastValue, history);
98 |
99 | lastValue = new QuotaHistoryData(history.getDate(), newQuotaValue, history.getValue());
100 | }
101 | dataList.add(lastValue);
102 | }
103 |
104 | return Optional.of(dataList);
105 | }
106 |
107 | private BigDecimal calculateQuota(QuotaHistoryData yesterdayValue, AssetHistory history) {
108 | BigDecimal dayVariation = calculatePercentage(yesterdayValue.getValue(), history.getValue());
109 | return yesterdayValue.getQuota().add(yesterdayValue.getQuota().multiply(dayVariation));
110 | }
111 |
112 | private BigDecimal calculatePercentage(BigDecimal firstValue, BigDecimal actualValue) {
113 | return actualValue.subtract(firstValue).divide(firstValue, 6, RoundingMode.CEILING);
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.url=jdbc:mysql://localhost:3306/cotas
2 | spring.datasource.username=root
3 | spring.datasource.password=123456
4 | server.port=8090
5 |
6 | spring.datasource.hikari.maximum-pool-size=5
7 | spring.datasource.hikari.connectionTimeout=30000
8 | spring.datasource.hikari.idleTimeout=600000
9 | spring.datasource.hikari.maxLifetime=1800000
10 |
11 | spring.jpa.show-sql=true
12 | spring.jpa.properties.hibernate.format_sql=true
13 |
14 | #Logging
15 | logging.level.com.zaxxer.hikari=INFO
16 |
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V1__create-project-tables.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE wallet (
2 | id MEDIUMINT NOT NULL AUTO_INCREMENT,
3 | name VARCHAR(100) NOT NULL,
4 | quota DECIMAL(14,7),
5 | total_value DECIMAL(14,7),
6 | quota_updated_at TIMESTAMP,
7 | created_at TIMESTAMP,
8 | updated_at TIMESTAMP,
9 | PRIMARY KEY (id)
10 | );
11 |
12 | CREATE TABLE asset (
13 | ticket VARCHAR(100) NOT NULL,
14 | type VARCHAR(30),
15 | PRIMARY KEY (ticket)
16 | );
17 |
18 | CREATE TABLE asset_history (
19 | id MEDIUMINT NOT NULL AUTO_INCREMENT,
20 | value DECIMAL(14,7),
21 | date date,
22 | asset_ticket VARCHAR(100) NOT NULL,
23 | PRIMARY KEY (id),
24 | FOREIGN KEY (asset_ticket)
25 | REFERENCES asset(ticket)
26 | );
27 |
28 | CREATE TABLE investment (
29 | id MEDIUMINT NOT NULL AUTO_INCREMENT,
30 | value DECIMAL(14,7),
31 | quantity INTEGER,
32 | date DATE,
33 | created_at TIMESTAMP,
34 | updated_at TIMESTAMP,
35 | asset_ticket VARCHAR(100),
36 | wallet_id MEDIUMINT,
37 | quota_holder_id MEDIUMINT,
38 | PRIMARY KEY (id),
39 | FOREIGN KEY (asset_ticket)
40 | REFERENCES asset(ticket),
41 | FOREIGN KEY (wallet_id)
42 | REFERENCES wallet(id)
43 | );
44 |
45 | CREATE TABLE wallet_investments (
46 | wallet_id MEDIUMINT NOT NULL,
47 | investment_id MEDIUMINT NOT NULL,
48 | PRIMARY KEY (wallet_id, investment_id),
49 | FOREIGN KEY (wallet_id)
50 | REFERENCES wallet(id),
51 | FOREIGN KEY (investment_id)
52 | REFERENCES investment(id)
53 | );
54 |
55 | CREATE TABLE quota_holder (
56 | id MEDIUMINT NOT NULL AUTO_INCREMENT,
57 | name VARCHAR(255) NOT NULL,
58 | opt_in_at TIMESTAMP,
59 | opt_out_at TIMESTAMP,
60 | wallet_id MEDIUMINT,
61 | PRIMARY KEY (id)
62 | );
63 |
64 | CREATE TABLE wallet_quota_holders (
65 | wallet_id MEDIUMINT NOT NULL,
66 | quota_holder_id MEDIUMINT NOT NULL,
67 | PRIMARY KEY (wallet_id, quota_holder_id),
68 | FOREIGN KEY (wallet_id)
69 | REFERENCES wallet(id),
70 | FOREIGN KEY (quota_holder_id)
71 | REFERENCES quota_holder(id)
72 | );
73 |
74 |
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V2__insert_assethistory_data.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO asset (ticket, type)
2 | values
3 | ('ITSA4', 'ACAO'),
4 | ('JHSF3', 'ACAO'),
5 | ('MOVI3', 'ACAO');
6 |
7 | insert into asset_history (value, date, asset_ticket)
8 | values
9 | (5.67, '2019-11-29', 'JHSF3'),
10 | (6.13, '2019-12-02', 'JHSF3'),
11 | (6.12, '2019-12-03', 'JHSF3'),
12 | (6.10, '2019-12-04', 'JHSF3'),
13 | (6.21, '2019-12-05', 'JHSF3'),
14 | (6.40, '2019-12-06', 'JHSF3'),
15 | (6.37, '2019-12-09', 'JHSF3'),
16 | (6.32, '2019-12-10', 'JHSF3'),
17 | (6.53, '2019-12-11', 'JHSF3'),
18 | (6.64, '2019-12-12', 'JHSF3'),
19 | (6.71, '2019-12-13', 'JHSF3'),
20 | (6.82, '2019-12-16', 'JHSF3'),
21 | (6.73, '2019-12-17', 'JHSF3'),
22 | (6.73, '2019-12-18', 'JHSF3'),
23 | (6.63, '2019-12-19', 'JHSF3'),
24 | (6.76, '2019-12-20', 'JHSF3'),
25 | (7.08, '2019-12-23', 'JHSF3'),
26 | (7.24, '2019-12-26', 'JHSF3'),
27 | (7.06, '2019-12-27', 'JHSF3'),
28 | (7.13, '2019-12-30', 'JHSF3'),
29 | (7.60, '2020-01-02', 'JHSF3'),
30 | (8.12, '2020-01-03', 'JHSF3'),
31 | (8.16, '2020-01-06', 'JHSF3'),
32 | (8.23, '2020-01-07', 'JHSF3'),
33 | (8.10, '2020-01-08', 'JHSF3'),
34 | (8.13, '2020-01-09', 'JHSF3'),
35 | (8.16, '2020-01-10', 'JHSF3'),
36 | (8.40, '2020-01-13', 'JHSF3'),
37 | (8.61, '2020-01-14', 'JHSF3'),
38 | (8.75, '2020-01-15', 'JHSF3'),
39 | (8.60, '2020-01-16', 'JHSF3'),
40 | (8.64, '2020-01-17', 'JHSF3'),
41 | (8.75, '2020-01-20', 'JHSF3'),
42 | (8.66, '2020-01-21', 'JHSF3'),
43 | (8.58, '2020-01-22', 'JHSF3'),
44 | (8.95, '2020-01-23', 'JHSF3'),
45 | (13.96, '2019-11-06', 'ITSA4'),
46 | (13.94, '2019-11-07', 'ITSA4'),
47 | (13.76, '2019-11-08', 'ITSA4'),
48 | (13.89, '2019-11-11', 'ITSA4'),
49 | (13.73, '2019-11-12', 'ITSA4'),
50 | (13.5, '2019-11-13', 'ITSA4'),
51 | (13.63, '2019-11-14', 'ITSA4'),
52 | (13.6, '2019-11-18', 'ITSA4'),
53 | (13.42, '2019-11-19', 'ITSA4'),
54 | (13.51, '2019-11-21', 'ITSA4'),
55 | (13.71, '2019-11-22', 'ITSA4'),
56 | (13.38, '2019-11-25', 'ITSA4'),
57 | (13.06, '2019-11-26', 'ITSA4'),
58 | (13.22, '2019-11-27', 'ITSA4'),
59 | (13.25, '2019-11-28', 'ITSA4'),
60 | (13.27, '2019-11-29', 'ITSA4'),
61 | (13.36, '2019-12-02', 'ITSA4'),
62 | (13.33, '2019-12-03', 'ITSA4'),
63 | (13.65, '2019-12-04', 'ITSA4'),
64 | (13.59, '2019-12-05', 'ITSA4'),
65 | (13.46, '2019-12-06', 'ITSA4'),
66 | (13.68, '2019-12-09', 'ITSA4'),
67 | (13.66, '2019-12-10', 'ITSA4'),
68 | (13.62, '2019-12-11', 'ITSA4'),
69 | (13.6, '2019-12-12', 'ITSA4'),
70 | (13.7, '2019-12-13', 'ITSA4'),
71 | (13.57, '2019-12-16', 'ITSA4'),
72 | (13.83, '2019-12-17', 'ITSA4'),
73 | (13.97, '2019-12-18', 'ITSA4'),
74 | (13.98, '2019-12-19', 'ITSA4'),
75 | (13.85, '2019-12-20', 'ITSA4'),
76 | (13.95, '2019-12-23', 'ITSA4'),
77 | (14.19, '2019-12-26', 'ITSA4'),
78 | (14.13, '2019-12-27', 'ITSA4'),
79 | (14.09, '2019-12-30', 'ITSA4'),
80 | (14.35, '2020-01-02', 'ITSA4'),
81 | (14.14, '2020-01-03', 'ITSA4'),
82 | (14.11, '2020-01-06', 'ITSA4'),
83 | (13.92, '2020-01-07', 'ITSA4'),
84 | (13.71, '2020-01-08', 'ITSA4'),
85 | (13.44, '2020-01-09', 'ITSA4'),
86 | (13.35, '2020-01-10', 'ITSA4'),
87 | (13.53, '2020-01-13', 'ITSA4'),
88 | (13.68, '2020-01-14', 'ITSA4'),
89 | (13.46, '2020-01-15', 'ITSA4'),
90 | (13.49, '2020-01-16', 'ITSA4'),
91 | (13.6, '2020-01-17', 'ITSA4'),
92 | (13.41, '2020-01-20', 'ITSA4'),
93 | (13.1, '2020-01-21', 'ITSA4'),
94 | (13.29, '2020-01-22', 'ITSA4'),
95 | (13.55, '2020-01-23', 'ITSA4'),
96 | (15.68, '2019-11-06', 'MOVI3'),
97 | (16.29, '2019-11-07', 'MOVI3'),
98 | (15.86, '2019-11-08', 'MOVI3'),
99 | (15.81, '2019-11-11', 'MOVI3'),
100 | (15.39, '2019-11-12', 'MOVI3'),
101 | (15.70, '2019-11-13', 'MOVI3'),
102 | (16.39, '2019-11-14', 'MOVI3'),
103 | (16.58, '2019-11-18', 'MOVI3'),
104 | (16.58, '2019-11-19', 'MOVI3'),
105 | (16.94, '2019-11-21', 'MOVI3'),
106 | (16.83, '2019-11-22', 'MOVI3'),
107 | (16.75, '2019-11-25', 'MOVI3'),
108 | (16.95, '2019-11-26', 'MOVI3'),
109 | (16.61, '2019-11-27', 'MOVI3'),
110 | (16.85, '2019-11-28', 'MOVI3'),
111 | (16.50, '2019-11-29', 'MOVI3'),
112 | (16.63, '2019-12-02', 'MOVI3'),
113 | (16.91, '2019-12-03', 'MOVI3'),
114 | (17.01, '2019-12-04', 'MOVI3'),
115 | (17.00, '2019-12-05', 'MOVI3'),
116 | (17.10, '2019-12-06', 'MOVI3'),
117 | (17.00, '2019-12-09', 'MOVI3'),
118 | (17.09, '2019-12-10', 'MOVI3'),
119 | (16.92, '2019-12-11', 'MOVI3'),
120 | (17.09, '2019-12-12', 'MOVI3'),
121 | (17.55, '2019-12-13', 'MOVI3'),
122 | (17.90, '2019-12-16', 'MOVI3'),
123 | (17.76, '2019-12-17', 'MOVI3'),
124 | (18.49, '2019-12-18', 'MOVI3'),
125 | (18.63, '2019-12-19', 'MOVI3'),
126 | (18.70, '2019-12-20', 'MOVI3'),
127 | (18.99, '2019-12-23', 'MOVI3'),
128 | (19.15, '2019-12-26', 'MOVI3'),
129 | (19.12, '2019-12-27', 'MOVI3'),
130 | (19.12, '2019-12-30', 'MOVI3'),
131 | (19.58, '2020-01-02', 'MOVI3'),
132 | (20.10, '2020-01-03', 'MOVI3'),
133 | (20.04, '2020-01-06', 'MOVI3'),
134 | (20.00, '2020-01-07', 'MOVI3'),
135 | (20.10, '2020-01-08', 'MOVI3'),
136 | (20.38, '2020-01-09', 'MOVI3'),
137 | (20.33, '2020-01-10', 'MOVI3'),
138 | (20.55, '2020-01-13', 'MOVI3'),
139 | (20.65, '2020-01-14', 'MOVI3'),
140 | (20.62, '2020-01-15', 'MOVI3'),
141 | (20.46, '2020-01-16', 'MOVI3'),
142 | (20.63, '2020-01-17', 'MOVI3'),
143 | (20.86, '2020-01-20', 'MOVI3'),
144 | (20.93, '2020-01-21', 'MOVI3'),
145 | (20.82, '2020-01-22', 'MOVI3'),
146 | (21.40, '2020-01-23', 'MOVI3');
147 |
--------------------------------------------------------------------------------
/src/main/resources/db/migration/V3__create-missing-table-columns.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE wallet_history (
2 | id BIGINT NOT NULL AUTO_INCREMENT,
3 | wallet_id MEDIUMINT NOT NULL,
4 | quota DECIMAL(14,7),
5 | total_quotas DECIMAL(14,7),
6 | register_date DATE,
7 | PRIMARY KEY (id)
8 | );
9 |
10 | ALTER TABLE asset_history ADD COLUMN (
11 | quantity DECIMAL(14,7)
12 | );
13 |
14 | ALTER TABLE investment DROP COLUMN quota_holder_id;
15 |
--------------------------------------------------------------------------------
/src/test/java/com/javadevzone/cotas/CotasApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class CotasApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/com/javadevzone/cotas/controllers/QuotaHolderControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.controllers;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.javadevzone.cotas.CotasApplication;
5 | import com.javadevzone.cotas.entity.QuotaHolder;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.test.context.junit.jupiter.SpringExtension;
12 | import org.springframework.test.web.servlet.MockMvc;
13 | import org.springframework.test.web.servlet.MvcResult;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 | import static org.springframework.http.MediaType.APPLICATION_JSON;
17 | import static org.springframework.test.web.servlet.ResultMatcher.matchAll;
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
20 |
21 | @ExtendWith(SpringExtension.class)
22 | @SpringBootTest(classes = CotasApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
23 | @AutoConfigureMockMvc
24 | public class QuotaHolderControllerTest {
25 |
26 | @Autowired
27 | private ObjectMapper mapper;
28 |
29 | @Autowired
30 | private MockMvc mockMvc;
31 |
32 | @Test
33 | public void should_create_a_new_quota_holder() throws Exception {
34 | QuotaHolder mrs_lovely = QuotaHolder.builder().name("Mrs Lovely").build();
35 |
36 | MvcResult mvcResult = mockMvc.perform(post("/quotaHolder")
37 | .contentType(APPLICATION_JSON)
38 | .accept(APPLICATION_JSON)
39 | .content(mapper.writeValueAsString(mrs_lovely)))
40 | .andExpect(matchAll(status().isCreated()))
41 | .andReturn();
42 |
43 | QuotaHolder investidor = mapper.readValue(mvcResult.getResponse().getContentAsString(), QuotaHolder.class);
44 |
45 | assertThat(investidor.getId())
46 | .isNotNull();
47 |
48 | }
49 |
50 | @Test
51 | public void should_throw_exception_when_creating_new_quota_holder_without_name() throws Exception {
52 | mockMvc.perform(post("/quotaHolder")
53 | .contentType(APPLICATION_JSON)
54 | .accept(APPLICATION_JSON)
55 | .content(mapper.writeValueAsString(new QuotaHolder())))
56 | .andExpect(matchAll(status().is4xxClientError()))
57 | .andReturn();
58 |
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/com/javadevzone/cotas/controllers/WalletControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.controllers;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.javadevzone.cotas.CotasApplication;
5 | import com.javadevzone.cotas.entity.Wallet;
6 | import com.javadevzone.cotas.repository.WalletRepository;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
11 | import org.springframework.boot.test.context.SpringBootTest;
12 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
13 | import org.springframework.boot.web.server.LocalServerPort;
14 | import org.springframework.test.context.junit.jupiter.SpringExtension;
15 | import org.springframework.test.web.servlet.MockMvc;
16 | import org.springframework.test.web.servlet.MvcResult;
17 |
18 | import java.math.BigDecimal;
19 | import java.time.LocalDateTime;
20 | import java.util.Random;
21 |
22 | import static java.lang.String.format;
23 | import static org.assertj.core.api.Assertions.assertThat;
24 | import static org.hamcrest.Matchers.containsString;
25 | import static org.springframework.http.MediaType.APPLICATION_JSON;
26 | import static org.springframework.test.web.servlet.ResultMatcher.matchAll;
27 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
29 |
30 |
31 | @ExtendWith(SpringExtension.class)
32 | @SpringBootTest(classes = CotasApplication.class, webEnvironment = WebEnvironment.DEFINED_PORT)
33 | @AutoConfigureMockMvc
34 | public class WalletControllerTest {
35 |
36 | @Autowired
37 | private ObjectMapper mapper;
38 |
39 | @Autowired
40 | private MockMvc mockMvc;
41 |
42 | @Autowired
43 | private WalletRepository walletRepository;
44 |
45 | @LocalServerPort
46 | private int porta;
47 |
48 | @Test
49 | public void should_create_a_wallet_and_return_with_id() throws Exception {
50 | Wallet alaska = Wallet.builder().name("Alaska Black").build();
51 |
52 | MvcResult mvcResult = mockMvc.perform(post("/wallet")
53 | .contentType(APPLICATION_JSON)
54 | .accept(APPLICATION_JSON)
55 | .content(mapper.writeValueAsString(alaska)))
56 | .andExpect(matchAll(status().isCreated()))
57 | .andReturn();
58 |
59 | Wallet walletResult = mapper.readValue(mvcResult.getResponse().getContentAsString(), Wallet.class);
60 |
61 | assertThat(BigDecimal.ONE)
62 | .isEqualTo(walletResult.getQuota());
63 | }
64 |
65 | @Test
66 | public void should_get_a_wallet_and_return() throws Exception {
67 | Wallet savedAlaska = walletRepository.save(
68 | Wallet.builder()
69 | .createdAt(LocalDateTime.now())
70 | .quota(BigDecimal.ONE)
71 | .name("Alaska Black")
72 | .build());
73 |
74 | System.out.println("======================================================");
75 | System.out.println(porta);
76 |
77 | MvcResult mvcResult = mockMvc.perform(get("/wallet/" + savedAlaska.getId())
78 | .contentType(APPLICATION_JSON))
79 | .andExpect(matchAll(status().isOk()))
80 | .andReturn();
81 |
82 | Wallet dbWallet = walletRepository.findById(savedAlaska.getId())
83 | .orElseThrow(() -> new RuntimeException("Unable to find wallet"));
84 |
85 | Wallet walletResult = mapper.readValue(mvcResult.getResponse().getContentAsString(), Wallet.class);
86 |
87 | assertThat(dbWallet.getName())
88 | .isEqualTo(walletResult.getName());
89 | }
90 |
91 | @Test()
92 | public void should_get_a_wallet_that_doesnt_exist() throws Exception {
93 | long id = new Random().nextLong();
94 | mockMvc.perform(get("/wallet/" + id)
95 | .contentType(APPLICATION_JSON))
96 | .andExpect(matchAll(status().isNotFound()))
97 | .andExpect(status().reason(containsString(format("Wallet com ID [%s] não foi encontrada.", id))))
98 | .andReturn();
99 | }
100 |
101 | @Test
102 | public void should_update_a_wallet_and_return_the_new_wallet() throws Exception {
103 | Wallet savedAlaska = walletRepository.save(
104 | Wallet.builder()
105 | .createdAt(LocalDateTime.now())
106 | .quota(BigDecimal.ONE)
107 | .name("Alaska Black")
108 | .build());
109 | savedAlaska.setName("Alaska Black II");
110 |
111 | MvcResult mvcResult = mockMvc.perform(put("/wallet")
112 | .contentType(APPLICATION_JSON)
113 | .content(mapper.writeValueAsString(savedAlaska)))
114 | .andExpect(matchAll(status().isOk()))
115 | .andReturn();
116 |
117 | Wallet dbWallet = walletRepository.findById(savedAlaska.getId())
118 | .orElseThrow(() -> new RuntimeException("Unable to find wallet"));
119 |
120 | Wallet walletResult = mapper.readValue(mvcResult.getResponse().getContentAsString(), Wallet.class);
121 |
122 | assertThat(walletResult.getUpdatedAt())
123 | .isNotNull();
124 | assertThat(dbWallet.getName())
125 | .isEqualTo(walletResult.getName());
126 | }
127 |
128 | @Test
129 | public void should_delete_a_wallet_by_id_and_return_void() throws Exception {
130 | Wallet savedAlaska = walletRepository.save(
131 | Wallet.builder()
132 | .createdAt(LocalDateTime.now())
133 | .quota(BigDecimal.ONE)
134 | .name("Alaska Black")
135 | .build());
136 |
137 | mockMvc.perform(delete("/wallet/" + savedAlaska.getId()))
138 | .andExpect(matchAll(status().isNoContent()))
139 | .andReturn();
140 |
141 | assertThat(walletRepository.existsById(savedAlaska.getId()))
142 | .isFalse();
143 | }
144 |
145 | @Test()
146 | public void should_delete_a_wallet_that_doesnt_exist() throws Exception {
147 | long id = new Random().nextLong();
148 | mockMvc.perform(delete("/wallet/" + id)
149 | .contentType(APPLICATION_JSON))
150 | .andExpect(matchAll(status().isNotFound()))
151 | .andExpect(status().reason(containsString(format("Wallet com ID [%s] não foi encontrada.", id))))
152 | .andReturn();
153 | }
154 |
155 | }
--------------------------------------------------------------------------------
/src/test/java/com/javadevzone/cotas/services/InvestmentServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.services;
2 |
3 | import com.javadevzone.cotas.entity.*;
4 | import com.javadevzone.cotas.exceptions.WalletNotFoundException;
5 | import com.javadevzone.cotas.repository.AssetHistoryRepository;
6 | import com.javadevzone.cotas.repository.AssetRepository;
7 | import com.javadevzone.cotas.repository.InvestmentRepository;
8 | import org.junit.jupiter.api.Test;
9 | import org.junit.jupiter.api.extension.ExtendWith;
10 | import org.mockito.ArgumentCaptor;
11 | import org.mockito.InjectMocks;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.jupiter.MockitoExtension;
14 |
15 | import java.math.BigDecimal;
16 | import java.time.LocalDate;
17 |
18 | import static com.javadevzone.cotas.entity.enums.AssetType.ACAO;
19 | import static java.math.BigDecimal.valueOf;
20 | import static java.util.Collections.singletonList;
21 | import static java.util.Optional.of;
22 | import static org.assertj.core.api.Assertions.assertThat;
23 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
24 | import static org.junit.jupiter.api.Assertions.assertEquals;
25 | import static org.mockito.ArgumentMatchers.any;
26 | import static org.mockito.Mockito.verify;
27 | import static org.mockito.Mockito.when;
28 |
29 | @ExtendWith(MockitoExtension.class)
30 | class InvestmentServiceTest {
31 |
32 | @InjectMocks
33 | private InvestmentService investmentService;
34 | @Mock
35 | private AssetRepository assetRepository;
36 | @Mock
37 | private InvestmentRepository investmentRepository;
38 | @Mock
39 | private AssetHistoryRepository assetHistoryRepository;
40 |
41 |
42 | @Test
43 | public void given_new_quota_without_history_should_return_negative_profitable() {
44 | Wallet myWallet = getWallet();
45 | WalletHistory walletHistoryMock = WalletHistory.builder().quota(valueOf(1.189716))
46 | .totalQuotas(valueOf(1692)).wallet(myWallet).build();
47 |
48 | BigDecimal result = investmentService.calculateInvestmentsProfitability(walletHistoryMock, LocalDate.now());
49 | assertEquals(0, BigDecimal.valueOf(-1).compareTo(result));
50 |
51 | }
52 |
53 | @Test
54 | public void given_history_and_assets_will_calculate_the_profitability() {
55 | Wallet myWallet = getWallet();
56 | WalletHistory walletHistoryMock = WalletHistory.builder().quota(valueOf(1.189716))
57 | .totalQuotas(valueOf(1692)).wallet(myWallet).build();
58 | Asset jhsfTick = Asset.builder().ticket("JHSF3").build();
59 | when(investmentRepository.getQuantityByWalletAndAssetAndDateBefore(myWallet, jhsfTick, LocalDate.now()))
60 | .thenReturn(of(200L));
61 | when(assetHistoryRepository.findByAssetAndDate(any(Asset.class), any(LocalDate.class)))
62 | .thenReturn(of(AssetHistory.builder().value(new BigDecimal("6.82")).asset(jhsfTick).build()));
63 | when(assetRepository.findInvestedAssetsByDate(any(Wallet.class), any(LocalDate.class))).thenReturn(of(singletonList(jhsfTick)));
64 |
65 | BigDecimal result = investmentService.calculateInvestmentsProfitability(walletHistoryMock, LocalDate.now());
66 | assertEquals(0, BigDecimal.valueOf(-0.32240419).compareTo(result));
67 | }
68 |
69 | @Test
70 | public void given_a_new_investment_should_insert_in_the_wallet() {
71 | Asset asset = getSingleAsset();
72 | Wallet wallet = getWallet();
73 | Investment investment = Investment.builder()
74 | .asset(asset).quantity(500L).value(new BigDecimal("25.5")).wallet(wallet)
75 | .build();
76 | ArgumentCaptor managedInvestment = ArgumentCaptor.forClass(Investment.class);
77 |
78 | investmentService.addInvestment(investment);
79 |
80 | verify(investmentRepository)
81 | .save(managedInvestment.capture());
82 |
83 | assertThat(managedInvestment.getValue().getDate())
84 | .isEqualTo(LocalDate.now());
85 | }
86 |
87 | @Test
88 | public void given_a_new_investment_should_have_a_wallet_or_throws_wallet_notfound_exception() {
89 | Asset asset = getSingleAsset();
90 | Investment investment = Investment.builder().asset(asset).quantity(500L).value(new BigDecimal("25.5")).build();
91 |
92 | assertThatThrownBy(() -> investmentService.addInvestment(investment))
93 | .isInstanceOf(WalletNotFoundException.class);
94 | }
95 |
96 | private Wallet getWallet() {
97 | return Wallet.builder()
98 | .id(1L).name("PJ Investing Club")
99 | .build();
100 | }
101 |
102 | private Asset getSingleAsset() {
103 | return Asset.builder().type(ACAO).ticket("PETR4").build();
104 | }
105 |
106 | }
--------------------------------------------------------------------------------
/src/test/java/com/javadevzone/cotas/services/QuotaServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.javadevzone.cotas.services;
2 |
3 | import com.javadevzone.cotas.entity.Wallet;
4 | import com.javadevzone.cotas.entity.WalletHistory;
5 | import com.javadevzone.cotas.repository.WalletHistoryRepository;
6 | import com.javadevzone.cotas.repository.WalletRepository;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.mockito.InjectMocks;
10 | import org.mockito.Mock;
11 | import org.mockito.junit.jupiter.MockitoExtension;
12 |
13 | import java.math.BigDecimal;
14 | import java.time.LocalDate;
15 | import java.time.temporal.ChronoUnit;
16 | import java.util.Optional;
17 |
18 | import static java.util.Optional.ofNullable;
19 | import static org.assertj.core.api.Assertions.assertThat;
20 | import static org.mockito.ArgumentMatchers.any;
21 | import static org.mockito.ArgumentMatchers.anyLong;
22 | import static org.mockito.Mockito.lenient;
23 | import static org.mockito.Mockito.when;
24 |
25 | @ExtendWith(MockitoExtension.class)
26 | public class QuotaServiceTest {
27 |
28 | @InjectMocks
29 | private QuotaService quotaService;
30 | @Mock
31 | private WalletHistoryRepository walletHistoryRepository;
32 | @Mock
33 | private WalletRepository walletRepository;
34 | @Mock
35 | private InvestmentService investmentService;
36 |
37 | @Test
38 | public void given_a_wallet_with_a_new_investment_should_calculate_wallet_result_as_quota_and_return() {
39 | Wallet myWallet = Wallet.builder().id(1L).build();
40 | WalletHistory walletHistoryMock = WalletHistory.builder().quota(new BigDecimal("1.189716")).totalQuotas(new BigDecimal("1692")).build();
41 |
42 | lenient().when(walletHistoryRepository.findFirstByWalletAndRegisterDateIsBeforeOrderByRegisterDateDesc(any(Wallet.class), any(LocalDate.class)))
43 | .thenReturn(ofNullable(walletHistoryMock));
44 | when(walletRepository.findById(anyLong())).thenReturn(ofNullable(myWallet));
45 | lenient().when(investmentService.calculateInvestmentsProfitability(walletHistoryMock, LocalDate.now())).thenReturn(BigDecimal.valueOf(0.016393828));
46 | WalletHistory walletHistory = quotaService.calculateQuotaValue(myWallet.getId(), LocalDate.now());
47 |
48 | assertThat(walletHistory.getQuota())
49 | .isEqualTo(new BigDecimal("1.209220"));
50 | assertThat(walletHistory.getTotalQuotas())
51 | .isEqualTo(new BigDecimal("1692"));
52 | assertThat(walletHistory.getWalletValue())
53 | .isEqualTo(new BigDecimal("2046.000240"));
54 | }
55 |
56 | @Test
57 | public void given_a_wallet_with_more_than_one_asset_should_calculate_wallet_result_as_quota_and_return() {
58 | Wallet myWallet = Wallet.builder().id(1L).build();
59 | WalletHistory walletHistoryMock = WalletHistory.builder().quota(new BigDecimal("1.086883")).totalQuotas(new BigDecimal("11371.964351")).build();
60 |
61 | lenient().when(walletHistoryRepository.findFirstByWalletAndRegisterDateIsBeforeOrderByRegisterDateDesc(any(Wallet.class), any(LocalDate.class)))
62 | .thenReturn(ofNullable(walletHistoryMock));
63 | lenient().when(investmentService.calculateInvestmentsProfitability(any(WalletHistory.class), any(LocalDate.class))).thenReturn(BigDecimal.valueOf(-0.009303669));
64 | WalletHistory walletHistory = quotaService.calculateQuotaValue(myWallet.getId(), LocalDate.now().minus(1, ChronoUnit.DAYS));
65 |
66 | assertThat(walletHistory.getQuota())
67 | .isEqualTo(new BigDecimal("1.076772"));
68 | assertThat(walletHistory.getTotalQuotas())
69 | .isEqualTo(new BigDecimal("11371.964351"));
70 | assertThat(walletHistory.getWalletValue())
71 | .isEqualTo(new BigDecimal("12245.012799"));
72 | }
73 |
74 | @Test
75 | public void given_a_wallet_with_2_assets_and_a_negative_investment_should_calculate_wallet_result_as_quota_and_return() {
76 | Wallet myWallet = Wallet.builder().id(1L).build();
77 | WalletHistory walletHistoryMock = WalletHistory.builder()
78 | .quota(new BigDecimal("1.241210"))
79 | .totalQuotas(new BigDecimal("11371.964351")).build();
80 | lenient().when(walletHistoryRepository.findFirstByWalletAndRegisterDateIsBeforeOrderByRegisterDateDesc(any(Wallet.class), any(LocalDate.class)))
81 | .thenReturn(ofNullable(walletHistoryMock));
82 | when(walletRepository.findById(anyLong())).thenReturn(ofNullable(myWallet));
83 | lenient().when(investmentService.calculateInvestmentsProfitability(any(WalletHistory.class), any(LocalDate.class))).thenReturn(BigDecimal.valueOf(-0.006021543));
84 | WalletHistory walletHistory = quotaService.calculateQuotaValue(myWallet.getId(), LocalDate.now().minus(1, ChronoUnit.DAYS));
85 |
86 | assertThat(walletHistory.getQuota())
87 | .isEqualTo(new BigDecimal("1.233737"));
88 | assertThat(walletHistory.getTotalQuotas())
89 | .isEqualTo(new BigDecimal("11371.964351"));
90 | assertThat(walletHistory.getWalletValue())
91 | .isEqualTo(new BigDecimal("14030.013183"));
92 | }
93 |
94 | @Test
95 | public void given_a_wallet_should_calculate_wallet_result_as_quota_and_return() {
96 | Wallet myWallet = Wallet.builder().id(1L).build();
97 | WalletHistory walletHistoryMock = WalletHistory.builder().quota(new BigDecimal("1.209220")).totalQuotas(new BigDecimal("2819.999868")).build();
98 |
99 | lenient().when(walletHistoryRepository.findFirstByWalletAndRegisterDateIsBeforeOrderByRegisterDateDesc(any(Wallet.class), any(LocalDate.class)))
100 | .thenReturn(ofNullable(walletHistoryMock));
101 | when(walletRepository.findById(anyLong())).thenReturn(ofNullable(myWallet));
102 | lenient().when(investmentService.calculateInvestmentsProfitability(any(WalletHistory.class), any(LocalDate.class))).thenReturn(BigDecimal.valueOf(-0.01319611));
103 | WalletHistory walletHistory = quotaService.calculateQuotaValue(myWallet.getId(), LocalDate.now().minus(1, ChronoUnit.DAYS));
104 |
105 | assertThat(walletHistory.getQuota())
106 | .isEqualTo(new BigDecimal("1.193263"));
107 | assertThat(walletHistory.getTotalQuotas())
108 | .isEqualTo(new BigDecimal("2819.999868"));
109 | assertThat(walletHistory.getWalletValue())
110 | .isEqualTo(new BigDecimal("3365.001503"));
111 | }
112 |
113 | @Test
114 | public void given_a_wallet_should_calculate_wallet_result_when_not_has_not_previous_quota() {
115 | Wallet myWallet = Wallet.builder().id(1L).build();
116 |
117 | lenient().when(walletHistoryRepository.findFirstByWalletAndRegisterDateIsBeforeOrderByRegisterDateDesc(any(Wallet.class), any(LocalDate.class)))
118 | .thenReturn(Optional.empty());
119 | when(walletRepository.findById(anyLong())).thenReturn(ofNullable(myWallet));
120 | lenient().when(investmentService.calculateInvestmentsProfitability(any(WalletHistory.class), any(LocalDate.class))).thenReturn(BigDecimal.valueOf(0));
121 | WalletHistory walletHistory = quotaService.calculateQuotaValue(myWallet.getId(), LocalDate.now().minus(1, ChronoUnit.DAYS));
122 |
123 | assertThat(walletHistory.getQuota().compareTo(BigDecimal.ONE)).isEqualTo(0);
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.driver-class-name=org.h2.Driver
2 | spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;MODE=MYSQL
3 | spring.datasource.username=sa
4 | spring.datasource.password=sa
5 |
6 | spring.h2.console.enabled=true
7 | spring.h2.console.path=/h2-console
8 | spring.h2.console.settings.trace=false
9 | spring.h2.console.settings.web-allow-others=false
10 |
11 | spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
12 |
13 | server.port=8091
--------------------------------------------------------------------------------
/src/test/resources/jhsf-asset-history.json:
--------------------------------------------------------------------------------
1 | [
2 | {"id":1, "value":5.67, "date": "29/11/2019", "asset": {"ticket": "JHSF3"}},
3 | {"id":2, "value":6.13, "date": "02/12/2019", "asset": {"ticket": "JHSF3"}},
4 | {"id":3, "value":6.12, "date": "03/12/2019", "asset": {"ticket": "JHSF3"}},
5 | {"id":4, "value":6.10, "date": "04/12/2019", "asset": {"ticket": "JHSF3"}},
6 | {"id":5, "value":6.21, "date": "05/12/2019", "asset": {"ticket": "JHSF3"}},
7 | {"id":6, "value":6.40, "date": "06/12/2019", "asset": {"ticket": "JHSF3"}},
8 | {"id":7, "value":6.37, "date": "09/12/2019", "asset": {"ticket": "JHSF3"}},
9 | {"id":8, "value":6.32, "date": "10/12/2019", "asset": {"ticket": "JHSF3"}},
10 | {"id":9, "value":6.53, "date": "11/12/2019", "asset": {"ticket": "JHSF3"}},
11 | {"id":10, "value":6.64, "date": "12/12/2019", "asset": {"ticket": "JHSF3"}},
12 | {"id":11, "value":6.71, "date": "13/12/2019", "asset": {"ticket": "JHSF3"}},
13 | {"id":12, "value":6.82, "date": "16/12/2019", "asset": {"ticket": "JHSF3"}},
14 | {"id":13, "value":6.73, "date": "17/12/2019", "asset": {"ticket": "JHSF3"}},
15 | {"id":14, "value":6.73, "date": "18/12/2019", "asset": {"ticket": "JHSF3"}},
16 | {"id":15, "value":6.63, "date": "19/12/2019", "asset": {"ticket": "JHSF3"}},
17 | {"id":16, "value":6.76, "date": "20/12/2019", "asset": {"ticket": "JHSF3"}},
18 | {"id":17, "value":7.08, "date": "23/12/2019", "asset": {"ticket": "JHSF3"}},
19 | {"id":18, "value":7.24, "date": "26/12/2019", "asset": {"ticket": "JHSF3"}},
20 | {"id":19, "value":7.06, "date": "27/12/2019", "asset": {"ticket": "JHSF3"}},
21 | {"id":20, "value":7.13, "date": "30/12/2019", "asset": {"ticket": "JHSF3"}},
22 | {"id":21, "value":7.60, "date": "02/01/2020", "asset": {"ticket": "JHSF3"}},
23 | {"id":22, "value":8.12, "date": "03/01/2020", "asset": {"ticket": "JHSF3"}},
24 | {"id":23, "value":8.16, "date": "06/01/2020", "asset": {"ticket": "JHSF3"}},
25 | {"id":24, "value":8.23, "date": "07/01/2020", "asset": {"ticket": "JHSF3"}},
26 | {"id":25, "value":8.10, "date": "08/01/2020", "asset": {"ticket": "JHSF3"}},
27 | {"id":26, "value":8.13, "date": "09/01/2020", "asset": {"ticket": "JHSF3"}},
28 | {"id":27, "value":8.16, "date": "10/01/2020", "asset": {"ticket": "JHSF3"}},
29 | {"id":28, "value":8.40, "date": "13/01/2020", "asset": {"ticket": "JHSF3"}},
30 | {"id":29, "value":8.61, "date": "14/01/2020", "asset": {"ticket": "JHSF3"}},
31 | {"id":30, "value":8.75, "date": "15/01/2020", "asset": {"ticket": "JHSF3"}},
32 | {"id":31, "value":8.60, "date": "16/01/2020", "asset": {"ticket": "JHSF3"}},
33 | {"id":32, "value":8.64, "date": "17/01/2020", "asset": {"ticket": "JHSF3"}}
34 | ]
35 |
--------------------------------------------------------------------------------
/src/test/resources/jhsf-investments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"id":1, "value":5.64, "quantity": 300, "date": "29/11/2019", "asset": {"ticket": "JHSF3"}},
3 | {"id":2, "value":8.06, "quantity": -100, "date": "08/01/2020", "asset": {"ticket": "JHSF3"}},
4 | {"id":3, "value":8.25, "quantity": 100, "date": "13/01/2020", "asset": {"ticket": "JHSF3"}}
5 | ]
6 |
--------------------------------------------------------------------------------
/src/test/resources/mock-investments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"id":1, "value":5.64, "quantity": 300, "date": "29/11/2019", "asset": {"ticket": "JHSF3"}},
3 | {"id":2, "value":8.06, "quantity": -100, "date": "08/01/2020", "asset": {"ticket": "JHSF3"}},
4 | {"id":3, "value":8.25, "quantity": 100, "date": "13/01/2020", "asset": {"ticket": "JHSF3"}},
5 | {"id":4, "value":16.34, "quantity": 500, "date": "07/11/2019", "asset": {"ticket": "MOVI3"}}
6 | ]
7 |
--------------------------------------------------------------------------------