diff --git a/clearing-parent/registry-service/pom.xml b/clearing-parent/registry-service/pom.xml index 90d1d2dbb..47737e986 100644 --- a/clearing-parent/registry-service/pom.xml +++ b/clearing-parent/registry-service/pom.xml @@ -37,6 +37,10 @@ com.fasterxml.jackson.core jackson-databind + + org.springframework.integration + spring-integration-sftp + org.springframework.boot spring-boot-starter-test @@ -51,6 +55,13 @@ test-clearing test + + + + com.opencsv + opencsv + 5.5.2 + diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/RegistryServiceConfig.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/RegistryServiceConfig.java new file mode 100644 index 000000000..e3239957e --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/RegistryServiceConfig.java @@ -0,0 +1,22 @@ +package ru.spcex.clearing.registry.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.spcex.clearing.registry.config.settings.RegistryServiceSettings; + +import java.io.File; + +@Configuration +public class RegistryServiceConfig { + @Bean("reportOutDir") + public File outDir(RegistryServiceSettings settings) { + File outFolder = new File(settings.getReportsStore().getLocalTempDir()); + if (!outFolder.exists()) { + boolean mkDirOk = outFolder.mkdirs(); + if (!mkDirOk) throw new IllegalStateException("Can't create output directory for reports, registry-service was terminated"); + } else if (!outFolder.isDirectory()) { + throw new IllegalStateException("Output reports path from settings is not directory!"); + } + return outFolder; + } +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/SFTPReportsConfig.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/SFTPReportsConfig.java new file mode 100644 index 000000000..701d6bbcf --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/SFTPReportsConfig.java @@ -0,0 +1,100 @@ +package ru.spcex.clearing.registry.config; + +import com.jcraft.jsch.ChannelSftp; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.file.remote.session.CachingSessionFactory; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.sftp.gateway.SftpOutboundGateway; +import org.springframework.integration.sftp.outbound.SftpMessageHandler; +import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import org.springframework.integration.sftp.session.SftpFileInfo; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import ru.spcex.clearing.registry.config.settings.RegistryServiceSettings; + +import java.io.File; +import java.util.List; + +import static org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Command.LS; + +@Configuration +public class SFTPReportsConfig { + private final RegistryServiceSettings settings; + + public SFTPReportsConfig(RegistryServiceSettings settings) { + this.settings = settings; + } + + @Bean("reportsSftpSessionFactory") + public SessionFactory sftpSessionFactory() { + DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true); + factory.setHost(settings.getReportsStore().getServerIp()); + factory.setPort(settings.getReportsStore().getServerPort()); + factory.setUser(settings.getReportsStore().getUser()); + factory.setPassword(settings.getReportsStore().getPassword()); + factory.setAllowUnknownKeys(true); + return new CachingSessionFactory<>(factory); + } + + @Bean("reportsSftpHandler") + @ServiceActivator(inputChannel = "reportsToSftpChannel") + public MessageHandler handler(@Qualifier("reportsSftpSessionFactory") SessionFactory sessionFactory) { + SftpMessageHandler handler = new SftpMessageHandler(sessionFactory); + handler.setRemoteDirectoryExpression(new LiteralExpression(settings.getReportsStore().getOutDir())); + handler.setAutoCreateDirectory(true); + handler.setFileNameGenerator(message -> { + if (message.getPayload() instanceof File) { + return ((File) message.getPayload()).getName(); + } else { + throw new IllegalArgumentException("File must expected as payload."); + } + }); + return handler; + } + + @Bean("reportsListSftpChannel") + public MessageChannel listSftpChannel(@Qualifier("reportsSftpHandlerList") MessageHandler handler) { + DirectChannel dc = new DirectChannel(); + dc.subscribe(handler); + return dc; + } + + @Bean("reportsToSftpChannel") + public MessageChannel toSftpChannel(@Qualifier("reportsSftpHandler") MessageHandler handler) { + DirectChannel dc = new DirectChannel(); + dc.subscribe(handler); + return dc; + } + + @Bean("reportsSftpHandlerList") + @ServiceActivator(inputChannel = "reportsListSftpChannel") + public MessageHandler handlerList(@Qualifier("reportsSftpSessionFactory") SessionFactory sessionFactory) { + String expression = "'/%s'".formatted(settings.getReportsStore().getOutDir()); + return new SftpOutboundGateway(sessionFactory, LS.getCommand(), expression); + } + + @Bean("reportsSftpOutboundListFlow") + public IntegrationFlow sftpOutboundListFlow(@Qualifier("reportsSftpSessionFactory") SessionFactory sessionFactory) { + return IntegrationFlows.from("reportsListSftpChannel") + .handle(new SftpOutboundGateway(sessionFactory, "ls", "payload")) + .get(); + } + + @MessagingGateway(name = "reportsSftpGateway") + public interface ReportsGateway { + @Gateway(requestChannel = "reportsToSftpChannel") + void sendToSftp(File file); + + @Gateway(requestChannel = "reportsListSftpChannel") + List listFiles(String dir); + } +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/settings/RegistryServiceSettings.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/settings/RegistryServiceSettings.java index 797c0c13b..439d99cbb 100644 --- a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/settings/RegistryServiceSettings.java +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/settings/RegistryServiceSettings.java @@ -14,6 +14,8 @@ public class RegistryServiceSettings { private HazelcastClientParams hazelcast; private KafkaConsumerSettings kafkaConsumer; private KafkaProducerSettings kafkaProducer; + private ReportsStore reportsStore; + private boolean csvMode = false; public HazelcastClientParams getHazelcast() { return hazelcast; @@ -38,4 +40,20 @@ public class RegistryServiceSettings { public void setKafkaProducer(KafkaProducerSettings kafkaProducer) { this.kafkaProducer = kafkaProducer; } + + public ReportsStore getReportsStore() { + return reportsStore; + } + + public void setReportsStore(ReportsStore reportsStore) { + this.reportsStore = reportsStore; + } + + public boolean isCsvMode() { + return csvMode; + } + + public void setCsvMode(boolean csvMode) { + this.csvMode = csvMode; + } } diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/settings/ReportsStore.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/settings/ReportsStore.java new file mode 100644 index 000000000..4415f3abf --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/config/settings/ReportsStore.java @@ -0,0 +1,68 @@ +package ru.spcex.clearing.registry.config.settings; + +public class ReportsStore { + private String outDir; + private String localTempDir; + private String user; + private String password; + private String serverIp; + private int serverPort; + + public boolean isDeleteAfterSend() { + return deleteAfterSend; + } + + public void setDeleteAfterSend(boolean deleteAfterSend) { + this.deleteAfterSend = deleteAfterSend; + } + + private boolean deleteAfterSend; + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getServerIp() { + return serverIp; + } + + public void setServerIp(String serverIp) { + this.serverIp = serverIp; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public String getOutDir() { + return outDir; + } + + public void setOutDir(String outDir) { + this.outDir = outDir; + } + + public String getLocalTempDir() { + return localTempDir; + } + + public void setLocalTempDir(String localTempDir) { + this.localTempDir = localTempDir; + } +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/ReportUtils.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/ReportUtils.java new file mode 100644 index 000000000..4bf999f7c --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/ReportUtils.java @@ -0,0 +1,28 @@ +package ru.spcex.clearing.registry.reports; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.spcex.clearing.registry.config.SFTPReportsConfig; + +import java.io.File; +import java.util.Collection; + +public class ReportUtils { + private final static Logger log = LoggerFactory.getLogger(ReportUtils.class); + + public static void sendFilesToSftp(Collection files, + SFTPReportsConfig.ReportsGateway reportsGateway, + boolean deleteAfterSend) { + for (File file : files) { + reportsGateway.sendToSftp(file); + if (deleteAfterSend) { + boolean delete = file.delete(); + if (!delete) { + log.warn("Can't delete temp file {}", file.getAbsolutePath()); + } + } + } + } + + +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/CSVReport.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/CSVReport.java new file mode 100644 index 000000000..dea0634cc --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/CSVReport.java @@ -0,0 +1,4 @@ +package ru.spcex.clearing.registry.reports.bean; + +public interface CSVReport { +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/DepoBalanceRegisterReport.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/DepoBalanceRegisterReport.java new file mode 100644 index 000000000..76fa6e752 --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/DepoBalanceRegisterReport.java @@ -0,0 +1,109 @@ +package ru.spcex.clearing.registry.reports.bean; + +import com.opencsv.bean.CsvBindByName; +import com.opencsv.bean.CsvBindByPosition; +import com.opencsv.bean.CsvDate; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; + +public class DepoBalanceRegisterReport implements CSVReport { + @CsvBindByName(column = "Наименование Расчетного депозитария") + @CsvBindByPosition(position = 0) + private String houseName; + + @CsvBindByName(column = "Номер отчета") + @CsvBindByPosition(position = 1) + private String number; + + @CsvBindByName(column = "Дата") + @CsvBindByPosition(position = 2) + @CsvDate(value = "dd.MM.yyyy") + private LocalDate date; + + @CsvBindByName(column = "Время") + @CsvBindByPosition(position = 3) + @CsvDate(value = "HH:mm:ss") + private LocalTime time; + + @CsvBindByName(column = "Краткое наименование УК") + @CsvBindByPosition(position = 4) + private String company; + + @CsvBindByName(column = "Номер торгового раздела счета депо/ код раздела «к размещению/выкупу»") + @CsvBindByPosition(position = 5) + private String account; + + @CsvBindByName(column = "Код ЦБ") + @CsvBindByPosition(position = 6) + private String security; + + @CsvBindByName(column = "Количество ЦБ") + @CsvBindByPosition(position = 7) + private BigDecimal sum; + + public String getHouseName() { + return houseName; + } + + public void setHouseName(String houseName) { + this.houseName = houseName; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = time; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getSecurity() { + return security; + } + + public void setSecurity(String security) { + this.security = security; + } + + public BigDecimal getSum() { + return sum; + } + + public void setSum(BigDecimal sum) { + this.sum = sum; + } +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/MoneyBalanceRegisterReport.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/MoneyBalanceRegisterReport.java new file mode 100644 index 000000000..0be588bc2 --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/bean/MoneyBalanceRegisterReport.java @@ -0,0 +1,110 @@ +package ru.spcex.clearing.registry.reports.bean; + +import com.opencsv.bean.CsvBindByName; +import com.opencsv.bean.CsvBindByPosition; +import com.opencsv.bean.CsvDate; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; + +public class MoneyBalanceRegisterReport implements CSVReport { + @CsvBindByName(column = "Наименование Расчетного депозитария") + @CsvBindByPosition(position = 0) + private String houseName; + + @CsvBindByName(column = "Номер отчета") + @CsvBindByPosition(position = 1) + private String number; + + @CsvBindByName(column = "Дата") + @CsvBindByPosition(position = 2) + @CsvDate(value = "dd.MM.yyyy") + private LocalDate date; + + @CsvBindByName(column = "Время") + @CsvBindByPosition(position = 3) + @CsvDate(value = "HH:mm:ss") + private LocalTime time; + + @CsvBindByName(column = "Код валюты") + @CsvBindByPosition(position = 4) + private String currency; + + @CsvBindByName(column = "Краткое наименование УК") + @CsvBindByPosition(position = 5) + private String company; + + @CsvBindByName(column = "Номер торгового /клирингового счета/ счета внутреннего учета СПВБ") + @CsvBindByPosition(position = 6) + private String account; + + @CsvBindByName(column = "Остаток денежных средств") + @CsvBindByPosition(position = 7) + private BigDecimal sum; + + + public String getHouseName() { + return houseName; + } + + public void setHouseName(String houseName) { + this.houseName = houseName; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = time; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public BigDecimal getSum() { + return sum; + } + + public void setSum(BigDecimal sum) { + this.sum = sum; + } +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/CSVReportBuilder.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/CSVReportBuilder.java new file mode 100644 index 000000000..4ab63cfb7 --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/CSVReportBuilder.java @@ -0,0 +1,149 @@ +package ru.spcex.clearing.registry.reports.builders; + +import com.opencsv.CSVWriter; +import com.opencsv.ICSVWriter; +import com.opencsv.bean.*; +import com.opencsv.exceptions.CsvBadConverterException; +import com.opencsv.exceptions.CsvChainedException; +import com.opencsv.exceptions.CsvFieldAssignmentException; +import com.opencsv.exceptions.CsvRequiredFieldEmptyException; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.clearing.classes.objects.BusinessObject; +import ru.spcex.clearing.registry.reports.bean.CSVReport; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * Класс для построения отчетов в формате CSV + */ +public abstract class CSVReportBuilder { + protected final Logger log = LoggerFactory.getLogger(getClass()); + protected static DateTimeFormatter dateTimeFormatter_YYMMDDHHmmssSSS = DateTimeFormatter.ofPattern("yyMMddHHmmssSSS"); + + /** + * Имя отчета (для имени файла) + */ + protected abstract String reportName(); + + /** + * Преобразовать объект в строку отчета + */ + protected abstract R toRow(I input); + + /** + * Название отчета, используется для формирования имени отчета (без указания формата) + */ + protected String getReportFilename() { + return "%s_%s".formatted(reportName(), dateTimeFormatter_YYMMDDHHmmssSSS.format(LocalDateTime.now())); + } + + /** + * Формирование отчета на основе объектов + */ + public File buildReport(Collection inputObj, File outFolder) { + assert outFolder != null && outFolder.isDirectory() && inputObj != null; + List rows = new ArrayList<>(inputObj.size()); + for (I input : inputObj) rows.add(toRow(input)); + + File reportFile = new File(outFolder, normalizeFilenameForOS(getReportFilename() + ".csv")); + + try (Writer writer = new FileWriter(reportFile); + ICSVWriter icsvWriter = new CustomCSVWriter(writer)) { + CustomStrategy customStrategy = new CustomStrategy<>(); + customStrategy.setType(getReportClass()); + StatefulBeanToCsv sbc = new StatefulBeanToCsvBuilder(icsvWriter) + .withMappingStrategy(customStrategy) + .withLineEnd("\n") + .withSeparator(',') + .withQuotechar('\"') + .build(); + if (!rows.isEmpty()) sbc.write(rows); + else { + // Для записи заголовков в пустой файл + Constructor constructor = Objects.requireNonNull(getReportClass()).getDeclaredConstructor(); + R obj = constructor.newInstance(); + String[] headers = customStrategy.generateHeader(obj); + writer.write("\"" + String.join("\",\"", headers) + "\""); + } + + } catch (Exception e) { + log.error("Can't create report", e); + return null; + } + + return reportFile; + } + + protected abstract Class getReportClass(); + + protected String normalizeFilenameForOS(String filename) { + if (System.getProperty("os.name").toLowerCase().contains("windows")) + return filename.replace(":", "_"); + return filename; + } + + static class CustomCSVWriter extends CSVWriter { + public CustomCSVWriter(Writer writer) { + super(writer); + } + + @Override + protected void writeNext(String[] nextLine, boolean applyQuotesToAll, Appendable appendable) throws IOException { + if (nextLine != null) { + for (int i = 0; i < nextLine.length; i++) { + if (StringUtils.isEmpty(nextLine[i])) { + nextLine[i] = null; + } + } + } + super.writeNext(nextLine, applyQuotesToAll, appendable); + } + } + + static class CustomStrategy extends ColumnPositionMappingStrategy { + public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException { + super.generateHeader(bean); + String[] headers = new String[this.getFieldMap().values().size()]; + for (BeanField field : this.getFieldMap().values()) { + CsvBindByPosition positionAnnotation = field.getField().getAnnotation(CsvBindByPosition.class); + CsvBindByName nameAnnotation = field.getField().getAnnotation(CsvBindByName.class); + headers[positionAnnotation.position()] = nameAnnotation.column(); + } + return headers; + } + + @Override + public String[] transmuteBean(T bean) throws CsvFieldAssignmentException, CsvChainedException { + return super.transmuteBean(bean); + } + + @Override + protected CsvConverter determineConverter(Field field, Class elementType, String locale, String writeLocale, Class customConverter) throws CsvBadConverterException { + CsvConverter csvConverter = super.determineConverter(field, elementType, locale, writeLocale, customConverter); + if (csvConverter instanceof ConverterNumber && field.getType() == BigDecimal.class) { + CsvNumber csvNumberAnnotation = field.getAnnotation(CsvNumber.class); + String formatValue = csvNumberAnnotation.value(); + String writeFormat = csvNumberAnnotation.writeFormat(); + csvConverter = new ConverterNumber(BigDecimal.class, + Locale.getDefault().toString(), + Locale.getDefault().toString(), + Locale.getDefault(), + formatValue, + writeFormat); + } + return csvConverter; + } + + } +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/DepoBalanceRegisterReportBuilder.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/DepoBalanceRegisterReportBuilder.java new file mode 100644 index 000000000..5ba136057 --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/DepoBalanceRegisterReportBuilder.java @@ -0,0 +1,60 @@ +package ru.spcex.clearing.registry.reports.builders; + +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.registry.reports.bean.DepoBalanceRegisterReport; +import ru.spcex.platform.enumeration.AccountType; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.utils.enumeration.IEnumKey; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class DepoBalanceRegisterReportBuilder extends CSVReportBuilder { + private final static DateTimeFormatter yyyyMMddFormat = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private final Imdg companyImdg; + + public DepoBalanceRegisterReportBuilder(ImdgProvider imdgProvider) { + companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + } + + @Override + protected String reportName() { + return "depo_balance_register"; + } + + @Override + protected Class getReportClass() { + return DepoBalanceRegisterReport.class; + } + + @Override + protected DepoBalanceRegisterReport toRow(Registry input) { + LocalDate now = LocalDate.now(); + LocalTime nowTime = LocalTime.now(); + DepoBalanceRegisterReport entity = new DepoBalanceRegisterReport(); + + Company company = companyImdg.getSingleObjectByID(2L); + if (company != null) entity.setHouseName(company.getShortName()); + + entity.setNumber(yyyyMMddFormat.format(now)); + entity.setDate(now); + entity.setTime(nowTime); + + company = companyImdg.getSingleObjectByID(input.getCompanyId()); + if (company != null) entity.setCompany(company.getShortName()); + + AccountType accountType = IEnumKey.getEnumByKey(AccountType.class, input.getAccountType()); + if (accountType == AccountType.Clrn || accountType == AccountType.Info) entity.setAccount(input.getAccount()); + + entity.setSecurity(input.getSecuritySymbol()); + entity.setSum(input.getBalance()); + + return entity; + } + +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/MoneyBalanceRegisterReportBuilder.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/MoneyBalanceRegisterReportBuilder.java new file mode 100644 index 000000000..5c9d8e5f6 --- /dev/null +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/reports/builders/MoneyBalanceRegisterReportBuilder.java @@ -0,0 +1,60 @@ +package ru.spcex.clearing.registry.reports.builders; + +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.registry.reports.bean.MoneyBalanceRegisterReport; +import ru.spcex.platform.enumeration.AccountType; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.utils.enumeration.IEnumKey; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class MoneyBalanceRegisterReportBuilder extends CSVReportBuilder { + private final static DateTimeFormatter yyyyMMddFormat = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private final Imdg companyImdg; + + public MoneyBalanceRegisterReportBuilder(ImdgProvider imdgProvider) { + companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + } + + @Override + protected String reportName() { + return "money_balance_register"; + } + + @Override + protected Class getReportClass() { + return MoneyBalanceRegisterReport.class; + } + + @Override + protected MoneyBalanceRegisterReport toRow(Registry input) { + LocalDate now = LocalDate.now(); + LocalTime nowTime = LocalTime.now(); + MoneyBalanceRegisterReport entity = new MoneyBalanceRegisterReport(); + + Company company = companyImdg.getSingleObjectByID(2L); + if (company != null) entity.setHouseName(company.getShortName()); + + entity.setNumber(yyyyMMddFormat.format(now)); + entity.setDate(now); + entity.setTime(nowTime); + entity.setCurrency("RUR"); + + company = companyImdg.getSingleObjectByID(input.getCompanyId()); + if (company != null) entity.setCompany(company.getShortName()); + + AccountType accountType = IEnumKey.getEnumByKey(AccountType.class, input.getAccountType()); + if (accountType == AccountType.Clrn || accountType == AccountType.Info) entity.setAccount(input.getAccount()); + + entity.setSum(input.getBalance()); + + return entity; + } + +} diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterService.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterService.java index 9872262a4..b72d1a487 100644 --- a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterService.java +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterService.java @@ -6,6 +6,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.register.DepoBalanceRegister; import ru.clearing.classes.statics.data.registry.Registry; @@ -14,6 +15,10 @@ import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.domain.cud.schedule.LauncherCommandRequest; import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.clearing.registry.config.SFTPReportsConfig; +import ru.spcex.clearing.registry.config.settings.RegistryServiceSettings; +import ru.spcex.clearing.registry.reports.ReportUtils; +import ru.spcex.clearing.registry.reports.builders.DepoBalanceRegisterReportBuilder; import ru.spcex.clearing.registry.util.PreClearMap; import ru.spcex.platform.enumeration.AccountType; import ru.spcex.platform.enumeration.RegistryTradingParams; @@ -24,10 +29,13 @@ import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder; import ru.spcex.platform.imdg.api.predicate.specific.RegistryCodeSqlBuilder; import ru.spcex.platform.utils.enumeration.IEnumKey; +import java.io.File; import java.math.BigDecimal; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import static ru.spcex.platform.enumeration.Task.createRegistry_GRRT; @@ -39,14 +47,29 @@ public class DepoBalanceRegisterService extends QueueConsumer implements Initial private final Imdg registryHistoryMap; private final PreClearMap preClearMap; + private final DepoBalanceRegisterReportBuilder reportBuilder; + private final File outFolder; + private final RegistryServiceSettings settings; + + private final SFTPReportsConfig.ReportsGateway reportsSftpGateway; + private final boolean deleteAfterSend; + @Autowired public DepoBalanceRegisterService(Consumer kafkaQueue, Producer kafkaProducer, - ImdgProvider imdgProvider) { + ImdgProvider imdgProvider, + RegistryServiceSettings settings, + @Qualifier("reportOutDir") File outFolder, + SFTPReportsConfig.ReportsGateway reportsSftpGateway) { super(kafkaQueue, kafkaProducer); this.depoBalanceRegisterMap = imdgProvider.getImdg(IMDGDistributedNames.Map_DepoBalanceRegister, DepoBalanceRegister.class); this.registryHistoryMap = imdgProvider.getImdg(IMDGDistributedNames.Map_RegistryHistory, RegistryHistory.class); preClearMap = PreClearMap.instanceForInstantField(depoBalanceRegisterMap, "created"); + reportBuilder = new DepoBalanceRegisterReportBuilder(imdgProvider); + this.outFolder = outFolder; + this.settings = settings; + this.reportsSftpGateway = reportsSftpGateway; + this.deleteAfterSend = settings.getReportsStore().isDeleteAfterSend(); } @Override @@ -58,6 +81,7 @@ public class DepoBalanceRegisterService extends QueueConsumer implements Initial } public void depoBalanceRegisterNew(BaseRequest userRequest) { + boolean csvMode = settings.isCsvMode(); log.debug("LauncherCommandRequest received from {}", createRegistry_GRRT.topic()); ImdgPredicate predicate = getPredicateForRegistries(userRequest.getRequestPayload().getCompanyId()); Collection registryHistories = registryHistoryMap.getCollectionObjectsByPredicate(predicate); @@ -67,10 +91,16 @@ public class DepoBalanceRegisterService extends QueueConsumer implements Initial return; } log.debug("Select {} Registry history by query {}", registryHistories.size(), predicate); - preClearMap.preClearMap(); + if (!csvMode) preClearMap.preClearMap(); + List registryForReport = new ArrayList<>(); for (RegistryHistory registryHistory : registryHistories) { if (BigDecimal.ZERO.compareTo(registryHistory.getObject().getDiffBalance()) != 0) continue; - insertDepoBalanceRegister(registryHistory); + if (!csvMode) insertDepoBalanceRegister(registryHistory); + else registryForReport.add(registryHistory.getObject()); + } + if (csvMode) { + File reportFile = reportBuilder.buildReport(registryForReport, outFolder); + ReportUtils.sendFilesToSftp(List.of(reportFile), reportsSftpGateway, deleteAfterSend); } log.debug("Successfully processed"); } diff --git a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterService.java b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterService.java index adf2bc9e9..134f91666 100644 --- a/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterService.java +++ b/clearing-parent/registry-service/src/main/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterService.java @@ -6,6 +6,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.register.MoneyBalanceRegister; @@ -15,6 +16,10 @@ import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.domain.cud.schedule.LauncherCommandRequest; import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.clearing.registry.config.SFTPReportsConfig; +import ru.spcex.clearing.registry.config.settings.RegistryServiceSettings; +import ru.spcex.clearing.registry.reports.ReportUtils; +import ru.spcex.clearing.registry.reports.builders.MoneyBalanceRegisterReportBuilder; import ru.spcex.clearing.registry.util.PreClearMap; import ru.spcex.platform.enumeration.AccountType; import ru.spcex.platform.enumeration.RegistryTradingParams; @@ -25,10 +30,13 @@ import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder; import ru.spcex.platform.imdg.api.predicate.specific.RegistryCodeSqlBuilder; import ru.spcex.platform.utils.enumeration.IEnumKey; +import java.io.File; import java.math.BigDecimal; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import static ru.spcex.platform.enumeration.Task.createRegistry_GBRR; @@ -42,15 +50,31 @@ public class MoneyBalanceRegisterService extends QueueConsumer implements Initia private final Imdg companyMap; private final PreClearMap preClearMap; + private final MoneyBalanceRegisterReportBuilder reportBuilder; + private final File outFolder; + RegistryServiceSettings settings; + + private final SFTPReportsConfig.ReportsGateway reportsSftpGateway; + private final boolean deleteAfterSend; + + @Autowired public MoneyBalanceRegisterService(Consumer kafkaQueue, Producer kafkaProducer, - ImdgProvider imdgProvider) { + ImdgProvider imdgProvider, + RegistryServiceSettings settings, + @Qualifier("reportOutDir") File outFolder, + SFTPReportsConfig.ReportsGateway reportsSftpGateway) { super(kafkaQueue, kafkaProducer); this.moneyBalanceRegisterMap = imdgProvider.getImdg(IMDGDistributedNames.Map_MoneyBalanceRegister, MoneyBalanceRegister.class); this.registryHistoryMap = imdgProvider.getImdg(IMDGDistributedNames.Map_RegistryHistory, RegistryHistory.class); this.companyMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); preClearMap = PreClearMap.instanceForInstantField(moneyBalanceRegisterMap, "created"); + reportBuilder = new MoneyBalanceRegisterReportBuilder(imdgProvider); + this.outFolder = outFolder; + this.settings = settings; + this.reportsSftpGateway = reportsSftpGateway; + this.deleteAfterSend = settings.getReportsStore().isDeleteAfterSend(); } @Override @@ -62,6 +86,7 @@ public class MoneyBalanceRegisterService extends QueueConsumer implements Initia } public void moneyBalanceRegisterNew(BaseRequest userRequest) { + boolean csvMode = settings.isCsvMode(); log.debug("LauncherCommandRequest received from {}", createRegistry_GBRR.topic()); ImdgPredicate predicate = getSqlForRegistryHistory(userRequest.getRequestPayload().getCompanyId()); Collection registryHistories = registryHistoryMap.getCollectionObjectsByPredicate(predicate); @@ -70,10 +95,16 @@ public class MoneyBalanceRegisterService extends QueueConsumer implements Initia return; } log.debug("Select {} Registry history by query {}", registryHistories.size(), predicate); - preClearMap.preClearMap(); + if (!csvMode) preClearMap.preClearMap(); + List registryForReport = new ArrayList<>(); for (RegistryHistory registryHistory : registryHistories) { if (BigDecimal.ZERO.compareTo(registryHistory.getObject().getDiffBalance()) != 0) continue; - insertMoneyBalanceRegister(registryHistory); + if (!csvMode) insertMoneyBalanceRegister(registryHistory); + else registryForReport.add(registryHistory.getObject()); + } + if (csvMode) { + File reportFile = reportBuilder.buildReport(registryForReport, outFolder); + ReportUtils.sendFilesToSftp(List.of(reportFile), reportsSftpGateway, deleteAfterSend); } log.debug("successfully processed"); } diff --git a/clearing-parent/registry-service/src/main/resources/application.properties b/clearing-parent/registry-service/src/main/resources/application.properties index 49b0bb9c0..78d638e78 100644 --- a/clearing-parent/registry-service/src/main/resources/application.properties +++ b/clearing-parent/registry-service/src/main/resources/application.properties @@ -17,4 +17,13 @@ registry-service.kafka-producer.acks=all registry-service.kafka-producer.retries=0 registry-service.kafka-producer.batch-size=16384 registry-service.kafka-producer.linger-ms=1 -registry-service.kafka-producer.buffer-memory=33554432 \ No newline at end of file +registry-service.kafka-producer.buffer-memory=33554432 + +registry-service.csv-mode=true +registry-service.reports-store.local-temp-dir=./reports_out_temp +registry-service.reports-store.out-dir=reports +registry-service.reports-store.user=tester +registry-service.reports-store.password=password +registry-service.reports-store.server-ip=10.230.238.53 +registry-service.reports-store.server-port=2222 +registry-service.reports-store.delete-after-send=true \ No newline at end of file diff --git a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/KafkaConfigTest.java b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/KafkaConfigTest.java index 0ca299c99..5aaa2faa2 100644 --- a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/KafkaConfigTest.java +++ b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/KafkaConfigTest.java @@ -21,6 +21,8 @@ import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgId; import ru.spcex.platform.imdg.api.ImdgProvider; +import java.io.File; + import static org.mockito.Mockito.mock; import static ru.spcex.clearing.registry.utils.TestUtils.setFuture; import static ru.spcex.clearing.test.config.KafkaTestConfig.producerCaptors; @@ -58,4 +60,9 @@ public class KafkaConfigTest { return mockProducer; } + @Bean("outDir") + public File outDir() { + return new File("out"); + } + } diff --git a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/SFTPTestConfig.java b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/SFTPTestConfig.java new file mode 100644 index 000000000..3eb5e0288 --- /dev/null +++ b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/SFTPTestConfig.java @@ -0,0 +1,31 @@ +package ru.spcex.clearing.registry.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.sftp.session.SftpFileInfo; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +@Configuration +public class SFTPTestConfig { + @Bean + public SFTPReportsConfig.ReportsGateway reportsGateway(){ + return new ReportsGateway(); + } + + public static class ReportsGateway implements SFTPReportsConfig.ReportsGateway { + + @Override + public void sendToSftp(File file) { + + } + + @Override + public List listFiles(String dir) { + return new ArrayList<>(); + } + } + +} diff --git a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/TestConfig.java b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/TestConfig.java new file mode 100644 index 000000000..4d61b0ad4 --- /dev/null +++ b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/config/TestConfig.java @@ -0,0 +1,29 @@ +package ru.spcex.clearing.registry.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.spcex.clearing.registry.config.settings.RegistryServiceSettings; +import ru.spcex.clearing.registry.config.settings.ReportsStore; + +import java.io.File; + +@Configuration +public class TestConfig { + @Bean + public RegistryServiceSettings testSettings() { + RegistryServiceSettings registryServiceSettings = new RegistryServiceSettings(); + ReportsStore store = new ReportsStore(); + store.setLocalTempDir("out"); + registryServiceSettings.setReportsStore(store); + registryServiceSettings.setCsvMode(true); + return registryServiceSettings; + } + + @Bean("reportOutDir") + public File reportOutDir() { + File outDir = new File("out"); + outDir.mkdirs(); + return outDir; + } + +} diff --git a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterServiceTest.java b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterServiceTest.java index ec13531f1..e4766d3e0 100644 --- a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterServiceTest.java +++ b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/DepoBalanceRegisterServiceTest.java @@ -1,5 +1,6 @@ package ru.spcex.clearing.registry.service; +import com.opencsv.exceptions.CsvValidationException; import org.apache.kafka.clients.consumer.MockConsumer; import org.apache.kafka.clients.producer.Producer; import org.junit.jupiter.api.MethodOrderer; @@ -7,6 +8,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; @@ -16,7 +18,10 @@ import ru.clearing.classes.statics.data.registry.Registry; import ru.clearing.classes.statics.data.registry.RegistryHistory; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.cud.schedule.LauncherCommandRequest; +import ru.spcex.clearing.registry.config.SFTPTestConfig; +import ru.spcex.clearing.registry.config.TestConfig; import ru.spcex.clearing.registry.config.ValidationConfig; +import ru.spcex.clearing.registry.config.settings.RegistryServiceSettings; import ru.spcex.clearing.test.TestUtils; import ru.spcex.clearing.test.config.ImdgTestConfig; import ru.spcex.clearing.test.config.KafkaTestConfig; @@ -24,9 +29,11 @@ import ru.spcex.platform.enumeration.*; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; -import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; import java.math.BigDecimal; import java.time.Instant; +import java.util.Arrays; import java.util.Collection; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -38,9 +45,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; DepoBalanceRegisterService.class, ImdgTestConfig.class, KafkaTestConfig.class, + TestConfig.class, + SFTPTestConfig.class, }) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class DepoBalanceRegisterServiceTest { +class DepoBalanceRegisterServiceTest implements InitializingBean { private static int requestCount = 0; private static final Long companyId = 1L; @@ -63,15 +72,25 @@ class DepoBalanceRegisterServiceTest { @Qualifier("mockProducer") private Producer producer; - @PostConstruct - private void init() { + @Autowired + private RegistryServiceSettings settings; + + @Autowired + @Qualifier("reportOutDir") + private File outFolder; + + + @Override + public void afterPropertiesSet() throws Exception { ImdgTestConfig.waitAvailableImdgProviderAndAddAdminWithDefaultId(); //initializing of imdg registryHistoryMap = imdgProvider.getImdg(IMDGDistributedNames.Map_RegistryHistory, RegistryHistory.class); depoBalanceRegisterMap = imdgProvider.getImdg(IMDGDistributedNames.Map_DepoBalanceRegister, DepoBalanceRegister.class); // initialing objects for imdg repos + } + private RegistryHistory init() { Registry registry = new Registry(); registry.setRegistryDesignation(RegistryDesignation.A.getKey()); registry.setRegistryInstrumentType(RegistryInstrumentType.S.getKey()); @@ -87,11 +106,15 @@ class DepoBalanceRegisterServiceTest { registryHistory.setObject(registry); registryHistory.setEventTime(Instant.now()); registryHistoryMap.insert(registryHistory); + return registryHistory; } + @Test @Order(1) void executionRegisterNewForEventTime() { - + RegistryHistory source = init(); + boolean oldCsvMode = settings.isCsvMode(); + settings.setCsvMode(false); //making launcherCommand LauncherCommandRequest launcherCommandRequest = new LauncherCommandRequest(); @@ -112,5 +135,37 @@ class DepoBalanceRegisterServiceTest { assertEquals(account, depoBalanceRegister.getDepoCode()); assertEquals(balance, depoBalanceRegister.getQuantity()); assertEquals(SECURITY_SYMBOL, depoBalanceRegister.getSecuritySymbol()); + settings.setCsvMode(oldCsvMode); + registryHistoryMap.delete(source); } + + @Test + @Order(2) + void executionRegisterNewForEventTime_Csv() throws CsvValidationException, IOException { + RegistryHistory source = init(); + ReportTestUtils.resetDir(outFolder); + boolean oldCsvMode = settings.isCsvMode(); + settings.setCsvMode(true); + //making launcherCommand + LauncherCommandRequest launcherCommandRequest = new LauncherCommandRequest(); + + String jsonString = TestUtils.getJsonStringForSystem(launcherCommandRequest, 0L); + + TestUtils.addRecordToKafka((MockConsumer) depoBalanceRegisterService.getConsumer(), + Task.createRegistry_GRRT.topic(), + requestCount++, + 0, + jsonString); + + TestUtils.waitingSendAndCheckRecord(0L, producer); + + File outFile = outFolder.listFiles()[0]; + File expectedFile = new File(getClass().getClassLoader().getResource("expected_reports/depo_balance_register_expected.csv").getFile()); + ReportTestUtils.compareCSVFiles(expectedFile, outFile, Arrays.asList("Дата", "Время", "Номер отчета")); + + settings.setCsvMode(oldCsvMode); + ReportTestUtils.deleteDir(outFolder); + registryHistoryMap.delete(source); + } + } \ No newline at end of file diff --git a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterServiceTest.java b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterServiceTest.java index c4482a65c..285a14b08 100644 --- a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterServiceTest.java +++ b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/MoneyBalanceRegisterServiceTest.java @@ -1,5 +1,6 @@ package ru.spcex.clearing.registry.service; +import com.opencsv.exceptions.CsvValidationException; import org.apache.kafka.clients.consumer.MockConsumer; import org.apache.kafka.clients.producer.Producer; import org.junit.jupiter.api.MethodOrderer; @@ -7,6 +8,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; @@ -17,7 +19,10 @@ import ru.clearing.classes.statics.data.registry.Registry; import ru.clearing.classes.statics.data.registry.RegistryHistory; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.cud.schedule.LauncherCommandRequest; +import ru.spcex.clearing.registry.config.SFTPTestConfig; +import ru.spcex.clearing.registry.config.TestConfig; import ru.spcex.clearing.registry.config.ValidationConfig; +import ru.spcex.clearing.registry.config.settings.RegistryServiceSettings; import ru.spcex.clearing.test.TestUtils; import ru.spcex.clearing.test.config.ImdgTestConfig; import ru.spcex.clearing.test.config.KafkaTestConfig; @@ -25,10 +30,12 @@ import ru.spcex.platform.enumeration.*; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; -import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDate; +import java.util.Arrays; import java.util.Collection; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -39,9 +46,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; MoneyBalanceRegisterService.class, ImdgTestConfig.class, KafkaTestConfig.class, + TestConfig.class, + SFTPTestConfig.class, }) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class MoneyBalanceRegisterServiceTest { +class MoneyBalanceRegisterServiceTest implements InitializingBean { private static final String REGISTRY_DESIGNATION = RegistryDesignation.A.getKey(); private static final String REGISTRY_INSTRUMENT_TYPE = RegistryInstrumentType.M.getKey(); private static final String REGISTRY_FULL_NAME = "MY FULLNAME"; @@ -72,18 +81,28 @@ class MoneyBalanceRegisterServiceTest { @Qualifier("mockProducer") private Producer producer; - @PostConstruct - private void init() { + @Autowired + private RegistryServiceSettings settings; + + @Autowired + @Qualifier("reportOutDir") + private File outFolder; + + @Override + public void afterPropertiesSet() throws Exception { ImdgTestConfig.waitAvailableImdgProviderAndAddAdminWithDefaultId(); moneyBalanceRegisterMap = imdgProvider.getImdg(IMDGDistributedNames.Map_MoneyBalanceRegister, MoneyBalanceRegister.class); companyMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); registryHistoryMap = imdgProvider.getImdg(IMDGDistributedNames.Map_RegistryHistory, RegistryHistory.class); + + Company company = new Company(); + company.setId(2L); + company.setShortName(COMPANY_SHORTNAME); + companyMap.insert(company); } - @Test - @Order(1) - void executionRegisterNewForClearingDate() { + private RegistryHistory init() { Registry registry = new Registry(); registry.setTradingClearingRegistry("not null value"); registry.setDiffBalance(BigDecimal.ZERO); @@ -105,10 +124,15 @@ class MoneyBalanceRegisterServiceTest { registryHistory.setEventTime(Instant.now()); registryHistoryMap.insert(registryHistory); - Company company = new Company(); - company.setId(2L); - company.setShortName(COMPANY_SHORTNAME); - companyMap.insert(company); + return registryHistory; + } + + @Test + @Order(1) + void executionRegisterNewForClearingDate() { + RegistryHistory source = init(); + boolean oldCsvMode = settings.isCsvMode(); + settings.setCsvMode(false); //making launcherCommand LauncherCommandRequest launcherCommandRequest = new LauncherCommandRequest(); @@ -130,5 +154,38 @@ class MoneyBalanceRegisterServiceTest { assertEquals(REGISTRY_BALANCE, moneyBalanceRegister.getRemainderSum()); assertEquals(REGISTRY_ACCOUNT, moneyBalanceRegister.getAccount()); assertEquals(COMPANY_SHORTNAME, moneyBalanceRegister.getSetHouseName()); + + settings.setCsvMode(oldCsvMode); + registryHistoryMap.delete(source); + } + + + @Test + @Order(2) + void executionRegisterNewForClearingDate_CSV() throws CsvValidationException, IOException { + RegistryHistory source = init(); + boolean oldCsvMode = settings.isCsvMode(); + settings.setCsvMode(true); + + //making launcherCommand + LauncherCommandRequest launcherCommandRequest = new LauncherCommandRequest(); + launcherCommandRequest.setCompanyId(REGISTRY_COMPANY_ID); + String jsonString = TestUtils.getJsonStringForSystem(launcherCommandRequest, 0L); + + TestUtils.addRecordToKafka((MockConsumer) moneyBalanceRegisterService.getConsumer(), + Task.createRegistry_GBRR.topic(), + requestCount++, + 0, + jsonString); + + TestUtils.waitingSendAndCheckRecord(0L, producer); + + File outFile = outFolder.listFiles()[0]; + File expectedFile = new File(getClass().getClassLoader().getResource("expected_reports/money_balance_register_expected.csv").getFile()); + ReportTestUtils.compareCSVFiles(expectedFile, outFile, Arrays.asList("Дата", "Время", "Номер отчета")); + + settings.setCsvMode(oldCsvMode); + ReportTestUtils.deleteDir(outFolder); + registryHistoryMap.delete(source); } } \ No newline at end of file diff --git a/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/ReportTestUtils.java b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/ReportTestUtils.java new file mode 100644 index 000000000..a0b0c28d6 --- /dev/null +++ b/clearing-parent/registry-service/src/test/java/ru/spcex/clearing/registry/service/ReportTestUtils.java @@ -0,0 +1,106 @@ +package ru.spcex.clearing.registry.service; + +import com.opencsv.CSVParser; +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import com.opencsv.exceptions.CsvValidationException; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Assertions; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ReportTestUtils { + + public static void compareCSVFiles(File expectedFile, File actualFile, List columnsForSkip) throws IOException, CsvValidationException { + if (columnsForSkip == null) columnsForSkip = Collections.emptyList(); + CSVParser csvParser = new CSVParserBuilder().withSeparator(',').withQuoteChar('\"').build(); + CSVReader expectedCSVReader = new CSVReaderBuilder(new FileReader(expectedFile)).withCSVParser(csvParser).build(); + CSVReader actualCSVReader = new CSVReaderBuilder(new FileReader(actualFile)).withCSVParser(csvParser).build(); + + Map expectedValueForColumn = new HashMap<>(); + Map actualValueForColumn = new HashMap<>(); + + String[] expectedHeaders = expectedCSVReader.readNext(); + String[] actualHeaders = actualCSVReader.readNext(); + assertEquals(expectedHeaders.length, actualHeaders.length); + + for (int headerIdx = 0; headerIdx < expectedHeaders.length; headerIdx++) { + String expectedHeader = expectedHeaders[headerIdx]; + String actualHeader = actualHeaders[headerIdx]; + if (!columnsForSkip.contains(expectedHeader)) expectedValueForColumn.put(expectedHeader, headerIdx); + if (!columnsForSkip.contains(actualHeader)) actualValueForColumn.put(actualHeader, headerIdx); + } + + List expectedLines = new ArrayList<>(); + do { + String[] expectedLine = expectedCSVReader.readNext(); + if (expectedLine == null) break; + expectedLines.add(expectedLine); + } while (true); + + List actualLines = new ArrayList<>(); + do { + String[] actualLine = actualCSVReader.readNext(); + if (actualLine == null) break; + actualLines.add(actualLine); + } while (true); + if (expectedLines.size() != actualLines.size()) { + Assertions.fail("expected and actual lines not equal"); + } + + for (String[] actualLine : actualLines) { + boolean findEqual = false; + for (String[] expectedLine : expectedLines) { + boolean lineEqual = true; + for (String expectedHeader : expectedValueForColumn.keySet()) { + int expectedIdx = expectedValueForColumn.get(expectedHeader); + int actualIdx = actualValueForColumn.get(expectedHeader); + String expected = expectedLine[expectedIdx]; + String actual = actualLine[actualIdx]; + lineEqual = expected.equals(actual); + if (!lineEqual) break; + } + if (lineEqual) { + findEqual = true; + break; + } + } + if (findEqual) continue; + Assertions.fail("For actual line '%s' not found expected line".formatted(String.join(", ", actualLine))); + } + + List errors = new ArrayList<>(); + for (int idx = 0; idx < expectedHeaders.length; idx++) { + if (!Objects.equals(expectedHeaders[idx], actualHeaders[idx])) { + errors.add("expected header (%s) for idx = %d not equal actual header (%s)".formatted( + expectedHeaders[idx], + idx, + actualHeaders[idx]) + ); + } + } + if (!errors.isEmpty()) { + Assertions.fail("Files equal, but header sequence broken, cause: %s".formatted(String.join(",", errors))); + } + + expectedCSVReader.close(); + actualCSVReader.close(); + } + + public static void resetDir(File folder) throws IOException { + FileUtils.deleteDirectory(folder); + folder = new File("out"); + folder.mkdirs(); + } + + public static void deleteDir(File folder) throws IOException { + FileUtils.deleteDirectory(folder); + } + +} diff --git a/clearing-parent/registry-service/src/test/resources/expected_reports/depo_balance_register_expected.csv b/clearing-parent/registry-service/src/test/resources/expected_reports/depo_balance_register_expected.csv new file mode 100644 index 000000000..e5a4204ac --- /dev/null +++ b/clearing-parent/registry-service/src/test/resources/expected_reports/depo_balance_register_expected.csv @@ -0,0 +1,2 @@ +"Наименование Расчетного депозитария","Номер отчета","Дата","Время","Краткое наименование УК","Номер торгового раздела счета депо/ код раздела «к размещению/выкупу»","Код ЦБ","Количество ЦБ" +,"20231228","28.12.2023","18:54:21",,,"SECURITY_SYMBOL","1" diff --git a/clearing-parent/registry-service/src/test/resources/expected_reports/money_balance_register_expected.csv b/clearing-parent/registry-service/src/test/resources/expected_reports/money_balance_register_expected.csv new file mode 100644 index 000000000..eb4bf85f1 --- /dev/null +++ b/clearing-parent/registry-service/src/test/resources/expected_reports/money_balance_register_expected.csv @@ -0,0 +1,2 @@ +"Наименование Расчетного депозитария","Номер отчета","Дата","Время","Код валюты","Краткое наименование УК","Номер торгового /клирингового счета/ счета внутреннего учета СПВБ","Остаток денежных средств" +"ANY VALUE","20231229","29.12.2023","10:34:29","RUR",,"CLRN VALUE","123411"