RRPC, REX notification
This commit is contained in:
parent
20b05c763d
commit
f069bab18a
9 changed files with 338 additions and 25 deletions
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue