From 705b24910d8b5242dc751d64d54d17935a3be879 Mon Sep 17 00:00:00 2001 From: etreschenkov Date: Fri, 7 Jul 2023 19:44:47 +0300 Subject: [PATCH] http://jira.mfd.msk:8088/browse/CLS-410 --- .../stage/SecondaryAuctionT0Session.java | 4 +- .../FormingPaymentInstructionSecondaryT0.java | 350 ++++++++++++++++++ .../platform/enumeration/AccountType.java | 3 +- 3 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/FormingPaymentInstructionSecondaryT0.java diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/SecondaryAuctionT0Session.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/SecondaryAuctionT0Session.java index 18ddce14f..f04eb6d78 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/SecondaryAuctionT0Session.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/SecondaryAuctionT0Session.java @@ -38,7 +38,7 @@ public class SecondaryAuctionT0Session extends AbstractSession implements Initia private final InclusionObligations inclusionObligations; private final InspectionObligations inspectionObligations; private final FormingRegistersOnOS formingRegistersOnOS; - private final FormingPaymentInstruction formingPaymentInstruction; + private final FormingPaymentInstructionSecondaryT0 formingPaymentInstruction; private final UnlockResources unlockResources; private final FinishingSession finishingSession; private final EndStageNotification endStageNotification; @@ -55,7 +55,7 @@ public class SecondaryAuctionT0Session extends AbstractSession implements Initia ObligationAdmission obligationsAdmission, InclusionObligations inclusionObligations, FormingRegistersOnOS formingRegistersOnOS, - FormingPaymentInstruction formingPaymentInstruction, + FormingPaymentInstructionSecondaryT0 formingPaymentInstruction, UnlockResources unlockResources, FinishingSession finishingSession, EndStageNotification endStageNotification, diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/FormingPaymentInstructionSecondaryT0.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/FormingPaymentInstructionSecondaryT0.java new file mode 100644 index 000000000..2a208e3e6 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/FormingPaymentInstructionSecondaryT0.java @@ -0,0 +1,350 @@ +package ru.spcex.clearing.session.stage.impl; + +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.stereotype.Service; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.clearing.classes.statics.data.sdf.SDf03; +import ru.clearing.classes.statics.data.sdf.SDf12; +import ru.clearing.classes.statics.data.security.Security; +import ru.spcex.clearing.error.ClearingError; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.Consts; +import ru.spcex.clearing.platform.messaging.domain.cud.balance.ExportToFileRequest; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.clearing.service.SpecifUtil; +import ru.spcex.clearing.service.builder.PaymentInstructionBuilderFinalMkrDeals; +import ru.spcex.clearing.session.stage.ISessionStage; +import ru.spcex.clearing.session.stage.StageResult; +import ru.spcex.clearing.session.stage.Task; +import ru.spcex.clearing.session.stage.task.FormingPaymentInstructionDealsMkrPayload; +import ru.spcex.platform.enumeration.*; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgId; +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.collection.Pair; +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.enumeration.SimpleMessageResolver; +import ru.spcex.platform.utils.number.BigDecimalUtil; +import ru.spcex.platform.utils.time.TimeUtil; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static ru.spcex.platform.enumeration.RegistryTradingParams.*; +import static ru.spcex.platform.utils.number.BigDecimalUtil.safeBD; + +@Service +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class FormingPaymentInstructionSecondaryT0 implements ISessionStage { + private final Logger log = LoggerFactory.getLogger(getClass()); + //todo remove (set all in single method setImdg(provider -> setImdg1();setIdGenerator();...) + private ImdgProvider imdgProvider; + private ImdgId idGenerator; + private Imdg registryImdg; + private Imdg paymentInstructionImdg; + private Imdg securityImdg; + private Imdg accountImdg; + private Imdg companyImdg; + private Imdg sDf03Imdg; + private Imdg sDf12Imdg; + private KafkaSender kafkaSender; + private final IMessageResolver msgResolver = new SimpleMessageResolver(); + + @Autowired + public FormingPaymentInstructionSecondaryT0(ImdgProvider imdgProvider, + KafkaSender kafkaSender) { + this.kafkaSender = kafkaSender; + this.imdgProvider = imdgProvider; + this.idGenerator = imdgProvider.getImdgIdGenerator(); + this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); + this.securityImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Security, Security.class); + this.accountImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class); + this.companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + this.sDf03Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf03, SDf03.class); + this.sDf12Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf12, SDf12.class); + this.paymentInstructionImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class); + } + + @Override + public StageResult submit(Task task) { + FormingPaymentInstructionDealsMkrPayload payload = (FormingPaymentInstructionDealsMkrPayload) task.getData(); + switch (task.getTaskType()) { + case FormingPaymentInstruction -> { + return formingPaymentInstructions(payload.getSessionId(), payload.getPaymentInstructionReturns()); + } + default -> { + throw new IllegalStateException("Unknown task type: " + task.getTaskType()); + } + } + } + + + private Collection selectRegistries() { + ImdgPredicateBuilder rgsPb = registryImdg.predicateBuilder(); + RegistryCodeSqlBuilder registryCodeSqlBuilder = RegistryCodeSqlBuilder.getInstance( + LM_T, CM_T + ); + ImdgPredicate rgsCodePrdct = rgsPb.and( + rgsPb.sql(registryCodeSqlBuilder.build()), + rgsPb.equals("settlementDate", LocalDate.now())); + Collection registries = registryImdg.getCollectionObjectsByPredicate(rgsCodePrdct); + return registries.stream() + .filter(rgs -> Objects.equals(rgs.getValueDate(), rgs.getSettlementDate())) + .collect(Collectors.toList()); + } + /** + * аккаунт-счет с accountType='TRAN' - это счет СПВБ который принадлежит самой бирже
+ * хранят деньги разных участников
+ * в сделке:
+ * регистр CMAT кому переводить (registry.account)
+ * регистр LMAT кто переводит деньги (registry.account)
+ * в итоге создается 2 PaymentInstruction: LMAT -> TRAN счет -> CMAT счет + */ + private StageResult formingPaymentInstructions(Long sessionId, Collection paymentInstructionReturns) { + Collection registries = selectRegistries(); + log.debug("found registries.size() = {}", registries.size()); + //клиринговый счет, через который будут проводиться сделки + Account tranAcc = accountImdg.getSingleObjectBySQL("accountType = '%s' and status = '%s' and processingSign = '%s'" + .formatted(AccountType.Dtrn.getKey(), AccountStatus.ACTIVE.getKey(), Allowed.ALLOWED.getKey())); + if (tranAcc == null) { + return new StageResult<>( + new EnumMessage(ClearingError.AccountNotPresent, "accountType = " + AccountType.Tran.getKey()), + false); + } + log.debug("found tranAcc.id = {}", tranAcc.getId()); + //группируем регистры по groupId + Map> groups = registries + .stream() + .collect(Collectors.groupingBy(Registry::getGroupId)); + log.debug("groups.size = {}", groups.size()); + List paymentInstructionDeals = new ArrayList<>(); + for (Map.Entry> group : groups.entrySet()) { + Long groupId = group.getKey(); + Function findByCode = rgsCode -> group.getValue() + .stream() + .filter(rgs -> equalsByRegistry(rgsCode, rgs)) + .findFirst() + .orElse(null); + //интересуют обязательства и требования по деньгам + Registry cs_t = findByCode.apply(CS_T); + Registry ls_t = findByCode.apply(LS_T); + if (cs_t == null || ls_t == null) { + log.error("groupId {} cmt_t {} ls_t {} - both must be present", groupId, cs_t, ls_t); + continue; + } + { + //изменение активов - блокируем средства беред отправкой sdf'ов + Optional amfO = findRelatedAsset(ls_t.getTradingClearingRegistryId(), ls_t.getCompanyId(), AS_F); + Optional payerAmtO = findRelatedAsset(ls_t.getTradingClearingRegistryId(), ls_t.getCompanyId(), AS_T); + Optional ambO = findRelatedAsset(ls_t.getTradingClearingRegistryId(), ls_t.getCompanyId(), AS_B); + Optional receiverAmtO = findRelatedAsset(cs_t.getTradingClearingRegistryId(), cs_t.getCompanyId(), AS_B); + log.debug("changing A* registers based on LM_T.id={} and CM_T.id={} found AM*F.id={}, AM*T.id={}, AM*B.id={}, AM*B.id={}", + ls_t.getId(), + cs_t.getId(), + amfO.map(Registry::getId).orElse(null), + payerAmtO.map(Registry::getId).orElse(null), + ambO.map(Registry::getId).orElse(null), + receiverAmtO.map(Registry::getId).orElse(null) + ); + Instant now = Instant.now(); + amfO.ifPresent(amf -> { + amf.setBalance(safeBD(amf.getBalance()).subtract(safeBD(ls_t.getBalance()))); + setUpdatedStoreInImdg(amf, now); + }); + payerAmtO.ifPresent(amt -> { + amt.setSettledDebit(safeBD(amt.getSettledDebit()).add(safeBD(ls_t.getBalance()))); + setUpdatedStoreInImdg(amt, now); + }); + ambO.ifPresent(amb -> { + amb.setBalance(safeBD(amb.getBalance()).add(safeBD(ls_t.getBalance()))); + setUpdatedStoreInImdg(amb, now); + }); + receiverAmtO.ifPresent(amt -> { + // у отправителя и получателя одинаково, см. в FormingPaymentInstruction + amt.setSettledDebit(safeBD(amt.getSettledDebit()).add(safeBD(ls_t.getBalance()))); + setUpdatedStoreInImdg(amt, now); + }); + } + log.trace("generating payment instruction for groupId {} cmt_t {} ls_t {}", + groupId, + cs_t.getId(), + ls_t.getId()); + Pair pmtInstrs = PaymentInstructionBuilderFinalMkrDeals + .builder(imdgProvider) + .cm_t(cs_t) + .lm_t(ls_t) + .tranAcc(tranAcc) + .sessionId(sessionId) + .build(); + Pair.forEach(pmtInstrs, pmtInstr -> { + paymentInstructionImdg.insert(pmtInstr); + paymentInstructionDeals.add(pmtInstr); + }); + log.trace("generated PaymentInstructions for groupId {}: pmtInstr1.id={} pmtInstr2.id={}", + groupId, + pmtInstrs.getFirst().getId(), + pmtInstrs.getSecond().getId()); + } + + log.debug("PaymentInstructions return size {}, PaymentInstructions deals size {}. sending SDF03", + paymentInstructionReturns.size(), + paymentInstructionDeals.size()); + List returnsAndDeals = Stream.concat(paymentInstructionReturns.stream(), paymentInstructionDeals.stream()).toList(); + sendSdfs(returnsAndDeals); + StageResult> stageResult = new StageResult<>(null, true); + stageResult.setStageResult(returnsAndDeals); + return stageResult; + } + + + private void setUpdatedStoreInImdg(Registry rgs, Instant now) { + rgs.setUpdated(now); + registryImdg.update(rgs); + } + + private Optional findRelatedAsset(Long tradingClearingRegistryId, Long companyId, RegistryTradingParams rgsCode) { + ImdgPredicateBuilder rgsPrdBldr = registryImdg.predicateBuilder(); + RegistryCodeSqlBuilder codeSql = RegistryCodeSqlBuilder.getInstance(rgsCode); + ImdgPredicate assetCondition = rgsPrdBldr.and( + rgsPrdBldr.equals("tradingClearingRegistryId", tradingClearingRegistryId), + rgsPrdBldr.equals("companyId", companyId), + rgsPrdBldr.sql(codeSql.build()) + ); + return Optional.ofNullable(registryImdg.getSingleObjectByPredicate(assetCondition)); + } + + private void sendSdfs(List formedPaymentInstructions) { + List sDf03Created = new ArrayList<>(); + for (PaymentInstruction paymentInstruction : formedPaymentInstructions) { + Account account = accountImdg.getSingleObjectByID(paymentInstruction.getCreditLeg_accountId()); + if (List.of(AccountType.Corr, AccountType.Clrn, AccountType.Tran, AccountType.Anlt) + .contains(IEnumKey.getEnumByKey(AccountType.class, account.getAccountType()))) { + sDf03Created.add(newSDf03(paymentInstruction)); + } + } + + Long sdf03GroupId = !sDf03Created.isEmpty() ? imdgProvider.getImdgIdGenerator().nextId() : null; + for (SDf03 sDf03 : sDf03Created) { + sDf03.setGenerationId(sdf03GroupId); + sDf03Imdg.insert(sDf03); + } + + if (sdf03GroupId != null) { + ExportToFileRequest exportToFileRequest = new ExportToFileRequest(); + exportToFileRequest.setNameOfTable("DF-03"); + exportToFileRequest.setSdfGroupId(sdf03GroupId); + kafkaSender.sendRequestToQueue(Consts.EXPORT_PROCESS, exportToFileRequest); + } + } + + private SDf03 newSDf03(PaymentInstruction paymentInstruction) { + log.debug("creating sdf03"); + SDf03 sDf03 = new SDf03(); + sDf03.setId(idGenerator.nextId()); + sDf03.setSeg_type("S"); + sDf03.setDoc_type("002"); + String strId = paymentInstruction.getId().toString(); + String strIdCut = strId.length() > 16 ? strId.substring(strId.length() - 16) : strId; + sDf03.setDocnm_ref(strIdCut); + sDf03.setC_acc_deb(paymentInstruction.getCreditLeg_account()); + + String senderSbankName = ""; + if (paymentInstruction.getSenderId().equals(1L)) { + senderSbankName = paymentInstruction.getPayeeBankName(); + } else { + Company company = companyImdg.getSingleObjectByID(paymentInstruction.getSenderId()); + if (company != null) { + senderSbankName = company.getShortName(); + } + } + String[] bnkNmeParts = SpecifUtil.split5SegmentsBy35Symbols(senderSbankName); + for (int i = 0; i < bnkNmeParts.length; i++) { + if (i == 0) { + sDf03.setSbanknam1(bnkNmeParts[i]); + } + if (i == 1) { + sDf03.setSbanknam2(bnkNmeParts[i]); + } + if (i == 2) { + sDf03.setSbanknam3(bnkNmeParts[i]); + } + if (i == 3) { + sDf03.setSbanknam4(bnkNmeParts[i]); + } + if (i == 4) { + sDf03.setSbanknam5(bnkNmeParts[i]); + } + } + + sDf03.setC_acc_cred(paymentInstruction.getDebitLeg_account()); + + String addresseeSbankName = ""; + if (paymentInstruction.getAddresseeId().equals(1L)) { + addresseeSbankName = paymentInstruction.getAddresseeBankName(); + } else { + Company company = companyImdg.getSingleObjectByID(paymentInstruction.getAddresseeId()); + if (company != null) { + addresseeSbankName = company.getShortName(); + } + } + sDf03.setRbanknam1(addresseeSbankName); + sDf03.setRbanknam2(addresseeSbankName); + sDf03.setRbanknam3(addresseeSbankName); + sDf03.setRbanknam4(addresseeSbankName); + sDf03.setRbanknam5(addresseeSbankName); + + sDf03.setPay_date(payDateFormatter.format(TimeUtil.toLocalDate(paymentInstruction.getPaymentDate()))); + sDf03.setPay_val("RUR"); + String sumDeb = paymentInstruction.getDebitLeg_amount() != null ? paymentInstruction.getDebitLeg_amount().toString() : ""; + sDf03.setSum_deb(BigDecimalUtil.limitDecimalPlaces(sumDeb, 2)); + sDf03.setSpecif_1(paymentInstruction.getPaymentPurpose()); + sDf03.setGenerationTime(Instant.now()); + sDf03.setPaymentInstructionId(paymentInstruction.getId()); + log.debug("successfully processed, new id {}", sDf03.getId()); + return sDf03; + } + + private SDf12 newSDf12(PaymentInstruction paymentInstruction) { + log.debug("creating sdf12"); + SDf12 sDf12 = new SDf12(); + sDf12.setId(idGenerator.nextId()); + sDf12.setOutDocument(sDf12.getId().toString()); + sDf12.setDirection("DELFREE"); + sDf12.setQuantity(BigDecimalUtil.limitDecimalPlaces(paymentInstruction.getCreditLeg_amount().toString(), 2)); + sDf12.setSecurityCode(paymentInstruction.getCreditLeg_securityId().toString()); + sDf12.setDepoCodeSender(paymentInstruction.getCreditLeg_account()); + sDf12.setDepoCodeAdressee(paymentInstruction.getDebitLeg_account()); +// sDf12.setTransactionNumber(); + sDf12.setGenerationTime(Instant.now()); + return sDf12; + } + + DateTimeFormatter payDateFormatter = DateTimeFormatter.ofPattern("dd.MM.yy"); + + private static boolean equalsByRegistry(RegistryTradingParams code, Registry rgs) { + return code.equalByRegistry( + IEnumKey.getEnumByKey(RegistryDesignation.class, rgs.getRegistryDesignation()), + IEnumKey.getEnumByKey(RegistryInstrumentType.class, rgs.getRegistryInstrumentType()), + IEnumKey.getEnumByKey(RegistryCapacity.class, rgs.getRegistryCapacity()), + IEnumKey.getEnumByKey(RegistryUnit.class, rgs.getRegistryUnit()) + ); + + } +} diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java index 8f8ad6fdb..eae32d556 100644 --- a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java @@ -3,7 +3,8 @@ package ru.spcex.platform.enumeration; import ru.spcex.platform.utils.enumeration.IEnumKey; public enum AccountType implements IEnumKey { - Clrn("CLRN"), Bank("BANK"), Info("INFO"), Tran("TRAN"), Corr("CORR"), Anlt("ANLT"), Depo("DEPO"); + Clrn("CLRN"), Bank("BANK"), Info("INFO"), Tran("TRAN"), Corr("CORR"), Anlt("ANLT"), Depo("DEPO"), + Dtrn("DTRN"); private final String key;