Compare commits

..

10 commits

Author SHA1 Message Date
Mikhail Trofimov
af824c7d07 Used jdbcTemplate for finding fids 2025-09-30 10:59:26 +03:00
Mikhail Trofimov
7a4695281e Removed unused methods; Uncommented schedulers 2025-09-29 13:12:00 +03:00
Mikhail Trofimov
e07b203686 Use FlagsService2 2025-09-29 13:04:39 +03:00
Mikhail Trofimov
8c8ee75292 Added test for FlagsService2 2025-09-29 11:51:46 +03:00
Mikhail Trofimov
4f55c0df42 Added flags fetching by jdbc template 2025-09-29 10:10:21 +03:00
Mikhail Trofimov
e70f07b04b Added debug logs 2025-09-25 18:03:07 +03:00
Mikhail Trofimov
45ca5f4440 Changed logic for loading flag90days from Ru_Paymt; Added tests 2025-09-25 18:02:05 +03:00
Mikhail Trofimov
d1064b6b9f Minor fix calc flag5000 2025-09-25 16:51:19 +03:00
Mikhail Trofimov
b42e4f70df Added finding flags for RU_PASTDUEARREAR; Updated tests 2025-09-25 15:46:04 +03:00
Mikhail Trofimov
c245790eb5 Added test variant of counting flags 500 and 90_days from RU_PASTDUEARREAR 2025-09-25 13:07:39 +03:00
18 changed files with 1105 additions and 355 deletions

View file

@ -8,9 +8,9 @@ server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b
# datasources # datasources
# indic # indic
ct.indic-data-source-settings.driver=com.ibm.db2.jcc.DB2Driver ct.indic-data-source-settings.driver=com.ibm.db2.jcc.DB2Driver
ct.indic-data-source-settings.url=${DB_URL_INDIC:jdbc:db2://10.230.227.101:2668/cprosd22:currentSchema=INDIC;} ct.indic-data-source-settings.url=${DB_URL_INDIC:jdbc:db2://10.230.227.100:2668/cprosd22:currentSchema=INDIC;}
ct.indic-data-source-settings.username=${USERNAME_INDIC:tester} ct.indic-data-source-settings.username=${USERNAME_INDIC:ros9appl}
ct.indic-data-source-settings.password=${PASSWORD_INDIC:tester} ct.indic-data-source-settings.password=${PASSWORD_INDIC:R0s9Appl}
# signal # signal
ct.signal-data-source-settings.driver=org.postgresql.Driver ct.signal-data-source-settings.driver=org.postgresql.Driver

View file

@ -126,6 +126,11 @@
<version>3.0.4</version> <version>3.0.4</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId> <artifactId>mockito-junit-jupiter</artifactId>

View file

@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings; import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -47,5 +48,10 @@ public class IndicDataSourceConfig extends DatabaseConfig {
sqlSessionFactoryBean.setTypeHandlersPackage("ru.nbch.credit_tracker.config.datasource.handlers"); sqlSessionFactoryBean.setTypeHandlersPackage("ru.nbch.credit_tracker.config.datasource.handlers");
return sqlSessionFactoryBean.getObject(); return sqlSessionFactoryBean.getObject();
} }
@Bean(name = "jdbcTemplateIndic")
public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(@Qualifier("indicDataSource") DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
} }

View file

@ -10,18 +10,8 @@ import java.util.List;
@Mapper @Mapper
public interface FlagsMapper { public interface FlagsMapper {
List<FlagsService.FlagFidData> findFlagsDebt5000(@Param("fids") List<Integer> fids); List<FlagsService.RuPastDuarearData> findRuPastDuarearData(@Param("fids") List<Integer> fids);
List<FlagsService.FlagFidData> findFlag90ByPastDueArrear(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate); List<FlagsService.RuPaymtData> findRuPaymtData(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate);
List<FlagsService.FlagFidData> findFlag90ByRuPaymt(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate);
List<FlagsService.Debt5000Data> findFlagsDebt5000NewWithOrderBy(@Param("fids") List<Integer> fids);
List<FlagsService.Flag90DaysData> findFlag90ByPastDueArrearNewWithOrderBy(@Param("fids") List<Integer> fids);
List<FlagsService.Flag90DaysData> findFlag90ByRuPaymtNewWithOrderBy(@Param("fids") List<Integer> fids);
} }

View file

@ -15,7 +15,6 @@ public interface PhoneFidMapMapper {
List<PhoneFidMap> findFullPhoneFidMap(@Param("packageId") Integer packageId, @Param("offset") int offset, @Param("limit") int limit); List<PhoneFidMap> findFullPhoneFidMap(@Param("packageId") Integer packageId, @Param("offset") int offset, @Param("limit") int limit);
void updatePhoneFidMaps(@Param("subjects") List<PhoneFidMap> subjects); void updatePhoneFidMaps(@Param("subjects") List<PhoneFidMap> subjects);
void updatePhoneFid(@Param("subject") PhoneFidMap subject);
void updatePhoneFidMapsErrors(@Param("subjects") List<PhoneFidMap> subjects); void updatePhoneFidMapsErrors(@Param("subjects") List<PhoneFidMap> subjects);

View file

@ -1,22 +1,8 @@
package ru.nbch.credit_tracker.service; package ru.nbch.credit_tracker.service;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings; import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.dto.Hit; import ru.nbch.credit_tracker.entities.dto.Hit;
@ -36,6 +22,21 @@ import ru.nbch.credit_tracker.service.xml.stax.MonitoringSignalResponseStaxXmlWr
import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor; import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor;
import ru.nbch.credit_tracker.utils.CTUtil; import ru.nbch.credit_tracker.utils.CTUtil;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/** /**
* Сервис периодического опроса официального сервиса сигналов на наличие новых отчетов по пакетам находящимся в статусе ON_MONITORING * Сервис периодического опроса официального сервиса сигналов на наличие новых отчетов по пакетам находящимся в статусе ON_MONITORING
*/ */
@ -59,9 +60,9 @@ public class MonitoringService {
} }
public void run() { public void run() {
// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("monitoring-")); ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("monitoring-"));
// long period = config.getMonitoringInterval() != null ? config.getMonitoringInterval() : 15; long period = config.getMonitoringInterval() != null ? config.getMonitoringInterval() : 15;
// scheduler.scheduleAtFixedRate(this::monitor, 0, period, TimeUnit.MINUTES); scheduler.scheduleAtFixedRate(this::monitor, 0, period, TimeUnit.MINUTES);
} }
private void monitor() { private void monitor() {

View file

@ -15,10 +15,10 @@ import ru.nbch.credit_tracker.entities.external_request.signals.Signals;
import ru.nbch.credit_tracker.entities.external_response.monitoring_report_error.MonitoringReportErrors; import ru.nbch.credit_tracker.entities.external_response.monitoring_report_error.MonitoringReportErrors;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest; import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.enums.Status; import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
import ru.nbch.credit_tracker.service.indic.FlagsService; import ru.nbch.credit_tracker.service.indic.FlagsService;
import ru.nbch.credit_tracker.service.indic.FlagsService2;
import ru.nbch.credit_tracker.service.indic.PersonService; import ru.nbch.credit_tracker.service.indic.PersonService;
import ru.nbch.credit_tracker.service.indic.PhoneService; import ru.nbch.credit_tracker.service.indic.PhoneService2;
import ru.nbch.credit_tracker.service.signal.HitsService; import ru.nbch.credit_tracker.service.signal.HitsService;
import ru.nbch.credit_tracker.service.signal.PhoneFidMapService; import ru.nbch.credit_tracker.service.signal.PhoneFidMapService;
import ru.nbch.credit_tracker.service.signal.UserPackageService; import ru.nbch.credit_tracker.service.signal.UserPackageService;
@ -44,9 +44,8 @@ public class SignalService {
private final HitsService hitsService; private final HitsService hitsService;
private final UserPackageService userPackageService; private final UserPackageService userPackageService;
private final PhoneFidMapService phoneFidMapService; private final PhoneFidMapService phoneFidMapService;
private final PhoneFidMapMapper phoneFidMapMapper; private final PhoneService2 phoneService;
private final PhoneService phoneService; private final FlagsService2 flagsService;
private final FlagsService flagsService;
private final PersonService personService; private final PersonService personService;
private final StaxXmlProcessor staxXmlProcessor; private final StaxXmlProcessor staxXmlProcessor;
@ -56,16 +55,14 @@ public class SignalService {
HitsService hitsService, HitsService hitsService,
UserPackageService userPackageService, UserPackageService userPackageService,
PhoneFidMapService phoneFidMapService, PhoneFidMapService phoneFidMapService,
PhoneFidMapMapper mapper, PhoneService2 phoneService,
PhoneService phoneService, FlagsService2 flagsService,
FlagsService flagsService,
PersonService personService, PersonService personService,
StaxXmlProcessor staxXmlProcessor) { StaxXmlProcessor staxXmlProcessor) {
this.config = config; this.config = config;
this.hitsService = hitsService; this.hitsService = hitsService;
this.userPackageService = userPackageService; this.userPackageService = userPackageService;
this.phoneFidMapService = phoneFidMapService; this.phoneFidMapService = phoneFidMapService;
this.phoneFidMapMapper = mapper;
this.phoneService = phoneService; this.phoneService = phoneService;
this.flagsService = flagsService; this.flagsService = flagsService;
this.personService = personService; this.personService = personService;

View file

@ -2,9 +2,12 @@ package ru.nbch.credit_tracker.service;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.enums.Status; import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.service.signal.SignalRestService; import ru.nbch.credit_tracker.service.signal.SignalRestService;
import java.util.List;
/** /**
* Сервис для завершения обработки пакетов находящихся в статусе PROCESSING * Сервис для завершения обработки пакетов находящихся в статусе PROCESSING
* Запускается при старте приложения и отрабатывает 1 раз * Запускается при старте приложения и отрабатывает 1 раз
@ -26,13 +29,13 @@ public class UpdateProcessingPackagesService {
} }
public void run() { public void run() {
// log.info("Start updating processing packages"); log.info("Start updating processing packages");
// List<UserPackage> packages = signalService.findPackagesOnProcessing(); List<UserPackage> packages = signalService.findPackagesOnProcessing();
// for (UserPackage userPackage : packages) { for (UserPackage userPackage : packages) {
// log.info("Updating flags for Package {}", userPackage.getPackageName()); log.info("Updating flags for Package {}", userPackage.getPackageName());
// process(userPackage.getId(), userPackage.getPackageName()); process(userPackage.getId(), userPackage.getPackageName());
// } }
// log.info("Finish updating processing packages"); log.info("Finish updating processing packages");
} }
private void process(Integer packageId, String packageName) { private void process(Integer packageId, String packageName) {

View file

@ -1,10 +1,7 @@
package ru.nbch.credit_tracker.service; package ru.nbch.credit_tracker.service;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings; import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
@ -12,6 +9,14 @@ import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.enums.Status; import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.service.signal.SignalRestService; import ru.nbch.credit_tracker.service.signal.SignalRestService;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/** /**
* Сервис периодического обновления фидов и флагов просрочек по пакетам находящимся в статусе ON_MONITORING * Сервис периодического обновления фидов и флагов просрочек по пакетам находящимся в статусе ON_MONITORING
* По обновленным пакетам формируется новый запрос постановки на мониторинг в официальный сервис сигналов * По обновленным пакетам формируется новый запрос постановки на мониторинг в официальный сервис сигналов
@ -38,8 +43,8 @@ public class UpdatingFlagsService {
} }
public void run() { public void run() {
// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("updating-flags-")); ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("updating-flags-"));
// scheduler.scheduleAtFixedRate(this::update, 0, updateFlagsInterval, TimeUnit.DAYS); scheduler.scheduleAtFixedRate(this::update, 0, updateFlagsInterval, TimeUnit.DAYS);
} }
private void update() { private void update() {

View file

@ -2,6 +2,7 @@ package ru.nbch.credit_tracker.service.indic;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -11,9 +12,7 @@ import ru.nbch.credit_tracker.mapper.indic.FlagsMapper;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.*;
import java.util.List;
import java.util.Map;
@Service @Service
public class FlagsService { public class FlagsService {
@ -29,14 +28,17 @@ public class FlagsService {
if (fids.isEmpty()) { if (fids.isEmpty()) {
return new HashMap<>(); return new HashMap<>();
} }
List<Integer> distinctFids = fids.stream().distinct().toList(); List<Integer> distinctFids = fids.stream()
.distinct()
.sorted(Comparator.naturalOrder())
.toList();
log.debug("Fids count: {}", distinctFids.size()); log.debug("Fids count: {}", distinctFids.size());
Map<Integer, Integer> flags5000 = calcFlag5000(distinctFids); Map<Integer, Integer> flags5000 = new HashMap<>();
calcFlag500Test(distinctFids); Map<Integer, Integer> flags90DaysByPastDueArrear = new HashMap<>();
Map<Integer, Integer> flags90DaysByPastDueArrear = calcFlag90DaysByPastDueArrear(distinctFids); Map<Integer, Integer> flags90DaysByRuPaymt = new HashMap<>();
calcFlag90DaysByPastDueArrearTest(distinctFids); findFlagsRuPastDueArrear(distinctFids, flags5000, flags90DaysByPastDueArrear);
Map<Integer, Integer> flags90DaysByRuPaymt = calcFlag90DaysByRuPaymt(distinctFids); findFlagsRuPaymt(distinctFids, flags90DaysByRuPaymt);
calcFlag90DaysByRuPaymtTest(distinctFids);
Map<Integer, FlagsData> result = new HashMap<>(); Map<Integer, FlagsData> result = new HashMap<>();
fids.forEach(fid -> { fids.forEach(fid -> {
@ -46,7 +48,7 @@ public class FlagsService {
} }
Integer flag90days = flags90DaysByPastDueArrear.get(fid); Integer flag90days = flags90DaysByPastDueArrear.get(fid);
if (flag90days == null) { if (flag90days == null || flag90days == 0) {
flag90days = flags90DaysByRuPaymt.get(fid); flag90days = flags90DaysByRuPaymt.get(fid);
if (flag90days == null) { if (flag90days == null) {
flag90days = 0; flag90days = 0;
@ -59,68 +61,118 @@ public class FlagsService {
} }
/** /**
* Выбирает данные из RU_PASTDUEARREAR и ищет по ним флаги просрочек
* По FID из RU_PASTDUEARREAR выбираются все уникальные ACC_SERIAL_NUM с максимальной датой REPORTED_DT. * По FID из RU_PASTDUEARREAR выбираются все уникальные ACC_SERIAL_NUM с максимальной датой REPORTED_DT.
* Если по хотя бы одной такой записи AMT_PAST_DUE > 5000, то флаг устанавливается в 1. * Если по хотя бы одной такой записи AMT_PAST_DUE > 5000, то flag500 устанавливается в 1.
* Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 0. * Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 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 Map<Integer, Integer> calcFlag5000(List<Integer> fids) { private void findFlagsRuPastDueArrear(List<Integer> fids, Map<Integer, Integer> flag5000Map, Map<Integer, Integer> flag90Days) {
log.debug("Start calculating flag 5000"); log.debug("Start calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
List<FlagFidData> rows = mapper.findFlagsDebt5000(fids); List<RuPastDuarearData> ruPastDuarearDataList = mapper.findRuPastDuarearData(fids);
Map<Integer, Integer> res = makeFidFlagMapByRs(fids, rows);
log.debug("Complete calculating flag 5000"); ruPastDuarearDataList.sort(Comparator.comparing(FlagsService.RuPastDuarearData::getFid) // сортируем сначала по фиду
return res; .thenComparing(FlagsService.RuPastDuarearData::getReportedDate, Comparator.reverseOrder())); // Затем сортируем по REPORTED_DT по убыванию
LocalDate yearAgo = LocalDate.now().minusYears(1);
for (Integer fid : fids) {
Iterator<RuPastDuarearData> iterator = ruPastDuarearDataList.iterator();
BigInteger prevAccSerialNum = null;
while (iterator.hasNext()) {
FlagsService.RuPastDuarearData data = iterator.next();
if (fid.equals(data.getFid())) {
if (flag5000Map.get(fid) == null) { // Первая запись в списке с макс REPORTED_DT. Нас интересует только она для флага 5000
if (prevAccSerialNum == null || !prevAccSerialNum.equals(data.getAccSerialNum())) {
if (data.getAmtPastDue() != null && data.getAmtPastDue().compareTo(BigDecimal.valueOf(5000)) >= 0) {
flag5000Map.put(fid, 1);
}
prevAccSerialNum = data.getAccSerialNum();
}
} }
private void calcFlag500Test(List<Integer> fids) { Integer flag90 = flag90Days.get(fid);
log.debug("Start findFlagsDebt5000NewWithOrderBy"); if (flag90 != null && flag90 == 1) {
mapper.findFlagsDebt5000NewWithOrderBy(fids); iterator.remove();
log.debug("Complete findFlagsDebt5000NewWithOrderBy"); break;
}
if (flag90 == null || flag90 == 0) {
if (data.getDaysPastDue() != null) {
boolean has90daysFlag = data.getDaysPastDue().compareTo(BigInteger.valueOf(90)) >= 0 && !data.getReportedDate().isBefore(yearAgo);
if (has90daysFlag) {
flag90Days.put(fid, 1);
iterator.remove();
break;
} else {
flag90Days.put(fid, 0);
iterator.remove();
}
} else {
iterator.remove();
}
}
}
}
// если мы так и не нашли флаги для этих фидов - выставляем их в 0
flag5000Map.putIfAbsent(fid, 0);
flag90Days.putIfAbsent(fid, 0);
}
log.debug("Complete calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
} }
/** /**
* Флаг = 1, если выполняется хотя бы одно из условий: * Выбирает данные из RU_PAYMT и ищет по ним флаги просрочек 90Days
* В INDIC.RU_PASTDUEARREAR или RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев. *
* flag90Days = 1, если выполняется хотя бы одно из условий:
* В INDIC.RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев.
* Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0. * Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
* @param fids - отсортирован по возрастанию фидов
* @param flag90Days - рещультирующая мапа флагов просрочек 90_days
*/ */
private Map<Integer, Integer> calcFlag90DaysByPastDueArrear(List<Integer> fids) { private void findFlagsRuPaymt(List<Integer> fids, Map<Integer, Integer> flag90Days) {
log.debug("Start calculating flag 90 by PastDueArrear"); log.debug("Start calculating flag 90 days by RU_PAPAYMT");
LocalDate yearAgo = LocalDate.now().minusYears(1); LocalDate yearAgo = LocalDate.now().minusYears(1);
List<FlagFidData> rows = mapper.findFlag90ByPastDueArrear(fids, yearAgo); List<RuPaymtData> ruPaymtDataList = mapper.findRuPaymtData(fids, yearAgo);
Map<Integer, Integer> res = makeFidFlagMapByRs(fids, rows); ruPaymtDataList.sort(Comparator.comparing(RuPaymtData::getFid, Comparator.naturalOrder()));
log.debug("Complete calculating flag 90 by PastDueArrear");
return res;
}
private void calcFlag90DaysByPastDueArrearTest(List<Integer> fids) {
log.debug("Start findFlag90ByPastDueArrearNewWithOrderBy");
mapper.findFlag90ByPastDueArrearNewWithOrderBy(fids);
log.debug("Complete findFlag90ByPastDueArrearNewWithOrderBy");
}
private Map<Integer, Integer> calcFlag90DaysByRuPaymt(List<Integer> fids) {
log.debug("Start calculating flag 90 by ruPaymt");
LocalDate yearAgo = LocalDate.now().minusYears(1);
List<FlagFidData> rows = mapper.findFlag90ByRuPaymt(fids, yearAgo);
Map<Integer, Integer> res = makeFidFlagMapByRs(fids, rows);
log.debug("Complete calculating flag 90 by ruPaymt");
return res;
}
private void calcFlag90DaysByRuPaymtTest(List<Integer> fids) {
log.debug("Start findFlag90ByRuPaymtNewWithOrderBy");
mapper.findFlag90ByRuPaymtNewWithOrderBy(fids);
log.debug("Complete findFlag90ByRuPaymtNewWithOrderBy");
}
private Map<Integer, Integer> makeFidFlagMapByRs(List<Integer> fids, List<FlagFidData> rows) {
Map<Integer, Integer> result = new HashMap<>();
for (Integer fid : fids) { for (Integer fid : fids) {
result.put(fid, 0); Iterator<RuPaymtData> iterator = ruPaymtDataList.iterator();
while (iterator.hasNext()) {
RuPaymtData data = iterator.next();
if (fid.equals(data.getFid())) {
Integer flag90 = flag90Days.get(fid);
if (flag90 != null && flag90 == 1) {
iterator.remove();
break;
} }
for (FlagFidData row : rows) {
result.put(row.getFid(), row.getFlag() != null ? row.getFlag() : 0); if (flag90 == null || flag90 == 0) {
if (data.getDaysPastDue() != null) {
boolean has90daysFlag = data.getDaysPastDue().compareTo(BigInteger.valueOf(90)) >= 0 && !data.getReportedDate().isBefore(yearAgo);
if (has90daysFlag) {
flag90Days.put(fid, 1);
iterator.remove();
break;
} else {
flag90Days.put(fid, 0);
iterator.remove();
} }
return result; } else {
iterator.remove();
}
}
}
}
flag90Days.putIfAbsent(fid, 0);
}
log.debug("Complete calculating flag 90 days by RU_PAPAYMT");
} }
@Getter @Getter
@ -133,23 +185,21 @@ public class FlagsService {
@Getter @Getter
@Setter @Setter
public static class FlagFidData { @AllArgsConstructor
private Integer fid; @NoArgsConstructor
private Integer flag; public static class RuPastDuarearData {
}
@Getter
@Setter
public static class Debt5000Data {
private Integer fid; private Integer fid;
private BigInteger accSerialNum; private BigInteger accSerialNum;
private LocalDate reportedDate; private LocalDate reportedDate;
private BigDecimal amtPastDue; private BigDecimal amtPastDue;
private BigInteger daysPastDue;
} }
@Getter @Getter
@Setter @Setter
public static class Flag90DaysData { @AllArgsConstructor
@NoArgsConstructor
public static class RuPaymtData {
private Integer fid; private Integer fid;
private BigInteger daysPastDue; private BigInteger daysPastDue;
private LocalDate reportedDate; private LocalDate reportedDate;

View file

@ -0,0 +1,256 @@
package ru.nbch.credit_tracker.service.indic;
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 java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class FlagsService2 {
private static final String SELECT_RU_PASDUEARREAR_DATA =
"SELECT FID, ACC_SERIAL_NUM, REPORTED_DT, AMT_PAST_DUE, DAYS_PAST_DUE " +
"FROM RU_PASTDUEARREAR " +
"WHERE FID IN (:fids) " +
"ORDER BY FID, REPORTED_DT DESC " +
"WITH UR";
private static final String SELECT_RU_PAYMT_DATA =
"SELECT FID, DAYS_PAST_DUE, REPORTED_DT " +
"FROM RU_PAYMT " +
"WHERE FID IN (:fids) " +
"AND REPORTED_DT >= :reportedDate " +
"WITH UR";
private final NamedParameterJdbcTemplate jdbcTemplate;
public FlagsService2(@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Map<Integer, FlagsService.FlagsData> findFlags(List<Integer> fids) {
if (fids.isEmpty()) {
return new HashMap<>();
}
List<Integer> distinctFids = fids.stream()
.distinct()
.sorted(Comparator.naturalOrder())
.toList();
log.debug("Fids count: {}", distinctFids.size());
Map<Integer, Integer> flags5000 = new HashMap<>();
Map<Integer, Integer> flags90DaysByPastDueArrear = new HashMap<>();
Map<Integer, Integer> flags90DaysByRuPaymt = new HashMap<>();
findFlagsRuPastDueArrear(distinctFids, flags5000, flags90DaysByPastDueArrear);
findFlagsRuPaymt(distinctFids, flags90DaysByRuPaymt);
Map<Integer, FlagsService.FlagsData> 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.
* <p>
* 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<Integer> fids, Map<Integer, Integer> flag5000Map, Map<Integer, Integer> 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
* <p>
* flag90Days = 1, если выполняется хотя бы одно из условий:
* В INDIC.RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев.
* Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
*
* @param fids - отсортирован по возрастанию фидов
* @param flag90Days - рещультирующая мапа флагов просрочек 90_days
*/
private void findFlagsRuPaymt(List<Integer> fids, Map<Integer, Integer> 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;
}
}
}

View file

@ -11,7 +11,6 @@ import ru.nbch.credit_tracker.mapper.indic.PhoneMapper;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@ -25,13 +24,8 @@ public class PhoneService {
/** /**
* Ищет фиды по номерам телефона * Ищет фиды по номерам телефона
* @return Map: key - номер телефона; value - fid * @return Map: key - fid; value - {@link PhoneFidMap}
*/ */
public Map<String, Integer> findFidsPhoneData(List<PhoneFidMap> subjects) {
List<String> phones = subjects.stream().map(subject -> "9" + subject.getPhone()).toList();
return convertFidsDataToMap(mapper.findFids(phones));
}
public Map<Integer, PhoneFidMap> defineFidByPhone(List<PhoneFidMap> subjects) { public Map<Integer, PhoneFidMap> defineFidByPhone(List<PhoneFidMap> subjects) {
Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>(); Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>();
List<String> phones = subjects.stream(). List<String> phones = subjects.stream().
@ -65,23 +59,6 @@ public class PhoneService {
return phoneFidMap; return phoneFidMap;
} }
private Map<String, Integer> convertFidsDataToMap(List<FidsData> fidsData) {
if (fidsData == null || fidsData.isEmpty()) {
return new HashMap<>();
}
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)
)
));
}
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor

View file

@ -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<Integer, PhoneFidMap> defineFidByPhone(List<PhoneFidMap> subjects) {
Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>();
List<String> 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<FidsData> 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<FidsData> 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;
}
}

View file

@ -5,224 +5,39 @@
<mapper namespace="ru.nbch.credit_tracker.mapper.indic.FlagsMapper"> <mapper namespace="ru.nbch.credit_tracker.mapper.indic.FlagsMapper">
<resultMap id="FlagsFidDataResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$FlagFidData"> <resultMap id="RuPastDuarearResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$RuPastDuarearData">
<result property="fid" column="fid" />
<result property="flag" column="flag" />
</resultMap>
<select id="findFlagsDebt5000" resultMap="FlagsFidDataResultMap">
WITH latest AS (
SELECT
fid,
acc_serial_num,
amt_past_due,
ROW_NUMBER() OVER (PARTITION BY fid, acc_serial_num ORDER BY reported_dt DESC) AS rn
FROM RU_PASTDUEARREAR
WHERE fid IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
)
SELECT
fid,
CASE WHEN SUM(CASE WHEN amt_past_due > 5000 THEN 1 ELSE 0 END) > 0
THEN 1 ELSE 0
END AS flag
FROM latest WHERE rn = 1
GROUP BY fid
WITH UR
</select>
<select id="findFlag90ByPastDueArrear" resultMap="FlagsFidDataResultMap">
WITH latest AS (
SELECT
r.fid,
r.days_past_due,
ROW_NUMBER() OVER (PARTITION BY r.fid ORDER BY r.reported_dt DESC) AS rn
FROM RU_PASTDUEARREAR r
WHERE r.fid IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
AND r.reported_dt >= #{reportedDate}
)
SELECT
fid,
CASE WHEN MAX(CASE WHEN days_past_due > 90 THEN 1 ELSE 0 END) = 1
THEN 1 ELSE 0
END AS flag
FROM latest WHERE rn = 1
GROUP BY fid
WITH UR
</select>
<select id="findFlag90ByRuPaymt" resultMap="FlagsFidDataResultMap">
WITH latest AS (
SELECT
r.fid,
r.days_past_due,
ROW_NUMBER() OVER (PARTITION BY r.fid ORDER BY r.reported_dt DESC) AS rn
FROM RU_PAYMT r
WHERE r.fid IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
AND r.reported_dt >= #{reportedDate}
)
SELECT
fid,
CASE WHEN MAX(CASE WHEN days_past_due > 90 THEN 1 ELSE 0 END) = 1
THEN 1 ELSE 0
END AS flag
FROM latest WHERE rn = 1
GROUP BY fid
WITH UR
</select>
<select id="findFlagsDebt5000Old" resultMap="FlagsFidDataResultMap">
SELECT rp.FID as fid,
CASE
WHEN EXISTS (SELECT 1
FROM RU_PASTDUEARREAR rp2
WHERE rp2.FID = rp.FID
AND rp2.ACC_SERIAL_NUM = rp.ACC_SERIAL_NUM
AND rp2.REPORTED_DT = rp.MAX_REP_DATE
AND rp2.AMT_PAST_DUE > 5000) THEN 1
ELSE 0
END as flag
FROM (SELECT FID,
ACC_SERIAL_NUM,
MAX(REPORTED_DT) as MAX_REP_DATE
FROM RU_PASTDUEARREAR
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
GROUP BY FID, ACC_SERIAL_NUM) rp
GROUP BY rp.FID, rp.ACC_SERIAL_NUM, rp.MAX_REP_DATE
WITH UR
</select>
<select id="findFlag90ByPastDueArrearOld" resultMap="FlagsFidDataResultMap">
SELECT rp.FID as fid,
CASE
WHEN EXISTS (SELECT 1
FROM RU_PASTDUEARREAR rp2
WHERE rp2.FID = rp.FID
AND rp2.REPORTED_DT >= #{reportedDate}
AND rp2.DAYS_PAST_DUE > 90) THEN 1
ELSE 0
END as flag
FROM (SELECT FID,
DAYS_PAST_DUE
FROM RU_PASTDUEARREAR
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
GROUP BY FID, DAYS_PAST_DUE) rp
GROUP BY rp.FID, rp.DAYS_PAST_DUE
WITH UR
</select>
<select id="findFlag90ByRuPaymtOld" resultMap="FlagsFidDataResultMap">
SELECT rp.FID as fid,
CASE
WHEN EXISTS (SELECT 1
FROM RU_PAYMT rp2
WHERE rp2.FID = rp.FID
AND rp2.REPORTED_DT >= #{reportedDate}
AND rp2.DAYS_PAST_DUE > 90) THEN 1
ELSE 0
END as flag
FROM (SELECT FID,
DAYS_PAST_DUE
FROM RU_PAYMT
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
GROUP BY FID, DAYS_PAST_DUE) rp
GROUP BY rp.FID, rp.DAYS_PAST_DUE
WITH UR
</select>
<resultMap id="Debt500ResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$Debt5000Data">
<result property="fid" column="fid" /> <result property="fid" column="fid" />
<result property="accSerialNum" column="ACC_SERIAL_NUM" /> <result property="accSerialNum" column="ACC_SERIAL_NUM" />
<result property="reportedDate" column="REPORTED_DT" /> <result property="reportedDate" column="REPORTED_DT" />
<result property="amtPastDue" column="AMT_PAST_DUE" /> <result property="amtPastDue" column="AMT_PAST_DUE" />
<result property="daysPastDue" column="DAYS_PAST_DUE" />
</resultMap> </resultMap>
<select id="findFlagsDebt5000New" resultMap="Debt500ResultMap"> <resultMap id="RuPaymtResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$RuPaymtData">
SELECT FID, ACC_SERIAL_NUM, REPORTED_DT, AMT_PAST_DUE
FROM RU_PASTDUEARREAR
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
with UR
</select>
<select id="findFlagsDebt5000NewWithOrderBy" resultMap="Debt500ResultMap">
SELECT FID, ACC_SERIAL_NUM, REPORTED_DT, AMT_PAST_DUE
FROM RU_PASTDUEARREAR
WHERE fid IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
ORDER BY FID DESC
with UR
</select>
<resultMap id="Flag90ByPastDueArrearResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$Flag90DaysData">
<result property="fid" column="fid" /> <result property="fid" column="fid" />
<result property="daysPastDue" column="DAYS_PAST_DUE" /> <result property="daysPastDue" column="DAYS_PAST_DUE" />
<result property="daysPastDue" column="REPORTED_DT" /> <result property="reportedDate" column="REPORTED_DT" />
</resultMap> </resultMap>
<select id="findFlag90ByPastDueArrearNew" resultMap="Flag90ByPastDueArrearResultMap"> <select id="findRuPastDuarearData" resultMap="RuPastDuarearResultMap">
SELECT FID, DAYS_PAST_DUE SELECT FID, ACC_SERIAL_NUM, REPORTED_DT, AMT_PAST_DUE, DAYS_PAST_DUE
FROM RU_PASTDUEARREAR FROM RU_PASTDUEARREAR
WHERE FID IN WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")"> <foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid} #{fid}
</foreach> </foreach>
AND REPORTED_DT >= #{reportedDate} with UR
WITH UR
</select> </select>
<select id="findFlag90ByPastDueArrearNewWithOrderBy"> <select id="findRuPaymtData" resultMap="RuPaymtResultMap">
SELECT FID, DAYS_PAST_DUE, REPORTED_DT
FROM RU_PASTDUEARREAR
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
ORDER BY FID DESC
WITH UR
</select>
<select id="findFlag90ByRuPaymtNew">
SELECT FID, DAYS_PAST_DUE
FROM RU_PAYMT
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
AND REPORTED_DT >= #{reportedDate}
WITH UR
</select>
<select id="findFlag90ByRuPaymtNewWithOrderBy">
SELECT FID, DAYS_PAST_DUE, REPORTED_DT SELECT FID, DAYS_PAST_DUE, REPORTED_DT
FROM RU_PAYMT FROM RU_PAYMT
WHERE FID IN WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")"> <foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid} #{fid}
</foreach> </foreach>
ORDER BY FID DESC AND REPORTED_DT >= #{reportedDate}
WITH UR with UR
</select> </select>
</mapper> </mapper>

View file

@ -42,17 +42,6 @@
</foreach> </foreach>
</update> </update>
<update id="updatePhoneFid" parameterType="ru.nbch.credit_tracker.entities.dto.PhoneFidMap">
UPDATE signals_phone_fid_map
SET
fid = #{subject.fid},
flag_debt5000 = #{subject.flagDebt5000},
flag_90_12 = #{subject.flag90days},
calculated_at = #{subject.calculatedAt},
error_text = #{subject.errorText}
WHERE id = #{subject.id}
</update>
<update id="updatePhoneFidMapsErrors" parameterType="java.util.List"> <update id="updatePhoneFidMapsErrors" parameterType="java.util.List">
<foreach collection="subjects" item="subject" separator=";"> <foreach collection="subjects" item="subject" separator=";">
UPDATE signals_phone_fid_map UPDATE signals_phone_fid_map

View file

@ -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<Integer> 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<Integer, FlagsService.FlagsData> 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<Integer> 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<Integer, FlagsService.FlagsData> 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<Integer> 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<Integer, FlagsService.FlagsData> 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);
}
}

View file

@ -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<Integer> 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<Integer, FlagsService.FlagsData> 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<Integer> 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<Integer, FlagsService.FlagsData> 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<Integer> 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<Integer, FlagsService.FlagsData> 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<FlagsService.RuPastDuarearData> 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<FlagsService.RuPaymtData> 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);
}
}

View file

@ -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> 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<Integer, PhoneFidMap> 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<PhoneService2.FidsData> 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<PhoneService2.FidsData> 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());
}