diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ValidationConfig.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ValidationConfig.java index a7fdb5a56..97dd2ab78 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ValidationConfig.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ValidationConfig.java @@ -343,6 +343,22 @@ public class ValidationConfig { }; } + @Bean + public Function sdf20Validator() { + return sdf20 -> { + ImdgValidationContext ctx = new ImdgValidationContext<>(); + ctx.setValidatedObject(sdf20); + ctx.addImdg(IMDGDistributedNames.Map_Company, imdgCompany); + ctx.addImdg(IMDGDistributedNames.Map_Account, imdgAccount); + return new ValidatorImpl<>(ctx, + Sdf20ValidationRule.AccountPresent, + Sdf20ValidationRule.CompanyPresent, + Sdf20ValidationRule.TCRPresent, + Sdf20ValidationRule.DS_TFound, + Sdf20ValidationRule.AS_TFound); + }; + } + @Bean("userRoleVerification") public UserRoleVerification userRoleVerification(ImdgProvider imdgProvider, IMessageResolver msgs) { return new UserRoleVerification(imdgProvider, msgs, ClearingError.UserVerifyDenial); diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/AssetTBFProcessing.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/AssetTBFProcessing.java index 62019aed7..b3a775a73 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/AssetTBFProcessing.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/AssetTBFProcessing.java @@ -5,25 +5,33 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.registry.Registry; import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.platform.enumeration.CurrencyCode; +import ru.spcex.platform.enumeration.RegistryTradingParams; import ru.spcex.platform.enumeration.RegistryUnit; 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.number.BigDecimalUtil; import java.math.BigDecimal; import java.time.Instant; import java.util.Optional; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; +import static ru.spcex.platform.enumeration.RegistryTradingParams.*; + @Service public class AssetTBFProcessing { private final Logger log = LoggerFactory.getLogger(getClass()); - private final Imdg registryImdg; + private final Imdg rgsImdg; private final RegistryManager rgsMng; public AssetTBFProcessing(ImdgProvider imdgProvider, RegistryManager rgsMng) { - this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); + this.rgsImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); this.rgsMng = rgsMng; } @@ -38,8 +46,8 @@ public class AssetTBFProcessing { am_b.setUpdated(now); am_f.setUpdated(now); - registryImdg.update(am_b); - registryImdg.update(am_f); + rgsImdg.update(am_b); + rgsImdg.update(am_f); } public void processByAm_b(Registry am_b, BigDecimal sum) { @@ -58,6 +66,27 @@ public class AssetTBFProcessing { } } + public Optional searchMoneyByAccAndCompany(Long companyId, Long accountId) { + ImdgPredicateBuilder pb = rgsImdg.predicateBuilder(); + Function prdct = rgsCode -> pb.and( + pb.sql(RegistryCodeSqlBuilder.getInstance(rgsCode).build()), + pb.equals("companyId", companyId), + pb.equals("accountId", accountId), + pb.equals("securitySymbol", CurrencyCode.RUB.getKey()) + ); + Registry amf = rgsImdg.getFirstObjectByPredicate(prdct.apply(AM_F)); + Registry amb = rgsImdg.getFirstObjectByPredicate(prdct.apply(AM_B)); + Registry amt = rgsImdg.getFirstObjectByPredicate(prdct.apply(AM_T)); + if (amf != null && amb != null && amt != null) { + return Optional.of(new AssetTrio(amf, amb, amt)); + } + return Optional.empty(); + } + + public boolean insufficientBalance(Registry registry, BigDecimal amount) { + return BigDecimalUtil.safeBD(registry.getBalance()).compareTo(BigDecimalUtil.safeBD(amount)) < 0; + } + //точно будет аналогично и с другими полями private void safeWrapField(Supplier getter, Consumer setter) { BigDecimal fieldValue = getter.get(); diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/AssetTrio.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/AssetTrio.java new file mode 100644 index 000000000..6045b2ebe --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/AssetTrio.java @@ -0,0 +1,6 @@ +package ru.spcex.clearing.service; + +import ru.clearing.classes.statics.data.registry.Registry; + +public record AssetTrio(Registry am_f, Registry am_b, Registry am_t) { +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/RegistryManager.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/RegistryManager.java index 5cfe94d4c..f0b201770 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/RegistryManager.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/RegistryManager.java @@ -89,6 +89,25 @@ public class RegistryManager { return Optional.ofNullable(dmt); } + public Optional searchByAccSecContract(Long accountId, String securitySymbol, String contract, RegistryTradingParams rgsCde) { + if (TextUtil.isEmpty(contract) + || TextUtil.isEmpty(securitySymbol) + || accountId == null + || rgsCde == null) + return Optional.empty(); + ImdgPredicateBuilder pb = rgsImdg.predicateBuilder(); + return Optional.ofNullable( + rgsImdg.getSingleObjectByPredicate( + pb.and( + pb.sql(RegistryCodeSqlBuilder.getInstance(rgsCde).build()), + pb.equals("accountId", accountId), + pb.equals("securitySymbol", securitySymbol), + pb.equals("contract", contract) + ) + ) + ); + } + public Optional searchDmtClrn(Registry rgs) { ImdgPredicate dmtClrnPrdct = RegistryDm_tClrnPredicate .instance(rgs.getCompanyId(), rgs.getCounterPartyId()) diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf54Creator.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf54Creator.java index d35304dd2..64a6253b1 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf54Creator.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf54Creator.java @@ -46,7 +46,10 @@ public class Sdf54Creator { this.idGenerator = imdgProvider.getImdgIdGenerator(); } + private final static DateTimeFormatter docnmRefFormatter = DateTimeFormatter.ofPattern("yyMMddHHmmssSSS"); + public SDf54 create(PaymentInstruction paymentInstruction) { + Instant now = Instant.now(); AccountType creditAccType = getAccountType(paymentInstruction.getCreditLeg_accountId()); AccountType debAccType = getAccountType(paymentInstruction.getDebitLeg_accountId()); String c_acc_deb = null; @@ -118,9 +121,7 @@ public class Sdf54Creator { sDf54.setDoc_type("002"); - String strId = paymentInstruction.getId().toString(); - String strIdCut = strId.length() > 16 ? strId.substring(strId.length() - 16) : strId; - sDf54.setDocnm_ref(strIdCut); + sDf54.setDocnm_ref(docnmRefFormatter.format(TimeUtil.toDateTime(now))); sDf54.setC_acc_deb(c_acc_deb); @@ -149,7 +150,7 @@ public class Sdf54Creator { String sumDeb = paymentInstruction.getDebitLeg_amount() != null ? paymentInstruction.getDebitLeg_amount().toString() : ""; sDf54.setSum_deb(BigDecimalUtil.limitDecimalPlaces(sumDeb, 2)); sDf54.setSpecif_1(paymentInstruction.getPaymentPurpose()); - sDf54.setGenerationTime(Instant.now()); + sDf54.setGenerationTime(now); log.debug("paymentInstruction.id={}, sdf54.id {}", paymentInstruction.getId(), sDf54.getId()); return sDf54; } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementService.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementService.java index 351805c6a..0af38e60c 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementService.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementService.java @@ -71,11 +71,11 @@ public class StatementService extends QueueConsumer implements InitializingBean .setConsumer(systemRequest -> { StatementRequest payload = systemRequest.getRequestPayload(); if (payload.getTable() == null - || !Arrays.asList(SdfTable.SDF_01, SdfTable.SDF_04, SdfTable.SDF_57, SdfTable.SDF_08, SdfTable.SDF_13, SdfTable.SDF_21, SdfTable.SDF_10).contains(payload.getTable())) { + || !Arrays.asList(SdfTable.SDF_01, SdfTable.SDF_04, SdfTable.SDF_57, SdfTable.SDF_08, SdfTable.SDF_13, SdfTable.SDF_21, SdfTable.SDF_10, SdfTable.SDF_55, SdfTable.SDF_20).contains(payload.getTable())) { log.debug("StatementService: skipping table {}", payload.getTable()); return; } - if (Arrays.asList(SdfTable.SDF_08, SdfTable.SDF_21, SdfTable.SDF_10).contains(payload.getTable())) { + if (Arrays.asList(SdfTable.SDF_08, SdfTable.SDF_21, SdfTable.SDF_10, SdfTable.SDF_55, SdfTable.SDF_20).contains(payload.getTable())) { stmtSrvV2.processReq(systemRequest); } else if (payload.isContinueSdf()) { processAccountAnswer(systemRequest); diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementServiceV2.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementServiceV2.java index 1195cb245..9c2e79a81 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementServiceV2.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/StatementServiceV2.java @@ -11,9 +11,7 @@ import ru.spcex.clearing.platform.messaging.domain.Consts; import ru.spcex.clearing.platform.messaging.domain.cud.account.sdf01.AccountSdf01Request; import ru.spcex.clearing.platform.messaging.domain.cud.balance.StatementRequest; import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; -import ru.spcex.clearing.service.executors.Sdf08Executor; -import ru.spcex.clearing.service.executors.Sdf10Executor; -import ru.spcex.clearing.service.executors.Sdf21Executor; +import ru.spcex.clearing.service.executors.*; import ru.spcex.clearing.service.model.Result; import ru.spcex.platform.enumeration.SdfTable; import ru.spcex.platform.imdg.api.Imdg; @@ -32,13 +30,23 @@ public class StatementServiceV2 { private final Sdf08Executor sdf08Executor; private final Sdf21Executor sdf21Executor; private final Sdf10Executor sdf10Executor; + private final Sdf55Executor sdf55Executor; + private final Sdf20Executor sdf20Executor; - public StatementServiceV2(ImdgProvider imdgProvider, KafkaSender kafkaSender, Sdf08Executor sdf08Executor, Sdf21Executor sdf21Executor, Sdf10Executor sdf10Executor) { + public StatementServiceV2(ImdgProvider imdgProvider, + KafkaSender kafkaSender, + Sdf08Executor sdf08Executor, + Sdf21Executor sdf21Executor, + Sdf10Executor sdf10Executor, + Sdf55Executor sdf55Executor, + Sdf20Executor sdf20Executor) { this.imdgProvider = imdgProvider; this.kafkaSender = kafkaSender; this.sdf08Executor = sdf08Executor; this.sdf21Executor = sdf21Executor; this.sdf10Executor = sdf10Executor; + this.sdf55Executor = sdf55Executor; + this.sdf20Executor = sdf20Executor; } public void processReq(BaseRequest systemRequest) { @@ -50,9 +58,9 @@ public class StatementServiceV2 { log.debug("table {} is not paired with any other SDF", table); //здесь switch case одиночные методы switch (table) { - case SDF_10 -> { - sdf10Executor.execute(systemRequest); - } + case SDF_10 -> sdf10Executor.execute(systemRequest); + case SDF_55 -> sdf55Executor.execute(systemRequest); + case SDF_20 -> sdf20Executor.execute(systemRequest); default -> log.error("unknown table {}", table); } return; diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf10Executor.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf10Executor.java index 8ac290f9c..83e4419b1 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf10Executor.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf10Executor.java @@ -165,7 +165,7 @@ public class Sdf10Executor { requests.add(requestFromStatement(stmt, company.getTradingCode(), tcr.getCode(), sDf10.getSecurityCode())); } else { SDf11 sDf11 = processedApproved(stmt, sDf10, now, sdf11GroupId); - createDs_t(tcr, company, validator.getStored(ValidationStored.SecurityBySecurityCode), stmt.getAmount(), sDf11.getOutDocument()); + createDs_t(tcr, company, validator.getStored(ValidationStored.SecurityBySecurityCode), stmt.getAmount(), sDf10.getOutDocument()); sdf11WasCreated = true; } } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf20Executor.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf20Executor.java new file mode 100644 index 000000000..f565c2bb2 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf20Executor.java @@ -0,0 +1,127 @@ +package ru.spcex.clearing.service.executors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; +import ru.clearing.classes.statics.data.sdf.SDf20; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.BaseRequest; +import ru.spcex.clearing.platform.messaging.domain.Consts; +import ru.spcex.clearing.platform.messaging.domain.cud.balance.StatementRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.clearing.AssetOperationListRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.clearing.AssetOperationRequest; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.clearing.service.RegistryManager; +import ru.spcex.clearing.service.validation.ValidationStored; +import ru.spcex.platform.enumeration.InOutDirection; +import ru.spcex.platform.enumeration.RegistryStatus; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.enumeration.IMessageResolver; +import ru.spcex.platform.utils.number.BigDecimalUtil; +import ru.spcex.platform.utils.validation.IValidator; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; + +@Service +public class Sdf20Executor { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Imdg sDf20Imdg; + private final Imdg rgsImdg; + private final Function validation; + private final IMessageResolver msgRslv; + private final KafkaSender kafka; + + @Autowired + public Sdf20Executor(ImdgProvider imdgProvider, + Function validation, + IMessageResolver msgRslv, + RegistryManager rgsMng, + KafkaSender kafka) { + this.sDf20Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf20, SDf20.class); + this.rgsImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); + this.validation = validation; + this.msgRslv = msgRslv; + this.kafka = kafka; + } + + public void execute(BaseRequest systemRequest) { + Instant now = Instant.now(); + StatementRequest payload = systemRequest.getRequestPayload(); + log.debug("executing {}.generationId={}", payload.getTable(), payload.getGroupId()); + SDf20 sDf20 = sDf20Imdg.getFirstObjectBySQL("generationId = %d".formatted(payload.getGroupId())); + if (sDf20 == null) { + log.error("sDf20 not found for generationId={}", payload.getGroupId()); + return; + } + IValidator validator = validation.apply(sDf20); + Optional err = validator.tillFirstError(); + if (err.isPresent()) { + log.error("sdf20.id={} error {}", sDf20.getId(), msgRslv.resolve(err.get())); + return; + } + Registry ds_t = validator.getStored(ValidationStored.Sdf20Dst); + Registry as_t = validator.getStored(ValidationStored.Sdf20Ast); + Company company = validator.getStored(ValidationStored.Sdf20Company); + TradingClearingRegistry tcr = validator.getStored(ValidationStored.Sdf20Tcr); + log.debug("sdf20.id={}, found as_t.id={}, ds_t.id={}", sDf20.getId(), as_t.getId(), ds_t.getId()); + + ds_t.setComment(sDf20.getTransactionNumber()); + ds_t.setRegistryStatus(RegistryStatus.OK.getKey()); + ds_t.setUpdated(now); + + BigDecimal quantity = BigDecimalUtil.parseLeadingZeroes(sDf20.getQuantity()); + BigDecimal balance = BigDecimalUtil.safeBD(as_t.getBalance()); + if ("ADDFREE".equalsIgnoreCase(sDf20.getOperationCode())) { + as_t.setBalance(balance.add(quantity)); + } else if ("DELFREE".equals(sDf20.getOperationCode())) { + as_t.setBalance(balance.subtract(quantity)); + } + as_t.setCheckBalance(BigDecimalUtil.parseLeadingZeroes(sDf20.getCloseBalance())); + as_t.setUpdated(now); + + rgsImdg.update(ds_t); + rgsImdg.update(as_t); + + //send gateway + { + Collection requests = new ArrayList<>(); + AssetOperationRequest req = requestFromStatement( + quantity, + sDf20.getSecurity(), + company.getTradingCode(), + tcr.getCode() + ); + requests.add(req); + + AssetOperationListRequest assetOperationListRequest = new AssetOperationListRequest(); + assetOperationListRequest.setAssetOperationRequests(requests); + kafka.sendRequestToQueue(Consts.ASSET_OPERATION, assetOperationListRequest); + } + } + + private AssetOperationRequest requestFromStatement(BigDecimal quantity, //из sdf + String securitySymbol, //из sdf + String tradingCode, //из компании + String tcrCode//из TCR + ) { + AssetOperationRequest req = new AssetOperationRequest(); + req.setStatementId(null); + req.setQuantity(quantity); //+ + req.setSecuritySymbol(securitySymbol); //+ + req.setTradingCode(tradingCode); //??? + req.setCode(tcrCode); //TCR + req.setDirection(InOutDirection.in.getKey()); + return req; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf55Executor.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf55Executor.java new file mode 100644 index 000000000..7b86a07e4 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf55Executor.java @@ -0,0 +1,55 @@ +package ru.spcex.clearing.service.executors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.clearing.classes.statics.data.sdf.SDf54; +import ru.clearing.classes.statics.data.sdf.SDf55; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.BaseRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.balance.StatementRequest; +import ru.spcex.platform.enumeration.TransactionStatus; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +import java.time.Instant; + +@Service +public class Sdf55Executor { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Imdg sDf54Imdg; + private final Imdg sDf55Imdg; + private final Imdg pmtImdg; + + + public Sdf55Executor(ImdgProvider imdgProvider) { + this.sDf54Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf54, SDf54.class); + this.sDf55Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf55, SDf55.class); + this.pmtImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class); + } + + public void execute(BaseRequest systemRequest) { + StatementRequest payload = systemRequest.getRequestPayload(); + log.debug("executing {}.generationId={}", payload.getTable(), payload.getGroupId()); + SDf55 sDf55 = sDf55Imdg.getFirstObjectBySQL("generationId = %d".formatted(payload.getGroupId())); + if (sDf55 == null) { + log.error("sDf55 not found for generationId={}", payload.getGroupId()); + return; + } + SDf54 sDf54 = sDf54Imdg.getFirstObjectBySQL("docnm_ref = '%s'".formatted(sDf55.getDocnm_ref())); + if (sDf54 == null) { + log.error("sDf54 not found for sDf55.id={} docnm_ref={}", sDf55.getId(), sDf55.getDocnm_ref()); + return; + } + PaymentInstruction pmt = pmtImdg.getSingleObjectByID(sDf54.getGenerationId()); + if (pmt == null) { + log.error("pmt not found for sDf54.id={} generationId={}", sDf54.getId(), sDf54.getGenerationId()); + return; + } + log.debug("sdf55.id={}, found sdf54.id={}, PaymentInstruction.id={}", sDf55.getId(), sDf54.getId(), pmt.getId()); + pmt.setTransactionStatus(TransactionStatus.ok.getKey()); + pmt.setUpdated(Instant.now()); + pmtImdg.update(pmt); + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/payment/PaymentInstructionOutboundService.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/payment/PaymentInstructionOutboundService.java index d1ea34713..144ecbf53 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/payment/PaymentInstructionOutboundService.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/payment/PaymentInstructionOutboundService.java @@ -21,6 +21,8 @@ import ru.spcex.clearing.platform.messaging.domain.cud.payment.PIClearingOutbond import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; import ru.spcex.clearing.platform.messaging.service.Status; import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.clearing.service.AssetTBFProcessing; +import ru.spcex.clearing.service.AssetTrio; import ru.spcex.clearing.service.RegistryManager; import ru.spcex.clearing.service.Sdf54Creator; import ru.spcex.clearing.service.builder.PaymentInstructionBuilderV2; @@ -35,9 +37,11 @@ import ru.spcex.platform.imdg.api.ImdgProvider; import ru.spcex.platform.utils.enumeration.EnumMessage; import ru.spcex.platform.utils.enumeration.IEnumId; import ru.spcex.platform.utils.enumeration.IMessageResolver; +import ru.spcex.platform.utils.number.BigDecimalUtil; import ru.spcex.platform.utils.validation.IValidator; import java.math.BigDecimal; +import java.time.Instant; import java.util.Optional; import java.util.function.Function; @@ -59,12 +63,13 @@ public class PaymentInstructionOutboundService { private final RegistryManager rgsMng; private final Sdf54Creator sdf54Creator; private final KafkaSender kafkaSender; + private final AssetTBFProcessing assetsMng; @Autowired public PaymentInstructionOutboundService(UserRoleVerification rights, IMessageResolver msgs, ImdgProvider imdgProvider, - Function validation, RegistryManager rgsMng, KafkaSender kafkaSender) { + Function validation, RegistryManager rgsMng, KafkaSender kafkaSender, AssetTBFProcessing assetsMng) { this.imdgProvider = imdgProvider; this.rights = rights; this.msgs = msgs; @@ -80,10 +85,12 @@ public class PaymentInstructionOutboundService { this.idGenerator = imdgProvider.getImdgIdGenerator(); this.rgsMng = rgsMng; this.kafkaSender = kafkaSender; + this.assetsMng = assetsMng; } public RequestInfoUpdate sendOutBoundPayment(BaseRequest req) { log.info("new PIClearingOutbondActionNewRequest.id: {}", req.getId()); + Instant now = Instant.now(); PIClearingOutbondActionNewRequest payload = req.getRequestPayload(); if (!rights.userHasRole(req.getUserId(), UserRole.Admin)) { return error(req.getId(), ClearingError.UserVerifyDenial); @@ -97,7 +104,7 @@ public class PaymentInstructionOutboundService { Account accDeb = validator.getStored(ValidationStored.PIOutboundAccountDeb); Company addressee = cmpImdg.getSingleObjectByID(payload.getAddresseeId()); Company sender = cmpImdg.getSingleObjectByID(payload.getSenderId()); - BigDecimal amount = payload.getCreditLeg_amount(); + BigDecimal amount = BigDecimalUtil.safeBD(payload.getCreditLeg_amount()); log.debug("all checks passed, accCred.id={}, accDeb.id={}, addressee.id={}, sender.id={}, amount: {}", accCred.getId(), accDeb.getId(), addressee.getId(), sender.getId(), amount); @@ -128,14 +135,25 @@ public class PaymentInstructionOutboundService { pmtImdg.insert(pmt); log.debug("new PaymentInstruction.id: {}", pmt.getId()); + Optional assets = assetsMng.searchMoneyByAccAndCompany(addressee.getId(), accCred.getId()); + if (assets.isEmpty() || assetsMng.insufficientBalance(assets.get().am_f(), amount)) { + pmt.setTransactionStatus(TransactionStatus.cher.getKey()); + pmt.setUpdated(now); + pmtImdg.update(pmt); + log.debug("insufficient balance, pmt.id: {}, am*f.balance={}", + pmt.getId(), assets.map(AssetTrio::am_f).map(Registry::getBalance).orElse(null)); + return error(req.getId(), ClearingError.BalanceInsufficient); + } + log.debug("assets found and balance is ok. am*b.id={}, am*t.id={}, am*f.id={}", + assets.get().am_b().getId(), assets.get().am_t().getId(), assets.get().am_f().getId()); + assetsMng.process(assets.get().am_b(), assets.get().am_t(), assets.get().am_f(), amount.negate()); SDf54 sDf54 = sdf54Creator.create(pmt); - Long sdf54Id = idGenerator.nextId(); - sDf54.setId(sdf54Id); - sDf54.setGenerationId(sdf54Id); + sDf54.setId(idGenerator.nextId()); + sDf54.setGenerationId(pmt.getId()); sdf54Imdg.insert(sDf54); log.debug("new sdf54.id: {}", sDf54.getId()); SdfClearingRequest exp = new SdfClearingRequest(); - exp.setGroupId(sdf54Id); + exp.setGroupId(sDf54.getGenerationId()); kafkaSender.sendRequestToQueue(Consts.SDF54_PROCESS, exp); return success(req.getId()); } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/PaymentOutboundValidationRule.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/PaymentOutboundValidationRule.java index 7ad54df63..c5425418a 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/PaymentOutboundValidationRule.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/PaymentOutboundValidationRule.java @@ -13,6 +13,7 @@ import ru.spcex.platform.imdg.validation.ImdgValidationContext; import ru.spcex.platform.utils.enumeration.EnumMessage; import ru.spcex.platform.utils.validation.IValidationRule; +import java.math.BigDecimal; import java.util.Optional; public enum PaymentOutboundValidationRule implements IValidationRule> { @@ -29,6 +30,9 @@ public enum PaymentOutboundValidationRule implements IValidationRule"); + return of(ClearingError.TCRegistryNotFound, ""); } context.storeObject(ValidationStored.Sdf10Tcr, tcr); return empty(); diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/Sdf20ValidationRule.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/Sdf20ValidationRule.java new file mode 100644 index 000000000..8c9234457 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/Sdf20ValidationRule.java @@ -0,0 +1,128 @@ +package ru.spcex.clearing.service.validation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; +import ru.clearing.classes.statics.data.sdf.SDf20; +import ru.spcex.clearing.error.ClearingError; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.service.RegistryManager; +import ru.spcex.platform.enumeration.AccountType; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.validation.ImdgValidationContext; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.text.TextUtil; +import ru.spcex.platform.utils.validation.IValidationRule; + +import java.util.Map; +import java.util.Optional; + +import static ru.spcex.platform.enumeration.RegistryTradingParams.AS_T; +import static ru.spcex.platform.enumeration.RegistryTradingParams.DS_T; + +public enum Sdf20ValidationRule implements IValidationRule> { + AccountPresent() { + @Override + public Optional validate(ImdgValidationContext context) { + SDf20 sdf20 = context.getValidatedObject(); + String depoCodeCl = sdf20.getDepoCodeCl(); + if (TextUtil.isEmpty(depoCodeCl)) { + return of(ClearingError.AccountNotFoundB, depoCodeCl); + } + Imdg accImdg = context.obtainMap(IMDGDistributedNames.Map_Account, Account.class); + Account acc = accImdg.getFirstObjectBySQL("account = '%s' and accountType = '%s'" + .formatted(depoCodeCl, AccountType.Clrn.getKey())); + if (acc == null) { + return of(ClearingError.AccountNotFoundB, depoCodeCl); + } + context.storeObject(ValidationStored.Sdf20Account, acc); + return empty(); + } + }, + CompanyPresent() { + @Override + public Optional validate(ImdgValidationContext context) { + Account acc = context.getStoredObject(ValidationStored.Sdf20Account); + if (acc == null) { + return of(ClearingError.CompanyNotFoundB, ""); + } + if (acc.getCompanyId() == null) { + return of(ClearingError.CompanyNotFoundB, ""); + } + Imdg cmpImdg = context.obtainMap(IMDGDistributedNames.Map_Company, Company.class); + Company company = cmpImdg.getSingleObjectByID(acc.getCompanyId()); + if (company == null) { + return of(ClearingError.CompanyNotFoundB, acc.getCompanyId()); + } + context.storeObject(ValidationStored.Sdf20Company, company); + return empty(); + } + }, + TCRPresent() { + @Override + public Optional validate(ImdgValidationContext context) { + Account acc = context.getStoredObject(ValidationStored.Sdf20Account); + if (acc == null) { + return of(ClearingError.TCRegistryNotFound, ""); + } + Imdg tcrImdg = context.obtainMap(IMDGDistributedNames.Map_TradingClearingRegistry, TradingClearingRegistry.class); + TradingClearingRegistry tcr = tcrImdg.getFirstObjectByFieldValues( + Map.of("depoAccountId", acc.getId()) + ); + if (tcr == null) { + return of(ClearingError.TCRegistryNotFound, ""); + } + context.storeObject(ValidationStored.Sdf20Tcr, tcr); + return empty(); + } + }, + DS_TFound() { + @Override + public Optional validate(ImdgValidationContext context) { + SDf20 sDf20 = context.getValidatedObject(); + String inDocument = sDf20.getInDocument(); + String security = sDf20.getSecurity(); + Account acc = context.getStoredObject(ValidationStored.Sdf20Account); + if (TextUtil.isEmpty(inDocument) || TextUtil.isEmpty(security) || acc == null) { + return of(ClearingError.RecordNotFound, "DS*T"); + } + Imdg rgsImdg = context.obtainMap(IMDGDistributedNames.Map_Registry, Registry.class); + RegistryManager rgsMng = new RegistryManager(rgsImdg); + Optional ds_t = rgsMng.searchByAccSecContract(acc.getId(), security, inDocument, DS_T); + if (ds_t.isEmpty()) { + return of(ClearingError.RecordNotFound, "DS*T"); + } + context.storeObject(ValidationStored.Sdf20Dst, ds_t.get()); + return empty(); + } + }, + AS_TFound() { + @Override + public Optional validate(ImdgValidationContext context) { + SDf20 sDf20 = context.getValidatedObject(); + String inDocument = sDf20.getInDocument(); + String security = sDf20.getSecurity(); + Account acc = context.getStoredObject(ValidationStored.Sdf20Account); + if (TextUtil.isEmpty(inDocument) || TextUtil.isEmpty(security) || acc == null) { + return of(ClearingError.RecordNotFound, "AS*T"); + } + Imdg rgsImdg = context.obtainMap(IMDGDistributedNames.Map_Registry, Registry.class); + RegistryManager rgsMng = new RegistryManager(rgsImdg); + Optional as_t = rgsMng.searchByAccSecContract(acc.getId(), security, inDocument, AS_T); + if (as_t.isEmpty()) { + return of(ClearingError.RecordNotFound, "AS*T"); + } + context.storeObject(ValidationStored.Sdf20Ast, as_t.get()); + return empty(); + } + } + ; + private final static Logger log = LoggerFactory.getLogger(Sdf20ValidationRule.class); + @Override + public String ruleName() { + return "Sdf20ValidationRule." + name(); + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/ValidationStored.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/ValidationStored.java index 64d18121c..2ba109dc3 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/ValidationStored.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/ValidationStored.java @@ -22,5 +22,7 @@ public enum ValidationStored { CompanyByTradingCode, + Sdf20Account, Sdf20Company, Sdf20Tcr, Sdf20Dst, Sdf20Ast, + SecurityBySecurityCode } diff --git a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf20MapStore.java b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf20MapStore.java index 8e7faf6de..744de5515 100644 --- a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf20MapStore.java +++ b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf20MapStore.java @@ -10,7 +10,6 @@ import ru.spcex.platform.utils.time.TimeUtil; import java.sql.ResultSet; import java.sql.SQLException; -import java.time.LocalDate; import java.util.List; @Component @@ -43,8 +42,7 @@ public class SDf20MapStore extends TemplateMapStore { log.debug("loadAllKeys from " + getTableName()); List keys; try { - keys = jdbcTemplate.query("select id from " + getTableName() + " where cast(generation_time as date) = ?", - new Object[]{LocalDate.now()}, + keys = jdbcTemplate.query("select id from " + getTableName(), (resultSet, i) -> resultSet.getLong("id")); } catch (Throwable e) { log.error("{}", ExceptionUtils.getStackTrace(e)); diff --git a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf21MapStore.java b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf21MapStore.java index 55e286a3d..9df5ea530 100644 --- a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf21MapStore.java +++ b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf21MapStore.java @@ -10,7 +10,6 @@ import ru.spcex.platform.utils.time.TimeUtil; import java.sql.ResultSet; import java.sql.SQLException; -import java.time.LocalDate; import java.util.List; @Component @@ -42,8 +41,7 @@ public class SDf21MapStore extends TemplateMapStore { log.debug("loadAllKeys from " + getTableName()); List keys; try { - keys = jdbcTemplate.query("select id from " + getTableName() + " where cast(generation_time as date) = ?", - new Object[]{LocalDate.now()}, + keys = jdbcTemplate.query("select id from " + getTableName(), (resultSet, i) -> resultSet.getLong("id")); } catch (Throwable e) { log.error("{}", ExceptionUtils.getStackTrace(e)); diff --git a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf57MapStore.java b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf57MapStore.java index 8ca395465..5e9670970 100644 --- a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf57MapStore.java +++ b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/SDf57MapStore.java @@ -10,7 +10,6 @@ import ru.spcex.platform.utils.time.TimeUtil; import java.sql.ResultSet; import java.sql.SQLException; -import java.time.LocalDate; import java.util.List; @Component @@ -42,8 +41,7 @@ public class SDf57MapStore extends TemplateMapStore { log.debug("loadAllKeys from " + getTableName()); List keys; try { - keys = jdbcTemplate.query("select id from " + getTableName() + " where cast(generation_time as date) = ?", - new Object[]{LocalDate.now()}, + keys = jdbcTemplate.query("select id from " + getTableName(), (resultSet, i) -> resultSet.getLong("id")); } catch (Throwable e) { log.error("{}", ExceptionUtils.getStackTrace(e)); diff --git a/platform-parent/platform-utils/src/main/java/ru/spcex/platform/utils/number/BigDecimalUtil.java b/platform-parent/platform-utils/src/main/java/ru/spcex/platform/utils/number/BigDecimalUtil.java index 9bc4ac8ba..5f3400f05 100644 --- a/platform-parent/platform-utils/src/main/java/ru/spcex/platform/utils/number/BigDecimalUtil.java +++ b/platform-parent/platform-utils/src/main/java/ru/spcex/platform/utils/number/BigDecimalUtil.java @@ -20,6 +20,20 @@ public class BigDecimalUtil { } } + /** + * для парсинга вида '-00001234'/'00000123' + * @param number + * @return + */ + public static BigDecimal parseLeadingZeroes(String number) { + try { + return new BigDecimal(number); + } catch (Throwable e) { + //оставил чтобы стукнул NLP если где-то окажется неровный формат чтобы уточнить поведение + return null; + } + } + /** * Для кейсов когда есть String поле с десятичной частью, и хотим ограничить число знаков после запятой */