diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/state/DataEnum.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/state/DataEnum.java index 9b1351cf3..4483531b1 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/state/DataEnum.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/state/DataEnum.java @@ -9,5 +9,6 @@ public enum DataEnum { counterPartyId, //Long obligationAdmissionStashedRgs, //Map paymentInstructionReturnMkr, //List + paymentInstructionSecurity, //List paymentInfo, } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/state/action/FormingPaymentInstructionSecuritiesAction.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/state/action/FormingPaymentInstructionSecuritiesAction.java new file mode 100644 index 000000000..98b55cf46 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/state/action/FormingPaymentInstructionSecuritiesAction.java @@ -0,0 +1,312 @@ +package ru.spcex.clearing.session.state.action; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Service; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.spcex.clearing.component.GroupRgsKey; +import ru.spcex.clearing.error.ClearingError; +import ru.spcex.clearing.error.ClearingRuntimeException; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.clearing.service.Sdf03Creator; +import ru.spcex.clearing.service.builder.PaymentInstructionBuilderForSecurities; +import ru.spcex.clearing.service.builder.PaymentInstructionBuilderV2; +import ru.spcex.clearing.service.registry.RegistryManager; +import ru.spcex.clearing.session.stage.TaskType; +import ru.spcex.clearing.session.stage.impl.Sdf03And12Sender; +import ru.spcex.clearing.session.stage.payment.group.PaymentGroup; +import ru.spcex.clearing.session.stage.payment.group.RegistryLiabilitiesGroup; +import ru.spcex.clearing.session.state.DataEnum; +import ru.spcex.clearing.session.state.SsnEvent; +import ru.spcex.platform.enumeration.AccountStatus; +import ru.spcex.platform.enumeration.AccountType; +import ru.spcex.platform.enumeration.Allowed; +import ru.spcex.platform.enumeration.RegistryStatus; +import ru.spcex.platform.enumeration.RegistryTradingParams; +import static ru.spcex.platform.enumeration.RegistryTradingParams.*; +import ru.spcex.platform.enumeration.Sender; +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.utils.enumeration.EnumMessage; +import static ru.spcex.platform.utils.number.BigDecimalUtil.safeBD; + +@Service +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class FormingPaymentInstructionSecuritiesAction extends AbstractSessionActionForOkErrorHandling { + private final Logger log = LoggerFactory.getLogger(getClass()); + + private ImdgProvider imdgProvider; + private Imdg registryImdg; + private Imdg paymentInstructionImdg; + private Imdg accountImdg; + private final RegistryManager rgsMng; + private final Sdf03And12Sender sdf03And12Sender; + + @Autowired + public FormingPaymentInstructionSecuritiesAction(ImdgProvider imdgProvider, + KafkaSender kafkaSender, + Sdf03Creator sdf03Creator, + RegistryManager rgsMng, + Sdf03And12Sender sdf03And12Sender) { + this.imdgProvider = imdgProvider; + this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); + this.accountImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class); + this.paymentInstructionImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class); + this.rgsMng = rgsMng; + this.sdf03And12Sender = sdf03And12Sender; + } + + @Override + protected void actualExecute(StateContext ctx) { + Long sessionId = ctx.getExtendedState().get(DataEnum.sessionId, Long.class); + + List paymentInstructionAll = new ArrayList<>(); + Instant now = Instant.now(); + { + Collection registries = selectSecurityRegistries(sessionId); + log.debug("found registries.size() = {}", registries.size()); + //группируем регистры по groupId и market + Map> groups = registries + .stream() + .collect(Collectors.groupingBy(r -> new GroupRgsKey(r.getGroupId(), r.getMarket()))); + + log.debug("groups.size = {}", groups.size()); + Map lstGroups = RegistryLiabilitiesGroup.group(groups); + for (Map.Entry> group : groups.entrySet()) { + Long groupId = group.getKey().groupId(); + String market = group.getKey().market(); + Function findByCode = rgsCode -> group.getValue() + .stream() + .filter(rgs -> RegistryManager.equalsByCode(rgsCode, rgs)) + .findFirst() + .orElse(null); + //интересуют обязательства и требования по деньгам + Registry cs_t = findByCode.apply(RegistryTradingParams.TS_T); + Registry ls_t = findByCode.apply(RegistryTradingParams.OS_T); + if (cs_t == null || ls_t == null) { + log.error("groupId/market {}/{} cmt_t {} ls_t {} - both must be present", groupId, market, cs_t, ls_t); + continue; + } + PaymentGroup ls_t_group = lstGroups.get(ls_t.getId()); + if (ls_t_group == null) { + log.debug("groupId/market {}/{} cmt_t {} ls_t {} were included in a group, skipping", groupId, market, cs_t, ls_t); + continue; + } + + Optional payerAstO = rgsMng.findRelatedAsset( + ls_t.getTradingClearingRegistryId(), + ls_t.getCompanyId(), + ls_t.getSecuritySymbol(), + AS_T + ); + payerAstO.ifPresent(as_t -> { + log.debug("groupId/market {}/{} cmt_t {} ls_t {} changing AS*T.id={} sender", groupId, market, cs_t, ls_t, as_t.getId()); + as_t.setSettledDebit(safeBD(as_t.getSettledDebit()).add(safeBD(ls_t_group.getSum()))); + setUpdatedStoreInImdg(as_t, now); + }); + Optional receiverAstO = rgsMng.findRelatedAsset( + cs_t.getTradingClearingRegistryId(), + cs_t.getCompanyId(), + cs_t.getSecuritySymbol(), + AS_T + ); + receiverAstO.ifPresent(as_t -> { + log.debug("groupId/market {}/{} cmt_t {} ls_t {} changing AS*T.id={} receiver", groupId, market, cs_t, ls_t, as_t.getId()); + as_t.setSettledCredit(safeBD(as_t.getSettledCredit()).add(safeBD(ls_t_group.getSum()))); + setUpdatedStoreInImdg(as_t, now); + }); + + + log.trace("generating payment instruction for groupId {} cmt_t {} ls_t {}, matched ls_t total {}, total balance {}, LS*T.id={}", + groupId, + cs_t.getId(), + ls_t.getId(), + ls_t_group.getLs_tRegistries().size(), + ls_t_group.getSum(), + ls_t_group.getLs_tRegistries().stream() + .map(Registry::getId) + .map(String::valueOf) + .collect(Collectors.joining(",", "[", "]")) + ); + + PaymentInstruction pmt = PaymentInstructionBuilderForSecurities.builder(imdgProvider) + .ls_t(ls_t) + .creditLegAccountId(ls_t.getAccountId()) + .creditLegAccount(ls_t.getAccount()) + .debitLegAccountId(cs_t.getAccountId()) + .debitLegAccount(cs_t.getAccount()) + .sessionId(sessionId) + .sender(ls_t.getCompanyId()) + .addressee(cs_t.getCompanyId()) + .purpose("Перевод по итогу клиринга") + .amount(ls_t_group.getSum()) + .build(); + + paymentInstructionImdg.insert(pmt); + paymentInstructionAll.add(pmt); + log.trace("generated PaymentInstructions for groupId/market {}/{}: pmtInstr.id={}", groupId, market, pmt.getId()); + } + } + { + Collection registries = selectMoneyRegistries(sessionId); + Account dtrnAcc = accountImdg.getFirstObjectBySQL("accountType = '%s' and status = '%s' and processingSign = '%s'" + .formatted(AccountType.Dtrn.getKey(), AccountStatus.ACTIVE.getKey(), Allowed.ALLOWED.getKey())); + + if (dtrnAcc == null) { + throw new ClearingRuntimeException( + new EnumMessage(ClearingError.AccountNotPresent, "accountType = %s" + .formatted(AccountType.Dtrn.getKey())) + ); + } + + log.debug("found AM*B.size() = {}", registries.size()); + //группируем регистры по groupId + for (Registry registry : registries) { + Account tranAcc = accountImdg.getFirstObjectBySQL(("accountType = '%s'" + + " and status = '%s'" + + " and processingSign = '%s'" + + " and currency = '%s'") + .formatted(AccountType.Tran.getKey(), + AccountStatus.ACTIVE.getKey(), + Allowed.ALLOWED.getKey(), + registry.getSecuritySymbol())); + + if (tranAcc == null) { + log.error("{} account not found for {}", AccountType.Tran.getKey(), registry.getSecuritySymbol()); + throw new ClearingRuntimeException( + new EnumMessage(ClearingError.AccountNotPresent, "accountType = %s" + .formatted(AccountType.Tran.getKey())) + ); + } + + if (registry.getBalance().compareTo(BigDecimal.ZERO) == 0) { + log.debug("Skip creating paymentInstruction by registry with 0 balance"); + continue; + } + boolean isPositiveBalance = registry.getBalance().compareTo(BigDecimal.ZERO) > 0; + Account counterAcc = null; + if (AccountType.Clrn.equalsByKey(registry.getAccountType())) { + counterAcc = accountImdg.getSingleObjectByID(registry.getAccountId()); + } else if (AccountType.Info.equalsByKey(registry.getAccountType())) { + counterAcc = accountImdg.getFirstObjectByFieldValues(Map.of("companyId", 1L, "accountType", AccountType.Anlt.getKey(), "currency", registry.getSecuritySymbol())); + } + Long senderId; + Long addresseeId; + Account debitLegAccount; + Account creditLegAccount; + BigDecimal amount; + if (isPositiveBalance) { + senderId = registry.getCompanyId(); + addresseeId = Sender.One.getId(); + debitLegAccount = tranAcc; + creditLegAccount = counterAcc; + amount = registry.getBalance() == null ? null : registry.getBalance().abs(); + Optional payerAmtO = rgsMng.findRelatedAssetTran( + registry.getTradingClearingRegistryId(), + registry.getCompanyId(), + registry.getSecuritySymbol(), + AM_T); + payerAmtO.ifPresent(amt -> { + amt.setSettledDebit(safeBD(amt.getSettledDebit()).add(safeBD(registry.getBalance().abs()))); + setUpdatedStoreInImdg(amt, now); + }); + } else { + senderId = Sender.One.getId(); + addresseeId = registry.getCompanyId(); + debitLegAccount = counterAcc; + creditLegAccount = tranAcc; + amount = registry.getBalance() == null ? null : registry.getBalance().abs(); + Optional payerAmtO = rgsMng.findRelatedAssetTran( + registry.getTradingClearingRegistryId(), + registry.getCompanyId(), + registry.getSecuritySymbol(), + AM_T + ); + payerAmtO.ifPresent(amt -> { + amt.setSettledCredit(safeBD(amt.getSettledCredit()).add(safeBD(registry.getBalance().abs()))); + setUpdatedStoreInImdg(amt, now); + }); + } + PaymentInstructionBuilderV2 paymentInstructionBuilder = PaymentInstructionBuilderV2.builder(imdgProvider) + .registry(registry) + .sender(senderId) + .addressee(addresseeId) + .debitLegAccount(debitLegAccount) + .creditLegAccount(creditLegAccount) + .amount(amount) + .sessionId(sessionId) + .currency(registry.getSecuritySymbol()) + .purpose(String.format("Перевод по итогу клиринга по ТКР %s", registry.getTradingClearingRegistry())); + PaymentInstruction paymentInstruction = paymentInstructionBuilder.build(); + log.debug("Created paymentInstruction by registry.id: {}", registry.getId()); + paymentInstructionImdg.insert(paymentInstruction); + registry.setPaymentId(paymentInstruction.getId()); + registry.setUpdated(now); + registryImdg.update(registry); + paymentInstructionAll.add(paymentInstruction); + } + } + + log.debug("PaymentInstruction size {}", paymentInstructionAll.size()); + sdf03And12Sender.sendSdfs(paymentInstructionAll, sessionId); + + ctx.getExtendedState().getVariables().put(DataEnum.paymentInstructionSecurity, paymentInstructionAll); + } + + private Collection selectSecurityRegistries(Long sessionId) { + ImdgPredicateBuilder rgsPb = registryImdg.predicateBuilder(); + RegistryCodeSqlBuilder registryCodeSqlBuilder = RegistryCodeSqlBuilder.getInstance( + RegistryTradingParams.TS_T, RegistryTradingParams.OS_T + ); + ImdgPredicate rgsCodePrdct = rgsPb.and( + rgsPb.sql(registryCodeSqlBuilder.build()), + rgsPb.equals("registryStatus", RegistryStatus.OK.getKey()), + rgsPb.equals("sessionId", sessionId), + rgsPb.equals("settlementDate", LocalDate.now())); + Collection registries = registryImdg.getCollectionObjectsByPredicate(rgsCodePrdct); + return registries.stream() + .filter(rgs -> Objects.equals(rgs.getValueDate(), rgs.getSettlementDate())) + .collect(Collectors.toList()); + } + + private Collection selectMoneyRegistries(Long sessionId) { + ImdgPredicateBuilder rgsPb = registryImdg.predicateBuilder(); + RegistryCodeSqlBuilder registryCodeSqlBuilder = RegistryCodeSqlBuilder.getInstance( + AM_B + ); + ImdgPredicate prdct = rgsPb.and( + rgsPb.sql(registryCodeSqlBuilder.build()), + rgsPb.equals("sessionId", sessionId) + ); + Collection registries = registryImdg.getCollectionObjectsByPredicate(prdct); + return registries.stream() + .filter(rgs -> Objects.equals(rgs.getValueDate(), rgs.getSettlementDate())) + .collect(Collectors.toList()); + } + + private void setUpdatedStoreInImdg(Registry rgs, Instant now) { + rgs.setUpdated(now); + registryImdg.update(rgs); + } +}