├── .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 | Java DevZone 3 |

4 | 5 |

6 | Backend for Quotas System 7 |

8 | 9 | 10 |

11 | GitHub 12 | 13 | GitHub contributors count 14 | 15 | GitHub stars count 16 | 17 | GitHub forks count 18 | 19 | Travis (.org) 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 | --------------------------------------------------------------------------------