Added project
This commit is contained in:
52
.gitignore
vendored
52
.gitignore
vendored
@@ -1,26 +1,36 @@
|
|||||||
# ---> Java
|
HELP.md
|
||||||
# Compiled class file
|
.gradle
|
||||||
*.class
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
# Log file
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
*.log
|
*.log
|
||||||
|
*.gz
|
||||||
|
|
||||||
# BlueJ files
|
### STS ###
|
||||||
*.ctxt
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
### IntelliJ IDEA ###
|
||||||
.mtj.tmp/
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
# Package Files #
|
### NetBeans ###
|
||||||
*.jar
|
/nbproject/private/
|
||||||
*.war
|
/nbbuild/
|
||||||
*.nar
|
/dist/
|
||||||
*.ear
|
/nbdist/
|
||||||
*.zip
|
/.nb-gradle/
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
replay_pid*
|
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|||||||
67
README.md
67
README.md
@@ -1,4 +1,65 @@
|
|||||||
# dfops-rest-service
|
# Задание
|
||||||
|
|
||||||
Тестовое задание. Напишите Spring-приложение, предоставляющее REST-сервис для приема и регистрации операций в базе данных по расчетам с водителями. У
|
Напишите Spring-приложение, предоставляющее REST-сервис для приема и регистрации операций в базе данных по расчетам с водителями. У каждого водителя может быть несколько лицевых счетов.
|
||||||
каждого водителя может быть несколько лицевых счетов.
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
СУБД PostgreSQL 10; создать структуру БД самостоятельно.
|
||||||
|
Доступ к данным реализовать с помощью Hibernate.
|
||||||
|
|
||||||
|
Реализовать операции REST-сервиса; прием параметров и возврат данных – в JSON:
|
||||||
|
* начисление на счет водителя,
|
||||||
|
* списание со счета водителя,
|
||||||
|
* получение текущего баланса по счету,
|
||||||
|
* перевод между собственными лицевыми счетами водителя,
|
||||||
|
* получение оборота за период по отдельному лицевому счету (дебет, кредит отдельно),
|
||||||
|
* получение подробного списка операций за период.
|
||||||
|
|
||||||
|
Для объемных операций (например, получение списка операций) реализовать постраничную выдачу.
|
||||||
|
|
||||||
|
## Дополнительные требования
|
||||||
|
|
||||||
|
* Создать Gradle-проект.
|
||||||
|
* Хранение исходников – в GIT (например, на gitlab.com).
|
||||||
|
* Дополнительным плюсом будет реализации unit-тестов.
|
||||||
|
|
||||||
|
# Комментарий к выполненной работе
|
||||||
|
|
||||||
|
Программу можно запустить в нескольких режимах используя профили spring boot
|
||||||
|
|
||||||
|
1. **DEFAULT** - в данном режиме используется база данных postgresql с настройками по умолчанию, а именно `url:jdbc:postgresql://localhost/test, username: test, password: test`. Для запуска используем следующие параметры `java -jar dfops.jar`
|
||||||
|
1. **DEMO** - в данном режиме используется база данных H2 DB. Для запуска используем следующие параметры `java -jar dfops.jar --spring.profiles.active=demo`
|
||||||
|
1. **PRODUCTION** - в данном режиме используется база данных postgresql c альтернативными настройками прописанными в файле `application-prod.properties`, данный файл должен находиться в том же каталоге где и запускаемый jar-файл программы. Для запуска используем следующие параметры `java -jar dfops.jar --spring.profiles.active=prod`
|
||||||
|
|
||||||
|
Пример содержимого файла `application-prod.properties`
|
||||||
|
|
||||||
|
```
|
||||||
|
DFOPS_PGSQL_DB_HOST=jdbc:postgresql://localhost
|
||||||
|
DFOPS_PGSQL_DB_PORT=5432
|
||||||
|
DFOPS_PGSQL_DB_NAME=test
|
||||||
|
DFOPS_PGSQL_DB_USER=test
|
||||||
|
DFOPS_PGSQL_DB_PASSWORD=test
|
||||||
|
```
|
||||||
|
|
||||||
|
Так же для запуска программы в linux, можно воспользоваться скриптом `dfops_linux.sh` , при этом запускаемый jar-файл должен называться `dfops.jar` и находиться в том же каталоге, где и скрипт. Выполните `dfops_linux.sh --help` для получения помощи. При запуске в режиме `PRODUCTION` будет выполнена проверка на наличие файла `application-prod.properties`, если он не найден, то запустится интерактивный режим, где будет предложено заполнить необходимые данные.
|
||||||
|
|
||||||
|
## Работа с программой
|
||||||
|
|
||||||
|
### Инициализация БД
|
||||||
|
|
||||||
|
Если работа ведётся с postgresql можно воспользуйтесь файлами `ddl-postgresql.sql`, `schema-postgresql.sql`, `data-postgresql.sql` для инициализации БД см. каталог `init_postgresql_db`
|
||||||
|
|
||||||
|
### Прочее
|
||||||
|
|
||||||
|
1. Точка входа `http://localhost:8080/rest/v1/`
|
||||||
|
1. Документацию по API использует OpenApi c ui. Для доступа к ui используем адрес `http://localhost:8080/rest/v1/swagger-ui/index.html?configUrl=/rest/v1/v3/api-docs/swagger-config` , в представлении json используем `http://localhost:8080/rest/v1/v3/api-docs`
|
||||||
|
1. В каталоге `docker` при наличии docker и docker compose можно запустить docker-образ с postgresql версии 10.
|
||||||
|
1. В gitlab настроен CI/CD. Есть возможность скачать последнюю версию артефакта по ссылке https://gitlab.com/Aleksandrov/dfops-rest-service/-/pipelines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Тестовое задание выполнил
|
||||||
|
|
||||||
|
Александров А.А. (alexandrov@resprojects.ru)
|
||||||
|
|
||||||
|
ссылка на профиль hh.ru - https://hh.ru/resume/7cdada75ff015e78530039ed1f366c4b4a5273
|
||||||
101
dfops_linux.sh
Executable file
101
dfops_linux.sh
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
EXECUTABLE_FILE=dfops.jar
|
||||||
|
PROPERTIES_FILE=application-prod.properties
|
||||||
|
|
||||||
|
HELP="Usage: dfops_linux [KEY]
|
||||||
|
|
||||||
|
Script without key is run program in DEFAULT mode.
|
||||||
|
|
||||||
|
DEFAULT mode uses postgresql with next parameters:
|
||||||
|
|
||||||
|
url: jdbc:postgresql://localhost/test
|
||||||
|
username: test
|
||||||
|
password: test
|
||||||
|
|
||||||
|
Also available next switches:
|
||||||
|
|
||||||
|
--prod - running program in PRODUCTION mode. For running in this mode needed additional
|
||||||
|
file application-prod.properties with PostgreSQL dataset information.
|
||||||
|
|
||||||
|
--demo - running program in DEMO mode. In this mode uses H2 database (in-memory) instead postgresql.
|
||||||
|
|
||||||
|
--help - display this is message
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
dfops_linux - run program in DEFAULT mode
|
||||||
|
|
||||||
|
dfops_linux --demo - run program in DEMO mode.
|
||||||
|
|
||||||
|
dfops_linux --prod - run program in PRODUCTION mode.
|
||||||
|
"
|
||||||
|
|
||||||
|
PROPERTIES_FILE_NOT_FOUND="
|
||||||
|
WARNING!
|
||||||
|
|
||||||
|
You try run program in PRODUCTION mode. For this mode need PostgreSQL but file
|
||||||
|
$PROPERTIES_FILE with dataset information is not found. Please fill next information and run program again!
|
||||||
|
|
||||||
|
"
|
||||||
|
|
||||||
|
if [ -f "$EXECUTABLE_FILE" ]; then
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Running program in DEFAULT mode"
|
||||||
|
java -jar "$EXECUTABLE_FILE"
|
||||||
|
else
|
||||||
|
case "$1" in
|
||||||
|
--help)
|
||||||
|
echo "$HELP"
|
||||||
|
;;
|
||||||
|
--demo)
|
||||||
|
echo "Running program in DEMO mode with H2 DB (in-memory)"
|
||||||
|
java -jar "$EXECUTABLE_FILE" --spring.profiles.active=demo
|
||||||
|
;;
|
||||||
|
--prod)
|
||||||
|
if [ -f "$PROPERTIES_FILE" ]; then
|
||||||
|
echo "Running program in PRODUCTION mode with PostgreSQL DB"
|
||||||
|
java -jar "$EXECUTABLE_FILE" --spring.profiles.active=prod
|
||||||
|
else
|
||||||
|
echo "$PROPERTIES_FILE_NOT_FOUND"
|
||||||
|
printf 'PostgreSQL database host name or IP address (default localhost): '
|
||||||
|
read -r DFOPS_PGSQL_DB_HOST
|
||||||
|
if [ -z "$DFOPS_PGSQL_DB_HOST" ]; then
|
||||||
|
DFOPS_PGSQL_DB_HOST="jdbc:postgresql://localhost"
|
||||||
|
else
|
||||||
|
DFOPS_PGSQL_DB_HOST="jdbc:postgresql://$DFOPS_PGSQL_DB_HOST"
|
||||||
|
fi
|
||||||
|
printf 'PostgreSQL database port (default 5432): '
|
||||||
|
read -r DFOPS_PGSQL_DB_PORT
|
||||||
|
if [ -z "$DFOPS_PGSQL_DB_PORT" ]; then
|
||||||
|
DFOPS_PGSQL_DB_PORT=5432
|
||||||
|
fi
|
||||||
|
printf 'PostgreSQL database name (default test): '
|
||||||
|
read -r DFOPS_PGSQL_DB_NAME
|
||||||
|
if [ -z "$DFOPS_PGSQL_DB_NAME" ]; then
|
||||||
|
DFOPS_PGSQL_DB_NAME="test"
|
||||||
|
fi
|
||||||
|
printf 'PostgreSQL database user name: '
|
||||||
|
read -r DFOPS_PGSQL_DB_USER
|
||||||
|
printf 'PostgreSQL database password: '
|
||||||
|
read -r -s DFOPS_PGSQL_DB_PASSWORD
|
||||||
|
echo
|
||||||
|
touch "$PROPERTIES_FILE"
|
||||||
|
{
|
||||||
|
echo "DFOPS_PGSQL_DB_HOST=$DFOPS_PGSQL_DB_HOST"
|
||||||
|
echo "DFOPS_PGSQL_DB_PORT=$DFOPS_PGSQL_DB_PORT"
|
||||||
|
echo "DFOPS_PGSQL_DB_NAME=$DFOPS_PGSQL_DB_NAME"
|
||||||
|
echo "DFOPS_PGSQL_DB_USER=$DFOPS_PGSQL_DB_USER"
|
||||||
|
echo "DFOPS_PGSQL_DB_PASSWORD=$DFOPS_PGSQL_DB_PASSWORD"
|
||||||
|
} > "$PROPERTIES_FILE"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "dfops_linux: unknown option $1"
|
||||||
|
echo "Try 'dfops_linux --help' for more information."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Executable file dfops.jar is not found!"
|
||||||
|
fi
|
||||||
5
docker/db/Dockerfile
Normal file
5
docker/db/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FROM postgres:10-alpine
|
||||||
|
COPY scripts/*.sql /docker-entrypoint-initdb.d/
|
||||||
|
ADD scripts/1_init_schema.sql /docker-entrypoint-initdb.d
|
||||||
|
ADD scripts/2_init_data.sql /docker-entrypoint-initdb.d
|
||||||
|
RUN chmod a+r /docker-entrypoint-initdb.d/*
|
||||||
32
docker/db/scripts/1_init_schema.sql
Normal file
32
docker/db/scripts/1_init_schema.sql
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
CREATE SEQUENCE seq_employees START 5000;
|
||||||
|
|
||||||
|
CREATE TABLE employees
|
||||||
|
(
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
email VARCHAR NOT NULL
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employees_unique_email_idx ON employees (email);
|
||||||
|
|
||||||
|
CREATE TABLE employee_personal_accounts (
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
employee_id BIGINT NOT NULL,
|
||||||
|
personal_account VARCHAR NOT NULL,
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employee_personal_accounts_unique_employee_account_idx
|
||||||
|
ON employee_personal_accounts (employee_id, personal_account);
|
||||||
|
|
||||||
|
CREATE TABLE personal_account_operations (
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
personal_account_id BIGINT NOT NULL,
|
||||||
|
operation_date_time TIMESTAMP NOT NULL,
|
||||||
|
operation_type VARCHAR NOT NULL,
|
||||||
|
-- Money data on PostgreSQL using Java https://stackoverflow.com/a/18170030
|
||||||
|
operation_value DECIMAL NOT NULL,
|
||||||
|
FOREIGN KEY (personal_account_id) REFERENCES employee_personal_accounts (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX personal_account_operations_unique_account_datetime_idx
|
||||||
|
ON personal_account_operations (personal_account_id, operation_date_time);
|
||||||
|
|
||||||
|
|
||||||
34
docker/db/scripts/2_init_data.sql
Normal file
34
docker/db/scripts/2_init_data.sql
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
INSERT INTO employees (name, email) VALUES
|
||||||
|
('Ivanov Ivan Ivanovich', 'ivanov@example.com'),
|
||||||
|
('Petrov Vasily Victorovich', 'petrov@example.com');
|
||||||
|
|
||||||
|
INSERT INTO employee_personal_accounts (personal_account, employee_id)
|
||||||
|
VALUES ('4154014152522741', 5000),
|
||||||
|
('4131668358915203', 5000),
|
||||||
|
('4281563275602455', 5000),
|
||||||
|
('4103234971123321', 5001),
|
||||||
|
('4132555843841699', 5001);
|
||||||
|
|
||||||
|
INSERT INTO personal_account_operations (operation_date_time, operation_type, operation_value, personal_account_id)
|
||||||
|
VALUES ('2020-05-30 10:00:00'::timestamp, 'DEPOSIT', 840.35, 5002),
|
||||||
|
('2020-05-28 11:05:10'::timestamp, 'DEPOSIT', 625.00, 5002),
|
||||||
|
('2020-05-25 11:41:10'::timestamp, 'DEPOSIT', 1080.45, 5002),
|
||||||
|
('2020-05-30 14:00:10'::timestamp, 'WITHDRAW', 652.33, 5002),
|
||||||
|
('2020-05-26 18:10:10'::timestamp, 'WITHDRAW', 420.00, 5002),
|
||||||
|
('2020-06-30 10:00:00'::timestamp, 'DEPOSIT', 1500.52, 5003),
|
||||||
|
('2020-06-30 11:05:10'::timestamp, 'DEPOSIT', 800.73, 5003),
|
||||||
|
('2020-06-30 14:00:10'::timestamp, 'WITHDRAW', 170.35, 5003),
|
||||||
|
('2020-06-30 18:10:10'::timestamp, 'WITHDRAW', 320.00, 5003),
|
||||||
|
('2020-07-15 12:05:10'::timestamp, 'DEPOSIT', 800.73, 5004),
|
||||||
|
('2020-07-15 12:41:10'::timestamp, 'DEPOSIT', 350.00, 5004),
|
||||||
|
('2020-07-15 15:00:10'::timestamp, 'WITHDRAW', 900.35, 5004),
|
||||||
|
('2020-07-15 17:10:10'::timestamp, 'WITHDRAW', 600.00, 5004),
|
||||||
|
('2020-05-15 11:05:10'::timestamp, 'DEPOSIT', 976.33, 5005),
|
||||||
|
('2020-05-15 11:41:10'::timestamp, 'DEPOSIT', 850.00, 5005),
|
||||||
|
('2020-05-15 14:00:10'::timestamp, 'WITHDRAW', 200.00, 5005),
|
||||||
|
('2020-05-15 18:10:10'::timestamp, 'WITHDRAW', 375.85, 5005),
|
||||||
|
('2020-04-30 09:00:00'::timestamp, 'DEPOSIT', 1200.52, 5006),
|
||||||
|
('2020-04-30 10:35:00'::timestamp, 'DEPOSIT', 300.53, 5006),
|
||||||
|
('2020-04-30 10:55:00'::timestamp, 'DEPOSIT', 450.60, 5006),
|
||||||
|
('2020-04-30 12:20:10'::timestamp, 'WITHDRAW', 300.00, 5006),
|
||||||
|
('2020-04-30 14:10:10'::timestamp, 'WITHDRAW', 402.95, 5006);
|
||||||
15
docker/docker-compose.yml
Normal file
15
docker/docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
build: ./db
|
||||||
|
container_name: postgres
|
||||||
|
volumes:
|
||||||
|
- ./db_data/:/var/lib/postgresql/data/
|
||||||
|
ports:
|
||||||
|
- "7654:5432"
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=test
|
||||||
|
- POSTGRES_PASSWORD=test
|
||||||
|
- POSTGRES_DB=test
|
||||||
184
gradlew
vendored
Executable file
184
gradlew
vendored
Executable file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ]; do
|
||||||
|
ls=$(ls -ld "$PRG")
|
||||||
|
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||||
|
if expr "$link" : '/.*' >/dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=$(dirname "$PRG")"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="$(pwd)"
|
||||||
|
cd "$(dirname \"$PRG\")/" >/dev/null
|
||||||
|
APP_HOME="$(pwd -P)"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=$(basename "$0")
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN*)
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin*)
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW*)
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP*)
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ]; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ]; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then
|
||||||
|
MAX_FD_LIMIT=$(ulimit -H -n)
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ]; then
|
||||||
|
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
|
||||||
|
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
|
||||||
|
|
||||||
|
JAVACMD=$(cygpath --unix "$JAVACMD")
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ]; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -)
|
||||||
|
CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition
|
||||||
|
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
|
||||||
|
else
|
||||||
|
eval $(echo args$i)="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$(expr $i + 1)
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save() {
|
||||||
|
for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
104
gradlew.bat
vendored
Normal file
104
gradlew.bat
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
40
init_postgresql_db/data-postgresql.sql
Normal file
40
init_postgresql_db/data-postgresql.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
DELETE FROM personal_account_operations;
|
||||||
|
DELETE FROM employee_personal_accounts;
|
||||||
|
DELETE FROM employees;
|
||||||
|
ALTER SEQUENCE seq_employees RESTART WITH 5000;
|
||||||
|
|
||||||
|
INSERT INTO employees (name, email) VALUES
|
||||||
|
('Ivanov Ivan Ivanovich', 'ivanov@example.com'),
|
||||||
|
('Petrov Vasily Victorovich', 'petrov@example.com');
|
||||||
|
|
||||||
|
INSERT INTO employee_personal_accounts (personal_account, employee_id)
|
||||||
|
VALUES ('4154014152522741', 5000),
|
||||||
|
('4131668358915203', 5000),
|
||||||
|
('4281563275602455', 5000),
|
||||||
|
('4103234971123321', 5001),
|
||||||
|
('4132555843841699', 5001);
|
||||||
|
|
||||||
|
INSERT INTO personal_account_operations (operation_date_time, operation_type, operation_value, personal_account_id)
|
||||||
|
VALUES ('2020-05-30 10:00:00'::timestamp, 'DEPOSIT', 840.35, 5002),
|
||||||
|
('2020-05-28 11:05:10'::timestamp, 'DEPOSIT', 625.00, 5002),
|
||||||
|
('2020-05-25 11:41:10'::timestamp, 'DEPOSIT', 1080.45, 5002),
|
||||||
|
('2020-05-30 14:00:10'::timestamp, 'WITHDRAW', 652.33, 5002),
|
||||||
|
('2020-05-26 18:10:10'::timestamp, 'WITHDRAW', 420.00, 5002),
|
||||||
|
('2020-06-30 10:00:00'::timestamp, 'DEPOSIT', 1500.52, 5003),
|
||||||
|
('2020-06-30 11:05:10'::timestamp, 'DEPOSIT', 800.73, 5003),
|
||||||
|
('2020-06-30 14:00:10'::timestamp, 'WITHDRAW', 170.35, 5003),
|
||||||
|
('2020-06-30 18:10:10'::timestamp, 'WITHDRAW', 320.00, 5003),
|
||||||
|
('2020-07-15 12:05:10'::timestamp, 'DEPOSIT', 800.73, 5004),
|
||||||
|
('2020-07-15 12:41:10'::timestamp, 'DEPOSIT', 350.00, 5004),
|
||||||
|
('2020-07-15 15:00:10'::timestamp, 'WITHDRAW', 900.35, 5004),
|
||||||
|
('2020-07-15 17:10:10'::timestamp, 'WITHDRAW', 600.00, 5004),
|
||||||
|
('2020-05-15 11:05:10'::timestamp, 'DEPOSIT', 976.33, 5005),
|
||||||
|
('2020-05-15 11:41:10'::timestamp, 'DEPOSIT', 850.00, 5005),
|
||||||
|
('2020-05-15 14:00:10'::timestamp, 'WITHDRAW', 200.00, 5005),
|
||||||
|
('2020-05-15 18:10:10'::timestamp, 'WITHDRAW', 375.85, 5005),
|
||||||
|
('2020-04-30 09:00:00'::timestamp, 'DEPOSIT', 1200.52, 5006),
|
||||||
|
('2020-04-30 10:35:00'::timestamp, 'DEPOSIT', 300.53, 5006),
|
||||||
|
('2020-04-30 10:55:00'::timestamp, 'DEPOSIT', 450.60, 5006),
|
||||||
|
('2020-04-30 12:20:10'::timestamp, 'WITHDRAW', 300.00, 5006),
|
||||||
|
('2020-04-30 14:10:10'::timestamp, 'WITHDRAW', 402.95, 5006);
|
||||||
|
|
||||||
3
init_postgresql_db/ddl-postgresql.sql
Normal file
3
init_postgresql_db/ddl-postgresql.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
CREATE DATABASE test;
|
||||||
|
CREATE USER "user" WITH password 'password';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE test TO "user";
|
||||||
35
init_postgresql_db/schema-postgresql.sql
Normal file
35
init_postgresql_db/schema-postgresql.sql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
DROP TABLE IF EXISTS personal_account_operations;
|
||||||
|
DROP TABLE IF EXISTS employee_personal_accounts;
|
||||||
|
DROP TABLE IF EXISTS employees;
|
||||||
|
DROP SEQUENCE IF EXISTS seq_employees cascade;
|
||||||
|
|
||||||
|
CREATE SEQUENCE seq_employees START 5000;
|
||||||
|
|
||||||
|
CREATE TABLE employees
|
||||||
|
(
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
email VARCHAR NOT NULL
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employees_unique_email_idx ON employees (email);
|
||||||
|
|
||||||
|
CREATE TABLE employee_personal_accounts (
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
employee_id BIGINT NOT NULL,
|
||||||
|
personal_account VARCHAR NOT NULL,
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employee_personal_accounts_unique_employee_account_idx
|
||||||
|
ON employee_personal_accounts (employee_id, personal_account);
|
||||||
|
|
||||||
|
CREATE TABLE personal_account_operations (
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
personal_account_id BIGINT NOT NULL,
|
||||||
|
operation_date_time TIMESTAMP NOT NULL,
|
||||||
|
operation_type VARCHAR NOT NULL,
|
||||||
|
-- Money data on PostgreSQL using Java https://stackoverflow.com/a/18170030
|
||||||
|
operation_value DECIMAL NOT NULL,
|
||||||
|
FOREIGN KEY (personal_account_id) REFERENCES employee_personal_accounts (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX personal_account_operations_unique_account_datetime_idx
|
||||||
|
ON personal_account_operations (personal_account_id, operation_date_time);
|
||||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'dfops'
|
||||||
13
src/main/java/ru/resprojects/dfops/DfopsApplication.java
Normal file
13
src/main/java/ru/resprojects/dfops/DfopsApplication.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package ru.resprojects.dfops;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class DfopsApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(DfopsApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
src/main/java/ru/resprojects/dfops/config/AppConfig.java
Normal file
22
src/main/java/ru/resprojects/dfops/config/AppConfig.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.resprojects.dfops.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AppConfig {
|
||||||
|
|
||||||
|
public static final String DEFAULT_PAGE_LIMIT = "10";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI customOpenApi() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.components(new Components())
|
||||||
|
.info(new Info().title("Driver Finance Operation REST Service API").description(
|
||||||
|
"REST-сервис для приема и регистрации операций в базе данных по расчетам с водителями."));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package ru.resprojects.dfops.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import ru.resprojects.dfops.config.AppConfig;
|
||||||
|
import ru.resprojects.dfops.dto.ResponseDto;
|
||||||
|
import ru.resprojects.dfops.dto.account.AccountDto;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ErrorMessage;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.service.AccountService;
|
||||||
|
import ru.resprojects.dfops.service.EmployeeService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/account")
|
||||||
|
@Tag(name ="Расчётный счёт работника", description = "REST API для работы с сущностью 'Account'")
|
||||||
|
public class AccountController {
|
||||||
|
|
||||||
|
private final EmployeeService employeeService;
|
||||||
|
|
||||||
|
private final AccountService accountService;
|
||||||
|
|
||||||
|
public AccountController(EmployeeService employeeService, AccountService accountService) {
|
||||||
|
this.employeeService = employeeService;
|
||||||
|
this.accountService = accountService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Вывод лицевых счетов работника",
|
||||||
|
description = "Возвращает список лицевых счетов выбранного работника",
|
||||||
|
tags = { "account" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ResponseDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "204",
|
||||||
|
description = "лицевые счета у выбранного работника отсутствуют",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "не найден работник у которого необходимо вывести список лицевых счетов",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/{employee_id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ResponseDto<Account>> getAll(
|
||||||
|
@Parameter(description="ID работника", required = true,
|
||||||
|
example = "5002", schema=@Schema(implementation = Long.class))
|
||||||
|
@PathVariable("employee_id") Long employee_id,
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода, задает номер текущей страницы",
|
||||||
|
example = "1", schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer pageNo,
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода, задает количество элементов на страницу",
|
||||||
|
example = AppConfig.DEFAULT_PAGE_LIMIT, schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "limit", defaultValue = AppConfig.DEFAULT_PAGE_LIMIT) Integer limit)
|
||||||
|
{
|
||||||
|
Employee employee = employeeService.get(employee_id);
|
||||||
|
ResponseDto<Account> response = new ResponseDto<>(accountService.getAll(employee.getId(), pageNo, limit));
|
||||||
|
if (response.getElements().isEmpty()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Поиск лицевого счёта",
|
||||||
|
description = "Возвращает информацию о лицевом счёте",
|
||||||
|
tags = { "account" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = Account.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "запрашиваемый объект не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Account> getById(
|
||||||
|
@Parameter(description="ID лицевого счёта", required = true,
|
||||||
|
example = "5002", schema=@Schema(implementation = Long.class))
|
||||||
|
@PathVariable("id") Long id
|
||||||
|
) {
|
||||||
|
return ResponseEntity.ok(accountService.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Создание новой записи",
|
||||||
|
description = "Создаёт новую запись и возвращает созданный объект",
|
||||||
|
tags = { "account" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = Account.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "работник для которого создаётся аккаунт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "409",
|
||||||
|
description = "элемент уже существует в базе",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Account> create(
|
||||||
|
@Parameter(description="Создаваемый объект лицевого счёта",
|
||||||
|
required=true, schema=@Schema(implementation = AccountDto.class))
|
||||||
|
@RequestBody AccountDto accountDto) {
|
||||||
|
if (accountDto == null) {
|
||||||
|
throw new BadResourceException("Request body is null");
|
||||||
|
}
|
||||||
|
Employee employee = employeeService.get(accountDto.getEmployeeId());
|
||||||
|
return ResponseEntity.ok(accountService.create(employee, accountDto.getPersonalAccount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Удаление записи",
|
||||||
|
description = "Удаление записи из базы данных",
|
||||||
|
tags = { "account" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "элемент не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@DeleteMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Void> delete(
|
||||||
|
@Parameter(description="ID лицевого счёта", required = true,
|
||||||
|
example = "5002", schema=@Schema(implementation = Long.class))
|
||||||
|
@PathVariable("id") Long id) {
|
||||||
|
accountService.delete(id);
|
||||||
|
return ResponseEntity.status(HttpStatus.OK).body(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
package ru.resprojects.dfops.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import ru.resprojects.dfops.config.AppConfig;
|
||||||
|
import ru.resprojects.dfops.dto.ResponseDto;
|
||||||
|
import ru.resprojects.dfops.dto.employee.EmployeeDto;
|
||||||
|
import ru.resprojects.dfops.exception.BadRequestException;
|
||||||
|
import ru.resprojects.dfops.exception.ErrorMessage;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.service.EmployeeService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/employee")
|
||||||
|
@Tag(name ="Работник (водитель)", description = "REST API для работы с сущностью 'Employee'")
|
||||||
|
public class EmployeeController {
|
||||||
|
|
||||||
|
private final EmployeeService employeeService;
|
||||||
|
|
||||||
|
public EmployeeController(EmployeeService employeeService) {
|
||||||
|
this.employeeService = employeeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Получить список работников",
|
||||||
|
description = "Возвращает список работников",
|
||||||
|
tags = { "employee" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ResponseDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "204",
|
||||||
|
description = "список работников пуст",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ResponseDto<Employee>> getAll(
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода, задает номер текущей страницы",
|
||||||
|
example = "1", schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer pageNo,
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода, задает количество элементов на страницу",
|
||||||
|
example = AppConfig.DEFAULT_PAGE_LIMIT,schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "limit", defaultValue = AppConfig.DEFAULT_PAGE_LIMIT) Integer limit)
|
||||||
|
{
|
||||||
|
ResponseDto<Employee> response = new ResponseDto<>(employeeService.getAll(pageNo, limit));
|
||||||
|
if (response.getElements().isEmpty()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Поиск работника",
|
||||||
|
description = "Возвращает информацию о работнике",
|
||||||
|
tags = { "employee" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = Employee.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "запрашиваемый объект не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Employee> getById(
|
||||||
|
@Parameter(description="ID работника", required = true,
|
||||||
|
example = "5000", schema=@Schema(implementation = Long.class))
|
||||||
|
@PathVariable("id") Long id) {
|
||||||
|
return ResponseEntity.ok(employeeService.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Поиск работника по e-mail",
|
||||||
|
description = "Возвращает информацию о работнике",
|
||||||
|
tags = { "employee" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = Employee.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "запрашиваемый объект не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/get", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Employee> getByEmail(
|
||||||
|
@Parameter(description="Email работника", required=true,
|
||||||
|
example = "ivanov@example.com", schema=@Schema(implementation = String.class))
|
||||||
|
@RequestParam(value = "email") String email) {
|
||||||
|
if (email == null || email.isBlank()) {
|
||||||
|
throw new BadRequestException("Email is null or empty");
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(employeeService.getByEmail(email));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Создание новой записи",
|
||||||
|
description = "Создаёт новую запись и возвращает созданный объект",
|
||||||
|
tags = { "employee" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = Employee.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "409",
|
||||||
|
description = "элемент уже существует в базе",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Employee> create(
|
||||||
|
@Parameter(description="Создаваемый объект",
|
||||||
|
required=true, schema=@Schema(implementation = EmployeeDto.class))
|
||||||
|
@RequestBody EmployeeDto employeeDto) {
|
||||||
|
if (employeeDto == null) {
|
||||||
|
throw new BadRequestException("Request body is null");
|
||||||
|
}
|
||||||
|
Employee employee = new Employee(employeeDto.getName(), employeeDto.getEmail());
|
||||||
|
return ResponseEntity.ok(employeeService.create(employee));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Удаление записи",
|
||||||
|
description = "Удаление записи из базы данных",
|
||||||
|
tags = { "employee" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "элемент не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@DeleteMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Employee> delete(
|
||||||
|
@Parameter(description="ID работника", required = true,
|
||||||
|
example = "5000", schema=@Schema(implementation = Long.class))
|
||||||
|
@PathVariable("id") Long id) {
|
||||||
|
employeeService.delete(id);
|
||||||
|
return ResponseEntity.status(HttpStatus.OK).body(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,605 @@
|
|||||||
|
package ru.resprojects.dfops.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import ru.resprojects.dfops.config.AppConfig;
|
||||||
|
import ru.resprojects.dfops.dto.ResponseDto;
|
||||||
|
import ru.resprojects.dfops.dto.account.AccountBalanceDto;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationAmountDto;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationDto;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationTransferDto;
|
||||||
|
import ru.resprojects.dfops.exception.BadRequestException;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ErrorMessage;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.service.AccountService;
|
||||||
|
import ru.resprojects.dfops.service.OperationService;
|
||||||
|
import ru.resprojects.dfops.util.DateTimeUtil;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/operation")
|
||||||
|
@Tag(name ="Операции над счётом", description = "REST API для работы с сущностью 'Operation'")
|
||||||
|
public class OperationController {
|
||||||
|
|
||||||
|
private final AccountService accountService;
|
||||||
|
|
||||||
|
private final OperationService operationService;
|
||||||
|
|
||||||
|
public OperationController(AccountService accountService, OperationService operationService) {
|
||||||
|
this.accountService = accountService;
|
||||||
|
this.operationService = operationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Вывод всех операций по лицевому счёту работника",
|
||||||
|
description = "Возвращает список всех операций по выбранному лицевому счету работника",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ResponseDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "204",
|
||||||
|
description = "список операций по счёту пуст",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/{account_id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ResponseDto<ru.resprojects.dfops.model.Operation>> getAll(
|
||||||
|
@Parameter(description="ID лицевого счёта", schema=@Schema(implementation = Long.class), required = true, example = "5002")
|
||||||
|
@PathVariable("account_id") Long accountId,
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода. Задает номер текущей страницы",
|
||||||
|
example = "1", schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer pageNo,
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода. Задает количество элементов на страницу",
|
||||||
|
example = AppConfig.DEFAULT_PAGE_LIMIT, schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "limit", defaultValue = AppConfig.DEFAULT_PAGE_LIMIT) Integer limit)
|
||||||
|
{
|
||||||
|
Account account = accountService.get(accountId);
|
||||||
|
ResponseDto<ru.resprojects.dfops.model.Operation> response = new ResponseDto<>(operationService.getAllByAccountId(account.getId(), pageNo, limit));
|
||||||
|
if (response.getElements().isEmpty()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Вывод всех операций за период по лицевому счёту работника",
|
||||||
|
description = "Возвращает список всех операций за период по выбранному лицевому счету работника",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ResponseDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "204",
|
||||||
|
description = "список операций по счёту пуст",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/{account_id}/filter", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ResponseDto<ru.resprojects.dfops.model.Operation>> getAllBetween(
|
||||||
|
@Parameter(description="ID лицевого счёта", schema=@Schema(implementation = Long.class), required = true, example = "5002")
|
||||||
|
@PathVariable("account_id") Long accountId,
|
||||||
|
@Parameter(description="Начальная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "01.05.2020-00:00", required = true)
|
||||||
|
@RequestParam(value = "startDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime startDateTime,
|
||||||
|
@Parameter(description="Конечная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "31.05.2020-23:59", required = true)
|
||||||
|
@RequestParam(value = "endDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime endDateTime,
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода. Задает номер текущей страницы.",
|
||||||
|
example = "1", schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer pageNo,
|
||||||
|
@Parameter(description="Параметр запроса для постраничного вывода. Задает количество элементов на страницу.",
|
||||||
|
example = AppConfig.DEFAULT_PAGE_LIMIT, schema=@Schema(implementation = Integer.class))
|
||||||
|
@RequestParam(value = "limit", defaultValue = AppConfig.DEFAULT_PAGE_LIMIT) Integer limit
|
||||||
|
)
|
||||||
|
{
|
||||||
|
checkDateTimeFilter(startDateTime, endDateTime);
|
||||||
|
Account account = accountService.get(accountId);
|
||||||
|
ResponseDto<ru.resprojects.dfops.model.Operation> response = new ResponseDto<>(
|
||||||
|
operationService.getAllByAccountIdBetween(account.getId(), startDateTime, endDateTime, pageNo, limit)
|
||||||
|
);
|
||||||
|
if (response.getElements().isEmpty()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Вывод оборота по операциям за период для лицевого счёта",
|
||||||
|
description = "Возвращает оборот по операциям за период для указанного лицевого счёта работника",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ResponseDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/{account_id}/operations_amount/filter", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ResponseDto<OperationAmountDto>> getOperationsAmountBetween(
|
||||||
|
@Parameter(description="ID лицевого счёта", schema=@Schema(implementation = Long.class), required = true, example = "5002")
|
||||||
|
@PathVariable("account_id") Long accountId,
|
||||||
|
@Parameter(description="Начальная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "01.05.2020-00:00", required = true)
|
||||||
|
@RequestParam(value = "startDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime startDateTime,
|
||||||
|
@Parameter(description="Конечная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "31.05.2020-23:59", required = true)
|
||||||
|
@RequestParam(value = "endDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime endDateTime
|
||||||
|
)
|
||||||
|
{
|
||||||
|
checkDateTimeFilter(startDateTime, endDateTime);
|
||||||
|
Account account = accountService.get(accountId);
|
||||||
|
List<OperationAmountDto> result = operationService.getOperationsAmountBetween(account.getId(), startDateTime, endDateTime);
|
||||||
|
ResponseDto<OperationAmountDto> responseDto = new ResponseDto<>();
|
||||||
|
responseDto.setElements(result);
|
||||||
|
responseDto.setPageCount(1);
|
||||||
|
responseDto.setCurrentPage(1);
|
||||||
|
responseDto.setTotalItems(result.size());
|
||||||
|
return ResponseEntity.ok(responseDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Вывод дебет лицевого счёта за период",
|
||||||
|
description = "Возвращает дебет лицевого счёта работника за указанный период",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = OperationAmountDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/{account_id}/debit/filter", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<OperationAmountDto> getDebitBetween(
|
||||||
|
@Parameter(description="ID лицевого счёта", schema=@Schema(implementation = Long.class), required = true, example = "5002")
|
||||||
|
@PathVariable("account_id") Long accountId,
|
||||||
|
@Parameter(description="Начальная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "01.05.2020-00:00", required = true)
|
||||||
|
@RequestParam(value = "startDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime startDateTime,
|
||||||
|
@Parameter(description="Конечная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "31.05.2020-23:59", required = true)
|
||||||
|
@RequestParam(value = "endDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime endDateTime
|
||||||
|
)
|
||||||
|
{
|
||||||
|
checkDateTimeFilter(startDateTime, endDateTime);
|
||||||
|
Account account = accountService.get(accountId);
|
||||||
|
BigDecimal result = operationService.getDebitBetween(account.getId(), startDateTime, endDateTime);
|
||||||
|
OperationAmountDto operationAmountDto = new OperationAmountDto(result, ru.resprojects.dfops.model.Operation.OperationType.DEPOSIT);
|
||||||
|
return ResponseEntity.ok(operationAmountDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Вывод кредит лицевого счёта",
|
||||||
|
description = "Возвращает кредит лицевого счёта работника за период",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = OperationAmountDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/{account_id}/credit/filter", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<OperationAmountDto> getCreditBetween(
|
||||||
|
@Parameter(description="ID лицевого счёта", schema=@Schema(implementation = Long.class), required = true, example = "5002")
|
||||||
|
@PathVariable("account_id") Long accountId,
|
||||||
|
@Parameter(description="Начальная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "01.05.2020-00:00", required = true)
|
||||||
|
@RequestParam(value = "startDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime startDateTime,
|
||||||
|
@Parameter(description="Конечная дата и время (формат dd.MM.yyyy-HH:mm)", schema=@Schema(implementation = String.class),
|
||||||
|
example = "31.05.2020-23:59", required = true)
|
||||||
|
@RequestParam(value = "endDateTime") @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN_REQUEST) LocalDateTime endDateTime
|
||||||
|
)
|
||||||
|
{
|
||||||
|
checkDateTimeFilter(startDateTime, endDateTime);
|
||||||
|
Account account = accountService.get(accountId);
|
||||||
|
BigDecimal result = operationService.getCreditBetween(account.getId(), startDateTime, endDateTime);
|
||||||
|
OperationAmountDto operationAmountDto = new OperationAmountDto(result, ru.resprojects.dfops.model.Operation.OperationType.WITHDRAW);
|
||||||
|
return ResponseEntity.ok(operationAmountDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Текущий баланс лицевого счёта",
|
||||||
|
description = "Выводит текущий баланс лицевого счёта",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = AccountBalanceDto.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/{account_id}/balance", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<AccountBalanceDto> getBalance(
|
||||||
|
@Parameter(description="ID лицевого счёта", schema=@Schema(implementation = Long.class), required = true, example = "5002")
|
||||||
|
@PathVariable("account_id") Long accountId) {
|
||||||
|
Account account = accountService.get(accountId);
|
||||||
|
BigDecimal balance = operationService.getCurrentBalance(account.getId());
|
||||||
|
AccountBalanceDto balanceDto = new AccountBalanceDto(account.getId(), account.getPersonalAccount(), balance);
|
||||||
|
return ResponseEntity.ok(balanceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Зачисление на лицевой счёт",
|
||||||
|
description = "Зачисление указанной суммы на лицевого счёта работника",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ru.resprojects.dfops.model.Operation.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "403",
|
||||||
|
description = "некорректные данные",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@PostMapping(value = "/deposit", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ru.resprojects.dfops.model.Operation> deposit(
|
||||||
|
@Parameter(description="Объект с даннами для проведения операции по лицевому счёту", required=true, schema=@Schema(implementation = OperationDto.class))
|
||||||
|
@RequestBody OperationDto operationDto
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (operationDto == null) {
|
||||||
|
throw new BadRequestException("Request body is null");
|
||||||
|
}
|
||||||
|
if (operationDto.getOperationType() == null) {
|
||||||
|
throw new BadRequestException("Unknown operation type");
|
||||||
|
}
|
||||||
|
if (!ru.resprojects.dfops.model.Operation.OperationType.DEPOSIT.equals(operationDto.getOperationType())) {
|
||||||
|
throw new BadRequestException("Must be DEPOSIT but " + operationDto.getOperationType());
|
||||||
|
}
|
||||||
|
Account account = accountService.get(operationDto.getAccountId());
|
||||||
|
return ResponseEntity.ok(operationService.deposit(account, operationDto.getAmount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Списание с лицевого счёта",
|
||||||
|
description = "Списание указанной суммы с лицевого счёта работника",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ru.resprojects.dfops.model.Operation.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "403",
|
||||||
|
description = "некорректные данные",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@PostMapping(value = "/withdraw", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ru.resprojects.dfops.model.Operation> withdraw(
|
||||||
|
@Parameter(description="Объект с даннами для проведения операции по лицевому счёту", required=true, schema=@Schema(implementation = OperationDto.class))
|
||||||
|
@RequestBody OperationDto operationDto
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (operationDto == null) {
|
||||||
|
throw new BadRequestException("Request body is null");
|
||||||
|
}
|
||||||
|
if (operationDto.getOperationType() == null) {
|
||||||
|
throw new BadRequestException("Unknown operation type");
|
||||||
|
}
|
||||||
|
if (!ru.resprojects.dfops.model.Operation.OperationType.WITHDRAW.equals(operationDto.getOperationType())) {
|
||||||
|
throw new BadRequestException("Must be WITHDRAW but " + operationDto.getOperationType());
|
||||||
|
}
|
||||||
|
Account account = accountService.get(operationDto.getAccountId());
|
||||||
|
return ResponseEntity.ok(operationService.withdraw(account, operationDto.getAmount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Перевод с лицевого счёта на лицевого счёта",
|
||||||
|
description = "Перевод с лицевого счёта на лицевой счёт работника",
|
||||||
|
tags = { "operation" }
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "успешная операция"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "некорректный запрос",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "лицевой счёт не найден",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "403",
|
||||||
|
description = "некорректные данные",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "500",
|
||||||
|
description = "неизвестная ошибка",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(implementation = ErrorMessage.class),
|
||||||
|
mediaType = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@PostMapping(value = "/transfer", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Void> transfer(
|
||||||
|
@Parameter(description="Объект с даннами для перевода суммы между счетами", required=true, schema=@Schema(implementation = OperationTransferDto.class))
|
||||||
|
@RequestBody OperationTransferDto operationTransferDto
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (operationTransferDto == null) {
|
||||||
|
throw new BadResourceException("Request body is null");
|
||||||
|
}
|
||||||
|
Account from = accountService.get(operationTransferDto.getAccountIdFrom());
|
||||||
|
Account to = accountService.get(operationTransferDto.getAccountIdTo());
|
||||||
|
operationService.transfer(from, to, operationTransferDto.getAmount());
|
||||||
|
return ResponseEntity.ok().body(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDateTimeFilter(LocalDateTime startDateTime, LocalDateTime endDateTime) {
|
||||||
|
if (startDateTime == null || endDateTime == null) {
|
||||||
|
throw new BadRequestException("Start date-time or end date-time is null");
|
||||||
|
}
|
||||||
|
if (startDateTime.compareTo(endDateTime) > 0) {
|
||||||
|
throw new BadRequestException("Start date-time later than end date-time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package ru.resprojects.dfops.controller;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import ru.resprojects.dfops.exception.BadRequestException;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ErrorMessage;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceAlreadyExistsException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class RestExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(value = {ResourceNotFoundException.class})
|
||||||
|
public ResponseEntity<ErrorMessage> resourceNotFound(HttpServletRequest request, ResourceNotFoundException exception) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(value = {BadResourceException.class})
|
||||||
|
public ResponseEntity<ErrorMessage> badResource(HttpServletRequest request, BadResourceException exception) {
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(value = {BadRequestException.class})
|
||||||
|
public ResponseEntity<ErrorMessage> badRequest(HttpServletRequest request, BadRequestException exception) {
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(value = {ResourceAlreadyExistsException.class})
|
||||||
|
public ResponseEntity<ErrorMessage> alreadyExistResource(HttpServletRequest request, ResourceAlreadyExistsException exception) {
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(exception.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<ErrorMessage> handleError(HttpServletRequest requesr, Exception exception) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorMessage("Unexpected error: " + exception.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
src/main/java/ru/resprojects/dfops/dto/ResponseDto.java
Normal file
47
src/main/java/ru/resprojects/dfops/dto/ResponseDto.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package ru.resprojects.dfops.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@Schema(description = "Объект для постраничного вывода набора элементов")
|
||||||
|
public class ResponseDto<T> implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -3654370454854576301L;
|
||||||
|
|
||||||
|
@Schema(description = "Список элементов")
|
||||||
|
@JsonProperty("elements")
|
||||||
|
private Collection<T> elements;
|
||||||
|
|
||||||
|
@Schema(description = "Номер текущей страницы")
|
||||||
|
@JsonProperty("currentPage")
|
||||||
|
private int currentPage;
|
||||||
|
|
||||||
|
@Schema(description = "Всего элементов")
|
||||||
|
@JsonProperty("totalItems")
|
||||||
|
private long totalItems;
|
||||||
|
|
||||||
|
@Schema(description = "Номер всего страниц")
|
||||||
|
@JsonProperty("totalPages")
|
||||||
|
private int pageCount;
|
||||||
|
|
||||||
|
public ResponseDto(Page<T> response) {
|
||||||
|
this.elements = response.getContent();
|
||||||
|
this.currentPage = response.getNumber() + 1;
|
||||||
|
this.totalItems = response.getTotalElements();
|
||||||
|
this.pageCount = response.getTotalPages();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.resprojects.dfops.dto.account;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Schema(description = "Объект для передачи данных о текущем балансе лицевого счёта")
|
||||||
|
public class AccountBalanceDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -82064792530944046L;
|
||||||
|
|
||||||
|
@Schema(description = "ID лицевого счёта")
|
||||||
|
@JsonProperty("account_id")
|
||||||
|
private Long accountId;
|
||||||
|
|
||||||
|
@Schema(description = "Номер лицевого счёта")
|
||||||
|
@JsonProperty("personal_account")
|
||||||
|
private String personalAccount;
|
||||||
|
|
||||||
|
@Schema(description = "Текущий баланс лицевого счёта")
|
||||||
|
@JsonProperty("balance")
|
||||||
|
private BigDecimal balance;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ru.resprojects.dfops.dto.account;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Schema(description = "Объект для передачи данных о регистрации лицевого счёта для выбранного работника")
|
||||||
|
public class AccountDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 8356762606534341601L;
|
||||||
|
|
||||||
|
@Schema(description = "id работника", example = "5000", required = true)
|
||||||
|
@JsonProperty("employee_id")
|
||||||
|
@NotNull
|
||||||
|
private Long employeeId;
|
||||||
|
|
||||||
|
@Schema(description = "лицевой счёт работника", example = "123456789", required = true)
|
||||||
|
@JsonProperty("personal_account")
|
||||||
|
@NotBlank
|
||||||
|
private String personalAccount;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package ru.resprojects.dfops.dto.employee;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Schema(description = "Объект для передачи данных о регистрации работника в БД")
|
||||||
|
public class EmployeeDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 313229127008191126L;
|
||||||
|
|
||||||
|
@Schema(description = "Имя работника", example = "Alex", required = true)
|
||||||
|
@JsonProperty("name")
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "E-mail работника", example = "alex@example.com", required = true)
|
||||||
|
@JsonProperty("email")
|
||||||
|
@NotBlank
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package ru.resprojects.dfops.dto.operation;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import ru.resprojects.dfops.model.Operation;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Объект для передачи данных о сумме и типе операций по счёту")
|
||||||
|
public class OperationAmountDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3448183919367413609L;
|
||||||
|
|
||||||
|
@Schema(description = "Сумма")
|
||||||
|
@JsonProperty("operation_sum")
|
||||||
|
private BigDecimal sum;
|
||||||
|
|
||||||
|
@Schema(description = "Тип операции: WITHDRAW - списание, DEPOSIT - зачисление")
|
||||||
|
@JsonProperty("operation_type")
|
||||||
|
private Operation.OperationType operationType;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.resprojects.dfops.dto.operation;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import ru.resprojects.dfops.model.Operation;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Schema(description = "Операция по лицевому счёту")
|
||||||
|
public class OperationDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 6502515027792967205L;
|
||||||
|
|
||||||
|
@Schema(description = "ID лицевого счёта", example = "5002", required = true)
|
||||||
|
@JsonProperty("account_id")
|
||||||
|
private Long accountId;
|
||||||
|
|
||||||
|
@Schema(description = "Сумма операции", example = "100.25", required = true)
|
||||||
|
@JsonProperty("operation_amount")
|
||||||
|
double amount;
|
||||||
|
|
||||||
|
@Schema(description = "Тип операции: DEPOSIT - начисление, WITHDRAW - списание", example = "WITHDRAW", required = true)
|
||||||
|
@JsonProperty("operation_type")
|
||||||
|
Operation.OperationType operationType;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.resprojects.dfops.dto.operation;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Schema(description = "Перевод между лицевыми счетами работника. ID лицевых счетов " +
|
||||||
|
"откуда переводят и куда переводят должны принадлежать одному и тому же работнику")
|
||||||
|
public class OperationTransferDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 5904536254136545140L;
|
||||||
|
|
||||||
|
@Schema(description = "ID лицевого счёта откуда переводят", example = "5002", required = true)
|
||||||
|
@JsonProperty("account_id_from")
|
||||||
|
private Long accountIdFrom;
|
||||||
|
|
||||||
|
@Schema(description = "ID лицевого счёта куда переводят", example = "5003", required = true)
|
||||||
|
@JsonProperty("account_id_to")
|
||||||
|
private Long accountIdTo;
|
||||||
|
|
||||||
|
@Schema(description = "Переводимая сумма", example = "100.00", required = true)
|
||||||
|
@JsonProperty("operation_amount")
|
||||||
|
double amount;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.resprojects.dfops.exception;
|
||||||
|
|
||||||
|
public class BadRequestException extends RuntimeException {
|
||||||
|
|
||||||
|
public BadRequestException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadRequestException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorMessage getErrorMessage() {
|
||||||
|
return new ErrorMessage(this.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.resprojects.dfops.exception;
|
||||||
|
|
||||||
|
public class BadResourceException extends RuntimeException {
|
||||||
|
|
||||||
|
public BadResourceException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadResourceException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorMessage getErrorMessage() {
|
||||||
|
return new ErrorMessage(this.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.resprojects.dfops.exception;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Форма вывода ошибки")
|
||||||
|
public class ErrorMessage implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4089956800265998558L;
|
||||||
|
|
||||||
|
@Schema(description = "Сообщение о ошибке")
|
||||||
|
@JsonProperty("msg")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.resprojects.dfops.exception;
|
||||||
|
|
||||||
|
public class ResourceAlreadyExistsException extends RuntimeException {
|
||||||
|
|
||||||
|
public ResourceAlreadyExistsException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceAlreadyExistsException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorMessage getErrorMessage() {
|
||||||
|
return new ErrorMessage(this.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.resprojects.dfops.exception;
|
||||||
|
|
||||||
|
public class ResourceNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
public ResourceNotFoundException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceNotFoundException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorMessage getErrorMessage() {
|
||||||
|
return new ErrorMessage(this.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package ru.resprojects.dfops.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.Access;
|
||||||
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
|
import javax.persistence.SequenceGenerator;
|
||||||
|
|
||||||
|
@MappedSuperclass
|
||||||
|
@Access(AccessType.FIELD)
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
public abstract class AbstractBaseEntity {
|
||||||
|
|
||||||
|
public static final int START_SEQ = 5000;
|
||||||
|
|
||||||
|
@Schema(description = "Уникальный идентификатор")
|
||||||
|
@Id
|
||||||
|
@SequenceGenerator(
|
||||||
|
name = "seq_employees",
|
||||||
|
sequenceName = "seq_employees",
|
||||||
|
allocationSize = 1,
|
||||||
|
initialValue = START_SEQ
|
||||||
|
)
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_employees")
|
||||||
|
@JsonProperty("id")
|
||||||
|
protected Long id;
|
||||||
|
|
||||||
|
protected AbstractBaseEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractBaseEntity(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
72
src/main/java/ru/resprojects/dfops/model/Account.java
Normal file
72
src/main/java/ru/resprojects/dfops/model/Account.java
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package ru.resprojects.dfops.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "employee_personal_accounts", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(
|
||||||
|
columnNames = {"employee_id", "personal_account"},
|
||||||
|
name = "employee_personal_accounts_unique_employee_account_idx"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Schema(description = "Счёт работника")
|
||||||
|
public class Account extends AbstractBaseEntity {
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "employee_id", nullable = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
private Employee employee;
|
||||||
|
|
||||||
|
@Schema(description = "Лицевой счёт работника")
|
||||||
|
@Column(name = "personal_account", nullable = false, unique = true)
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 2, max = 50)
|
||||||
|
@JsonProperty("personal_account")
|
||||||
|
private String personalAccount;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "account")
|
||||||
|
private List<Operation> operations;
|
||||||
|
|
||||||
|
public Account(Employee employee, String personalAccount) {
|
||||||
|
this(null, employee, personalAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account(Long id, Employee employee, String personalAccount) {
|
||||||
|
super(id);
|
||||||
|
this.employee = employee;
|
||||||
|
this.personalAccount = personalAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Account{" +
|
||||||
|
"personalAccount='" + personalAccount + '\'' +
|
||||||
|
", id=" + id +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
59
src/main/java/ru/resprojects/dfops/model/Employee.java
Normal file
59
src/main/java/ru/resprojects/dfops/model/Employee.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package ru.resprojects.dfops.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
import javax.validation.constraints.Email;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "employees", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "employees_unique_email_idx")})
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString(of = {"name", "email"})
|
||||||
|
@Schema(description = "Работник")
|
||||||
|
public class Employee extends AbstractBaseEntity {
|
||||||
|
|
||||||
|
@Schema(description = "Имя работника")
|
||||||
|
@Column(name = "name", nullable = false)
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 2, max = 100)
|
||||||
|
@JsonProperty("name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "E-mail работника")
|
||||||
|
@Column(name = "email", nullable = false, unique = true)
|
||||||
|
@Email
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 100)
|
||||||
|
@JsonProperty("email")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "employee")
|
||||||
|
private List<Account> accounts;
|
||||||
|
|
||||||
|
public Employee(String name, String email) {
|
||||||
|
this(null, name, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Employee(Long id, String name, String email) {
|
||||||
|
super(id);
|
||||||
|
this.name = name;
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/main/java/ru/resprojects/dfops/model/Operation.java
Normal file
94
src/main/java/ru/resprojects/dfops/model/Operation.java
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package ru.resprojects.dfops.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import ru.resprojects.dfops.util.DateTimeUtil;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "personal_account_operations", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(
|
||||||
|
columnNames = {"personal_account_id", "operation_date_time"},
|
||||||
|
name = "personal_account_operations_unique_account_datetime_idx"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Schema(description = "Операция по лицевому счёту")
|
||||||
|
public class Operation extends AbstractBaseEntity {
|
||||||
|
|
||||||
|
//TODO найти как отобразить в OpenApi Enum
|
||||||
|
public static enum OperationType {
|
||||||
|
DEPOSIT,
|
||||||
|
WITHDRAW
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "personal_account_id", nullable = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
@Schema(description = "Дата и время операции")
|
||||||
|
@Column(name = "operation_date_time", nullable = false)
|
||||||
|
@NotNull
|
||||||
|
@DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN)
|
||||||
|
@JsonProperty("operation_date_time")
|
||||||
|
private LocalDateTime dateTime;
|
||||||
|
|
||||||
|
@Schema(description = "Тип операции")
|
||||||
|
@Column(name = "operation_type", nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@NotNull
|
||||||
|
@JsonProperty("operation_type")
|
||||||
|
private OperationType operationType;
|
||||||
|
|
||||||
|
@Schema(description = "Сумма операции")
|
||||||
|
@Column(name = "operation_value", nullable = false)
|
||||||
|
@NotNull
|
||||||
|
@JsonProperty("operation_amount")
|
||||||
|
private BigDecimal operationValue;
|
||||||
|
|
||||||
|
public Operation(Account account, OperationType operationType, BigDecimal operationValue) {
|
||||||
|
this(null, account, operationType, operationValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Operation(Long id, Account account, OperationType operationType, BigDecimal operationValue) {
|
||||||
|
super(id);
|
||||||
|
this.account = account;
|
||||||
|
this.dateTime = LocalDateTime.now();
|
||||||
|
this.operationType = operationType;
|
||||||
|
this.operationValue = operationValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Operation{" +
|
||||||
|
" dateTime=" + dateTime +
|
||||||
|
", operationType=" + operationType +
|
||||||
|
", operationValue=" + operationValue +
|
||||||
|
", id=" + id +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package ru.resprojects.dfops.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface AccountRepository extends JpaRepository<Account, Long> {
|
||||||
|
|
||||||
|
|
||||||
|
Page<Account> findAllByEmployee_Id(long employeeId, Pageable pageable);
|
||||||
|
|
||||||
|
boolean existsByPersonalAccount(String personalAccount);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = {"employee"}, type = EntityGraph.EntityGraphType.LOAD)
|
||||||
|
@Query("SELECT a FROM Account a WHERE a.id=:id")
|
||||||
|
Optional<Account> getWithEmployee(@Param("id") long id);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.resprojects.dfops.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
|
||||||
|
|
||||||
|
Optional<Employee> findByEmail(String email);
|
||||||
|
|
||||||
|
boolean existsByEmail(String email);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package ru.resprojects.dfops.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationAmountDto;
|
||||||
|
import ru.resprojects.dfops.model.Operation;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface OperationRepository extends JpaRepository<Operation, Long> {
|
||||||
|
|
||||||
|
Page<Operation> getAllByAccount_Id(long accountId, Pageable pageable);
|
||||||
|
|
||||||
|
@SuppressWarnings("JpaQlInspection")
|
||||||
|
@Query("SELECT o " +
|
||||||
|
"FROM Operation o " +
|
||||||
|
"WHERE o.account.id=:accountId AND o.dateTime BETWEEN :startDate AND :endDate")
|
||||||
|
Page<Operation> getAllByAccountIdBetween(
|
||||||
|
@Param("accountId") long accountId,
|
||||||
|
@Param("startDate") LocalDateTime startDate,
|
||||||
|
@Param("endDate") LocalDateTime endDate,
|
||||||
|
Pageable pageable
|
||||||
|
);
|
||||||
|
|
||||||
|
@Query("SELECT new ru.resprojects.dfops.dto.operation.OperationAmountDto(sum(o.operationValue), o.operationType) " +
|
||||||
|
"FROM Operation o " +
|
||||||
|
"WHERE o.account.id=:accountId " +
|
||||||
|
"GROUP BY o.operationType")
|
||||||
|
List<OperationAmountDto> getOperationsAmount(@Param("accountId") long accountId);
|
||||||
|
|
||||||
|
@SuppressWarnings("JpaQlInspection")
|
||||||
|
@Query("SELECT new ru.resprojects.dfops.dto.operation.OperationAmountDto(sum(o.operationValue), o.operationType) " +
|
||||||
|
"FROM Operation o " +
|
||||||
|
"WHERE o.account.id=:accountId AND o.dateTime BETWEEN :startDate AND :endDate " +
|
||||||
|
"GROUP BY o.operationType")
|
||||||
|
List<OperationAmountDto> getOperationsAmountBetween(
|
||||||
|
@Param("accountId") long accountId,
|
||||||
|
@Param("startDate") LocalDateTime startDate,
|
||||||
|
@Param("endDate") LocalDateTime endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("JpaQlInspection")
|
||||||
|
@Query("SELECT sum(o.operationValue) " +
|
||||||
|
"FROM Operation o " +
|
||||||
|
"WHERE o.account.id=:accountId AND o.operationType = :operationType AND o.dateTime BETWEEN :startDate AND :endDate " +
|
||||||
|
"GROUP BY o.operationType")
|
||||||
|
Optional<BigDecimal> getOperationAmountBetween(
|
||||||
|
@Param("accountId") long accountId,
|
||||||
|
@Param("startDate") LocalDateTime startDate,
|
||||||
|
@Param("endDate") LocalDateTime endDate,
|
||||||
|
@Param("operationType") Operation.OperationType operationType
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceAlreadyExistsException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
|
||||||
|
|
||||||
|
public interface AccountService {
|
||||||
|
|
||||||
|
Account create(Employee employee, String personalAccount) throws BadResourceException, ResourceAlreadyExistsException;
|
||||||
|
|
||||||
|
void delete(long id) throws ResourceNotFoundException;
|
||||||
|
|
||||||
|
Account get(long id) throws ResourceNotFoundException;
|
||||||
|
|
||||||
|
Page<Account> getAll(long employeeId, int pageNumber, int rowPerPage);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceAlreadyExistsException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.repository.AccountRepository;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public class AccountServiceImpl implements AccountService {
|
||||||
|
|
||||||
|
private final AccountRepository accountRepository;
|
||||||
|
|
||||||
|
public AccountServiceImpl(AccountRepository accountRepository) {
|
||||||
|
this.accountRepository = accountRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public Account create(Employee employee, String personalAccount) throws BadResourceException, ResourceAlreadyExistsException {
|
||||||
|
if (employee == null) {
|
||||||
|
throw new BadResourceException("Failed to save account. Employee is null");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(personalAccount)) {
|
||||||
|
throw new BadResourceException("Failed to save account. Personal account is null or empty");
|
||||||
|
}
|
||||||
|
if (accountRepository.existsByPersonalAccount(personalAccount)) {
|
||||||
|
String message = String.format(
|
||||||
|
"Failed to save account. Bank account with %s for employee id %d is exists",
|
||||||
|
personalAccount,
|
||||||
|
employee.getId()
|
||||||
|
);
|
||||||
|
throw new ResourceAlreadyExistsException(message);
|
||||||
|
}
|
||||||
|
Account account = new Account(employee, personalAccount);
|
||||||
|
return accountRepository.save(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void delete(long id) throws ResourceNotFoundException {
|
||||||
|
if (get(id) != null) {
|
||||||
|
accountRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Account get(long id) throws ResourceNotFoundException {
|
||||||
|
return accountRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Cannot find Account with id: " + id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Account> getAll(long employeeId, int pageNumber, int rowPerPage) {
|
||||||
|
return accountRepository.findAllByEmployee_Id(employeeId, PageRequest.of(pageNumber - 1, rowPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceAlreadyExistsException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
|
||||||
|
public interface EmployeeService {
|
||||||
|
|
||||||
|
Employee create(Employee employee) throws ResourceAlreadyExistsException, BadResourceException;
|
||||||
|
|
||||||
|
void delete(long id) throws ResourceNotFoundException;
|
||||||
|
|
||||||
|
Employee get(long id) throws ResourceNotFoundException;
|
||||||
|
|
||||||
|
Employee getByEmail(String email) throws ResourceNotFoundException;
|
||||||
|
|
||||||
|
Page<Employee> getAll(int pageNumber, int rowPerPage);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceAlreadyExistsException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.repository.EmployeeRepository;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public class EmployeeServiceImpl implements EmployeeService {
|
||||||
|
|
||||||
|
private final EmployeeRepository employeeRepository;
|
||||||
|
|
||||||
|
public EmployeeServiceImpl(EmployeeRepository employeeRepository) {
|
||||||
|
this.employeeRepository = employeeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public Employee create(Employee employee) throws ResourceAlreadyExistsException, BadResourceException {
|
||||||
|
if (employee == null) {
|
||||||
|
throw new BadResourceException("Failed to save employee. Employee is null");
|
||||||
|
}
|
||||||
|
String email = employee.getEmail();
|
||||||
|
if (StringUtils.isEmpty(email)) {
|
||||||
|
throw new BadResourceException("Failed to save employee. Employee e-mail is null or empty");
|
||||||
|
}
|
||||||
|
if (employeeRepository.existsByEmail(email)) {
|
||||||
|
throw new ResourceAlreadyExistsException("Failed to save employee. Employee with email is exists");
|
||||||
|
}
|
||||||
|
return employeeRepository.save(employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void delete(long id) throws ResourceNotFoundException {
|
||||||
|
if (get(id) != null) {
|
||||||
|
employeeRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Employee get(long id) throws ResourceNotFoundException {
|
||||||
|
return employeeRepository.findById(id).orElseThrow(
|
||||||
|
() -> new ResourceNotFoundException("Cannot find Employee with id: " + id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Employee getByEmail(String email) throws ResourceNotFoundException {
|
||||||
|
return employeeRepository.findByEmail(email).orElseThrow(
|
||||||
|
() -> new ResourceNotFoundException("Cannot find Employee with e-mail: " + email)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Employee> getAll(int pageNumber, int rowPerPage) {
|
||||||
|
return employeeRepository.findAll(PageRequest.of(pageNumber - 1, rowPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationAmountDto;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Operation;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface OperationService {
|
||||||
|
|
||||||
|
Operation deposit(Account account, double amount);
|
||||||
|
|
||||||
|
Operation withdraw(Account account, double amount) throws BadResourceException;
|
||||||
|
|
||||||
|
BigDecimal getCurrentBalance(long accountId);
|
||||||
|
|
||||||
|
BigDecimal getDebitBetween(long accountId, LocalDateTime start, LocalDateTime end) throws ResourceNotFoundException;
|
||||||
|
|
||||||
|
BigDecimal getCreditBetween(long accountId, LocalDateTime start, LocalDateTime end) throws ResourceNotFoundException;
|
||||||
|
|
||||||
|
List<OperationAmountDto> getOperationsAmountBetween(long accountId, LocalDateTime start, LocalDateTime end);
|
||||||
|
|
||||||
|
Page<Operation> getAllByAccountId(long accountId, int pageNumber, int rowPerPage);
|
||||||
|
|
||||||
|
Page<Operation> getAllByAccountIdBetween(long accountId, LocalDateTime start, LocalDateTime end, int pageNumber, int rowPerPage);
|
||||||
|
|
||||||
|
void transfer(Account accountFrom, Account accountTo, double amount) throws BadResourceException;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationAmountDto;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Operation;
|
||||||
|
import ru.resprojects.dfops.repository.OperationRepository;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public class OperationServiceImpl implements OperationService {
|
||||||
|
|
||||||
|
private final OperationRepository operationRepository;
|
||||||
|
|
||||||
|
public OperationServiceImpl(OperationRepository operationRepository) {
|
||||||
|
this.operationRepository = operationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public Operation deposit(Account account, double amount) {
|
||||||
|
BigDecimal depositAmount = new BigDecimal(String.valueOf(amount));
|
||||||
|
Operation operation = new Operation(account, Operation.OperationType.DEPOSIT, depositAmount.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
return operationRepository.save(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public Operation withdraw(Account account, double amount) throws BadResourceException {
|
||||||
|
double currentBalance = getCurrentBalance(account.getId()).doubleValue();
|
||||||
|
if (currentBalance < amount) {
|
||||||
|
throw new BadResourceException(
|
||||||
|
"Cannot withdraw from account = " + account.getPersonalAccount()
|
||||||
|
+ " because current balance of account " + account.getPersonalAccount() + "(" + currentBalance + ")"
|
||||||
|
+ " < amount = " + amount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
BigDecimal withdrawAmount = new BigDecimal(String.valueOf(amount));
|
||||||
|
Operation operation = new Operation(account, Operation.OperationType.WITHDRAW, withdrawAmount.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
return operationRepository.save(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getCurrentBalance(long accountId) {
|
||||||
|
List<OperationAmountDto> operationAmountDtos = operationRepository.getOperationsAmount(accountId);
|
||||||
|
BigDecimal sum = new BigDecimal("0.00");
|
||||||
|
for (OperationAmountDto amountDto : operationAmountDtos) {
|
||||||
|
if (Operation.OperationType.WITHDRAW.equals(amountDto.getOperationType())) {
|
||||||
|
sum = sum.subtract(amountDto.getSum());
|
||||||
|
} else {
|
||||||
|
sum = sum.add(amountDto.getSum());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getDebitBetween(long accountId, LocalDateTime start, LocalDateTime end) throws ResourceNotFoundException {
|
||||||
|
BigDecimal sum = operationRepository.getOperationAmountBetween(accountId, start, end, Operation.OperationType.DEPOSIT).orElse(new BigDecimal("0.00"));
|
||||||
|
return sum.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getCreditBetween(long accountId, LocalDateTime start, LocalDateTime end) throws ResourceNotFoundException {
|
||||||
|
BigDecimal sum = operationRepository.getOperationAmountBetween(accountId, start, end, Operation.OperationType.WITHDRAW).orElse(new BigDecimal("0.00"));
|
||||||
|
return sum.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<OperationAmountDto> getOperationsAmountBetween(long accountId, LocalDateTime start, LocalDateTime end) {
|
||||||
|
return operationRepository.getOperationsAmountBetween(accountId, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Operation> getAllByAccountId(long accountId, int pageNumber, int rowPerPage) {
|
||||||
|
return operationRepository.getAllByAccount_Id(accountId, PageRequest.of(pageNumber - 1, rowPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Operation> getAllByAccountIdBetween(long accountId, LocalDateTime start, LocalDateTime end, int pageNumber, int rowPerPage) {
|
||||||
|
return operationRepository.getAllByAccountIdBetween(accountId, start, end, PageRequest.of(pageNumber - 1, rowPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void transfer(Account accountFrom, Account accountTo, double amount) throws BadResourceException {
|
||||||
|
if (!accountFrom.getEmployee().getId().equals(accountTo.getEmployee().getId())) {
|
||||||
|
throw new BadResourceException("Accounts from and account to do not belong to one employee");
|
||||||
|
}
|
||||||
|
withdraw(accountFrom, amount);
|
||||||
|
deposit(accountTo, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
src/main/java/ru/resprojects/dfops/util/DateTimeUtil.java
Normal file
31
src/main/java/ru/resprojects/dfops/util/DateTimeUtil.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package ru.resprojects.dfops.util;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
public final class DateTimeUtil {
|
||||||
|
|
||||||
|
public static final String DATE_TIME_PATTERN = "dd.MM.yyyy HH:mm";
|
||||||
|
public static final String DATE_TIME_PATTERN_REQUEST = "dd.MM.yyyy-HH:mm";
|
||||||
|
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
|
||||||
|
|
||||||
|
private DateTimeUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(LocalDateTime localDateTime) {
|
||||||
|
return localDateTime == null ? "" : localDateTime.format(DATE_TIME_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalDate parseLocalDate(String localDateString) {
|
||||||
|
return StringUtils.isEmpty(localDateString) ? null : LocalDate.parse(localDateString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalTime parseLocalTime(String localTimeString) {
|
||||||
|
return StringUtils.isEmpty(localTimeString) ? null : LocalTime.parse(localTimeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
80
src/main/resources/application.yml
Normal file
80
src/main/resources/application.yml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: pgsql
|
||||||
|
mvc:
|
||||||
|
servlet:
|
||||||
|
path: /rest/v1/
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
profiles: pgsql, prod
|
||||||
|
jpa:
|
||||||
|
database: postgresql
|
||||||
|
generate-ddl: true
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
jdbc:
|
||||||
|
lob:
|
||||||
|
non_contextual_creation: true
|
||||||
|
database-platform: org.hibernate.dialect.PostgreSQL9Dialect
|
||||||
|
open-in-view: false
|
||||||
|
datasource:
|
||||||
|
platform: postgresql
|
||||||
|
initialization-mode: never
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
profiles: pgsql
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://localhost/test
|
||||||
|
username: test
|
||||||
|
password: test
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
profiles: prod
|
||||||
|
datasource:
|
||||||
|
url: ${DFOPS_PGSQL_DB_HOST}:${DFOPS_PGSQL_DB_PORT}/${DFOPS_PGSQL_DB_NAME}
|
||||||
|
username: ${DFOPS_PGSQL_DB_USER}
|
||||||
|
password: ${DFOPS_PGSQL_DB_PASSWORD}
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
profiles: test, demo
|
||||||
|
jpa:
|
||||||
|
database: h2
|
||||||
|
open-in-view: false
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: none
|
||||||
|
database-platform: org.hibernate.dialect.H2Dialect
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:mem:restsrv;DB_CLOSE_ON_EXIT=FALSE
|
||||||
|
initialization-mode: always
|
||||||
|
platform: h2
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
profiles: demo, pgsql, test, prod
|
||||||
|
jpa:
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
format_sql: false
|
||||||
|
jdbc:
|
||||||
|
batch_size: 10
|
||||||
|
order_inserts: true
|
||||||
|
order_updates: true
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
profiles: test
|
||||||
|
jpa:
|
||||||
|
show-sql: false
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
generate_statistics: false
|
||||||
|
---
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
ru.resprojects: debug
|
||||||
|
org.springframework.transaction: debug
|
||||||
|
org.springframework: error
|
||||||
|
pattern:
|
||||||
|
file: "%d %p %c{1.} [%t] %m%n"
|
||||||
|
console: "%clr(%d{HH:mm:ss.SSS}){yellow} %clr(%-5p) %clr(---){faint} %clr([%t]){cyan} %clr(%logger{36}){blue} %clr(:){red} %clr(%msg){faint}%n"
|
||||||
|
file:
|
||||||
|
name: restsrv.log
|
||||||
|
max-size: 5MB
|
||||||
39
src/main/resources/data-h2.sql
Normal file
39
src/main/resources/data-h2.sql
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
DELETE FROM personal_account_operations;
|
||||||
|
DELETE FROM employee_personal_accounts;
|
||||||
|
DELETE FROM employees;
|
||||||
|
ALTER SEQUENCE seq_employees RESTART WITH 5000;
|
||||||
|
|
||||||
|
INSERT INTO employees (name, email) VALUES
|
||||||
|
('Ivanov Ivan Ivanovich', 'ivanov@example.com'),
|
||||||
|
('Petrov Vasily Victorovich', 'petrov@example.com');
|
||||||
|
|
||||||
|
INSERT INTO employee_personal_accounts (personal_account, employee_id)
|
||||||
|
VALUES ('4154014152522741', 5000),
|
||||||
|
('4131668358915203', 5000),
|
||||||
|
('4281563275602455', 5000),
|
||||||
|
('4103234971123321', 5001),
|
||||||
|
('4132555843841699', 5001);
|
||||||
|
|
||||||
|
INSERT INTO personal_account_operations (operation_date_time, operation_type, operation_value, personal_account_id)
|
||||||
|
VALUES (parsedatetime('2020-05-30 10:00:00', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 840.35, 5002),
|
||||||
|
(parsedatetime('2020-05-28 11:05:10', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 625.00, 5002),
|
||||||
|
(parsedatetime('2020-05-25 11:41:10', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 1080.45, 5002),
|
||||||
|
(parsedatetime('2020-05-30 14:00:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 652.33, 5002),
|
||||||
|
(parsedatetime('2020-05-26 18:10:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 420.00, 5002),
|
||||||
|
(parsedatetime('2020-06-30 10:00:00', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 1500.52, 5003),
|
||||||
|
(parsedatetime('2020-06-30 11:05:10', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 800.73, 5003),
|
||||||
|
(parsedatetime('2020-06-30 14:00:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 170.35, 5003),
|
||||||
|
(parsedatetime('2020-06-30 18:10:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 320.00, 5003),
|
||||||
|
(parsedatetime('2020-07-15 12:05:10', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 800.73, 5004),
|
||||||
|
(parsedatetime('2020-07-15 12:41:10', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 350.00, 5004),
|
||||||
|
(parsedatetime('2020-07-15 15:00:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 900.35, 5004),
|
||||||
|
(parsedatetime('2020-07-15 17:10:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 600.00, 5004),
|
||||||
|
(parsedatetime('2020-05-15 11:05:10', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 976.33, 5005),
|
||||||
|
(parsedatetime('2020-05-15 11:41:10', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 850.00, 5005),
|
||||||
|
(parsedatetime('2020-05-15 14:00:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 200.00, 5005),
|
||||||
|
(parsedatetime('2020-05-15 18:10:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 375.85, 5005),
|
||||||
|
(parsedatetime('2020-04-30 09:00:00', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 1200.52, 5006),
|
||||||
|
(parsedatetime('2020-04-30 10:35:00', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 300.53, 5006),
|
||||||
|
(parsedatetime('2020-04-30 10:55:00', 'yyyy-MM-dd hh:mm:ss'), 'DEPOSIT', 450.60, 5006),
|
||||||
|
(parsedatetime('2020-04-30 12:20:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 300.00, 5006),
|
||||||
|
(parsedatetime('2020-04-30 14:10:10', 'yyyy-MM-dd hh:mm:ss'), 'WITHDRAW', 402.95, 5006);
|
||||||
40
src/main/resources/data-postgresql.sql
Normal file
40
src/main/resources/data-postgresql.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
DELETE FROM personal_account_operations;
|
||||||
|
DELETE FROM employee_personal_accounts;
|
||||||
|
DELETE FROM employees;
|
||||||
|
ALTER SEQUENCE seq_employees RESTART WITH 5000;
|
||||||
|
|
||||||
|
INSERT INTO employees (name, email) VALUES
|
||||||
|
('Ivanov Ivan Ivanovich', 'ivanov@example.com'),
|
||||||
|
('Petrov Vasily Victorovich', 'petrov@example.com');
|
||||||
|
|
||||||
|
INSERT INTO employee_personal_accounts (personal_account, employee_id)
|
||||||
|
VALUES ('4154014152522741', 5000),
|
||||||
|
('4131668358915203', 5000),
|
||||||
|
('4281563275602455', 5000),
|
||||||
|
('4103234971123321', 5001),
|
||||||
|
('4132555843841699', 5001);
|
||||||
|
|
||||||
|
INSERT INTO personal_account_operations (operation_date_time, operation_type, operation_value, personal_account_id)
|
||||||
|
VALUES ('2020-05-30 10:00:00'::timestamp, 'DEPOSIT', 840.35, 5002),
|
||||||
|
('2020-05-28 11:05:10'::timestamp, 'DEPOSIT', 625.00, 5002),
|
||||||
|
('2020-05-25 11:41:10'::timestamp, 'DEPOSIT', 1080.45, 5002),
|
||||||
|
('2020-05-30 14:00:10'::timestamp, 'WITHDRAW', 652.33, 5002),
|
||||||
|
('2020-05-26 18:10:10'::timestamp, 'WITHDRAW', 420.00, 5002),
|
||||||
|
('2020-06-30 10:00:00'::timestamp, 'DEPOSIT', 1500.52, 5003),
|
||||||
|
('2020-06-30 11:05:10'::timestamp, 'DEPOSIT', 800.73, 5003),
|
||||||
|
('2020-06-30 14:00:10'::timestamp, 'WITHDRAW', 170.35, 5003),
|
||||||
|
('2020-06-30 18:10:10'::timestamp, 'WITHDRAW', 320.00, 5003),
|
||||||
|
('2020-07-15 12:05:10'::timestamp, 'DEPOSIT', 800.73, 5004),
|
||||||
|
('2020-07-15 12:41:10'::timestamp, 'DEPOSIT', 350.00, 5004),
|
||||||
|
('2020-07-15 15:00:10'::timestamp, 'WITHDRAW', 900.35, 5004),
|
||||||
|
('2020-07-15 17:10:10'::timestamp, 'WITHDRAW', 600.00, 5004),
|
||||||
|
('2020-05-15 11:05:10'::timestamp, 'DEPOSIT', 976.33, 5005),
|
||||||
|
('2020-05-15 11:41:10'::timestamp, 'DEPOSIT', 850.00, 5005),
|
||||||
|
('2020-05-15 14:00:10'::timestamp, 'WITHDRAW', 200.00, 5005),
|
||||||
|
('2020-05-15 18:10:10'::timestamp, 'WITHDRAW', 375.85, 5005),
|
||||||
|
('2020-04-30 09:00:00'::timestamp, 'DEPOSIT', 1200.52, 5006),
|
||||||
|
('2020-04-30 10:35:00'::timestamp, 'DEPOSIT', 300.53, 5006),
|
||||||
|
('2020-04-30 10:55:00'::timestamp, 'DEPOSIT', 450.60, 5006),
|
||||||
|
('2020-04-30 12:20:10'::timestamp, 'WITHDRAW', 300.00, 5006),
|
||||||
|
('2020-04-30 14:10:10'::timestamp, 'WITHDRAW', 402.95, 5006);
|
||||||
|
|
||||||
35
src/main/resources/schema-h2.sql
Normal file
35
src/main/resources/schema-h2.sql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
DROP TABLE IF EXISTS personal_account_operations;
|
||||||
|
DROP TABLE IF EXISTS employee_personal_accounts;
|
||||||
|
DROP TABLE IF EXISTS employees;
|
||||||
|
DROP SEQUENCE IF EXISTS seq_employees;
|
||||||
|
|
||||||
|
CREATE SEQUENCE seq_employees MINVALUE 5000;
|
||||||
|
|
||||||
|
CREATE TABLE employees
|
||||||
|
(
|
||||||
|
id BIGINT DEFAULT seq_employees.nextval PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
email VARCHAR NOT NULL
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employees_unique_email_idx ON employees (email);
|
||||||
|
|
||||||
|
CREATE TABLE employee_personal_accounts (
|
||||||
|
id BIGINT DEFAULT seq_employees.nextval PRIMARY KEY,
|
||||||
|
employee_id BIGINT NOT NULL,
|
||||||
|
personal_account VARCHAR NOT NULL,
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employee_personal_accounts_unique_employee_account_idx
|
||||||
|
ON employee_personal_accounts (employee_id, personal_account);
|
||||||
|
|
||||||
|
CREATE TABLE personal_account_operations (
|
||||||
|
id BIGINT DEFAULT seq_employees.nextval PRIMARY KEY,
|
||||||
|
personal_account_id BIGINT NOT NULL,
|
||||||
|
operation_date_time TIMESTAMP NOT NULL,
|
||||||
|
operation_type VARCHAR NOT NULL,
|
||||||
|
-- Money data on PostgreSQL using Java https://stackoverflow.com/a/18170030
|
||||||
|
operation_value DECIMAL NOT NULL,
|
||||||
|
FOREIGN KEY (personal_account_id) REFERENCES employee_personal_accounts (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX personal_account_operations_unique_account_datetime_idx
|
||||||
|
ON personal_account_operations (personal_account_id, operation_date_time);
|
||||||
35
src/main/resources/schema-postgresql.sql
Normal file
35
src/main/resources/schema-postgresql.sql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
DROP TABLE IF EXISTS personal_account_operations;
|
||||||
|
DROP TABLE IF EXISTS employee_personal_accounts;
|
||||||
|
DROP TABLE IF EXISTS employees;
|
||||||
|
DROP SEQUENCE IF EXISTS seq_employees cascade;
|
||||||
|
|
||||||
|
CREATE SEQUENCE seq_employees START 5000;
|
||||||
|
|
||||||
|
CREATE TABLE employees
|
||||||
|
(
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
email VARCHAR NOT NULL
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employees_unique_email_idx ON employees (email);
|
||||||
|
|
||||||
|
CREATE TABLE employee_personal_accounts (
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
employee_id BIGINT NOT NULL,
|
||||||
|
personal_account VARCHAR NOT NULL,
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX employee_personal_accounts_unique_employee_account_idx
|
||||||
|
ON employee_personal_accounts (employee_id, personal_account);
|
||||||
|
|
||||||
|
CREATE TABLE personal_account_operations (
|
||||||
|
id BIGINT PRIMARY KEY DEFAULT nextval('seq_employees'),
|
||||||
|
personal_account_id BIGINT NOT NULL,
|
||||||
|
operation_date_time TIMESTAMP NOT NULL,
|
||||||
|
operation_type VARCHAR NOT NULL,
|
||||||
|
-- Money data on PostgreSQL using Java https://stackoverflow.com/a/18170030
|
||||||
|
operation_value DECIMAL NOT NULL,
|
||||||
|
FOREIGN KEY (personal_account_id) REFERENCES employee_personal_accounts (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX personal_account_operations_unique_account_datetime_idx
|
||||||
|
ON personal_account_operations (personal_account_id, operation_date_time);
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package ru.resprojects.dfops.controller;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import ru.resprojects.dfops.dto.ResponseDto;
|
||||||
|
import ru.resprojects.dfops.dto.account.AccountDto;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.service.AccountService;
|
||||||
|
import ru.resprojects.dfops.service.EmployeeService;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@ActiveProfiles(profiles = {"test"})
|
||||||
|
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
|
||||||
|
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
|
||||||
|
config = @SqlConfig(encoding = "UTF-8"))
|
||||||
|
public class AccountControllerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AccountService accountService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmployeeService employeeService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestGetAllAccountsThenReturnStatus200AndNotEmptyAccountElementList() {
|
||||||
|
ParameterizedTypeReference<ResponseDto<Account>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Account>> response = restTemplate.exchange("/rest/v1/account/5000", HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getElements()).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestGetAllAccountsForEmployeeIdWithoutAccountsThenReturnStatus204() {
|
||||||
|
Employee employee = employeeService.create(new Employee("Alex", "alex@example.com"));
|
||||||
|
ParameterizedTypeReference<ResponseDto<Account>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Account>> response = restTemplate.exchange("/rest/v1/account/" + employee.getId(), HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestGetAllAccountsWithIncorrectEmployeeIdThenReturnStatus404() {
|
||||||
|
ParameterizedTypeReference<ResponseDto<Account>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Account>> response = restTemplate.exchange("/rest/v1/account/1", HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestCreateAccountThenReturnStatus200AccountWithId() {
|
||||||
|
AccountDto accountDto = new AccountDto(5000L, "1234");
|
||||||
|
|
||||||
|
ResponseEntity<Account> response = restTemplate.postForEntity("/rest/v1/account/create", accountDto, Account.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getId()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestCreateAccountWithIncorrectEmployeeIdThenReturnStatus404() {
|
||||||
|
AccountDto accountDto = new AccountDto(1L, "1234");
|
||||||
|
|
||||||
|
ResponseEntity<Account> response = restTemplate.postForEntity("/rest/v1/account/create", accountDto, Account.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestCreateAccountWithIncorrectBankAccountThenReturnStatus400() {
|
||||||
|
AccountDto accountDto = new AccountDto(5000L, null);
|
||||||
|
|
||||||
|
ResponseEntity<Account> response = restTemplate.postForEntity("/rest/v1/account/create", accountDto, Account.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestCreateAccountWithExistentBankAccountThenReturnStatus409() {
|
||||||
|
AccountDto accountDto = new AccountDto(5000L, "4154014152522741");
|
||||||
|
|
||||||
|
ResponseEntity<Account> response = restTemplate.postForEntity("/rest/v1/account/create", accountDto, Account.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestAccountByIdThenReturnStatus200AndAccount() {
|
||||||
|
ResponseEntity<Account> response = restTemplate.getForEntity("/rest/v1/account/get/5002", Account.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryRequestNonexistentAccountThenReturnStatus404() {
|
||||||
|
ResponseEntity<Account> response = restTemplate.getForEntity("/rest/v1/account/get/1010", Account.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceNotFoundException.class)
|
||||||
|
public void whenRequestDeleteAccountByIdThenDeleteAccount() {
|
||||||
|
restTemplate.delete("/rest/v1/account/5002");
|
||||||
|
|
||||||
|
accountService.get(5002);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryRequestDeleteAccountWithIncorrectIdThenReturnStatus404() {
|
||||||
|
ResponseEntity<Void> response = restTemplate.exchange("/rest/v1/account/1010", HttpMethod.DELETE, null, Void.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package ru.resprojects.dfops.controller;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import ru.resprojects.dfops.dto.ResponseDto;
|
||||||
|
import ru.resprojects.dfops.dto.employee.EmployeeDto;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.service.EmployeeService;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@ActiveProfiles(profiles = {"test"})
|
||||||
|
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
|
||||||
|
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
|
||||||
|
config = @SqlConfig(encoding = "UTF-8"))
|
||||||
|
public class EmployeeControllerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmployeeService employeeService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestGetAllEmployeesThenReturnStatus200AndNotEmptyElementList() {
|
||||||
|
ParameterizedTypeReference<ResponseDto<Employee>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Employee>> response = restTemplate.exchange("/rest/v1/employee/", HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getElements()).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestGetAllEmployeesWithPageLimitThenReturnStatus200AndNotEmptyElementList() {
|
||||||
|
ParameterizedTypeReference<ResponseDto<Employee>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Employee>> response = restTemplate.exchange("/rest/v1/employee/?page=1&limit=1", HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getElements()).isNotEmpty();
|
||||||
|
assertThat(response.getBody().getPageCount()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestGetEmployeeByIdThenReturnStatus200AndEmployee() {
|
||||||
|
Employee employee = employeeService.get(5001);
|
||||||
|
|
||||||
|
ResponseEntity<Employee> response = restTemplate.getForEntity("/rest/v1/employee/get/5001", Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getName()).isEqualTo(employee.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryRequestGetEmployeeByIdWithIncorrectIdThenReturnStatus404() {
|
||||||
|
ResponseEntity<Employee> response = restTemplate.getForEntity("/rest/v1/employee/get/1", Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestGetEmployeeByEmailThenReturnStatus200AndEmployee() {
|
||||||
|
Employee employee = employeeService.getByEmail("ivanov@example.com");
|
||||||
|
|
||||||
|
ResponseEntity<Employee> response = restTemplate.getForEntity("/rest/v1/employee/get?email=ivanov@example.com", Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getId()).isEqualTo(employee.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryRequestGetEmployeeByEmailWithIncorrectEmailThenReturnStatus404() {
|
||||||
|
ResponseEntity<Employee> response = restTemplate.getForEntity("/rest/v1/employee/get?email=123@example.com", Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestCreateEmployeeThenReturnStatus200AndCreatedEmployeeWithId() {
|
||||||
|
EmployeeDto employeeDto = new EmployeeDto("Alex", "alex@example.com");
|
||||||
|
ResponseEntity<Employee> response = restTemplate.postForEntity("/rest/v1/employee/create", employeeDto, Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getId()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryRequestCreateEmployeeWithIncorrectBodyThenReturnStatus403() {
|
||||||
|
//EmployeeDto employeeDto = new EmployeeDto("Alex", "alex@example.com");
|
||||||
|
ResponseEntity<Employee> response = restTemplate.postForEntity("/rest/v1/employee/create", null, Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryRequestCreateEmployeeWithEmptyNameThenReturnStatus403() {
|
||||||
|
EmployeeDto employeeDto = new EmployeeDto(null, "alex@example.com");
|
||||||
|
ResponseEntity<Employee> response = restTemplate.postForEntity("/rest/v1/employee/create", null, Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryRequestCreateEmployeeWithEmptyEmailThenReturnStatus403() {
|
||||||
|
EmployeeDto employeeDto = new EmployeeDto("Alex", null);
|
||||||
|
ResponseEntity<Employee> response = restTemplate.postForEntity("/rest/v1/employee/create", null, Employee.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceNotFoundException.class)
|
||||||
|
public void whenRequestDeleteEmployeeThenReturnDeleteEmployeeFromDb() {
|
||||||
|
restTemplate.delete("/rest/v1/employee/5001");
|
||||||
|
|
||||||
|
employeeService.get(5001);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
package ru.resprojects.dfops.controller;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import ru.resprojects.dfops.dto.ResponseDto;
|
||||||
|
import ru.resprojects.dfops.dto.account.AccountBalanceDto;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationAmountDto;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationDto;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationTransferDto;
|
||||||
|
import ru.resprojects.dfops.exception.ErrorMessage;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.model.Operation;
|
||||||
|
import ru.resprojects.dfops.service.AccountService;
|
||||||
|
import ru.resprojects.dfops.service.OperationService;
|
||||||
|
import ru.resprojects.dfops.util.DateTimeUtil;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Month;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@ActiveProfiles(profiles = {"test"})
|
||||||
|
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
|
||||||
|
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
|
||||||
|
config = @SqlConfig(encoding = "UTF-8"))
|
||||||
|
public class OperationControllerTest {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(OperationControllerTest.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OperationService operationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AccountService accountService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTransferFromOneAccountToAnotherAccountEmployeeThenReturnStatus200() {
|
||||||
|
OperationTransferDto transferDto = new OperationTransferDto(5002L, 5003L, 1.0);
|
||||||
|
|
||||||
|
ResponseEntity<Void> response = restTemplate.postForEntity("/rest/v1/operation/transfer", transferDto, Void.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryTransferFromOneAccountEmployeeOneToAnotherAccountEmployeeTwoThenReturnStatus400() {
|
||||||
|
OperationTransferDto transferDto = new OperationTransferDto(5002L, 5006L, 1.0);
|
||||||
|
|
||||||
|
ResponseEntity<Void> response = restTemplate.postForEntity("/rest/v1/operation/transfer", transferDto, Void.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryTransferFromOneAccountToAnotherAccountWithIncorrectAccountIdThenReturnStatus400() {
|
||||||
|
OperationTransferDto transferDto = new OperationTransferDto(null, 5006L, 1.0);
|
||||||
|
|
||||||
|
ResponseEntity<Void> response = restTemplate.postForEntity("/rest/v1/operation/transfer", transferDto, Void.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryTransferFromOneAccountToAnotherAccountWithNonexistentAccountIdThenReturnStatus404() {
|
||||||
|
OperationTransferDto transferDto = new OperationTransferDto(1L, 5006L, 1.0);
|
||||||
|
|
||||||
|
ResponseEntity<Void> response = restTemplate.postForEntity("/rest/v1/operation/transfer", transferDto, Void.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestWithdrawToAccountThenReturnStatus200() {
|
||||||
|
OperationDto operationDto = new OperationDto(5002L, 1L, Operation.OperationType.WITHDRAW);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/withdraw", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestWithdrawWithNonexistentAccountIdThenReturnStatus404() {
|
||||||
|
OperationDto operationDto = new OperationDto(1010L, 1L, Operation.OperationType.WITHDRAW);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/withdraw", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestWithdrawWithIncorrectAccountIdThenReturnStatus403() {
|
||||||
|
OperationDto operationDto = new OperationDto(null, 1L, Operation.OperationType.WITHDRAW);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/withdraw", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestWithdrawWithIncorrectOperationTypeIdThenReturnStatus400() {
|
||||||
|
OperationDto operationDto = new OperationDto(5005L, 1L, Operation.OperationType.DEPOSIT);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/withdraw", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestDepositToAccountThenReturnStatus200() {
|
||||||
|
OperationDto operationDto = new OperationDto(5002L, 1L, Operation.OperationType.DEPOSIT);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/deposit", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestDepositWithNonexistentAccountIdThenReturnStatus404() {
|
||||||
|
OperationDto operationDto = new OperationDto(1010L, 1L, Operation.OperationType.DEPOSIT);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/deposit", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestDepositWithIncorrectAccountIdThenReturnStatus403() {
|
||||||
|
OperationDto operationDto = new OperationDto(null, 1L, Operation.OperationType.DEPOSIT);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/deposit", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenRequestDepositWithIncorrectOperationTypeIdThenReturnStatus400() {
|
||||||
|
OperationDto operationDto = new OperationDto(null, 1L, Operation.OperationType.WITHDRAW);
|
||||||
|
|
||||||
|
ResponseEntity<Operation> response = restTemplate.postForEntity("/rest/v1/operation/deposit", operationDto, Operation.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAllOperationsForAccountThenReturnStatus200AndNotNullOperationList() {
|
||||||
|
ParameterizedTypeReference<ResponseDto<Operation>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Operation>> response = restTemplate.exchange("/rest/v1/operation/5002", HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getElements()).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenNoOperationsForAccountThenReturnStatus204() {
|
||||||
|
Account account = accountService.create(new Employee(5000L, "1", "1"), "123");
|
||||||
|
ParameterizedTypeReference<ResponseDto<Operation>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Operation>> response = restTemplate.exchange("/rest/v1/operation/" + account.getId(), HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTryGetAllOperationsForNonexistentAccountThenReturnStatus404() {
|
||||||
|
ParameterizedTypeReference<ResponseDto<Operation>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<Operation>> response = restTemplate.exchange("/rest/v1/operation/1", HttpMethod.GET, null, responseType);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetCurrentBalanceForAccountThenReturnStatus200AndCurrentBalance() {
|
||||||
|
BigDecimal balanceAccount5002 = operationService.getCurrentBalance(5002);
|
||||||
|
|
||||||
|
ResponseEntity<AccountBalanceDto> response = restTemplate.getForEntity("/rest/v1/operation/5002/balance", AccountBalanceDto.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getBalance()).isEqualByComparingTo(balanceAccount5002);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetCreditForPeriodForAccountThenReturnStatus200AndCredit() {
|
||||||
|
DateTimeFormatter format = DateTimeFormatter.ofPattern(DateTimeUtil.DATE_TIME_PATTERN_REQUEST);
|
||||||
|
LocalDateTime start = LocalDateTime.of(2020, Month.MAY, 1, 0, 0);
|
||||||
|
LocalDateTime end = LocalDateTime.of(2020, Month.MAY, 31, 23, 59);
|
||||||
|
String startDateTime = start.format(format);
|
||||||
|
String endDateTime = end.format(format);
|
||||||
|
BigDecimal creditAccount5002 = operationService.getCreditBetween(5002, start, end);
|
||||||
|
|
||||||
|
ResponseEntity<OperationAmountDto> response = restTemplate.getForEntity(
|
||||||
|
"/rest/v1/operation/5002/credit/filter?startDateTime=" + startDateTime + "&endDateTime=" + endDateTime,
|
||||||
|
OperationAmountDto.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getOperationType()).isEqualTo(Operation.OperationType.WITHDRAW);
|
||||||
|
assertThat(response.getBody().getSum()).isEqualByComparingTo(creditAccount5002);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetCreditForPeriodWithEndDateTimeLaterThanStartDateTimeThenReturnStatus400() {
|
||||||
|
DateTimeFormatter format = DateTimeFormatter.ofPattern(DateTimeUtil.DATE_TIME_PATTERN_REQUEST);
|
||||||
|
LocalDateTime start = LocalDateTime.of(2020, Month.MAY, 1, 0, 0);
|
||||||
|
LocalDateTime end = LocalDateTime.of(2020, Month.MAY, 31, 23, 59);
|
||||||
|
String startDateTime = end.format(format);
|
||||||
|
String endDateTime = start.format(format);
|
||||||
|
|
||||||
|
ResponseEntity<ErrorMessage> response = restTemplate.getForEntity(
|
||||||
|
"/rest/v1/operation/5002/credit/filter?startDateTime=" + startDateTime + "&endDateTime=" + endDateTime,
|
||||||
|
ErrorMessage.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetCreditForPeriodWithEmptyStartDateTimeThenReturnStatus400() {
|
||||||
|
String startDateTime = "";
|
||||||
|
String endDateTime = "";
|
||||||
|
|
||||||
|
ResponseEntity<ErrorMessage> response = restTemplate.getForEntity(
|
||||||
|
"/rest/v1/operation/5002/credit/filter?startDateTime=" + startDateTime + "&endDateTime=" + endDateTime,
|
||||||
|
ErrorMessage.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetCreditForPeriodWhereNoOperationWithdrawThenReturnStatus200AndCreditZero() {
|
||||||
|
DateTimeFormatter format = DateTimeFormatter.ofPattern(DateTimeUtil.DATE_TIME_PATTERN_REQUEST);
|
||||||
|
LocalDateTime start = LocalDateTime.of(2020, Month.JANUARY, 1, 0, 0);
|
||||||
|
LocalDateTime end = LocalDateTime.of(2020, Month.JANUARY, 31, 23, 59);
|
||||||
|
String startDateTime = start.format(format);
|
||||||
|
String endDateTime = end.format(format);
|
||||||
|
|
||||||
|
ResponseEntity<OperationAmountDto> response = restTemplate.getForEntity(
|
||||||
|
"/rest/v1/operation/5002/credit/filter?startDateTime=" + startDateTime + "&endDateTime=" + endDateTime,
|
||||||
|
OperationAmountDto.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getOperationType()).isEqualTo(Operation.OperationType.WITHDRAW);
|
||||||
|
assertThat(response.getBody().getSum()).isEqualByComparingTo(new BigDecimal("0.00"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetOperationsAmountForPeriodThenReturnStatus200AndOperationsAmount() {
|
||||||
|
DateTimeFormatter format = DateTimeFormatter.ofPattern(DateTimeUtil.DATE_TIME_PATTERN_REQUEST);
|
||||||
|
LocalDateTime start = LocalDateTime.of(2020, Month.MAY, 1, 0, 0);
|
||||||
|
LocalDateTime end = LocalDateTime.of(2020, Month.MAY, 31, 23, 59);
|
||||||
|
String startDateTime = start.format(format);
|
||||||
|
String endDateTime = end.format(format);
|
||||||
|
ParameterizedTypeReference<ResponseDto<OperationAmountDto>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
List<OperationAmountDto> operationAmountDtos = operationService.getOperationsAmountBetween(5002, start, end);
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<OperationAmountDto>> response = restTemplate.exchange(
|
||||||
|
"/rest/v1/operation/5002/operations_amount/filter?startDateTime=" + startDateTime + "&endDateTime=" + endDateTime,
|
||||||
|
HttpMethod.GET,
|
||||||
|
null,
|
||||||
|
responseType
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getElements()).isNotEmpty();
|
||||||
|
assertThat(response.getBody().getElements()).isSubsetOf(operationAmountDtos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetOperationsAmountForPeriodWhereNoDepositAndWithdrawThenReturnStatus200AndEmptyList() {
|
||||||
|
DateTimeFormatter format = DateTimeFormatter.ofPattern(DateTimeUtil.DATE_TIME_PATTERN_REQUEST);
|
||||||
|
LocalDateTime start = LocalDateTime.of(2020, Month.JANUARY, 1, 0, 0);
|
||||||
|
LocalDateTime end = LocalDateTime.of(2020, Month.JANUARY, 31, 23, 59);
|
||||||
|
String startDateTime = start.format(format);
|
||||||
|
String endDateTime = end.format(format);
|
||||||
|
ParameterizedTypeReference<ResponseDto<OperationAmountDto>> responseType = new ParameterizedTypeReference<>() { };
|
||||||
|
|
||||||
|
ResponseEntity<ResponseDto<OperationAmountDto>> response = restTemplate.exchange(
|
||||||
|
"/rest/v1/operation/5002/operations_amount/filter?startDateTime=" + startDateTime + "&endDateTime=" + endDateTime,
|
||||||
|
HttpMethod.GET,
|
||||||
|
null,
|
||||||
|
responseType
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).isNotNull();
|
||||||
|
assertThat(response.getBody().getElements()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import ru.resprojects.dfops.DfopsApplication;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceAlreadyExistsException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = DfopsApplication.class)
|
||||||
|
@ActiveProfiles(profiles = {"test"})
|
||||||
|
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
|
||||||
|
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
|
||||||
|
config = @SqlConfig(encoding = "UTF-8"))
|
||||||
|
public class AccountServiceTest {
|
||||||
|
|
||||||
|
private static final long EMPLOYEE_ID = 5000L;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AccountService accountService;
|
||||||
|
|
||||||
|
private Account savedAccount;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
Employee employee = new Employee(EMPLOYEE_ID, "Ivanov Ivan Ivanovich", "ivanov@example.com");
|
||||||
|
savedAccount = accountService.create(employee, "1234567899876543");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAllAccountsThenReturnNotEmptyList() {
|
||||||
|
Page<Account> accounts = accountService.getAll(EMPLOYEE_ID, 1, 10);
|
||||||
|
|
||||||
|
assertThat(accounts.hasContent()).isTrue();
|
||||||
|
assertThat(accounts.getContent()).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAccountByIdThenReturnAccount() {
|
||||||
|
Account account = accountService.get(savedAccount.getId());
|
||||||
|
|
||||||
|
assertThat(account).isNotNull();
|
||||||
|
assertThat(account).isEqualTo(savedAccount);
|
||||||
|
assertThat(account.getEmployee().getId()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteAccountTest() {
|
||||||
|
accountService.delete(savedAccount.getId());
|
||||||
|
|
||||||
|
Page<Account> accounts = accountService.getAll(EMPLOYEE_ID, 1, 10);
|
||||||
|
|
||||||
|
assertThat(accounts.hasContent()).isTrue();
|
||||||
|
assertThat(accounts.getContent().contains(savedAccount)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadResourceException.class)
|
||||||
|
public void whenTrySaveNullThenException() {
|
||||||
|
accountService.create(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceAlreadyExistsException.class)
|
||||||
|
public void whenSaveAccountWithExistingBankAccountThenException() {
|
||||||
|
accountService.create(savedAccount.getEmployee(), savedAccount.getPersonalAccount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceNotFoundException.class)
|
||||||
|
public void whenTryDeleteByNonexistentIdThenException() {
|
||||||
|
accountService.delete(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceNotFoundException.class)
|
||||||
|
public void whenGetByNonexistentIdThenException() {
|
||||||
|
accountService.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import ru.resprojects.dfops.DfopsApplication;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceAlreadyExistsException;
|
||||||
|
import ru.resprojects.dfops.exception.ResourceNotFoundException;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = DfopsApplication.class)
|
||||||
|
@ActiveProfiles(profiles = {"test"})
|
||||||
|
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
|
||||||
|
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
|
||||||
|
config = @SqlConfig(encoding = "UTF-8"))
|
||||||
|
public class EmployeeServiceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmployeeService employeeService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSaveEmployeeThenReturnEmployeeWithId() {
|
||||||
|
Employee employee = new Employee("Alex", "example@example.com");
|
||||||
|
|
||||||
|
Employee employeeWithId = employeeService.create(employee);
|
||||||
|
|
||||||
|
assertThat(employeeWithId).isNotNull();
|
||||||
|
assertThat(employeeWithId.getId()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetByEmailThenReturnEmploy() {
|
||||||
|
Employee employee = new Employee("Alex", "example@example.com");
|
||||||
|
employeeService.create(employee);
|
||||||
|
|
||||||
|
Employee existingEmployee = employeeService.getByEmail("example@example.com");
|
||||||
|
|
||||||
|
assertThat(existingEmployee).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteEmployeeTest() {
|
||||||
|
Employee employee = new Employee("Alex", "example@example.com");
|
||||||
|
Employee savedEmployee = employeeService.create(employee);
|
||||||
|
|
||||||
|
employeeService.delete(savedEmployee.getId());
|
||||||
|
|
||||||
|
Page<Employee> employees = employeeService.getAll(1, 10);
|
||||||
|
assertThat(employees.hasContent()).isTrue();
|
||||||
|
assertThat(employees.getContent().contains(savedEmployee)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAllEmployeesThenReturnNotEmptyListOfEmployees() {
|
||||||
|
Page<Employee> employees = employeeService.getAll(1, 10);
|
||||||
|
|
||||||
|
assertThat(employees.getContent()).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadResourceException.class)
|
||||||
|
public void whenTrySaveNullThenException() {
|
||||||
|
employeeService.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceAlreadyExistsException.class)
|
||||||
|
public void whenSaveEmployeeWithExistingEmailThenException() {
|
||||||
|
Employee employee = new Employee("Alex", "example@example.com");
|
||||||
|
employeeService.create(employee);
|
||||||
|
|
||||||
|
employeeService.create(employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceNotFoundException.class)
|
||||||
|
public void whenTryDeleteByNonexistentIdThenException() {
|
||||||
|
employeeService.delete(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceNotFoundException.class)
|
||||||
|
public void whenGetByNonexistentIdThenException() {
|
||||||
|
employeeService.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ResourceNotFoundException.class)
|
||||||
|
public void whenGetByNonexistentEmailThenException() {
|
||||||
|
employeeService.getByEmail("abc@example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package ru.resprojects.dfops.service;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import ru.resprojects.dfops.DfopsApplication;
|
||||||
|
import ru.resprojects.dfops.dto.operation.OperationAmountDto;
|
||||||
|
import ru.resprojects.dfops.exception.BadResourceException;
|
||||||
|
import ru.resprojects.dfops.model.Account;
|
||||||
|
import ru.resprojects.dfops.model.Employee;
|
||||||
|
import ru.resprojects.dfops.model.Operation;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.Month;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = DfopsApplication.class)
|
||||||
|
@ActiveProfiles(profiles = {"test"})
|
||||||
|
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
|
||||||
|
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
|
||||||
|
config = @SqlConfig(encoding = "UTF-8"))
|
||||||
|
public class OperationServiceTest {
|
||||||
|
|
||||||
|
private static final long EMPLOYEE_ONE_ID = 5000L;
|
||||||
|
private static final long EMPLOYEE_TWO_ID = 5001L;
|
||||||
|
private static final long ACCOUNT_ONE_EMPLOYEE_ONE_ID = 5002L;
|
||||||
|
private static final long ACCOUNT_TWO_EMPLOYEE_ONE_ID = 5003L;
|
||||||
|
private static final long ACCOUNT_THREE_EMPLOYEE_TWO_ID = 5006L;
|
||||||
|
private static final double CURRENT_BALANCE_ACCOUNT_ONE = 1473.47;
|
||||||
|
private static final long ALL_OPERATIONS_ACCOUNT_ONE = 5;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OperationService operationService;
|
||||||
|
|
||||||
|
private Account accountOne;
|
||||||
|
private Account accountTwo;
|
||||||
|
private Account accountThree;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
Employee employeeOne = new Employee(EMPLOYEE_ONE_ID, "Ivanov Ivan Ivanovich", "ivanov@example.com");
|
||||||
|
Employee employeeTwo = new Employee(EMPLOYEE_TWO_ID, "Petrov Vasily Victorovich", "petrov@example.com");
|
||||||
|
accountOne = new Account(ACCOUNT_ONE_EMPLOYEE_ONE_ID, employeeOne, "4154014152522741");
|
||||||
|
accountTwo = new Account(ACCOUNT_TWO_EMPLOYEE_ONE_ID, employeeOne, "4131668358915203");
|
||||||
|
accountThree = new Account(ACCOUNT_THREE_EMPLOYEE_TWO_ID, employeeTwo, "4132555843841699");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getCurrentBalanceTest() {
|
||||||
|
double currentBalance = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
|
||||||
|
assertThat(currentBalance).isEqualTo(CURRENT_BALANCE_ACCOUNT_ONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDepositToAccountThenIncreaseCurrentBalance() {
|
||||||
|
double amount = 200;
|
||||||
|
double originalBalance = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
|
||||||
|
operationService.deposit(accountOne, amount);
|
||||||
|
|
||||||
|
double changedBalance = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
assertThat(changedBalance).isGreaterThan(originalBalance);
|
||||||
|
assertThat(changedBalance - originalBalance).isEqualTo(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenWithdrawFromAccountThenDecreaseCurrentBalance() {
|
||||||
|
double amount = 200;
|
||||||
|
double originalBalance = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
|
||||||
|
operationService.withdraw(accountOne, amount);
|
||||||
|
|
||||||
|
double changedBalance = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
assertThat(changedBalance).isLessThan(originalBalance);
|
||||||
|
assertThat(originalBalance - changedBalance).isEqualTo(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadResourceException.class)
|
||||||
|
public void whenTryWithdrawFromAccountMoreThanCurrentBalanceThenException() {
|
||||||
|
double amount = 200;
|
||||||
|
double originalBalance = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
|
||||||
|
operationService.withdraw(accountOne, originalBalance + amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetDebitBetweenThenReturnDebitAtPeriod() {
|
||||||
|
double depositAmount = 200.00;
|
||||||
|
LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 0));
|
||||||
|
LocalDateTime end = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 59));
|
||||||
|
operationService.deposit(accountOne, depositAmount);
|
||||||
|
|
||||||
|
double debitAtPeriod = operationService.getDebitBetween(ACCOUNT_ONE_EMPLOYEE_ONE_ID, start, end).doubleValue();
|
||||||
|
|
||||||
|
assertThat(debitAtPeriod).isEqualTo(depositAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetDebitBetweenWithNonexistentAccountIdThenReturnZeroValue() {
|
||||||
|
LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 0));
|
||||||
|
LocalDateTime end = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 59));
|
||||||
|
|
||||||
|
BigDecimal result = operationService.getDebitBetween(123, start, end);
|
||||||
|
|
||||||
|
assertThat(result).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetCreditBetweenThenReturnCreditAtPeriod() {
|
||||||
|
double withdrawAmount = 200.00;
|
||||||
|
LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 0));
|
||||||
|
LocalDateTime end = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 59));
|
||||||
|
operationService.withdraw(accountOne, withdrawAmount);
|
||||||
|
|
||||||
|
double creditAtPeriod = operationService.getCreditBetween(ACCOUNT_ONE_EMPLOYEE_ONE_ID, start, end).doubleValue();
|
||||||
|
|
||||||
|
assertThat(creditAtPeriod).isEqualTo(withdrawAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetCreditBetweenWithNonexistentAccountIdThenReturnZeroValue() {
|
||||||
|
LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 0));
|
||||||
|
LocalDateTime end = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 59));
|
||||||
|
|
||||||
|
BigDecimal result = operationService.getCreditBetween(123, start, end);
|
||||||
|
|
||||||
|
assertThat(result).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAllByAccountIdThenReturnAllOperationsForAccountId() {
|
||||||
|
Page<Operation> allOperationsForAccountOne = operationService.getAllByAccountId(ACCOUNT_ONE_EMPLOYEE_ONE_ID, 1, 10);
|
||||||
|
|
||||||
|
assertThat(allOperationsForAccountOne.hasContent()).isTrue();
|
||||||
|
assertThat(allOperationsForAccountOne.getContent()).isNotEmpty();
|
||||||
|
assertThat(allOperationsForAccountOne.getContent().size()).isEqualTo(ALL_OPERATIONS_ACCOUNT_ONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAllByAccountIdWithNonexistentAccountThenReturnEmptyList() {
|
||||||
|
Page<Operation> allOperationsForAccountOne = operationService.getAllByAccountId(123, 1, 10);
|
||||||
|
|
||||||
|
assertThat(allOperationsForAccountOne.hasContent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAllByAccountIdBetweenThenReturnAllOperationsAtPeriodForAccountId() {
|
||||||
|
operationService.deposit(accountOne, 10.00);
|
||||||
|
operationService.withdraw(accountOne, 10.00);
|
||||||
|
LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 0));
|
||||||
|
LocalDateTime end = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 59));
|
||||||
|
Page<Operation> allOperationsForAccountOne = operationService.getAllByAccountIdBetween(ACCOUNT_ONE_EMPLOYEE_ONE_ID, start, end, 1, 10);
|
||||||
|
|
||||||
|
assertThat(allOperationsForAccountOne.hasContent()).isTrue();
|
||||||
|
assertThat(allOperationsForAccountOne.getContent()).isNotEmpty();
|
||||||
|
assertThat(allOperationsForAccountOne.getContent().size()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAllByAccountIdBetweenWithNonexistentAccountThenReturnEmptyList() {
|
||||||
|
LocalDateTime start = LocalDateTime.of(LocalDate.of(2020, Month.JANUARY, 1), LocalTime.of(0, 0));
|
||||||
|
LocalDateTime end = LocalDateTime.of(LocalDate.of(2020, Month.DECEMBER, 31), LocalTime.of(23, 59));
|
||||||
|
Page<Operation> allOperationsForAccountOne = operationService.getAllByAccountIdBetween(123, start, end, 1, 10);
|
||||||
|
|
||||||
|
assertThat(allOperationsForAccountOne.hasContent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTransferFromAccountOneToAccountTwoThenWithdrawFromAccountOneAndDepositToAccountTwo() {
|
||||||
|
double transferAmount = 1.00;
|
||||||
|
double currentBalanceAccountOne = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
double currentBalanceAccountTwo = operationService.getCurrentBalance(ACCOUNT_TWO_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
|
||||||
|
operationService.transfer(accountOne, accountTwo, transferAmount);
|
||||||
|
|
||||||
|
double currentBalanceAfterTransferAccountOne = operationService.getCurrentBalance(ACCOUNT_ONE_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
double currentBalanceAfterTransferAccountTwo = operationService.getCurrentBalance(ACCOUNT_TWO_EMPLOYEE_ONE_ID).doubleValue();
|
||||||
|
|
||||||
|
assertThat(currentBalanceAfterTransferAccountOne).isLessThan(currentBalanceAccountOne);
|
||||||
|
assertThat(currentBalanceAfterTransferAccountTwo).isGreaterThan(currentBalanceAccountTwo);
|
||||||
|
assertThat(currentBalanceAccountOne - currentBalanceAfterTransferAccountOne).isEqualTo(transferAmount);
|
||||||
|
assertThat(currentBalanceAfterTransferAccountTwo - currentBalanceAccountTwo).isEqualTo(transferAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadResourceException.class)
|
||||||
|
public void whenTransferFromAccountOneToAccountThreeThenException() {
|
||||||
|
double transferAmount = 1.00;
|
||||||
|
|
||||||
|
operationService.transfer(accountOne, accountThree, transferAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadResourceException.class)
|
||||||
|
public void whenTransferFromAccountOneToAccountTwoButTransferAmountMoreThanCurrentBalanceFromAccountOneThenException() {
|
||||||
|
double transferAmount = operationService.getCurrentBalance(accountOne.getId()).doubleValue() + 10.00;
|
||||||
|
|
||||||
|
operationService.transfer(accountOne, accountThree, transferAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO переделать тест
|
||||||
|
@Test
|
||||||
|
public void whenGetOperationsAmountBetweenThenReturnListOfOperationsAmount() {
|
||||||
|
LocalDateTime start = LocalDateTime.of(LocalDate.of(2020, Month.MAY, 1), LocalTime.of(0, 0));
|
||||||
|
LocalDateTime end = LocalDateTime.of(LocalDate.of(2020, Month.MAY, 31), LocalTime.of(23, 59));
|
||||||
|
List<OperationAmountDto> result = operationService.getOperationsAmountBetween(ACCOUNT_ONE_EMPLOYEE_ONE_ID, start, end);
|
||||||
|
|
||||||
|
assertThat(result).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user