result = new HashMap<>();
+ fids.forEach(fid -> {
+ Integer flagDebt5000 = flags5000.get(fid);
+ if (flagDebt5000 == null) {
+ flagDebt5000 = 0;
+ }
+
+ Integer flag90days = flags90DaysByPastDueArrear.get(fid);
+ if (flag90days == null || flag90days == 0) {
+ flag90days = flags90DaysByRuPaymt.get(fid);
+ if (flag90days == null) {
+ flag90days = 0;
+ }
+ }
+ result.put(fid, new FlagsService.FlagsData(flagDebt5000, flag90days));
+ });
+
+ return result;
+ }
+
+ /**
+ * Выбирает данные из RU_PASTDUEARREAR и ищет по ним флаги просрочек
+ * По FID из RU_PASTDUEARREAR выбираются все уникальные ACC_SERIAL_NUM с максимальной датой REPORTED_DT.
+ * Если по хотя бы одной такой записи AMT_PAST_DUE > 5000, то flag500 устанавливается в 1.
+ * Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 0.
+ *
+ * flag90Days = 1, если выполняется хотя бы одно из условий:
+ * В INDIC.RU_PASTDUEARREAR у данного FID есть запись с DAYS_PAST_DUE ≥ 90 и REPORTED_DT >= текущая дата – 12 месяцев.
+ * Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
+ *
+ * @param fids - отсортирован по возрастанию фидов
+ * @param flag5000Map - рещультирующая мапа флагов просрочек debt5000
+ * @param flag90Days - рещультирующая мапа флагов просрочек 90_days
+ */
+ private void findFlagsRuPastDueArrear(List fids, Map flag5000Map, Map flag90Days) {
+ log.debug("Start calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
+ MapSqlParameterSource params = new MapSqlParameterSource();
+ params.addValue("fids", fids);
+
+ jdbcTemplate.query(SELECT_RU_PASDUEARREAR_DATA, params, (ResultSet rs) -> {
+ Integer currentFid = null;
+ BigInteger prevAccSerialNum = null;
+ boolean processed5000ForCurrentId = false;
+ boolean processed90DaysForCurrentId = false;
+ LocalDate yearAgo = LocalDate.now().minusYears(1);
+
+ while (rs.next()) {
+ Integer fid = rs.getInt("FID");
+
+ // Переход к новому fid. Сброс состояния
+ if (!fid.equals(currentFid)) {
+ currentFid = fid;
+ prevAccSerialNum = null;
+ processed5000ForCurrentId = false;
+ processed90DaysForCurrentId = false;
+
+ // Инициализируем значения по умолчанию
+ flag5000Map.putIfAbsent(fid, 0);
+ flag90Days.putIfAbsent(fid, 0);
+ }
+
+ // Если для текущего FID уже обработали оба флага, пропускаем остальные записи
+ if (processed5000ForCurrentId && processed90DaysForCurrentId) {
+ continue;
+ }
+
+ // Обработка флага 5000
+ if (!processed5000ForCurrentId && flag5000Map.get(fid) == 0) {
+ BigInteger currentAccSerialNum = getBigInteger(rs, "ACC_SERIAL_NUM");
+ BigDecimal amtPastDue = getBigDecimal(rs, "AMT_PAST_DUE");
+
+ if (prevAccSerialNum == null || !prevAccSerialNum.equals(currentAccSerialNum)) {
+ if (amtPastDue != null && amtPastDue.compareTo(BigDecimal.valueOf(5000)) >= 0) {
+ flag5000Map.put(fid, 1);
+ processed5000ForCurrentId = true;
+ }
+ prevAccSerialNum = currentAccSerialNum;
+ }
+ }
+
+ // Обработка флага 90 дней
+ if (!processed90DaysForCurrentId) {
+ Integer currentFlag90 = flag90Days.get(fid);
+
+ if (currentFlag90 != null && currentFlag90 == 1) {
+ processed90DaysForCurrentId = true;
+ continue;
+ }
+
+ if (currentFlag90 == null || currentFlag90 == 0) {
+ BigInteger days = getBigInteger(rs, "DAYS_PAST_DUE");
+ LocalDate reportedDate = rs.getObject("REPORTED_DT", LocalDate.class);
+
+ if (days != null) {
+ boolean has90daysFlag = days.compareTo(BigInteger.valueOf(90)) >= 0
+ && reportedDate != null && !reportedDate.isBefore(yearAgo);
+
+ if (has90daysFlag) {
+ flag90Days.put(fid, 1);
+ processed90DaysForCurrentId = true;
+ } else {
+ flag90Days.put(fid, 0);
+ }
+ } else {
+ flag90Days.put(fid, 0);
+ }
+ }
+ }
+ }
+ return null;
+ });
+ log.debug("Complete calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
+ }
+
+ /**
+ * Выбирает данные из RU_PAYMT и ищет по ним флаги просрочек 90Days
+ *
+ * flag90Days = 1, если выполняется хотя бы одно из условий:
+ * В INDIC.RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE ≥ 90 и REPORTED_DT >= текущая дата – 12 месяцев.
+ * Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
+ *
+ * @param fids - отсортирован по возрастанию фидов
+ * @param flag90Days - рещультирующая мапа флагов просрочек 90_days
+ */
+ private void findFlagsRuPaymt(List fids, Map flag90Days) {
+ log.debug("Start calculating flag 90 days by RU_PAPAYMT");
+ LocalDate yearAgo = LocalDate.now().minusYears(1);
+ MapSqlParameterSource params = new MapSqlParameterSource();
+ params.addValue("fids", fids);
+ params.addValue("reportedDate", yearAgo);
+ jdbcTemplate.query(SELECT_RU_PAYMT_DATA, params, rs -> {
+ Integer currentFid = null;
+ boolean processed90DaysForCurrentId = false;
+
+ while (rs.next()) {
+ Integer fid = rs.getInt("FID");
+
+ // Переход к новому fid. Сброс состояния
+ if (!fid.equals(currentFid)) {
+ currentFid = fid;
+ processed90DaysForCurrentId = false;
+
+ // Инициализируем значения по умолчанию
+ flag90Days.putIfAbsent(fid, 0);
+ }
+
+ // Обработка флага 90 дней
+ if (!processed90DaysForCurrentId) {
+ Integer currentFlag90 = flag90Days.get(fid);
+
+ if (currentFlag90 != null && currentFlag90 == 1) {
+ processed90DaysForCurrentId = true;
+ continue;
+ }
+
+ if (currentFlag90 == null || currentFlag90 == 0) {
+ BigInteger days = getBigInteger(rs, "DAYS_PAST_DUE");
+ LocalDate date = rs.getObject("REPORTED_DT", LocalDate.class);
+
+ if (days != null) {
+ boolean has90daysFlag = days.compareTo(BigInteger.valueOf(90)) >= 0
+ && date != null && !date.isBefore(yearAgo);
+
+ if (has90daysFlag) {
+ flag90Days.put(fid, 1);
+ processed90DaysForCurrentId = true;
+ } else {
+ flag90Days.put(fid, 0);
+ }
+ } else {
+ flag90Days.put(fid, 0);
+ }
+ }
+ }
+ }
+ return null;
+ });
+ log.debug("Complete calculating flag 90 days by RU_PAPAYMT");
+ }
+
+
+ private BigInteger getBigInteger(ResultSet rs, String columnName) {
+ try {
+ String value = rs.getString(columnName);
+ return value != null ? new BigInteger(value) : null;
+ } catch (SQLException e) {
+ return null;
+ }
+ }
+
+ private BigDecimal getBigDecimal(ResultSet rs, String columnName) {
+ try {
+ return rs.getBigDecimal(columnName);
+ } catch (SQLException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/service/indic/PhoneService.java b/src/main/java/ru/nbch/credit_tracker/service/indic/PhoneService.java
index d063be1..7cd8a87 100644
--- a/src/main/java/ru/nbch/credit_tracker/service/indic/PhoneService.java
+++ b/src/main/java/ru/nbch/credit_tracker/service/indic/PhoneService.java
@@ -1,18 +1,18 @@
package ru.nbch.credit_tracker.service.indic;
+import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.mapper.indic.PhoneMapper;
import java.time.LocalDate;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.*;
+@Slf4j
@Service
public class PhoneService {
@@ -24,32 +24,45 @@ public class PhoneService {
/**
* Ищет фиды по номерам телефона
- * @return Map: key - номер телефона; value - fid
+ * @return Map: key - fid; value - {@link PhoneFidMap}
*/
- public Map findFidsPhoneData(List subjects) {
- List phones = subjects.stream().map(subject -> "9" + subject.getPhone()).toList();
- return convertFidsDataToMap(mapper.findFids(phones));
- }
+ public Map defineFidByPhone(List subjects) {
+ Map phoneFidMap = new HashMap<>();
+ List phones = subjects.stream().
+ map(subject -> "9" + subject.getPhone())
+ .toList();
- private Map convertFidsDataToMap(List fidsData) {
- if (fidsData == null || fidsData.isEmpty()) {
- return new HashMap<>();
+ subjects.sort(
+ Comparator.comparing(
+ PhoneFidMap::getPhone,
+ Comparator.naturalOrder()
+ )
+ );
+
+ //отсортированный список по NUMBER, FID и FILE_SINCE_DT
+ List phonesByFid = mapper.findFids(phones);
+ log.debug("Start merging fids");
+ for (PhoneFidMap phoneFid : subjects) {
+ Iterator iterator = phonesByFid.iterator();
+ while (iterator.hasNext()) {
+ FidsData fidsData = iterator.next();
+ if (fidsData.getPhone().equals(9 + phoneFid.getPhone())) {
+ //fixme доступ по ссылке, страшно, придумать что-то еще
+ phoneFidMap.put(fidsData.getFid(), phoneFid);
+ iterator.remove();
+ break;
+ }
+ }
}
- return fidsData.stream()
- .collect(Collectors.groupingBy(
- FidsData::getPhone,
- Collectors.collectingAndThen(
- Collectors.maxBy(
- Comparator.comparing(FidsData::getFileSinceDate)
- .thenComparing(FidsData::getFid)
- ),
- data -> data.map(FidsData::getFid).orElse(null)
- )
- ));
+ log.debug("End merging fids");
+
+ return phoneFidMap;
}
@Getter
@Setter
+ @AllArgsConstructor
+ @NoArgsConstructor
public static class FidsData {
private Integer fid;
private String phone;
diff --git a/src/main/java/ru/nbch/credit_tracker/service/indic/PhoneService2.java b/src/main/java/ru/nbch/credit_tracker/service/indic/PhoneService2.java
new file mode 100644
index 0000000..52f5a8c
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/service/indic/PhoneService2.java
@@ -0,0 +1,96 @@
+package ru.nbch.credit_tracker.service.indic;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Service;
+import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
+
+import java.time.LocalDate;
+import java.util.*;
+
+@Slf4j
+@Service
+public class PhoneService2 {
+
+ private static final String SELCT_FIDS = """
+ SELECT
+ pn.number,
+ pn.fid,
+ pn.file_since_dt
+ FROM PHONE_NORM pn
+ JOIN (SELECT DISTINCT fid FROM NAME) nf
+ ON nf.fid = pn.fid
+ WHERE number in (:phones)
+ ORDER BY
+ pn.number ASC,
+ pn.file_since_dt DESC,
+ pn.fid DESC
+ WITH UR
+ """;
+
+ private final NamedParameterJdbcTemplate jdbcTemplate;
+
+ public PhoneService2(@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ /**
+ * Ищет фиды по номерам телефона
+ *
+ * @return Map: key - fid; value - {@link PhoneFidMap}
+ */
+ public Map defineFidByPhone(List subjects) {
+ Map phoneFidMap = new HashMap<>();
+
+ List phones = subjects.stream().
+ map(subject -> "9" + subject.getPhone())
+ .toList();
+
+ subjects.sort(
+ Comparator.comparing(
+ PhoneFidMap::getPhone,
+ Comparator.naturalOrder()
+ )
+ );
+
+ MapSqlParameterSource params = new MapSqlParameterSource();
+ params.addValue("phones", phones);
+
+ List phonesByFid = jdbcTemplate.query(SELCT_FIDS, params, (rs, rowNum) ->
+ new FidsData(rs.getInt("fid"), rs.getString("number"), rs.getObject("file_since_dt", LocalDate.class))
+ );
+
+ log.debug("Start merging fids");
+ for (PhoneFidMap phoneFid : subjects) {
+ Iterator iterator = phonesByFid.iterator();
+ while (iterator.hasNext()) {
+ FidsData fidsData = iterator.next();
+ if (fidsData.getPhone().equals(9 + phoneFid.getPhone())) {
+ //fixme доступ по ссылке, страшно, придумать что-то еще
+ phoneFidMap.put(fidsData.getFid(), phoneFid);
+ iterator.remove();
+ break;
+ }
+ }
+ }
+ log.debug("End merging fids");
+
+ return phoneFidMap;
+ }
+
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class FidsData {
+ private Integer fid;
+ private String phone;
+ private LocalDate fileSinceDate;
+ }
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/utils/ValidationUtil.java b/src/main/java/ru/nbch/credit_tracker/utils/ValidationUtil.java
index d0068b2..4885a27 100644
--- a/src/main/java/ru/nbch/credit_tracker/utils/ValidationUtil.java
+++ b/src/main/java/ru/nbch/credit_tracker/utils/ValidationUtil.java
@@ -1,5 +1,6 @@
package ru.nbch.credit_tracker.utils;
+import lombok.extern.slf4j.Slf4j;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.Subject;
import ru.nbch.credit_tracker.enums.SignalError;
@@ -10,25 +11,30 @@ import java.util.regex.Pattern;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.clientExcp;
+@Slf4j
public class ValidationUtil {
- private static String phoneRegex = "^\\d{9}$";
- private static String packageNameRegex = "^[A-Z0-9_]{1,22}$";
- private static String npIdRegex = "^\\S{1,36}$";
+ private static final String phoneRegex = "^\\d{9}$";
+ private static final String packageNameRegex = "^[A-Z0-9_]{1,22}$";
+ private static final String npIdRegex = "^\\S{1,36}$";
public static void validateMonitoringRequest(MonitoringRequest request) throws SignalClientException {
if (request == null) {
throw clientExcp(SignalError.ERROR_011, XmlErrorFormat.MonitoringError);
}
- if (request.getUserId() == null) {
- throw clientExcp(SignalError.ERROR_002, XmlErrorFormat.MonitoringError, "UserId");
- }
-
if (request.getPackageName() == null) {
throw clientExcp(SignalError.ERROR_002, XmlErrorFormat.MonitoringError, "PackageName");
}
+ if (!validatePackageName(request.getPackageName())) {
+ throw clientExcp(SignalError.ERROR_008, XmlErrorFormat.MonitoringError);
+ }
+
+ if (request.getUserId() == null) {
+ throw clientExcp(SignalError.ERROR_002, XmlErrorFormat.MonitoringError, "UserId");
+ }
+
if (request.getPassword() == null) {
throw clientExcp(SignalError.ERROR_002, XmlErrorFormat.MonitoringError, "Password");
}
@@ -41,16 +47,12 @@ public class ValidationUtil {
throw clientExcp(SignalError.ERROR_002, XmlErrorFormat.MonitoringError, "Subject");
}
- if (!validatePackageName(request.getPackageName())) {
- throw clientExcp(SignalError.ERROR_008, XmlErrorFormat.MonitoringError);
- }
-
for (Subject subject : request.getSubjects().getSubject()) {
- if (!validatePhone(subject.getClientId())) {
- throw clientExcp(SignalError.ERROR_003, XmlErrorFormat.MonitoringError);
- }
if (!validateNPId(subject.getId())) {
- throw clientExcp(SignalError.ERROR_010, XmlErrorFormat.MonitoringError);
+ throw clientExcp(SignalError.ERROR_010, XmlErrorFormat.MonitoringError, subject.getId());
+ }
+ if (!validatePhone(subject.getClientId())) {
+ throw clientExcp(SignalError.ERROR_003, XmlErrorFormat.MonitoringError, subject.getId());
}
}
}
diff --git a/src/main/java/ru/nbch/credit_tracker/utils/error/ExceptionUtils.java b/src/main/java/ru/nbch/credit_tracker/utils/error/ExceptionUtils.java
index a15457c..2e94e78 100644
--- a/src/main/java/ru/nbch/credit_tracker/utils/error/ExceptionUtils.java
+++ b/src/main/java/ru/nbch/credit_tracker/utils/error/ExceptionUtils.java
@@ -12,7 +12,7 @@ public class ExceptionUtils {
return new SignalClientException(new EnumKeyMessage(enumKeyCode, args), xmlErrorFormat);
}
- public static SignalServerException serverExcp(IEnumKeyCode enumKeyCode, XmlErrorFormat xmlErrorFormat, Object... args) {
- return new SignalServerException(new EnumKeyMessage(enumKeyCode, args), xmlErrorFormat);
+ public static SignalServerException serverExcp(IEnumKeyCode enumKeyCode, XmlErrorFormat xmlErrorFormat, Exception cause, Object... args) {
+ return new SignalServerException(new EnumKeyMessage(enumKeyCode, args), xmlErrorFormat, cause);
}
}
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index 0825d76..20aa7c4 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -8,7 +8,7 @@
- %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
utf-8
@@ -63,12 +63,12 @@
-
+
-
+
diff --git a/src/main/resources/mybatis/mapper/indic/FlagsMapper.xml b/src/main/resources/mybatis/mapper/indic/FlagsMapper.xml
index 24572db..a186744 100644
--- a/src/main/resources/mybatis/mapper/indic/FlagsMapper.xml
+++ b/src/main/resources/mybatis/mapper/indic/FlagsMapper.xml
@@ -5,76 +5,39 @@
-
+
-
+
+
+
+
-
\ No newline at end of file
diff --git a/src/main/resources/mybatis/mapper/indic/PhoneMapper.xml b/src/main/resources/mybatis/mapper/indic/PhoneMapper.xml
index bd754a9..856a06d 100644
--- a/src/main/resources/mybatis/mapper/indic/PhoneMapper.xml
+++ b/src/main/resources/mybatis/mapper/indic/PhoneMapper.xml
@@ -12,14 +12,21 @@
- SELECT pn.NUMBER AS number, pn.FID AS fid, pn.FILE_SINCE_DT AS fileSinceDate
- FROM PHONE_NORM pn
- INNER JOIN NAME n ON n.FID = pn.FID
+ SELECT
+ pn.number,
+ pn.fid,
+ pn.file_since_dt
+ FROM indic.PHONE_NORM pn
+ JOIN (SELECT DISTINCT fid FROM indic.NAME) nf
+ ON nf.fid = pn.fid
WHERE number in
#{phone}
- ORDER BY fileSinceDate DESC, fid DESC
+ ORDER BY
+ pn.number ASC,
+ pn.file_since_dt DESC,
+ pn.fid DESC
WITH UR
\ No newline at end of file
diff --git a/src/test/java/ru/nbch/credit_tracker/service/FlagsService2Test.java b/src/test/java/ru/nbch/credit_tracker/service/FlagsService2Test.java
new file mode 100644
index 0000000..77bce91
--- /dev/null
+++ b/src/test/java/ru/nbch/credit_tracker/service/FlagsService2Test.java
@@ -0,0 +1,280 @@
+package ru.nbch.credit_tracker.service;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import ru.nbch.credit_tracker.service.indic.FlagsService;
+import ru.nbch.credit_tracker.service.indic.FlagsService2;
+
+import javax.sql.DataSource;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class FlagsService2Test {
+
+ private static final LocalDate INITIAL_DATE = LocalDate.parse("2025-09-25");
+
+ private static final String RU_PASTDUEAREAR_CREATE = """
+ create table RU_PASTDUEARREAR
+ (
+ FID INTEGER not null,
+ SERIAL_NUM BIGINT not null primary key,
+ ACC_SERIAL_NUM BIGINT not null,
+ REPORTED_DT DATE not null,
+ AMT_PAST_DUE DECIMAL(17, 2),
+ DAYS_PAST_DUE BIGINT
+ );
+ """;
+
+ private static final String RU_PASTDUEAREAR_DROP = """
+ DROP TABLE IF EXISTS RU_PASTDUEARREAR;
+ """;
+
+ private static final String RU_PAYMT_DROP = """
+ DROP TABLE IF EXISTS RU_PAYMT;
+ """;
+
+ private static final String RU_PAYMT_CREATE = """
+ create table RU_PAYMT
+ (
+ FID INTEGER not null,
+ SERIAL_NUM BIGINT not null primary key,
+ DAYS_PAST_DUE INTEGER,
+ REPORTED_DT DATE not null
+ );
+ """;
+
+ private static final String FILL_RU_PASTDUEARREAR = String.format("""
+ INSERT INTO RU_PASTDUEARREAR VALUES
+ (1, 1, 1, '%s', 36000.00, 159), -- просрочка 5000 и 90_days по этой записи
+ (2, 2, 2, '%s', 3200.00, null), -- не макс reported_date для того же acc_serial_num
+ (2, 3, 2, '%s', 6700.00, null), -- не макс reported_date для того же acc_serial_num
+ (2, 4, 2, '%s', 5700.00, null), -- просрочка 5000 по этой записи
+ (3, 5, 3, '%s', 300000000000.00, null),
+ (2, 6, 2, '%s', 0.00, null), -- не макс reported_date для того же acc_serial_num
+ (4, 7, 4, '%s', 4000.00, null), -- нет просрочек
+ (5, 8, 5, '%s', 2000.31, 182), -- не макс reported_date для того же acc_serial_num
+ (5, 9, 5, '%s', 2000.31, 212), -- не макс reported_date для того же acc_serial_num
+ (5, 10, 5, '%s', 2000.31, null), -- не макс reported_date для того же acc_serial_num
+ (5, 11, 5, '%s', 2000.31, 273), -- не макс reported_date для того же acc_serial_num
+ (5, 12, 5, '%s', 2000.31, null), -- не макс reported_date для того же acc_serial_num
+ (5, 13, 5, '%s', 7000.31, 307), -- не макс reported_date для того же acc_serial_num
+ (5, 14, 5, '%s', 2000.31, 338), -- не макс reported_date для того же acc_serial_num, просрочка 90_days будет взята у этой записи
+ (5, 15, 5, '%s', 7000.31, 368), -- Просрочка 5000 будет взята у этой записи. Уникальный acc_serial_num с макс reported_dt
+ (5, 16, 5, '%s', 2000.31, 399), -- не макс reported_date для того же acc_serial_num
+ (5, 17, 5, '%s', 5000.31, 5), -- не макс reported_date для того же acc_serial_num
+ (6, 18, 6, '%s', 3000, null), -- Нет просрочки 5000, нет просрочки 90_days (90_days будет в RU_PAYMT)
+ (7, 19, 7, '%s', 5500, 90), -- не макс reported_date для того же acc_serial_num. 90_days будет взята отсюда
+ (7, 20, 7, '%s', 3000, null) -- Нет просрочки 5000. Уникальный acc_serial_num с макс reported_dt
+ """,
+ calcLocalDateFromInitialDate("2025-05-31"),
+ calcLocalDateFromInitialDate("2021-12-29"),
+ calcLocalDateFromInitialDate("2022-01-28"),
+ calcLocalDateFromInitialDate("2025-04-06"),
+ calcLocalDateFromInitialDate("2025-01-14"),
+ calcLocalDateFromInitialDate("2021-11-29"),
+ calcLocalDateFromInitialDate("2025-12-06"),
+ calcLocalDateFromInitialDate("2021-04-01"),
+ calcLocalDateFromInitialDate("2021-05-01"),
+ calcLocalDateFromInitialDate("2021-05-31"),
+ calcLocalDateFromInitialDate("2021-07-01"),
+ calcLocalDateFromInitialDate("2021-07-31"),
+ calcLocalDateFromInitialDate("2021-08-04"),
+ calcLocalDateFromInitialDate("2025-09-08"),
+ calcLocalDateFromInitialDate("2025-09-09"),
+ calcLocalDateFromInitialDate("2021-11-04"),
+ calcLocalDateFromInitialDate("2022-01-04"),
+ calcLocalDateFromInitialDate("2025-09-09"),
+ calcLocalDateFromInitialDate("2025-09-09"),
+ calcLocalDateFromInitialDate("2025-09-10"));
+
+ private static final String FILL_RU_PAYMT = String.format("""
+ INSERT INTO RU_PAYMT VALUES
+ (1, 1, null, '%s'),
+ (2, 2, 30, '%s'),
+ (3, 3, null, '%s'),
+ (3, 4, 100, '%s'),
+ (4, 5, 10, '%s'),
+ (5, 6, 10, '%s'),
+ (6, 7, 90, '%s'),
+ (6, 8, 80, '%s'),
+ (7, 9, null, '%s'),
+ (8, 10, null, '%s'),
+ (8, 11, 83, '%s'),
+ (8, 12, 91, '%s'),
+ (9, 13, 100, '%s')
+ """,
+ calcLocalDateFromInitialDate("2024-11-09"),
+ calcLocalDateFromInitialDate("2024-11-09"),
+ calcLocalDateFromInitialDate("2024-11-10"),
+ calcLocalDateFromInitialDate("2024-11-15"),
+ calcLocalDateFromInitialDate("2024-11-15"),
+ calcLocalDateFromInitialDate("2023-11-15"),
+ calcLocalDateFromInitialDate("2024-11-09"),
+ calcLocalDateFromInitialDate("2024-10-09"),
+ calcLocalDateFromInitialDate("2024-10-09"),
+ calcLocalDateFromInitialDate("2024-10-09"),
+ calcLocalDateFromInitialDate("2024-10-09"),
+ calcLocalDateFromInitialDate("2024-10-09"),
+ calcLocalDateFromInitialDate("2024-10-09"));
+
+
+ private DataSource dataSource;
+ private NamedParameterJdbcTemplate jdbcTemplate;
+ private FlagsService2 flagsService;
+
+ @BeforeEach
+ void setUp() {
+ dataSource = DataSourceBuilder.create()
+ .url("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=DB2;DATABASE_TO_LOWER=TRUE")
+ .driverClassName("org.h2.Driver")
+ .username("sa")
+ .password("")
+ .build();
+
+ jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+
+ jdbcTemplate.getJdbcTemplate().execute(RU_PASTDUEAREAR_DROP);
+ jdbcTemplate.getJdbcTemplate().execute(RU_PAYMT_DROP);
+
+ jdbcTemplate.getJdbcTemplate().execute(RU_PASTDUEAREAR_CREATE);
+ jdbcTemplate.getJdbcTemplate().execute(RU_PAYMT_CREATE);
+
+ flagsService = new FlagsService2(jdbcTemplate);
+ }
+
+ @Test
+ public void testfindFlags() {
+ jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PASTDUEARREAR);
+ jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PAYMT);
+
+ List fids = new ArrayList<>(List.of(
+ 1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
+ 3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 4, // нет никаких просрочек
+ 7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
+ 9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
+ );
+
+ Map resultMap = flagsService.findFlags(fids);
+
+ Assertions.assertEquals(9, resultMap.size());
+ Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
+
+ Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
+ }
+
+ @Test
+ public void testfindFlagsWithoutRuPaymt() {
+ jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PASTDUEARREAR);
+
+ List fids = new ArrayList<>(List.of(
+ 1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
+ 3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 4, // нет никаких просрочек
+ 7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
+ 9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
+ );
+
+ Map resultMap = flagsService.findFlags(fids);
+
+ Assertions.assertEquals(9, resultMap.size());
+ Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
+
+ Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(3).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(6).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(8).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(9).getFlag90days());
+ }
+
+ @Test
+ public void testfindFlagsWithoutRuPastduearreae() {
+ jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PAYMT);
+
+ List fids = new ArrayList<>(List.of(
+ 1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
+ 3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 4, // нет никаких просрочек
+ 7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
+ 9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
+ );
+
+ Map resultMap = flagsService.findFlags(fids);
+
+ Assertions.assertEquals(9, resultMap.size());
+ Assertions.assertEquals(0, resultMap.get(1).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(2).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(3).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(5).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
+
+ Assertions.assertEquals(0, resultMap.get(1).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(5).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(7).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
+ }
+
+ private static LocalDate calcLocalDateFromInitialDate(String date) {
+ LocalDateTime now = LocalDateTime.now();
+ long daysPassed = ChronoUnit.DAYS.between(INITIAL_DATE, now);
+ return LocalDate.parse(date).plusDays(daysPassed);
+ }
+}
+
+
diff --git a/src/test/java/ru/nbch/credit_tracker/service/FlagsServiceTest.java b/src/test/java/ru/nbch/credit_tracker/service/FlagsServiceTest.java
new file mode 100644
index 0000000..2284db9
--- /dev/null
+++ b/src/test/java/ru/nbch/credit_tracker/service/FlagsServiceTest.java
@@ -0,0 +1,218 @@
+package ru.nbch.credit_tracker.service;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.nbch.credit_tracker.mapper.indic.FlagsMapper;
+import ru.nbch.credit_tracker.service.indic.FlagsService;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class FlagsServiceTest {
+
+ private static final LocalDate INITIAL_DATE = LocalDate.parse("2025-09-25");
+
+ @Mock
+ FlagsMapper flagsMapper;
+
+ @Test
+ public void testfindFlags() {
+ FlagsService flagsService = new FlagsService(flagsMapper);
+
+ when(flagsMapper.findRuPastDuarearData(Mockito.anyList())).thenReturn(ruPastDuarearDataList);
+ when(flagsMapper.findRuPaymtData(Mockito.anyList(), Mockito.any())).thenReturn(ruPaymtDataList);
+
+ List fids = new ArrayList<>(List.of(
+ 1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
+ 3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 4, // нет никаких просрочек
+ 7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
+ 9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
+ );
+
+ Map resultMap = flagsService.findFlags(fids);
+
+ Assertions.assertEquals(9, resultMap.size());
+ Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
+
+ Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
+ }
+
+ @Test
+ public void testfindFlagsWithoutRuPaymt() {
+ FlagsService flagsService = new FlagsService(flagsMapper);
+
+ when(flagsMapper.findRuPastDuarearData(Mockito.anyList())).thenReturn(ruPastDuarearDataList);
+ when(flagsMapper.findRuPaymtData(Mockito.anyList(), Mockito.any())).thenReturn(new ArrayList<>());
+
+ List fids = new ArrayList<>(List.of(
+ 1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
+ 3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 4, // нет никаких просрочек
+ 7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
+ 9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
+ );
+
+ Map resultMap = flagsService.findFlags(fids);
+
+ Assertions.assertEquals(9, resultMap.size());
+ Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
+ Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
+
+ Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(3).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(6).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(8).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(9).getFlag90days());
+ }
+
+
+ @Test
+ public void testfindFlagsWithoutRuPastduearreae() {
+ FlagsService flagsService = new FlagsService(flagsMapper);
+
+ when(flagsMapper.findRuPastDuarearData(Mockito.anyList())).thenReturn(new ArrayList<>());
+ when(flagsMapper.findRuPaymtData(Mockito.anyList(), Mockito.any())).thenReturn(ruPaymtDataList);
+
+ List fids = new ArrayList<>(List.of(
+ 1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
+ 3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 4, // нет никаких просрочек
+ 7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
+ 6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
+ 8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
+ 9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
+ );
+
+ Map resultMap = flagsService.findFlags(fids);
+
+ Assertions.assertEquals(9, resultMap.size());
+ Assertions.assertEquals(0, resultMap.get(1).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(2).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(3).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(5).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
+ Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
+
+ Assertions.assertEquals(0, resultMap.get(1).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(5).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
+ Assertions.assertEquals(0, resultMap.get(7).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
+ Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
+ }
+
+
+ private final List ruPastDuarearDataList = new ArrayList<>(
+ List.of(
+ new FlagsService.RuPastDuarearData(1, BigInteger.valueOf(1), calcLocalDateFromInitialDate("2025-05-31"), BigDecimal.valueOf(36000.00), BigInteger.valueOf(159)), // просрочка 5000 и 90_days по этой записи
+
+ new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2021-12-29"), BigDecimal.valueOf(3200.00), null), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2022-01-28"), BigDecimal.valueOf(6700.00), null), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2025-04-06"), BigDecimal.valueOf(5700.00), null), // просрочка 5000 по этой записи
+
+ new FlagsService.RuPastDuarearData(3, BigInteger.valueOf(3), calcLocalDateFromInitialDate("2025-01-14"), BigDecimal.valueOf(300000000000.00), null),
+
+ new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2021-11-29"), BigDecimal.valueOf(0.00), null), // не макс reported_date для того же acc_serial_num
+
+ new FlagsService.RuPastDuarearData(4, BigInteger.valueOf(4), calcLocalDateFromInitialDate("2025-12-06"), BigDecimal.valueOf(4000.00), null), // нет просрочек
+
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-04-01"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(182)), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-05-01"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(212)), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-05-31"), BigDecimal.valueOf(2000.31), null), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-07-01"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(273)), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-07-31"), BigDecimal.valueOf(2000.31), null), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-08-04"), BigDecimal.valueOf(7000.31), BigInteger.valueOf(307)), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2025-09-08"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(338)), // не макс reported_date для того же acc_serial_num, просрочка 90_days будет взята у этой записи
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(51), calcLocalDateFromInitialDate("2025-09-09"), BigDecimal.valueOf(7000.31), BigInteger.valueOf(368)), // Просрочка 5000 будет взята у этой записи. Уникальный acc_serial_num с макс reported_dt
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-11-04"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(399)), // не макс reported_date для того же acc_serial_num
+ new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2022-01-04"), BigDecimal.valueOf(5000.31), BigInteger.valueOf(5)), // не макс reported_date для того же acc_serial_num
+
+ new FlagsService.RuPastDuarearData(6, BigInteger.valueOf(6), calcLocalDateFromInitialDate("2025-09-09"), BigDecimal.valueOf(3000), null), // Нет просрочки 5000, нет просрочки 90_days (90_days будет в RU_PAYMT)
+
+ new FlagsService.RuPastDuarearData(7, BigInteger.valueOf(7), calcLocalDateFromInitialDate("2025-09-09"), BigDecimal.valueOf(5500), BigInteger.valueOf(90)), // не макс reported_date для того же acc_serial_num. 90_days будет взята отсюда
+ new FlagsService.RuPastDuarearData(7, BigInteger.valueOf(7), calcLocalDateFromInitialDate("2025-09-10"), BigDecimal.valueOf(3000), null) // Нет просрочки 5000. Уникальный acc_serial_num с макс reported_dt
+
+ )
+ );
+
+ private final List ruPaymtDataList = new ArrayList<>(
+ List.of(
+ new FlagsService.RuPaymtData(1, null, calcLocalDateFromInitialDate("2024-11-09")),
+ new FlagsService.RuPaymtData(2, BigInteger.valueOf(30), calcLocalDateFromInitialDate("2024-11-09")),
+ new FlagsService.RuPaymtData(3, null, calcLocalDateFromInitialDate("2024-11-10")),
+ new FlagsService.RuPaymtData(3, BigInteger.valueOf(100), calcLocalDateFromInitialDate("2024-11-15")),
+ new FlagsService.RuPaymtData(4, BigInteger.valueOf(10), calcLocalDateFromInitialDate("2024-11-15")),
+ new FlagsService.RuPaymtData(5, BigInteger.valueOf(10), calcLocalDateFromInitialDate("2023-11-15")),
+ new FlagsService.RuPaymtData(6, BigInteger.valueOf(90), calcLocalDateFromInitialDate("2024-11-09")),
+ new FlagsService.RuPaymtData(6, BigInteger.valueOf(80), calcLocalDateFromInitialDate("2024-10-09")),
+ new FlagsService.RuPaymtData(7, null, calcLocalDateFromInitialDate("2024-10-09")),
+ new FlagsService.RuPaymtData(8, null, calcLocalDateFromInitialDate("2024-10-09")),
+ new FlagsService.RuPaymtData(8, BigInteger.valueOf(83), calcLocalDateFromInitialDate("2024-10-09")),
+ new FlagsService.RuPaymtData(8, BigInteger.valueOf(91), calcLocalDateFromInitialDate("2024-10-09")),
+ new FlagsService.RuPaymtData(9, BigInteger.valueOf(100), calcLocalDateFromInitialDate("2024-10-09"))
+ )
+ );
+
+ private LocalDate calcLocalDateFromInitialDate(String date) {
+ LocalDateTime now = LocalDateTime.now();
+ long daysPassed = ChronoUnit.DAYS.between(INITIAL_DATE, now);
+ return LocalDate.parse(date).plusDays(daysPassed);
+ }
+}
diff --git a/src/test/java/ru/nbch/credit_tracker/service/PhoneService2Test.java b/src/test/java/ru/nbch/credit_tracker/service/PhoneService2Test.java
new file mode 100644
index 0000000..a8f14c0
--- /dev/null
+++ b/src/test/java/ru/nbch/credit_tracker/service/PhoneService2Test.java
@@ -0,0 +1,63 @@
+package ru.nbch.credit_tracker.service;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
+import ru.nbch.credit_tracker.service.indic.PhoneService2;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class PhoneService2Test {
+
+ @Mock
+ NamedParameterJdbcTemplate jdbcTemplate;
+
+ @Test
+ public void defineFidByPhoneTest() {
+ PhoneService2 phoneService = new PhoneService2(jdbcTemplate);
+
+ fidsDataWithEqualSinceDt.sort(byDbOrder);
+ when(jdbcTemplate.query(anyString(), any(MapSqlParameterSource.class), any(RowMapper.class))).thenReturn(fidsDataWithEqualSinceDt);
+
+ List phoneFidMap = new ArrayList<>();
+ phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
+ phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
+ phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
+ phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
+
+ Map res = phoneService.defineFidByPhone(phoneFidMap);
+ Assertions.assertEquals(3, res.size());
+ Assertions.assertEquals("1111", res.get(124).getPhone());
+ Assertions.assertEquals("1112", res.get(125).getPhone());
+ Assertions.assertEquals("1114", res.get(126).getPhone());
+ }
+
+ private final List fidsDataWithEqualSinceDt = new ArrayList<>(
+ List.of(
+ new PhoneService2.FidsData(123, "91111", LocalDate.of(2025, 9, 10)),
+ new PhoneService2.FidsData(124, "91111", LocalDate.of(2025, 9, 10)),
+ new PhoneService2.FidsData(125, "91112", LocalDate.of(2024, 10, 10)),
+ new PhoneService2.FidsData(126, "91114", LocalDate.of(2024, 10, 11))
+ )
+ );
+
+ Comparator byDbOrder =
+ Comparator.comparing(PhoneService2.FidsData::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
+ .thenComparing(PhoneService2.FidsData::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
+ .thenComparing(PhoneService2.FidsData::getFid, Comparator.reverseOrder());
+}
diff --git a/src/test/java/ru/nbch/credit_tracker/service/PhoneServiceTest.java b/src/test/java/ru/nbch/credit_tracker/service/PhoneServiceTest.java
new file mode 100644
index 0000000..b3973e6
--- /dev/null
+++ b/src/test/java/ru/nbch/credit_tracker/service/PhoneServiceTest.java
@@ -0,0 +1,60 @@
+package ru.nbch.credit_tracker.service;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
+import ru.nbch.credit_tracker.mapper.indic.PhoneMapper;
+import ru.nbch.credit_tracker.service.indic.PhoneService;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class PhoneServiceTest {
+
+ @Mock
+ PhoneMapper phoneMapper;
+
+ @Test
+ public void defineFidByPhoneTest() {
+ PhoneService phoneService = new PhoneService(phoneMapper);
+
+ fidsDataWithEqualSinceDt.sort(byDbOrder);
+ when(phoneMapper.findFids(Mockito.anyList())).thenReturn(fidsDataWithEqualSinceDt);
+
+ List phoneFidMap = new ArrayList<>();
+ phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
+ phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
+ phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
+ phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
+
+ Map res = phoneService.defineFidByPhone(phoneFidMap);
+ Assertions.assertEquals(3, res.size());
+ Assertions.assertEquals("1111", res.get(124).getPhone());
+ Assertions.assertEquals("1112", res.get(125).getPhone());
+ Assertions.assertEquals("1114", res.get(126).getPhone());
+ }
+
+ private final List fidsDataWithEqualSinceDt = new ArrayList<>(
+ List.of(
+ new PhoneService.FidsData(123, "91111", LocalDate.of(2025, 9, 10)),
+ new PhoneService.FidsData(124, "91111", LocalDate.of(2025, 9, 10)),
+ new PhoneService.FidsData(125, "91112", LocalDate.of(2024, 10, 10)),
+ new PhoneService.FidsData(126, "91114", LocalDate.of(2024, 10, 11))
+ )
+ );
+
+ Comparator byDbOrder =
+ Comparator.comparing(PhoneService.FidsData::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
+ .thenComparing(PhoneService.FidsData::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
+ .thenComparing(PhoneService.FidsData::getFid, Comparator.reverseOrder());
+}
diff --git a/src/test/java/ru/nbch/credit_tracker/utils/ValidationUtilTest.java b/src/test/java/ru/nbch/credit_tracker/utils/ValidationUtilTest.java
index b8146f3..aea414b 100644
--- a/src/test/java/ru/nbch/credit_tracker/utils/ValidationUtilTest.java
+++ b/src/test/java/ru/nbch/credit_tracker/utils/ValidationUtilTest.java
@@ -69,12 +69,12 @@ public class ValidationUtilTest {
Arguments.of(
"/monitoring_request/invalid_phone_request.zip",
SignalError.ERROR_003,
- Collections.emptyList().toArray(new Object[0])
+ new Object[]{"abc123"}
),
Arguments.of(
"/monitoring_request/invalid_id_request.zip",
SignalError.ERROR_010,
- Collections.emptyList().toArray(new Object[0])
+ new Object[]{"#####"}
)
);
}