Sdf21Executor.java

This commit is contained in:
ialbert 2023-08-03 21:13:23 +03:00
parent 050c742313
commit 7fa4b6aec5

View file

@ -0,0 +1,459 @@
package ru.spcex.clearing.service.executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import ru.clearing.classes.statics.data.account.Account;
import ru.clearing.classes.statics.data.account.AccountBalance;
import ru.clearing.classes.statics.data.account.ClearingAccount;
import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.misc.Currency;
import ru.clearing.classes.statics.data.registry.Registry;
import ru.clearing.classes.statics.data.registry.TradingClearingRegistry;
import ru.clearing.classes.statics.data.sdf.SDf02;
import ru.clearing.classes.statics.data.sdf.SDf21;
import ru.clearing.classes.statics.data.security.Security;
import ru.clearing.classes.statics.data.statement.Statement;
import ru.spcex.clearing.error.ClearingError;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.cud.balance.StatementRequest;
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
import ru.spcex.clearing.service.AnltSearcher;
import ru.spcex.clearing.service.LoggingService;
import ru.spcex.clearing.service.builder.RegistrySecurityBuilder;
import ru.spcex.clearing.service.model.Result;
import ru.spcex.clearing.service.validation.ValidationStored;
import ru.spcex.clearing.session.stage.util.RegistryUtil;
import ru.spcex.platform.enumeration.*;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgProvider;
import ru.spcex.platform.imdg.api.predicate.ImdgPredicate;
import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder;
import ru.spcex.platform.imdg.api.predicate.specific.RegistryCodeSqlBuilder;
import ru.spcex.platform.imdg.api.predicate.specific.SecuritySelector;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.enumeration.IEnumKey;
import ru.spcex.platform.utils.enumeration.IMessageResolver;
import ru.spcex.platform.utils.text.TextUtil;
import ru.spcex.platform.utils.validation.IValidator;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
import static ru.spcex.platform.utils.number.BigDecimalUtil.safeBD;
@Service
public class Sdf21Executor extends AbstractExecutor<SDf21> {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Imdg<Statement> statementImdg;
private final Imdg<Registry> registryImdg;
private final Function<SDf21, IValidator> sDf21Validator;
private final LoggingService errorLogger;
private final Imdg<SDf02> sdf02Imdg;
private final ImdgProvider imdgProvider;
private final IMessageResolver errorResolver;
private final Imdg<AccountBalance> accountBalanceImdg;
private final Imdg<ClearingAccount> clearingAccountImdg;
private final Imdg<TradingClearingRegistry> tradingClearingRegistryImdg;
private final Imdg<Account> accountImdg;
private final Imdg<Company> companyImdg;
private final Imdg<Security> securityImdg;
private final Imdg<Currency> currencyImdg;
private final SecuritySelector<Security> scrSlct;
private final AnltSearcher anltSearcher;
private final IMessageResolver messageResolver;
private final Pattern pattern = Pattern.compile("№.*");
public Sdf21Executor(@Qualifier("sdf21Validator") Function<SDf21, IValidator> sDf21Validator,
LoggingService errorLogger,
ImdgProvider imdgProvider,
IMessageResolver errorResolver, AnltSearcher anltSearcher, IMessageResolver messageResolver) {
this.statementImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Statement, Statement.class);
this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class);
this.sdf02Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf02, SDf02.class);
this.accountBalanceImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_AccountBalance, AccountBalance.class);
this.clearingAccountImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ClearingAccount, ClearingAccount.class);
this.tradingClearingRegistryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_TradingClearingRegistry, TradingClearingRegistry.class);
this.currencyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Currency, Currency.class);
this.accountImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class);
this.companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class);
this.sDf21Validator = sDf21Validator;
this.errorLogger = errorLogger;
this.imdgProvider = imdgProvider;
this.errorResolver = errorResolver;
this.scrSlct = new SecuritySelector<>(imdgProvider, Security.class);
this.anltSearcher = anltSearcher;
this.messageResolver = messageResolver;
this.securityImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Security, Security.class);
}
//todo доделать контроль sdf01 и sdf57
@Override
public String exportTableName() {
return null;
}
@Override
public boolean isNeedToSendCommand() {
return false;
}
@Override
public void sendCommand(KafkaSender kafkaSender, Result result) {}
//V - Изменение statement по sDf57
//
//1. Перед изменением statement необходимо выполнить ряд проверок:
// 1.1. Найти в company запись, у которой company.tradingCode=sDf57.deal_deb. Если такой записи нет, записать в лог ошибку (5211) "Компания %s не найдена".
// 1.2. Найти в company запись, у которой company.tradingCode=sDf57.deal_cred. Если такой записи нет, записать в лог ошибку (5211) "Компания %s не найдена".
// 1.3. Проверить, есть ли в таблице account счет, у которого account.account=sDf57.c_acc_deb. Если такой записи нет, записать в лог ошибку (5217) "Счет %s не найден".
// 1.4. Проверить, есть ли в таблице account счет, у которого account.account=sDf57.c_acc_cred. Если такой записи нет, записать в лог ошибку (5217) "Счет %s не найден".
// 1.5. Проверить, что sDf57.pay_val=RUR. Иначе записать в лог ошибку (5213) "Валюта %s не найдена".
//2. В зависимости от результата прохождения проверок в п.1 раздела Изменение statement по sDf57:
// 2.1. Если все проверки пройдены, необходимо сформировать 2 новые записи согласно описанию с соответствующим тэгом,
// где первая запись будет содержать информацию о плательщике (по sDf57.deal_deb),
// а вторая о получателе (по sDf57.deal_cred) и связаны по одному inSDfId (= sDf57.id).
// 2.2. Иначе запись в statement не добавляется.
//3. По итогу добавления statement:
// Если в п.1 раздела Изменение statement по sDf57 ошибок не обнаружено, то инициировать изменение таблицы registry согласно описанию с соответствующим тэгом.
// По итогу изменения registry должны быть обновлены значения полей в statement:
// - operationStatus;
// - errorCode в случае выявления ошибки;
// - errorText в случае выявления ошибки.
public Result execute(Collection<SDf21> sdf, StatementRequest statementRequest) {
Result result = new Result();
Long generationIdForGroup = imdgProvider.getImdgIdGenerator().nextId();
result.setGenerationId(generationIdForGroup);
log.info("SDF21 execution: sdf21 number={}, groupId={}", sdf.size(), sdf.stream().findFirst().map(SDf21::getGenerationId).orElse(null));
for (SDf21 sdf21 : sdf) {
IValidator validator = sDf21Validator.apply(sdf21);
Optional<EnumMessage> error = validator.tillFirstError();
if (error.isPresent()) {
log.error("error while validating sdf21.id={} - {}", sdf21.getId(), messageResolver.resolve(error.get()));
continue;
}
Company company = validator.getStored(ValidationStored.Sdf21Company);
Account account = validator.getStored(ValidationStored.Sdf21Account);
log.debug("company.id={}, account.id={}", company.getId(), account.getId());
//адресат CREDIT / владелец DEBIT
Statement stmt = create(sdf21, company, account);
statementImdg.insert(stmt);
log.debug("created statement.id={}", stmt.getId());
Optional<EnumMessage> err = validateActiveness(company, account);
if (err.isEmpty()) {
Optional<Registry> astFound = RegistrySearch.builder(registryImdg)
.rgsCode(RegistryTradingParams.AS_T)
.companyId(company.getId()) //раньше было stmt.getAddresseeId()
.accountId(account.getId()) //раньше было stmt.getAccountId()
.find();
astFound.ifPresentOrElse(rgs -> {
log.debug("stmt.id={}, found AS*T.id={}, updating...", stmt.getId(), rgs.getId());
updateReg(stmt, rgs);
//добавил создание если не найдены
Registry registryUnitB = findRegByUnit(company.getId(), account.getId(), stmt.getContract(), rgs, RegistryUnit.B).orElse(copyRegB(rgs));
Registry registryUnitF = findRegByUnit(company.getId(), account.getId(), stmt.getContract(), rgs, RegistryUnit.F).orElse(copyRegF(rgs, null)); //передаю null т.к. отнимется ниже по коду
registryUnitF.setBalance(rgs.getBalance().subtract(registryUnitB.getBalance()));
registryUnitF.setUpdated(Instant.now());
//registry F всегда меняется
if (registryUnitF.getId() == null) {
registryImdg.insert(registryUnitF);
log.debug("stmt.id={}, new AM*F.id={}", stmt.getId(), registryUnitF.getId());
} else {
registryImdg.update(registryUnitF);
log.debug("stmt.id={}, updated AM*F.id={}", stmt.getId(), registryUnitF.getId());
}
//нужно сохранить registry B только если его не было
if (registryUnitB.getId() == null) {
registryImdg.insert(registryUnitB);
log.debug("stmt.id={}, new AM*B.id={}", stmt.getId(), registryUnitB.getId());
}
registryImdg.update(rgs);
log.debug("stmt.id={}, updated AM*T.id={}: balance {}/debit {}/ credit {}",
stmt.getId(),
rgs.getId(),
rgs.getBalance(),
rgs.getDebit(),
rgs.getCredit());
}, () -> {
Registry registry = RegistrySecurityBuilder.builder(imdgProvider)
.statement(stmt)
.company(company)
.account(account)
.designation(RegistryDesignation.A)
.build();
calcBalance(registry, stmt);
Registry registryB = copyRegB(registry);
Registry registryF = copyRegF(registry, registryB.getBalance());
registryImdg.insert(registry);
registryImdg.insert(registryF);
registryImdg.insert(registryB);
log.debug("stmt.id={}, new AM*T.id={}, new AM*F.id={}, new AM*B.id={}",
stmt.getId(),
registry.getId(),
registryF.getId(),
registryB.getId());
});
stmt.setOperationStatus(OperationStatus.Executed.getKey());
statementImdg.update(stmt);
log.debug("stmt.id={} status -> {}", stmt.getId(), OperationStatus.Executed.getKey());
} else {
log.debug("statement.id={} activness validation failed {}", stmt.getId(), messageResolver.resolve(err.get()));
stmt.setErrorCodeId(err.get().getSubject().getId()); // fixme ErrorText insert
stmt.setOperationStatus(OperationStatus.Rejected.getKey());
statementImdg.update(stmt);
}
}
return result;
}
private Registry copyRegB(Registry rgs) {
Registry rgsB = rgs.clone();
rgsB.setRegistryUnit(RegistryUnit.B.getKey());
rgsB.setRegistryCode(RegistryUtil.clearingCode(rgsB));
rgsB.setBalance(BigDecimal.ZERO);
rgsB.setDebit(BigDecimal.ZERO);
rgsB.setCredit(BigDecimal.ZERO);
rgsB.setDiffBalance(BigDecimal.ZERO);
rgsB.setCheckBalance(BigDecimal.ZERO);
return rgsB;
}
private Registry copyRegF(Registry rgs, BigDecimal rgsBBalance) {
Registry rgsF = rgs.clone();
rgsF.setRegistryUnit(RegistryUnit.F.getKey());
rgsF.setRegistryCode(RegistryUtil.clearingCode(rgsF));
rgsF.setBalance(safeBD(rgs.getBalance()).subtract(safeBD(rgsBBalance)));
rgsF.setCredit(BigDecimal.ZERO);
rgsF.setDiffBalance(BigDecimal.ZERO);
rgsF.setCheckBalance(BigDecimal.ZERO);
return rgsF;
}
private Optional<EnumMessage> validateActiveness(Company company, Account account) {
if (!WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus())) {
return Optional.of(new EnumMessage(ClearingError.CompanyNotActive, company.getId()));
}
if (!WorkflowStatus.Active.equalsByKey(account.getStatus())) {
return Optional.of(new EnumMessage(ClearingError.AccountNotActive, account.getId()));
}
return Optional.empty();
}
private Statement create(SDf21 sdf21, Company cmp, Account acc) {
Statement statement = new Statement();
statement.setAddresseeId(cmp.getId());
statement.setSenderId(Sender.Prc.getId());
statement.setStatementType(StatementType.incr.getKey());
//fixme statement.setComment(sdf21.());
statement.setAccountId(acc.getId());
Security security = scrSlct.selectSecurityBySymbol(sdf21.getSecurityCode());
if (security != null) {
statement.setSecurityId(security.getId());
}
statement.setAccount(acc.getAccount());
OperationCode operationCode = IEnumKey.getEnumByKey(OperationCode.class, sdf21.getOperationCode());
if (operationCode != null) {
switch (operationCode) {
case RECFREE -> statement.setInOutDirection(InOutDirection.in.getKey());
case DELFREE -> statement.setInOutDirection(InOutDirection.out.getKey());
}
}
statement.setSettlementDate(payDate(sdf21.getOperationDate()));
statement.setAmount(TextUtil.isEmpty(sdf21.getQuantity()) ? null : new BigDecimal(sdf21.getQuantity()));
statement.setOperationStatus(OperationStatus.Pending.getKey());
statement.setInSDfId(sdf21.getId());
statement.setInOutSDfType(InOutSDfType.type21.getKey());
statement.setClearingDate(LocalDate.now());
statement.setCreated(Instant.now());
return statement;
}
private void calcBalance(Registry rgs, Statement statement) {
InOutDirection inOutDirection = IEnumKey.getEnumByKey(InOutDirection.class, statement.getInOutDirection());
//считаю balance при создании нулевым и исхожу из этого
//При добавлении (на базе изменения statement по sDf57):
//Если значение statement.inOutDirection=IN, то:
//= текущее значение registry.balance + statement.amount
//
//Если значение statement.inOutDirection=OUT, то:
//= текущее значение registry.balance - statement.amount
switch (inOutDirection) {
case in -> {
rgs.setBalance(statement.getAmount());
rgs.setCredit(statement.getAmount());
}
case out -> {
rgs.setBalance(statement.getAmount().negate());
rgs.setDebit(statement.getAmount().negate());
}
}
AccountType accType = IEnumKey.getEnumByKey(AccountType.class, rgs.getAccountType());
if (!AccountType.Info.equals(accType)) {//fixme nuzhno li?
rgs.setDiffBalance(safeBD(rgs.getBalance()).subtract(safeBD(rgs.getCheckBalance())));
}
rgs.setDiffBalance(safeBD(rgs.getBalance()).subtract(safeBD(rgs.getCheckBalance())));
rgs.setBalanceDimension(BalanceDimension.PICS.getKey()); //fixme???
}
private void updateReg(Statement s, Registry r) {
InOutDirection direction = IEnumKey.getEnumByKey(InOutDirection.class, s.getInOutDirection());
switch (direction) {
case in -> {
r.setBalance(safeBD(r.getBalance()).add(safeBD(s.getAmount())));
r.setCredit(safeBD(r.getCredit()).add(safeBD(s.getAmount())));
}
case out -> {
r.setBalance(safeBD(r.getBalance()).subtract(safeBD(s.getAmount())));
r.setDebit(safeBD(r.getDebit()).add(safeBD(s.getAmount())));
}
}
AccountType accType = IEnumKey.getEnumByKey(AccountType.class, r.getAccountType());
if (!AccountType.Info.equals(accType)) {
r.setDiffBalance(safeBD(r.getBalance()).subtract(safeBD(r.getCheckBalance())));
}
r.setUpdated(Instant.now());
}
private static class RegistrySearch {
private final ImdgPredicateBuilder pb;
private final Imdg<Registry> registryImdg;
private ImdgPredicate predicate;
private RegistrySearch(Imdg<Registry> registryImdg) {
this.registryImdg = registryImdg;
this.pb = registryImdg.predicateBuilder();
}
private static RegistrySearch builder(Imdg<Registry> registryImdg) {
RegistrySearch registrySearch = new RegistrySearch(registryImdg);
registrySearch.predicate = registrySearch.pb.alwaysTrue();
return registrySearch;
}
private RegistrySearch rgsCode(RegistryTradingParams rgsCode) {
String sql = RegistryCodeSqlBuilder.getInstance(rgsCode).build();
predicate = pb.and(predicate, pb.sql(sql));
return this;
}
private RegistrySearch companyId(Long companyId) {
predicate = pb.and(predicate, pb.equals("companyId", companyId));
return this;
}
private RegistrySearch accountType(AccountType accountType) {
predicate = pb.and(predicate, pb.equals("accountType", accountType.getKey()));
return this;
}
private RegistrySearch accountId(Long accountId) {
predicate = pb.and(predicate, pb.equals("accountId", accountId));
return this;
}
private RegistrySearch contract(String contract) {
predicate = pb.and(predicate, pb.equals("contract", contract));
return this;
}
private RegistrySearch status() {
predicate = pb.and(predicate, pb.or(
pb.equals("registryStatus", RegistryStatus.PROC.getKey()),
pb.equals("registryStatus", RegistryStatus.MNG.getKey())
));
return this;
}
private Optional<Registry> find() {
return Optional.ofNullable(registryImdg.getSingleObjectByPredicate(predicate));
}
}
private Optional<Registry> findRegByUnit(Long companyId, Long accountId, String contract, Registry registry, RegistryUnit unit) {
RegistryTradingParams p = new RegistryTradingParams(
IEnumKey.getEnumByKey(RegistryDesignation.class, registry.getRegistryDesignation()),
IEnumKey.getEnumByKey(RegistryInstrumentType.class, registry.getRegistryInstrumentType()),
IEnumKey.getEnumByKey(RegistryCapacity.class, registry.getRegistryCapacity()),
unit
);
String sql = RegistryCodeSqlBuilder.getInstance(p).build();
ImdgPredicateBuilder pb = registryImdg.predicateBuilder();
ImdgPredicate rgstrPredicate = pb.and(pb.sql(sql),
pb.sql(sql),
pb.equals("companyId", companyId),
pb.equals("accountId", accountId)
);
if (RegistryDesignation.D.equalsByKey(registry.getRegistryDesignation()) && !TextUtil.isEmpty(contract)) {
rgstrPredicate = pb.and(rgstrPredicate, pb.equals("contract", contract));
}
return Optional.ofNullable(registryImdg.getSingleObjectByPredicate(rgstrPredicate));
}
DateTimeFormatter payDateFormatter = DateTimeFormatter.ofPattern("dd.MM.yy");
private LocalDate payDate(String payDate) {
if (TextUtil.isEmpty(payDate)) {
return null;
}
return LocalDate.parse(payDate, payDateFormatter);
}
private static String getTkrCodeFromComment(String comment) {
if (comment == null) {
return null;
}
comment = comment.toUpperCase();
int tcrIndex = comment.indexOf("ТКР");
if (tcrIndex == -1) {
return null;
}
comment = comment.substring(tcrIndex + 3);
return comment.replaceAll("\\s+", "");
}
private static String getContractFromSpecif(String specif) {
if (specif == null) {
return null;
}
int index = specif.indexOf("");
if (index == -1) {
return null;
}
StringBuilder buffer = new StringBuilder();
String afterN = specif.substring(index + 1);
int countSlashes = 0;
for(int i = 0; i < afterN.length(); i++) {
char c = afterN.charAt(i);
if (!Character.isWhitespace(c)) {
buffer.append(c);
if (c == '/') {
countSlashes++;
}
} else {
if (countSlashes == 3) {
break;
}
}
}
return buffer.toString();
}
@FunctionalInterface
private static interface TriFunction<T1, T2, T3, R> {
R apply(T1 arg1, T2 arg2, T3 arg3);
}
}