RRPC, REX notification

This commit is contained in:
akulikov 2022-12-29 14:00:35 +03:00
parent 20b05c763d
commit f069bab18a
9 changed files with 338 additions and 25 deletions

View file

@ -0,0 +1,43 @@
package ru.spcex.clearing.reports.notifications;
import ru.spcex.clearing.reports.exceptions.ConfigException;
import ru.spcex.clearing.reports.services.DOCXService;
import java.io.File;
import java.net.URL;
import java.time.format.DateTimeFormatter;
public abstract class NotificationBuilder {
protected final DateTimeFormatter filenameDateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy'T'HH:mm:ss");
protected final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
protected final DOCXService docxService;
protected NotificationBuilder(String keyPattern) {
this.docxService = new DOCXService();
if (!docxService.init(getTemplateFile(), keyPattern))
throw new ConfigException("Can't create NotificationBuilder: docxService not initialized");
}
abstract public File buildNotification(Long consumerId);
protected File getTemplateFile() {
File templateFile;
String templateResourceName = getTemplateResourceName();
try {
URL resource = getClass().getClassLoader().getResource(templateResourceName);
if (resource == null) throw new Exception();
templateFile = new File(resource.toURI());
} catch (Exception e) {
throw new ConfigException("Can't read template resource " + templateResourceName, e);
}
return templateFile;
}
protected String normalizeFilenameForOS(String filename) {
if (System.getProperty("os.name").toLowerCase().contains("windows"))
return filename.replace(":", "_");
return filename;
}
abstract protected String getTemplateResourceName();
}

View file

@ -0,0 +1,106 @@
package ru.spcex.clearing.reports.notifications;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.profile.ProfileDocument;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgProvider;
import java.io.File;
import java.time.LocalDate;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Component
public class REXNotificationBuilder extends NotificationBuilder {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Imdg<ProfileDocument> profileDocumentImdg;
private final Imdg<Company> companyImdg;
public REXNotificationBuilder(ImdgProvider imdgProvider) {
super(null);
profileDocumentImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ProfileDocument, ProfileDocument.class);
companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class);
}
@Override
public File buildNotification(Long consumerId) {
String sql = "companyId = %d AND documentType = 'XCNT'".formatted(consumerId);
Collection<ProfileDocument> profileDocuments = profileDocumentImdg.getCollectionObjectsBySQL(sql);
ProfileDocument currentProfileDocument = null;
if (profileDocuments.size() >= 1) {
Optional<ProfileDocument> profileDocumentOptional = profileDocuments.stream().max((o1, o2) -> {
LocalDate o1ValidFromDate = o1.getValidFromDate();
LocalDate o2ValidFromDate = o2.getValidFromDate();
o1ValidFromDate = o1ValidFromDate == null ? LocalDate.MIN : o1ValidFromDate;
o2ValidFromDate = o2ValidFromDate == null ? LocalDate.MIN : o2ValidFromDate;
return o1ValidFromDate.compareTo(o2ValidFromDate);
});
currentProfileDocument = profileDocumentOptional.get();
} else {
log.warn("Profile_document with sql {} not found", sql);
return null;
}
Long notificationId = currentProfileDocument.getId();
LocalDate validFromDate = currentProfileDocument.getValidFromDate();
if (validFromDate == null) {
log.error("validFromDate is null for profile_document {}", currentProfileDocument);
return null;
}
Long companyId = currentProfileDocument.getCompanyId();
if (companyId == null) {
log.error("companyId is null for profile_document {}", currentProfileDocument);
return null;
}
Company company = companyImdg.getSingleObjectByID(companyId);
if (company == null) {
log.error("Company for companyId={} is null", companyId);
return null;
}
String companyClearingCode = company.getClearingCode();
if (StringUtils.isBlank(companyClearingCode)) {
log.error("Company.clearing_code for companyId={} is null", companyId);
return null;
}
LocalDate notificationDate = LocalDate.now();
int day = notificationDate.getDayOfMonth();
int month = notificationDate.getMonthValue();
int year = notificationDate.getYear();
String dateStr = "%02d%02d%04d".formatted(day, month, year);
Map<String, String> valuesForTemplate = new HashMap<>();
valuesForTemplate.put("${dateStr}", dateStr);
valuesForTemplate.put("${count}", String.valueOf(notificationId));
valuesForTemplate.put("${currDay}", "%02d".formatted(day));
valuesForTemplate.put("${currMonth}", "%02d".formatted(month));
valuesForTemplate.put("${currYear}", "%04d".formatted(year % 100));
valuesForTemplate.put("${validFromDate}", "%02d.%02d.%04d".formatted(day, month, year));
String outputFilename = "REX.%s.%s.docx".formatted(companyClearingCode, filenameDateTimeFormatter.format(notificationDate));
outputFilename = normalizeFilenameForOS(outputFilename);
File outputFile = new File(outputFilename);
try {
docxService.insertValues(outputFile, valuesForTemplate, true);
} catch (Exception e) {
log.error("Can't insert values into template.", e);
return null;
}
return outputFile;
}
@Override
protected String getTemplateResourceName() {
return "templates/REX_TEMPLATE.docx";
}
}

View file

@ -0,0 +1,112 @@
package ru.spcex.clearing.reports.notifications;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import ru.clearing.classes.objects.BusinessObject;
import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.company.CompanySymbols;
import ru.clearing.classes.statics.data.company.relation.Relation;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgProvider;
import java.io.File;
import java.time.Instant;
import java.time.LocalDate;
import java.util.*;
@Component
public class RRPCNotificationBuilder extends NotificationBuilder {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Imdg<Company> companyImdg;
private final Imdg<Relation> relationImdg;
private final Imdg<CompanySymbols> companySymbolsImdg;
public RRPCNotificationBuilder(ImdgProvider imdgProvider) {
super(null);
companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class);
relationImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Relation, Relation.class);
companySymbolsImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_CompanySymbols, CompanySymbols.class);
}
@Override
public File buildNotification(Long consumerId) {
Company company = companyImdg.getSingleObjectByID(consumerId);
if (company == null) {
log.error("Company for companyId={} is null", consumerId);
return null;
}
String companyClearingCode = company.getClearingCode();
if (StringUtils.isBlank(companyClearingCode)) {
log.error("Company.clearing_code for companyId={} is null", consumerId);
return null;
}
String relationsSql = "consumerId = %d".formatted(consumerId);
Relation relation = null;
Collection<Relation> relations = relationImdg.getCollectionObjectsBySQL(relationsSql);
if (relations.size() >= 1) {
Optional<Relation> relationOptional = relations.stream().max(Comparator.comparing(BusinessObject::getUpdated));
relation = relationOptional.get();
} else {
log.warn("Relation with sql {} not found", relationsSql);
return null;
}
Long notificationId = relation.getId();
Instant updatedAt = relation.getUpdated();
if (updatedAt == null) {
log.error("updated_at is null for relation {}", relation);
return null;
}
String companySymbolsSql = "companyId = %d AND companySymbol = INN";
CompanySymbols companySymbols = null;
Collection<CompanySymbols> companySymbolsCollection = companySymbolsImdg.getCollectionObjectsBySQL(companySymbolsSql);
if (companySymbolsCollection.size() >= 1) {
companySymbols = companySymbolsCollection.iterator().next();
} else {
log.warn("CompanySymbols with sql {} not found", companySymbolsSql);
return null;
}
LocalDate notificationDate = LocalDate.now();
int day = notificationDate.getDayOfMonth();
int month = notificationDate.getMonthValue();
int year = notificationDate.getYear();
String dateStr = "%02d%02d%04d".formatted(day, month, year);
Map<String, String> valuesForTemplate = new HashMap<>();
valuesForTemplate.put("${dateStr}", dateStr);
valuesForTemplate.put("${count}", String.valueOf(notificationId));
valuesForTemplate.put("${currDay}", "%02d".formatted(day));
valuesForTemplate.put("${currMonth}", "%02d".formatted(month));
valuesForTemplate.put("${currYear}", "%04d".formatted(year % 100));
valuesForTemplate.put("${relationUpdatedAt}", dateFormatter.format(updatedAt));
valuesForTemplate.put("${companyFullName}", company.getFullName());
valuesForTemplate.put("${companyShortName}", company.getShortName());
valuesForTemplate.put("${companySymbolsINN}", companySymbols.getCompanySymbolValue());
valuesForTemplate.put("${companyClearingCode}", companyClearingCode);
String outputFilename = "RRPC.%s.%s.docx".formatted(companyClearingCode, filenameDateTimeFormatter.format(notificationDate));
outputFilename = normalizeFilenameForOS(outputFilename);
File outputFile = new File(outputFilename);
try {
docxService.insertValues(outputFile, valuesForTemplate, true);
} catch (Exception e) {
log.error("Can't insert values into template.", e);
return null;
}
return outputFile;
}
@Override
protected String getTemplateResourceName() {
return "templates/RRPC_TEMPLATE.docx";
}
}

View file

@ -2,6 +2,7 @@ package ru.spcex.clearing.reports.services;
import org.apache.poi.xwpf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.LinkedList;
@ -10,6 +11,7 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
/**
@ -22,58 +24,86 @@ import java.util.stream.Collectors;
* идентификатору потом можно установить стиль, который будет применён к вставляемой строке.
*/
public class DOCXService {
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* При инициализации сохраняет все имеющиеся ключи (ищет по маске keyPattern), найденные в текстовых элементах документа
*/
private List<KeyPositionInDoc> keyPositions = new LinkedList<>();
/**
* Паттерн поиска ключей в файле по умолчанию
*/
private final Pattern DEFAULT_KEY_PATTERN = Pattern.compile("\\$\\{.*?}");
/**
* Паттерн поиска ключей в файле
*/
private Pattern KEY_PATTERN = Pattern.compile("\\$\\{.*?}");
private Pattern keyPattern = DEFAULT_KEY_PATTERN;
/**
* Файл шаблона
*/
private File templateFile;
/**
* Флаг инициализации
*/
private boolean initialized = false;
/**
* (ре)Инициализация сервиса
*/
public void init(String keyPattern) {
KEY_PATTERN = Pattern.compile(keyPattern);
public boolean init(File templateFile, String keyPattern) {
initialized = false;
if (templateFile == null || !templateFile.exists() || !templateFile.isFile()) {
log.warn("Invalid templateFile");
return false;
}
this.templateFile = templateFile;
if (keyPattern != null) {
try {
this.keyPattern = Pattern.compile(keyPattern);
} catch (PatternSyntaxException e) {
log.warn("Invalid keyPattern");
return false;
}
} else {
this.keyPattern = DEFAULT_KEY_PATTERN;
}
keyPositions = new LinkedList<>();
initialized = true;
return true;
}
/**
* Вставка значений в шаблон. Шаблон может содержать любое количество ключей, при этом эти ключи должны содержаться в
* valuesMap.
*
* @param templateFile Файл шаблона
* @param outputFile Выходной файл. Должен быть либо файлом (будет перезаписан), либо отсутствовать
* @param valuesMap Мапа значений. В файле шаблона будут искаться ключи этой мапы и заменяться на соответствующие значения.
* @param log Если будет передан null, то метод будет выбрасывать исключения. В противном случае будет записан лог и возвращено false
* @param checkKeys True - строгий режим, где будет проверено наличие всех ключей из файла в valuesMap.
* False - мягкий режим, отсутствующие ключи будут пропущены
*
* @return true - если вставка прошла успешно
*/
public boolean insertValues(File templateFile,
File outputFile,
public boolean insertValues(File outputFile,
Map<String, String> valuesMap,
Logger log,
boolean checkKeys) throws Exception {
assert templateFile != null && templateFile.exists() && templateFile.isFile();
if (!initialized) {
log.warn("DOCXService not initialized");
return false;
}
if (outputFile == null || outputFile.isDirectory()) {
String logMsg = "Can't open output file. Output file: %s".formatted(outputFile == null ? "null" : outputFile.getAbsolutePath());
if (log == null) throw new Exception(logMsg);
log.error(logMsg);
log.error("Can't open output file. Output file: {}", outputFile == null ? "null" : outputFile.getAbsolutePath());
return false;
}
if (outputFile.exists()) {
if (log != null) log.warn("Output file {} exist and will be overwritten", outputFile.getAbsolutePath());
log.warn("Output file {} exist and will be overwritten", outputFile.getAbsolutePath());
boolean deleteOk = outputFile.delete();
if (!deleteOk) {
String logMsg = "Can't delete file %s".formatted(outputFile.getAbsolutePath());
if (log == null) throw new Exception(logMsg);
log.error(logMsg);
log.error("Can't delete file {}", outputFile.getAbsolutePath());
return false;
}
}
@ -94,11 +124,9 @@ public class DOCXService {
List<String> notPresentInMap = new LinkedList<>();
boolean checkOk = checkIntersection(valuesMap, notPresentInMap);
if (!checkOk) {
String logMsg = "Can't insert values in file %s. Keys not in map: %s."
.formatted(templateFile.getAbsolutePath(),
String.join(", ", notPresentInMap));
if (log == null) throw new Exception(logMsg);
log.error(logMsg);
log.error("Can't insert values in file {}. Keys not in map: {}.",
templateFile.getAbsolutePath(),
String.join(", ", notPresentInMap));
return false;
}
}
@ -178,7 +206,7 @@ public class DOCXService {
for (int currPos = 0; currPos < MAX_POSITION; currPos++) {
String currText = run.getText(currPos);
if (currText == null) break;
Matcher matcher = KEY_PATTERN.matcher(currText);
Matcher matcher = keyPattern.matcher(currText);
while (matcher.find()) {
String key = matcher.group();
keyPositions.add(new KeyPositionInDoc(key, run, currPos));

View file

@ -23,6 +23,7 @@ import java.util.Map;
public class QCommandExecutor extends QueueConsumer implements InitializingBean {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Map<ReportId, ReportDataCollector> collectorForReport = new HashMap<>();
// private final ImdgId idGenerator;
// private final KafkaSender kafkaReqProducer;
@ -71,4 +72,5 @@ public class QCommandExecutor extends QueueConsumer implements InitializingBean
}
log.debug("successfully processed");
}
}

View file

@ -19,10 +19,9 @@ class DOCXServiceTest {
valuesMap.put("${test3}", "======== TEST 3 =========");
DOCXService service = new DOCXService();
service.insertValues(new File(getClass().getClassLoader().getResource("REC.ACTV_TEST.docx").getPath()),
new File("output.docx"),
service.init(new File(getClass().getClassLoader().getResource("REC.ACTV_TEST.docx").getPath()), null);
service.insertValues(new File("output.docx"),
valuesMap,
null,
false);
File file = new File("output.docx");

View file

@ -0,0 +1,23 @@
package ru.spcex.clearing.platform.messaging.domain.cud.reports;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Запрос на формирование уведомления
*/
public class NotificationRequest {
/**
* Идентификатор участника клиринга
*/
@JsonProperty
protected String consumerId;
public String getConsumerId() {
return consumerId;
}
public void setConsumerId(String consumerId) {
this.consumerId = consumerId;
}
}