diff --git a/doc/partition_migration.MD b/doc/partition_migration.MD new file mode 100644 index 00000000..52c45a2e --- /dev/null +++ b/doc/partition_migration.MD @@ -0,0 +1,40 @@ +# Перевод таблиц в режим партиционирвания + +1. Перед выполнением всех скриптов необходимо сделать бэкап целевой таблицы. +2. В рамках перехода существующих крупных таблиц в режим работы с партициями + необходимо выполнить скрипты, начиная с [этого](/src/main/resources/db/migration/V28__add_paуment_partition.sql) + и закачивая последним __partition_ скриптом. +3. После применения скриптов появится пустая партиционированная таблица с постфиксом _new - клон целевой таблицы. +4. Необходимо скопировать данные из целевой таблицы в новую партиционированную. Для этих данных + предусмотрен процесс миграции в исторические партиции для новой созданной таблицы. +Пример для таблицы _dw.payment_: + + ``` + INSERT INTO payment_new SELECT * FROM payment; + ``` + Надо учесть, что таблицы большие и необходим downtime для daway для копирвания данных. +5. После того как данные будут смигрированны, таблица _payment_ переименовывается в _payment_old_, а + _payment_new_ в _payment_. + + ``` + ALTER TABLE dw.payment RENAME TO payment_old; + ALTER TABLE dw.payment_new RENAME TO payment; + ``` + +6. Делается небольшой сдвиг соответствующего offset в kafka для подгрузки данных, пришедших за время + downtime, и вновь запускается daway. + +## План отката + +В случае каких либо проблем предусмотрен план отката. + +1. Удаление новой таблицы + + ``` + DROP TABLE dw.payments_new. + ``` + +2. Переименование payment в payment_new, а payment_old в payment. То есть обратный процесс. + +После успешного завершения миграции удалить старую таблицу dw.payment_old. + diff --git a/pom.xml b/pom.xml index bc2726ba..a16b59ee 100644 --- a/pom.xml +++ b/pom.xml @@ -330,7 +330,7 @@ org.jooq.meta.postgres.PostgresDatabase .* - schema_version|.*func|get_adjustment.*|get_cashflow.*|get_payment.*|get_payout.*|get_refund.* + schema_version|.*func|get_adjustment.*|get_cashflow.*|get_payment.*|get_payout.*|get_refund.*|.*_new|.*_20.* ${db.schema} diff --git "a/src/main/resources/db/migration/V28__add_pa\321\203ment_partition.sql" "b/src/main/resources/db/migration/V28__add_pa\321\203ment_partition.sql" new file mode 100644 index 00000000..8235377c --- /dev/null +++ "b/src/main/resources/db/migration/V28__add_pa\321\203ment_partition.sql" @@ -0,0 +1,77 @@ +--payment partition +DO +$$ + DECLARE + table_name TEXT := 'payment'; + is_partitioned BOOLEAN; + partition_name TEXT; + partition_field TEXT := 'event_created_at'; + partition_interval INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date DATE := '2021-12-01'; --- начало времен + end_date DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; + BEGIN + -- Проверяем, является ли таблица партиционированной + SELECT COUNT(*) > 0 + INTO is_partitioned + FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid + WHERE c.relname = table_name; + + IF is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; + ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING NOT NULL, + payment_id CHARACTER VARYING NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + party_id CHARACTER VARYING NOT NULL, + shop_id CHARACTER VARYING NOT NULL, + domain_revision BIGINT NOT NULL, + party_revision BIGINT, + amount BIGINT NOT NULL, + currency_code CHARACTER VARYING NOT NULL, + make_recurrent BOOLEAN, + sequence_id BIGINT, + change_id INTEGER, + wtime TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + external_id CHARACTER VARYING COLLATE pg_catalog."default", + payment_flow_type dw.payment_flow_type NOT NULL, + payment_flow_on_hold_expiration CHARACTER VARYING COLLATE pg_catalog."default", + payment_flow_held_until TIMESTAMP WITHOUT TIME ZONE, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); + EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); + start_date := start_date + partition_step_interval; + END LOOP; + END IF; + END +$$; \ No newline at end of file diff --git "a/src/main/resources/db/migration/V29__add_pa\321\203ment_session_info_partition.sql" "b/src/main/resources/db/migration/V29__add_pa\321\203ment_session_info_partition.sql" new file mode 100644 index 00000000..51871e6e --- /dev/null +++ "b/src/main/resources/db/migration/V29__add_pa\321\203ment_session_info_partition.sql" @@ -0,0 +1,80 @@ +--payment_session_info partition +DO +$$ + DECLARE +table_name TEXT := 'payment_session_info'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_session_info_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_session_info_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING, + payment_id CHARACTER VARYING, + sequence_id BIGINT, + change_id INTEGER, + session_status dw.payment_session_status NOT NULL, + reason character varying, + payment_session_result dw.payment_session_result, + payment_terminal INTEGER, + user_interaction BOOLEAN, + user_interaction_url CHARACTER VARYING, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git "a/src/main/resources/db/migration/V30__add_pa\321\203ment_status_info_partition.sql" "b/src/main/resources/db/migration/V30__add_pa\321\203ment_status_info_partition.sql" new file mode 100644 index 00000000..ab12862d --- /dev/null +++ "b/src/main/resources/db/migration/V30__add_pa\321\203ment_status_info_partition.sql" @@ -0,0 +1,81 @@ +--payment_status_info partition +DO +$$ + DECLARE +table_name TEXT := 'payment_status_info'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_status_info_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_status_info_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING NOT NULL, + payment_id CHARACTER VARYING NOT NULL, + status dw.payment_status NOT NULL, + reason CHARACTER VARYING, + amount BIGINT, + currency_code CHARACTER VARYING, + cart_json CHARACTER VARYING, + current BOOLEAN NOT NULL DEFAULT false, + wtime TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + sequence_id BIGINT, + change_id INTEGER, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git a/src/main/resources/db/migration/V31__add_withdrawal_partition.sql b/src/main/resources/db/migration/V31__add_withdrawal_partition.sql new file mode 100644 index 00000000..2ca7f4f3 --- /dev/null +++ b/src/main/resources/db/migration/V31__add_withdrawal_partition.sql @@ -0,0 +1,94 @@ +--withdrawal partition +DO +$$ + DECLARE +table_name TEXT := 'withdrawal'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (withdrawal_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.withdrawal_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + event_occured_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + sequence_id INTEGER NOT NULL, + wallet_id CHARACTER VARYING NOT NULL, + destination_id CHARACTER VARYING NOT NULL, + withdrawal_id CHARACTER VARYING NOT NULL, + provider_id_legacy CHARACTER VARYING, + amount BIGINT NOT NULL, + currency_code CHARACTER VARYING NOT NULL, + withdrawal_status dw.withdrawal_status NOT NULL, + withdrawal_transfer_status dw.withdrawal_transfer_status, + wtime TIMESTAMP WITHOUT TIME ZONE DEFAULT timezone(''utc''::text, now()) NOT NULL, + current BOOLEAN DEFAULT true NOT NULL, + fee BIGINT, + provider_fee BIGINT, + external_id CHARACTER VARYING, + context_json CHARACTER VARYING, + withdrawal_status_failed_failure_json CHARACTER VARYING, + provider_id INTEGER, + terminal_id CHARACTER VARYING, + exchange_rate DECIMAL (10,4), + exchange_amount_from BIGINT, + exchange_currency_from CHARACTER VARYING, + exchange_amount_to BIGINT, + exchange_currency_to CHARACTER VARYING, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (withdrawal_id, sequence_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git a/src/main/resources/db/migration/V32__add_invoice_status_info_partition.sql b/src/main/resources/db/migration/V32__add_invoice_status_info_partition.sql new file mode 100644 index 00000000..9dc04409 --- /dev/null +++ b/src/main/resources/db/migration/V32__add_invoice_status_info_partition.sql @@ -0,0 +1,78 @@ +--invoice_status_info partition +DO +$$ + DECLARE +table_name TEXT := 'invoice_status_info'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (invoice_status_info_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.invoice_status_info_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING COLLATE pg_catalog."default" NOT NULL, + status dw.invoice_status NOT NULL, + details CHARACTER VARYING COLLATE pg_catalog."default", + wtime TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + current BOOLEAN DEFAULT false NOT NULL, + sequence_id BIGINT, + change_id INTEGER, + external_id CHARACTER VARYING COLLATE pg_catalog."default", + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git "a/src/main/resources/db/migration/V33__add_pa\321\203ment_additional_info_partition.sql" "b/src/main/resources/db/migration/V33__add_pa\321\203ment_additional_info_partition.sql" new file mode 100644 index 00000000..80e10082 --- /dev/null +++ "b/src/main/resources/db/migration/V33__add_pa\321\203ment_additional_info_partition.sql" @@ -0,0 +1,88 @@ +--payment_status_info partition +DO +$$ + DECLARE +table_name TEXT := 'payment_additional_info'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_additional_info_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_additional_info_id_seq''::regclass), + event_created_at timestamp without time zone NOT NULL, + invoice_id CHARACTER VARYING NOT NULL, + payment_id CHARACTER VARYING NOT NULL, + transaction_id CHARACTER VARYING, + extra_json CHARACTER VARYING, + rrn CHARACTER VARYING, + approval_code CHARACTER VARYING, + acs_url CHARACTER VARYING, + md CHARACTER VARYING, + term_url CHARACTER VARYING, + eci CHARACTER VARYING, + cavv CHARACTER VARYING, + xid CHARACTER VARYING, + cavv_algorithm CHARACTER VARYING, + three_ds_verification CHARACTER VARYING, + current BOOLEAN NOT NULL DEFAULT false, + wtime TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + sequence_id BIGINT, + change_id INTEGER, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git a/src/main/resources/db/migration/V34__add_withdrawal_session_partition.sql b/src/main/resources/db/migration/V34__add_withdrawal_session_partition.sql new file mode 100644 index 00000000..1e193bc9 --- /dev/null +++ b/src/main/resources/db/migration/V34__add_withdrawal_session_partition.sql @@ -0,0 +1,110 @@ +--withdrawal_session partition +DO +$$ + DECLARE +table_name TEXT := 'withdrawal_session'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (withdrawal_session_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.withdrawal_session_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + event_occured_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + sequence_id integer NOT NULL, + withdrawal_session_id CHARACTER VARYING NOT NULL, + withdrawal_session_status dw.withdrawal_session_status NOT NULL, + provider_id_legacy CHARACTER VARYING, + withdrawal_id CHARACTER VARYING NOT NULL, + destination_card_token CHARACTER VARYING, + destination_card_payment_system VARCHAR, + destination_card_bin CHARACTER VARYING, + destination_card_masked_pan CHARACTER VARYING, + amount bigint NOT NULL, + currency_code CHARACTER VARYING NOT NULL, + sender_party_id CHARACTER VARYING, + sender_provider_id CHARACTER VARYING, + sender_class_id CHARACTER VARYING, + sender_contract_id CHARACTER VARYING, + receiver_party_id CHARACTER VARYING, + receiver_provider_id CHARACTER VARYING, + receiver_class_id CHARACTER VARYING, + receiver_contract_id CHARACTER VARYING, + adapter_state CHARACTER VARYING, + tran_info_id CHARACTER VARYING, + tran_info_timestamp TIMESTAMP WITHOUT TIME ZONE, + tran_info_json CHARACTER VARYING, + wtime TIMESTAMP WITHOUT TIME ZONE DEFAULT timezone(''utc''::text, now()) NOT NULL, + current BOOLEAN DEFAULT true NOT NULL, + failure_json CHARACTER VARYING, + resource_type dw.destination_resource_type NOT NULL, + resource_crypto_wallet_id CHARACTER VARYING, + resource_crypto_wallet_type CHARACTER VARYING, + resource_crypto_wallet_data CHARACTER VARYING, + resource_bank_card_type CHARACTER VARYING, + resource_bank_card_issuer_country CHARACTER VARYING, + resource_bank_card_bank_name CHARACTER VARYING, + tran_additional_info CHARACTER VARYING, + tran_additional_info_rrn CHARACTER VARYING, + tran_additional_info_json CHARACTER VARYING, + provider_id INTEGER, + resource_digital_wallet_id CHARACTER VARYING, + resource_digital_wallet_data CHARACTER VARYING, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (withdrawal_session_id, sequence_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git "a/src/main/resources/db/migration/V35__add_pa\321\203ment_route_partition.sql" "b/src/main/resources/db/migration/V35__add_pa\321\203ment_route_partition.sql" new file mode 100644 index 00000000..5bb5703a --- /dev/null +++ "b/src/main/resources/db/migration/V35__add_pa\321\203ment_route_partition.sql" @@ -0,0 +1,78 @@ +--payment_route partition +DO +$$ + DECLARE +table_name TEXT := 'payment_route'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_route_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_route_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING NOT NULL, + payment_id CHARACTER VARYING NOT NULL, + route_provider_id INTEGER, + route_terminal_id INTEGER, + sequence_id BIGINT, + change_id INTEGER, + wtime TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + current BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git "a/src/main/resources/db/migration/V36__add_pa\321\203ment_fee_partition.sql" "b/src/main/resources/db/migration/V36__add_pa\321\203ment_fee_partition.sql" new file mode 100644 index 00000000..3af624e2 --- /dev/null +++ "b/src/main/resources/db/migration/V36__add_pa\321\203ment_fee_partition.sql" @@ -0,0 +1,80 @@ +--payment_fee partition +DO +$$ + DECLARE +table_name TEXT := 'payment_fee'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_fee_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_fee_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING NOT NULL, + payment_id CHARACTER VARYING NOT NULL, + fee BIGINT, + provider_fee BIGINT, + external_fee BIGINT, + guarantee_deposit BIGINT, + current BOOLEAN NOT NULL DEFAULT false, + wtime TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + sequence_id BIGINT, + change_id INTEGER, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git a/src/main/resources/db/migration/V37__add_invoice_partition.sql b/src/main/resources/db/migration/V37__add_invoice_partition.sql new file mode 100644 index 00000000..2c115045 --- /dev/null +++ b/src/main/resources/db/migration/V37__add_invoice_partition.sql @@ -0,0 +1,86 @@ +--invoice partition +DO +$$ + DECLARE +table_name TEXT := 'invoice'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (invoice_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.invoice_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING COLLATE pg_catalog."default" NOT NULL, + party_id CHARACTER VARYING COLLATE pg_catalog."default" NOT NULL, + shop_id CHARACTER VARYING COLLATE pg_catalog."default" NOT NULL, + party_revision BIGINT, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + details_product CHARACTER VARYING COLLATE pg_catalog."default" NOT NULL, + details_description CHARACTER VARYING COLLATE pg_catalog."default", + due TIMESTAMP WITHOUT TIME ZONE NOT NULL, + amount BIGINT NOT NULL, + currency_code CHARACTER VARYING COLLATE pg_catalog."default" NOT NULL, + context bytea, + template_id CHARACTER VARYING COLLATE pg_catalog."default", + wtime TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + sequence_id BIGINT, + change_id INTEGER, + external_id CHARACTER VARYING COLLATE pg_catalog."default", + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git "a/src/main/resources/db/migration/V38__add_pa\321\203ment_risk_data_partition.sql" "b/src/main/resources/db/migration/V38__add_pa\321\203ment_risk_data_partition.sql" new file mode 100644 index 00000000..17359f45 --- /dev/null +++ "b/src/main/resources/db/migration/V38__add_pa\321\203ment_risk_data_partition.sql" @@ -0,0 +1,77 @@ +--payment_risk_data partition +DO +$$ + DECLARE +table_name TEXT := 'payment_risk_data'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_risk_data_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_risk_data_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING, + payment_id CHARACTER VARYING, + risk_score dw.risk_score NOT NULL, + current BOOLEAN NOT NULL DEFAULT false, + wtime TIMESTAMP WITHOUT TIME ZONe NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + sequence_id BIGINT, + change_id INTEGER, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git a/src/main/resources/db/migration/V39__add_payment_payer_info_partition.sql b/src/main/resources/db/migration/V39__add_payment_payer_info_partition.sql new file mode 100644 index 00000000..9f7c0913 --- /dev/null +++ b/src/main/resources/db/migration/V39__add_payment_payer_info_partition.sql @@ -0,0 +1,102 @@ +--payment_payer_info partition +DO +$$ + DECLARE +table_name TEXT := 'payment_payer_info'; + is_partitioned +BOOLEAN; + partition_name +TEXT; + partition_field +TEXT := 'event_created_at'; + partition_interval +INTERVAL := '60 months'; --- задаем нужный интервал для создания партиций + partition_step_interval +INTERVAL := '1 month'; --- задаем нужный шаг интервала партиции + start_date +DATE := '2021-12-01'; --- начало времен; + end_date +DATE := date_trunc('MONTH', start_date)::DATE + partition_interval; +BEGIN + -- Проверяем, является ли таблица партиционированной +SELECT COUNT(*) > 0 +INTO is_partitioned +FROM pg_class c + JOIN pg_partitioned_table p ON c.oid = p.partrelid +WHERE c.relname = table_name; + +IF +is_partitioned THEN + --- Таблица уже является партиционированной. Пропускаем партиционирование + RETURN; +ELSE + --- Таблица не является партиционированной. + --- 1. Создаем партиционированную таблицу копию оригинальной с постфиксом _new. + --- Учитываем constraint с учетом поля партиционирования. + --- Продолжим использовать предыдущую последовательность (payment_payer_info_id_seq), поэтому используем BIGINT вместо BIGSERIAL + EXECUTE format('CREATE TABLE IF NOT EXISTS dw.%I_new + ( + id BIGINT NOT NULL DEFAULT nextval(''dw.payment_payer_info_id_seq''::regclass), + event_created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + invoice_id CHARACTER VARYING, + payment_id CHARACTER VARYING, + payer_type dw.payer_type NOT NULL, + payment_tool_type dw.payment_tool_type NOT NULL, + bank_card_token CHARACTER VARYING, + bank_card_payment_system CHARACTER VARYING, + bank_card_bin CHARACTER VARYING, + bank_card_masked_pan CHARACTER VARYING, + bank_card_token_provider CHARACTER VARYING, + payment_terminal_type CHARACTER VARYING, + digital_wallet_provider CHARACTER VARYING, + digital_wallet_id CHARACTER VARYING, + payment_session_id CHARACTER VARYING, + ip_address CHARACTER VARYING, + fingerprint CHARACTER VARYING, + phone_number CHARACTER VARYING, + email CHARACTER VARYING, + customer_id CHARACTER VARYING, + customer_binding_id CHARACTER VARYING, + customer_rec_payment_tool_id CHARACTER VARYING, + recurrent_parent_invoice_id CHARACTER VARYING, + recurrent_parent_payment_id CHARACTER VARYING, + crypto_currency_type CHARACTER VARYING, + mobile_phone_cc CHARACTER VARYING, + mobile_phone_ctn CHARACTER VARYING, + issuer_country CHARACTER VARYING, + bank_name CHARACTER VARYING, + bank_card_cardholder_name CHARACTER VARYING, + mobile_operator CHARACTER VARYING, + wtime TIMESTAMP WITHOUT TIME ZONe NOT NULL DEFAULT (now() AT TIME ZONE ''utc''::text), + sequence_id BIGINT, + change_id INTEGER, + + CONSTRAINT %I_new_pkey PRIMARY KEY (id, %s), + CONSTRAINT %I_new_uniq UNIQUE (invoice_id, payment_id, sequence_id, change_id, %s) + ) PARTITION BY RANGE ( %s );', + table_name, + table_name, + partition_field, + table_name, + partition_field, + partition_field); + + + --- 2. Создаем партиции + WHILE +start_date < end_date + LOOP + partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM'); +EXECUTE format(' + CREATE TABLE dw.%I PARTITION OF dw.%I_new FOR VALUES FROM (%L) TO (%L);', + partition_name, + table_name, + start_date, + start_date + partition_step_interval + ); +start_date +:= start_date + partition_step_interval; +END LOOP; +END IF; +END +$$; \ No newline at end of file diff --git a/src/test/java/dev/vality/daway/IntegrationTest.java b/src/test/java/dev/vality/daway/IntegrationTest.java index 1447a41d..f1c5016f 100644 --- a/src/test/java/dev/vality/daway/IntegrationTest.java +++ b/src/test/java/dev/vality/daway/IntegrationTest.java @@ -35,7 +35,7 @@ @Slf4j @PostgresqlSpringBootITest -public class IntegrationTest { +class IntegrationTest { @Autowired private InvoicingService invoicingService; @@ -77,7 +77,7 @@ public class IntegrationTest { private final String shopId = "shop_id"; @Test - public void test() { + void test() { cleanUpTables(); List machineEventsFirst = getInitialInvoicePaymentEvents(invoiceId, paymentId); diff --git a/src/test/java/dev/vality/daway/dao/DaoTests.java b/src/test/java/dev/vality/daway/dao/DaoTests.java index 53e26331..bf563742 100644 --- a/src/test/java/dev/vality/daway/dao/DaoTests.java +++ b/src/test/java/dev/vality/daway/dao/DaoTests.java @@ -26,6 +26,8 @@ import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.LongStream; @@ -311,8 +313,10 @@ void paymentDaoTest() { jdbcTemplate.execute("truncate table dw.payment cascade"); Payment first = RandomBeans.random(Payment.class); first.setId(1L); + first.setEventCreatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)); Payment second = RandomBeans.random(Payment.class); second.setId(2L); + second.setEventCreatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)); paymentDao.saveBatch(Arrays.asList(first, second)); assertEquals(first, paymentDao.get(first.getInvoiceId(), first.getPaymentId())); assertEquals(second, paymentDao.get(second.getInvoiceId(), second.getPaymentId())); diff --git a/src/test/java/dev/vality/daway/handler/dominant/impl/PaymentRouningRulesHandlerTest.java b/src/test/java/dev/vality/daway/handler/dominant/impl/PaymentRoutingRulesHandlerTest.java similarity index 96% rename from src/test/java/dev/vality/daway/handler/dominant/impl/PaymentRouningRulesHandlerTest.java rename to src/test/java/dev/vality/daway/handler/dominant/impl/PaymentRoutingRulesHandlerTest.java index ae92c3af..a0156010 100644 --- a/src/test/java/dev/vality/daway/handler/dominant/impl/PaymentRouningRulesHandlerTest.java +++ b/src/test/java/dev/vality/daway/handler/dominant/impl/PaymentRoutingRulesHandlerTest.java @@ -13,13 +13,13 @@ import java.util.List; @ExtendWith(MockitoExtension.class) -public class PaymentRouningRulesHandlerTest { +class PaymentRoutingRulesHandlerTest { @Mock private PaymentRoutingRulesDaoImpl paymentRoutingRulesDao; @Test - public void convertToDatabaseObjectTest() { + void convertToDatabaseObjectTest() { RoutingRulesObject paymentRoutingRulesObject = buildPaymentRoutingRulesObject(); PaymentRoutingRulesHandler handler = new PaymentRoutingRulesHandler(paymentRoutingRulesDao); handler.setDomainObject(DomainObject.routing_rules(paymentRoutingRulesObject)); diff --git a/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java b/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java index 6f04f2c0..ec29e553 100644 --- a/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java +++ b/src/test/java/dev/vality/daway/service/PaymentWrapperServiceTest.java @@ -7,7 +7,6 @@ import dev.vality.daway.domain.enums.PaymentChangeType; import dev.vality.daway.domain.tables.pojos.CashFlow; import dev.vality.daway.domain.tables.pojos.CashFlowLink; -import dev.vality.daway.domain.tables.pojos.PaymentCashChange; import dev.vality.daway.domain.tables.pojos.PaymentFee; import dev.vality.daway.model.CashFlowWrapper; import dev.vality.daway.model.InvoicingKey; @@ -19,6 +18,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.HashSet; import java.util.List; @@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @PostgresqlSpringBootITest -public class PaymentWrapperServiceTest { +class PaymentWrapperServiceTest { @Autowired private PaymentWrapperService paymentWrapperService; @@ -65,7 +65,7 @@ public class PaymentWrapperServiceTest { private static final String paymentIdSecond = "paymentIdSecond"; @Test - public void processTest() { + void processTest() { List paymentWrappers = preparePaymentWrappers(); paymentWrapperService.save(paymentWrappers); @@ -74,7 +74,7 @@ public void processTest() { } @Test - public void duplicationTest() { + void duplicationTest() { List paymentWrappers = preparePaymentWrappers(); paymentWrapperService.save(paymentWrappers); @@ -87,6 +87,9 @@ public void duplicationTest() { private List preparePaymentWrappers() { List paymentWrappers = RandomBeans.randomListOf(2, PaymentWrapper.class); + paymentWrappers.stream() + .map(PaymentWrapper::getPayment) + .forEach(payment -> payment.setEventCreatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS))); paymentWrappers.forEach(pw -> { pw.setCashFlowWrapper(new CashFlowWrapper( RandomBeans.random(CashFlowLink.class),