diff --git a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java index 0f9e0153d..340cd5b18 100644 --- a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java +++ b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java @@ -72,7 +72,7 @@ public class BankAccountValidationConfig { AccountError.DictionaryNotFound, false), ValidateSymbolsRule.notRequired("БИК", - BankAccountUpdateRequest::getBankIdentificationCode, + BankAccountNewRequest::getBankIdentificationCode, AccountError.BadSymbol), ValidateSymbolsRule.required("Наименование компании", BankAccountNewRequest::getBankName, diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/register/RegistryController.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/register/RegistryController.java index 0fefc1160..77f2cfbb7 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/register/RegistryController.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/register/RegistryController.java @@ -192,7 +192,7 @@ public class RegistryController extends AbstractQueueController { @ApiOperation(value = "Вывод средств с регистра невыясненных активов") @ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = CudResponse.class), @ApiResponse(code = 400, message = "Ошибка валидации", response = BasicSpcexResponse.class)}) - @RequestMapping(value = "/withdrawalFunds", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "/withdrawalFunds", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public CudResponse dmauRegistryNew( @ApiParam(value = "Параметры команды в JSON формате.", required = true) diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterNewAction.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterNewAction.java index 74b308982..466bb6a08 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterNewAction.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterNewAction.java @@ -12,9 +12,12 @@ public class RiskParameterNewAction implements IAction @ApiModelProperty(value = "Курс.", example = "1234") @JsonProperty private BigDecimal value; - @ApiModelProperty(value = "Ставка сопряженной валюты.", example = "1234") + @ApiModelProperty(value = "Ставка переноса при нехватке рублей.", example = "1234") @JsonProperty private BigDecimal quoteValue; + @ApiModelProperty(value = "Ставка переноса при нехватке валюты.", example = "1234") + @JsonProperty + private BigDecimal penaltyValue; @ApiModelProperty(value = "Код расчёта.", example = "1234") @JsonProperty private String settlementType; @@ -30,6 +33,7 @@ public class RiskParameterNewAction implements IAction RiskParameterNewRequest request = new RiskParameterNewRequest(); request.setValue(this.value); request.setQuoteValue(this.quoteValue); + request.setPenaltyValue(this.penaltyValue); request.setSettlementType(this.settlementType); request.setCurrencyPairId(this.currencyPairId); request.setWorkflowStatus(this.workflowStatus); @@ -73,4 +77,8 @@ public class RiskParameterNewAction implements IAction public void setWorkflowStatus(String workflowStatus) { this.workflowStatus = workflowStatus; } + + public void setPenaltyValue(BigDecimal penaltyValue) { + this.penaltyValue = penaltyValue; + } } diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterUpdateAction.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterUpdateAction.java index 581628feb..6c3d9f073 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterUpdateAction.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/securities/RiskParameterUpdateAction.java @@ -15,9 +15,12 @@ public class RiskParameterUpdateAction implements IAction - + @@ -1478,8 +1478,6 @@ - - @@ -2570,20 +2568,23 @@ - + + - + - + + - + - + + @@ -2726,7 +2727,7 @@ - + @@ -2748,7 +2749,7 @@ - + diff --git a/clearing-parent/classes/src/main/java/ru/clearing/classes/statics/data/security/RiskParameter.java b/clearing-parent/classes/src/main/java/ru/clearing/classes/statics/data/security/RiskParameter.java index 7eb083de2..cbafa813f 100644 --- a/clearing-parent/classes/src/main/java/ru/clearing/classes/statics/data/security/RiskParameter.java +++ b/clearing-parent/classes/src/main/java/ru/clearing/classes/statics/data/security/RiskParameter.java @@ -10,6 +10,7 @@ public class RiskParameter extends BusinessObject { private Long currencyPairId; private String workflowStatus; private BigDecimal quoteValue; + private BigDecimal penaltyValue; public BigDecimal getValue() { return value; @@ -58,4 +59,12 @@ public class RiskParameter extends BusinessObject { public void setQuoteValue(BigDecimal quoteValue) { this.quoteValue = quoteValue; } + + public BigDecimal getPenaltyValue() { + return penaltyValue; + } + + public void setPenaltyValue(BigDecimal penaltyValue) { + this.penaltyValue = penaltyValue; + } } 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 049b76667..bdce7f0da 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 @@ -1,5 +1,6 @@ package ru.spcex.clearing.config; +import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -41,11 +42,14 @@ 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.domain.cud.payment.PIClearingOutbondActionNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.registry.DmauRegistryRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.IdentificationFundsRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.RegistryChangeRefundDateRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.RegistryChangeStatusExtractRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.RegistryReturnDepositRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.RegistrySplitDepositActionRequest; +import ru.spcex.clearing.service.AssetTrio; +import ru.spcex.clearing.service.registry.AssetTBFProcessing; import ru.spcex.clearing.service.validation.AnltAccLoad; import ru.spcex.clearing.service.validation.ClrngValidationStored; import ru.spcex.clearing.service.validation.CompanyByTradingCodeValidationRule; @@ -68,18 +72,27 @@ import ru.spcex.clearing.service.validation.SecurityBySecurityCodeValidationRule import ru.spcex.clearing.service.validation.SessionTerminationValidationRule; import ru.spcex.clearing.service.validation.SplitDepositRequestValidationRule; import ru.spcex.clearing.service.validation.StatusExtractValidationRule; +import ru.spcex.clearing.service.validation.WithdrawalFundsValidationRule; +import ru.spcex.clearing.service.validation.WithdrawalFundsValidationStore; import ru.spcex.clearing.util.security.UserRoleVerification; import ru.spcex.clearing.validation.common.rules.DictionaryPresentRule; import ru.spcex.clearing.validation.common.rules.FieldRequiredRule; import ru.spcex.clearing.validation.common.rules.IdPresentRule; import ru.spcex.platform.enumeration.ClearingCategory; +import ru.spcex.platform.enumeration.CompanySymbol; 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.IdEqualPredicate; import ru.spcex.platform.imdg.validation.ImdgValidationContext; import ru.spcex.platform.imdg.validation.LogPrefixId; import ru.spcex.platform.imdg.validation.rule.CurrencyCodeValidationRule; import ru.spcex.platform.imdg.validation.rule.PresentById; +import ru.spcex.platform.imdg.validation.rule.PresentByPredicate; +import ru.spcex.platform.utils.enumeration.EnumMessage; import ru.spcex.platform.utils.enumeration.IMessageResolver; +import ru.spcex.platform.utils.validation.IValidationRule; import ru.spcex.platform.utils.validation.IValidator; import ru.spcex.platform.utils.validation.ValidatorImpl; @@ -113,8 +126,9 @@ public class ValidationConfig { Imdg pmtImdg; Imdg ctgrImdg; ImdgProvider imdgProvider; + AssetTBFProcessing assetsMng; - public ValidationConfig(ImdgProvider imdgProvider) { + public ValidationConfig(ImdgProvider imdgProvider, AssetTBFProcessing assetsMng) { this.imdgRelation = imdgProvider.getImdg(IMDGDistributedNames.Map_Relation, Relation.class); this.imdgCompany = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); this.imdgCurrency = imdgProvider.getImdg(IMDGDistributedNames.Map_Currency, Currency.class); @@ -142,6 +156,7 @@ public class ValidationConfig { this.currencyPairSecurityImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_CurrencyPairSecurity, CurrencyPairSecurity.class); this.digitalCertificateSecurityImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_DigitalCertificateSecurity, DigitalCertificateSecurity.class); this.imdgProvider = imdgProvider; + this.assetsMng = assetsMng; } //@Bean("adminRoleVerification") @@ -399,6 +414,80 @@ public class ValidationConfig { }; } + @Bean("withdrawalFundsValidator") + public Function withdrawalFundsValidator() { + return identificationFundsRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(identificationFundsRequest); + context.addImdg(IMDGDistributedNames.Map_Account, imdgAccount); + context.addImdg(IMDGDistributedNames.Map_Company, imdgCompany); + context.addImdg(IMDGDistributedNames.Map_CompanySymbols, imdgCompanySymbols); + context.addImdg(IMDGDistributedNames.Map_TradingClearingRegistry, imdgTradingClearingRegistry); + context.addImdg(IMDGDistributedNames.Map_TradingClearingRegistryList, imdgTradingClearingRegistryList); + + context.setLogPrefix(dmauRegistryRequest -> + "[debitCorrAccount:%s]".formatted(dmauRegistryRequest.getDebitCorrAccount() + )); + + ImdgPredicateBuilder cmpSymbPb = imdgCompanySymbols.predicateBuilder(); + var cmpSymbCheck = PresentByPredicate.builder( + (Function) req -> cmpSymbPb.and( + cmpSymbPb.equals("companyId", req.getSenderId()), + cmpSymbPb.equals("companySymbol", CompanySymbol.BIC.getKey()) + )) + .mapName(IMDGDistributedNames.Map_CompanySymbols) + .errorProvider(req -> new EnumMessage(ClearingError.RecordNotFound, "companySymbol")) + .store(WithdrawalFundsValidationStore.CompanySymbol) + .build() + ; + + ImdgPredicateBuilder cmpPb = imdgCompany.predicateBuilder(); + var cmpCheck = PresentByPredicate.builder(new IdEqualPredicate<>( + cmpPb, DmauRegistryRequest::getSenderId + )) + .mapName(IMDGDistributedNames.Map_Company) + .errorProvider(req -> new EnumMessage(ClearingError.RecordNotFound, "company")) + .store(WithdrawalFundsValidationStore.Company) + .build(); + + ImdgPredicateBuilder accPb = imdgAccount.predicateBuilder(); + var accCheck = PresentByPredicate.builder(new IdEqualPredicate<>( + accPb, DmauRegistryRequest::getCreditLegAccountId + )) + .mapName(IMDGDistributedNames.Map_Account) + .errorProvider(req -> new EnumMessage(ClearingError.RecordNotFound, "account")) + .store(WithdrawalFundsValidationStore.Account) + .build(); + + var assetPresent = new IValidationRule>() { + @Override + public Optional validate(ImdgValidationContext ctx) { + Company cmp = ctx.getStoredObject(WithdrawalFundsValidationStore.Company); + Account acc = ctx.getStoredObject(WithdrawalFundsValidationStore.Account); + Optional assetsTrio = assetsMng.searchMoneyByAccAndCompany(cmp.getId(), acc.getId()); + if (assetsTrio.isEmpty()) { + return of(ClearingError.RecordNotFound, "AM_F/AM_B/AM_T"); + } + ctx.storeObject(WithdrawalFundsValidationStore.AssetTrio, assetsTrio.get()); + return empty(); + } + + @Override + public String ruleName() { + return "assets-present"; + } + }; + + return new ValidatorImpl<>(context, + WithdrawalFundsValidationRule.Fields, + cmpSymbCheck, + cmpCheck, + accCheck, + assetPresent + ); + }; + } + @Bean("splitDepositValidator") public Function splitDepositValidator() { return refundDateRequest -> { diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/messages/ClearingMessage.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/messages/ClearingMessage.java index cf150d1ff..d349eb641 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/messages/ClearingMessage.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/messages/ClearingMessage.java @@ -6,6 +6,7 @@ public enum ClearingMessage implements IEnumKey { WithdrawalFreeFromTkr("payment-instruction.purpose.withdrawal.tcr"), WithdrawalOfFunds("payment-instruction.purpose.withdrawal"), urgent("sdf54.urgent"), + withdrawalOfFundsDmau("withdrawal.funds.dmau"), ; private final String key; diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java index 5a4698be4..ebab4d998 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java @@ -23,6 +23,7 @@ import ru.spcex.clearing.platform.messaging.domain.cud.clearing.SessionContinueE import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonIdRequest; import ru.spcex.clearing.platform.messaging.domain.cud.gateway.AssetOperationApprovalRequest; import ru.spcex.clearing.platform.messaging.domain.cud.payment.PIClearingOutbondActionNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.registry.DmauRegistryRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.IdentificationFundsRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.RegistryChangeRefundDateRequest; import ru.spcex.clearing.platform.messaging.domain.cud.registry.RegistryChangeStatusExtractRequest; @@ -40,6 +41,7 @@ import ru.spcex.clearing.service.execution.ExecutionFondComponent; import ru.spcex.clearing.service.executors.Sdf06Executor; import ru.spcex.clearing.service.executors.Sdf10Executor; import ru.spcex.clearing.service.payment.PaymentInstructionOutboundService; +import ru.spcex.clearing.service.payment.WithdrawalFundsService; import ru.spcex.clearing.session.stage.SessionTerminator; import ru.spcex.clearing.session.stage.TaskType; import ru.spcex.clearing.session.stage.impl.BalanceRevise; @@ -65,6 +67,7 @@ public class EventsReceiver extends QueueConsumerV2 implements InitializingBean private final StatementServiceV2 statementService; private final SessionTerminator sessionTerminator; private final PaymentInstructionOutboundService pmtOutboundService; + private final WithdrawalFundsService withdrawalFundsService; private final ExecutionCurrencyComponent execCurrUpload; private final ExecutionDepositComponent execDepUpload; private final ExecutionFondComponent execFondUpload; @@ -81,7 +84,7 @@ public class EventsReceiver extends QueueConsumerV2 implements InitializingBean Sdf05Sender sdf05Sender, StatementServiceV2 statementService, SessionTerminator sessionTerminator, - PaymentInstructionOutboundService pmtOutboundService, + PaymentInstructionOutboundService pmtOutboundService, WithdrawalFundsService withdrawalFundsService, ExecutionCurrencyComponent execCurrUpload, ExecutionDepositComponent execDepUpload, ExecutionFondComponent execFondUpload) { @@ -98,6 +101,7 @@ public class EventsReceiver extends QueueConsumerV2 implements InitializingBean this.statementService = statementService; this.sessionTerminator = sessionTerminator; this.pmtOutboundService = pmtOutboundService; + this.withdrawalFundsService = withdrawalFundsService; this.execCurrUpload = execCurrUpload; this.execDepUpload = execDepUpload; this.execFondUpload = execFondUpload; @@ -144,6 +148,9 @@ public class EventsReceiver extends QueueConsumerV2 implements InitializingBean callback(PIClearingOutbondActionNewRequest.class) .setFunction(pmtOutboundService::sendOutBoundPayment) .forDestination(Consts.PAYMENT_INSTRUCTION_CLEARING_OUTBOUND_ACTION, callbacks::put); + callback(DmauRegistryRequest.class) + .setFunction(withdrawalFundsService::withdrawalFunds) + .forDestination(Consts.REGISTRY_DMAU_CREATE_ACTION, callbacks::put); callback(StatementRequest.class) .setConsumer(statementService::processReq) .forDestination(Consts.STATEMENT_PROCESS, callbacks::put); 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 3bc18ade7..4ad0cc08d 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 @@ -124,9 +124,11 @@ public class Sdf54Creator { sDf54.setKpp_deb(kppDeb); } - Account accDebAcc = accountImdg.getSingleObjectByID(pmt.getDebitLeg_accountId()); - if (accDebAcc == null || !AccountStatus.ACTIVE.equalsByKey(accDebAcc.getStatus())) { - log.warn("paymentInstruction[{}].DebitLeg_accountId={} account not exist or not active.", pmt.getId(), pmt.getDebitLeg_accountId()); + if (pmt.getDebitLeg_accountId() != null) { + Account accDebAcc = accountImdg.getSingleObjectByID(pmt.getDebitLeg_accountId()); + if (accDebAcc == null || !AccountStatus.ACTIVE.equalsByKey(accDebAcc.getStatus())) { + log.warn("paymentInstruction[{}].DebitLeg_accountId={} account not exist or not active.", pmt.getId(), pmt.getDebitLeg_accountId()); + } } BankAccount bnkAcc = bankAccountImdg.getFirstObjectBySQL("accountId = %d".formatted(pmt.getDebitLeg_accountId())); if (bnkAcc != null) { 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 index f7ac9ec83..5dd4a02cd 100644 --- 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 @@ -69,7 +69,11 @@ public class Sdf55Executor { pmt.setUpdated(Instant.now()); pmtImdg.update(pmt); String curr = TextUtil.isEmpty(sDf55.getPay_val()) ? CurrencyCode.RUB.getKey() : sDf55.getPay_val(); - dmiService.setContract(tcr.getId(), curr, sDf54.getDocnm_ref(), sDf55.getDocnmprev()); + if (tcr != null) { + dmiService.setContract(tcr.getId(), curr, sDf54.getDocnm_ref(), sDf55.getDocnmprev()); + } else { + dmiService.setContractByAcc(pmt.getCreditLeg_accountId(), curr, sDf54.getDocnm_ref(), sDf55.getDocnmprev()); + } assets.process(assetTrio.a__b(), assetTrio.a__t(), assetTrio.a__f(), diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf57Executor.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf57Executor.java index ec201d1e6..f62f6e24c 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf57Executor.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/executors/Sdf57Executor.java @@ -453,6 +453,7 @@ public class Sdf57Executor extends AbstractExecutor { Company cmp = stmtCmpAcc.company(); Account acc = stmtCmpAcc.account(); Optional asts = createRegistryIfNeeded.apply(stmtCmpAcc); + InOutDirection direction = IEnumKey.getEnumByKey(InOutDirection.class, stmt.getInOutDirection()); if (accountIsAnlt(acc)) { AnltSearcher.AnltSearch searchResult = anltSearcher.loadByAnlt(stmt.getComment(), sdf57.getPay_val()); if (searchResult.isFound()) { @@ -475,7 +476,7 @@ public class Sdf57Executor extends AbstractExecutor { assets.process(trio.a__b(), trio.a__t(), trio.a__f(), BigDecimal.ZERO); }); } - } else if (InOutDirection.in.equals(IEnumKey.getEnumByKey(InOutDirection.class, stmt.getInOutDirection()))) { + } else { log.debug("stmt.id={} comment='{}' error: {}. Operating through DMAU registry", stmt.getId(), stmt.getComment(), @@ -499,17 +500,14 @@ public class Sdf57Executor extends AbstractExecutor { .build(); newDmau.setRegistryUnit(RegistryUnit.U.getKey()); newDmau.setRegistryCode(RegistryUtil.clearingCode(newDmau)); - newDmau.setBalance(safeBD(stmt.getAmount())); + updateDmauReg(stmt, newDmau); registryImdg.insert(newDmau); log.debug("stmt.id={} created DMAU.id={}", stmt.getId(), newDmau.getId()); }); //DMAU подразумевает увеличение остатка поля balance и credit registry.code=DMAU String genTime = formatInstant(sdf57.getGenerationTime(), readableDateTime); - notification.sendNotification(ObjectType.rgst, - ("Зачисление средств на регистр невыясненных денежных средств. " - + "Дата и время поступления: %s. " - + "ID платежного поручения: %d").formatted(genTime, sdf57.getDbfId()), - Priority.HIGH); + String ntfMsg = dmauNotificationText(direction, genTime, sdf57); + notification.sendNotification(ObjectType.rgst, ntfMsg, Priority.HIGH); } } else if (OperationStatus.Executed.equalsByKey(stmt.getOperationStatus())) { if (asts.isPresent()) { @@ -555,6 +553,25 @@ public class Sdf57Executor extends AbstractExecutor { return result; } + private String dmauNotificationText(InOutDirection direction, String genTime, SDf57 sdf57) { + if (direction == null) return "notification error: unknown direction null"; + switch (direction) { + case in -> { + return ("Зачисление средств на регистр невыясненных денежных средств. " + + "Дата и время поступления: %s. " + + "ID платежного поручения: %d").formatted(genTime, sdf57.getDbfId()); + } + case out -> { + return ("Списание средств с регистра невыясненных денежных средств %s. " + + "Дата и время списания: %s. " + + "Объем списания: %s").formatted( + sdf57.getAcc_deb(), genTime, sdf57.getSum_deb() + ); + } + default -> {return "notification error: unknown direction " + direction;} + } + } + private boolean accountIsAnlt(Account account) { if (account == null) return false; boolean anlt = AccountType.Anlt.getKey().equals(account.getAccountType()); diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/payment/WithdrawalFundsService.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/payment/WithdrawalFundsService.java new file mode 100644 index 000000000..05cba3432 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/payment/WithdrawalFundsService.java @@ -0,0 +1,194 @@ +package ru.spcex.clearing.service.payment; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDate; +import java.util.Optional; +import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.company.CompanySymbols; +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.clearing.classes.statics.data.sdf.SDf54; +import ru.spcex.clearing.error.ClearingError; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.messages.ClearingMessage; +import ru.spcex.clearing.platform.messaging.domain.BaseRequest; +import ru.spcex.clearing.platform.messaging.domain.Consts; +import ru.spcex.clearing.platform.messaging.domain.cud.clearing.SdfClearingRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.registry.DmauRegistryRequest; +import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.clearing.service.AssetTrio; +import ru.spcex.clearing.service.Sdf54Creator; +import ru.spcex.clearing.service.SpecifUtil; +import ru.spcex.clearing.service.registry.AssetTBFProcessing; +import ru.spcex.clearing.service.registry.DmiService; +import ru.spcex.clearing.service.validation.WithdrawalFundsValidationStore; +import ru.spcex.clearing.util.LocaleUtil; +import ru.spcex.clearing.util.security.UserRoleVerification; +import ru.spcex.clearing.util.services.RequestHelper; +import ru.spcex.platform.enumeration.CurrencyCode; +import ru.spcex.platform.enumeration.InOutDirection; +import ru.spcex.platform.enumeration.TransactionStatus; +import ru.spcex.platform.enumeration.UserRole; +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.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.enumeration.IMessageResolver; +import ru.spcex.platform.utils.localization.SpringPropertiesLocalizer; +import ru.spcex.platform.utils.log.ExceptionUtils; +import ru.spcex.platform.utils.text.TextUtil; +import ru.spcex.platform.utils.time.TimeUtil; +import ru.spcex.platform.utils.validation.IValidator; + +@Service +public class WithdrawalFundsService { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Function withdrawalFundsVal; + private final Sdf54Creator sdf54Creator; + private final Imdg sdf54Imdg; + private final Imdg pmtImdg; + private final Imdg rgsImdg; + private final KafkaSender kafkaSender; + private final ImdgId idGen; + private final RequestHelper reqHelp; + private final UserRoleVerification rights; + private final IMessageResolver msgResolver; + private final SpringPropertiesLocalizer localizer; + private final AssetTBFProcessing assetsMng; + private final DmiService dmiService; + + public WithdrawalFundsService(Function withdrawalFundsVal, Sdf54Creator sdf54Creator, ImdgProvider imdgProvider, KafkaSender kafkaSender, RequestHelper reqHelp, UserRoleVerification rights, IMessageResolver msgResolver, SpringPropertiesLocalizer localizer, AssetTBFProcessing assetsMng, DmiService dmiService) { + this.withdrawalFundsVal = withdrawalFundsVal; + this.sdf54Creator = sdf54Creator; + this.kafkaSender = kafkaSender; + this.sdf54Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf54, SDf54.class); + this.pmtImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class); + this.rgsImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); + this.idGen = imdgProvider.getImdgIdGenerator(); + this.reqHelp = reqHelp; + this.rights = rights; + this.msgResolver = msgResolver; + this.localizer = localizer; + this.assetsMng = assetsMng; + this.dmiService = dmiService; + } + + public RequestInfoUpdate withdrawalFunds(BaseRequest req) { + if (!rights.userHasRole(req.getUserId(), UserRole.Admin)) { + return reqHelp.error(req.getId(), ClearingError.UserVerifyDenial); + } + DmauRegistryRequest payload = req.getRequestPayload(); + IValidator validator = withdrawalFundsVal.apply(payload); + Optional err = validator.tillFirstError(); + if (err.isPresent()) { + log.error("DmauRegistryRequest.id={} debitCorrAccount={} validation error: {}", + req.getId(), + payload != null ? payload.getDebitCorrAccount() : null, + msgResolver.resolve(err.get())); + return reqHelp.error(req.getId(), err.get()); + } + log.info("DmauRegistryRequest.id={} debitCorrAccount={} validation passed", req.getId(), payload.getDebitCorrAccount()); + CompanySymbols cmpSymb = validator.getStored(WithdrawalFundsValidationStore.CompanySymbol); + Company cmp = validator.getStored(WithdrawalFundsValidationStore.Company); + Account acc = validator.getStored(WithdrawalFundsValidationStore.Account); + AssetTrio assets = validator.getStored(WithdrawalFundsValidationStore.AssetTrio); + String curr = TextUtil.isEmpty(payload.getSecuritySymbol()) ? CurrencyCode.RUB.getKey() : payload.getSecuritySymbol(); + + Instant now = Instant.now(); + LocalDate nowDate = TimeUtil.toLocalDate(now); + PaymentInstruction pmtIns = new PaymentInstruction(); + pmtIns.setSenderId(payload.getSenderId()); + pmtIns.setPayeeBic(cmpSymb.getCompanySymbolValue()); + pmtIns.setAdresseeBic(payload.getDebitBIC()); + pmtIns.setPayeeBankName(cmp.getShortName()); + pmtIns.setAddresseeBankName(payload.getDebitBankName()); + pmtIns.setPaymentDate(now); + pmtIns.setPaymentPurpose(extractPaymentPurpose(payload)); + pmtIns.setSettlementDate(nowDate); + pmtIns.setCreditLeg_amount(payload.getAmount()); + pmtIns.setDebitLeg_amount(payload.getAmount()); + pmtIns.setCreditLeg_accountId(payload.getCreditLegAccountId()); + pmtIns.setCreditLeg_account(acc.getAccount()); + pmtIns.setDebit_csAccount(payload.getDebitCorrAccount()); + pmtIns.setDebitLeg_account(payload.getDebitLegAccount()); + pmtIns.setCreditLeg_direction(InOutDirection.out.getKey()); + pmtIns.setDebitLeg_direction(InOutDirection.in.getKey()); + pmtIns.setCreditLeg_currencyCode(payload.getSecuritySymbol()); + pmtIns.setDebitLeg_currencyCode(payload.getSecuritySymbol()); + pmtIns.setCreditLeg_securityId(payload.getSecurityId()); + pmtIns.setDebitLeg_securityId(payload.getSecurityId()); + pmtIns.setTransactionStatus(TransactionStatus.stld.getKey()); + pmtIns.setCreated(now); + pmtIns.setClearingDate(nowDate); + pmtImdg.insert(pmtIns); + log.info("created PaymentInstruction.id={}", pmtIns.getId()); + SDf54 sDf54; + try { + sDf54 = sdf54Creator.create(pmtIns); + } catch (IllegalStateException e) { + log.error(ExceptionUtils.getStackTrace(e)); + return failedResponse(req, now, pmtIns); + } + sDf54.setId(idGen.nextId()); + sDf54.setGenerationId(pmtIns.getId()); + sDf54.setRbankcode(pmtIns.getAdresseeBic()); + sDf54.setC_acc_cred(pmtIns.getDebit_csAccount()); + SpecifUtil.fillSegmentsBy35Symbols(pmtIns.getAddresseeBankName(), + sDf54::setRbanknam1, + sDf54::setRbanknam2, + sDf54::setRbanknam3, + sDf54::setRbanknam4, + sDf54::setRbanknam5); + SpecifUtil.fillSegmentsBy35Symbols(payload.getDebitCompanyName(), + sDf54::setRclientn1, + sDf54::setRclientn2, + sDf54::setRclientn3, + sDf54::setRclientn4 + ); + sDf54.setInn_cred(payload.getDebitINN()); + sDf54.setKpp_cred(payload.getDebitKPP()); + sdf54Imdg.insert(sDf54); + log.info("created sdf54.id={}", sDf54.getId()); + + Registry d__i = dmiService.createDmi(payload.getAmount().negate(), assets.a__t()); + d__i.setComment(sDf54.getDocnm_ref()); + rgsImdg.insert(d__i); + log.debug("created {}.id={}; recalculating assests...", d__i.getRegistryCode(), d__i.getId()); + assetsMng.process(assets.a__b(), assets.a__t(), assets.a__f(), BigDecimal.ZERO); + + SdfClearingRequest exp = new SdfClearingRequest(); + exp.setGroupId(sDf54.getGenerationId()); + kafkaSender.sendRequestToQueue(Consts.SDF54_PROCESS, exp); + + return reqHelp.success(req.getId()); + } + + private RequestInfoUpdate failedResponse(BaseRequest req, + Instant now, + PaymentInstruction pmtIns) { + pmtIns.setTransactionStatus(TransactionStatus.fail.getKey()); + pmtIns.setUpdated(now); + pmtImdg.update(pmtIns); + return reqHelp.error(req.getId(), ClearingError.GeneralError); + } + + private String extractPaymentPurpose(DmauRegistryRequest payload) { + if (!TextUtil.isEmpty(payload.getPaymentPurpose())) { + return payload.getPaymentPurpose(); + } + if (CurrencyCode.RUB.equalsByKey(payload.getSecuritySymbol())) { + return localizer.resolve(ClearingMessage.withdrawalOfFundsDmau, LocaleUtil.ru, payload.getSecuritySymbol()); + } else { + return localizer.resolve(ClearingMessage.withdrawalOfFundsDmau, LocaleUtil.eng, payload.getSecuritySymbol()); + } + } + +} + diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/registry/DmiService.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/registry/DmiService.java index 72581f901..16daa75ab 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/registry/DmiService.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/registry/DmiService.java @@ -92,42 +92,55 @@ public class DmiService { String securitySymbol, String comment, String contract) { - findDmiByComment(tcrId, securitySymbol, comment).ifPresentOrElse(d__i -> { - d__i.setContract(contract); - d__i.setUpdated(Instant.now()); - try { - Long dbfId = Long.valueOf(contract); - SDf57 sdf57 = sDf57Imdg.getFirstObjectBySQL("dbfId=%d".formatted(dbfId)); - if (sdf57 != null) { - ImdgPredicateBuilder pb = statementImdg.predicateBuilder(); - ImdgPredicate stmtPrdct = pb.and( - pb.equals("inSDfId", sdf57.getId()), - pb.equals("inOutSDfType", InOutSDfType.type57.getKey()) - ); - Statement stmt = statementImdg.getFirstObjectByPredicate(stmtPrdct); - if (stmt != null && OperationStatus.Executed.equalsByKey(stmt.getOperationStatus())) { - d__i.setRegistryStatus(RegistryStatus.OK.getKey()); - } else { - log.warn("tcrId={} securitySymbol={} comment={} sdf57 dbfId={} found, stmt.id={} status {}. d**i status will not be updated", - tcrId, - securitySymbol, - comment, - dbfId, - stmt != null ? stmt.getId() : null, - stmt != null ? stmt.getOperationStatus() : null); - } - } - } catch (Throwable e) { - log.error("sdf57 search by {}: error {}", - contract, - ExceptionUtils.getStackTrace(e)); - } - rgsImdg.update(d__i); - log.trace("set {}.id={} contract {}", d__i.getRegistryCode(), d__i.getId(), contract); - }, () -> log.trace("didn't find DM*I by tcr.id={} securitySymbol={} comment={}", + findDmiByComment(tcrId, securitySymbol, comment).ifPresentOrElse( + (d__i) -> updateD__i(d__i, contract, securitySymbol, comment), + () -> log.trace("didn't find DM*I by tcr.id={} securitySymbol={} comment={}", tcrId, securitySymbol, comment)); } + public void setContractByAcc(Long accId, + String securitySymbol, + String comment, + String contract) { + findDmiByCommentAcc(accId, securitySymbol, comment).ifPresentOrElse( + (d__i) -> updateD__i(d__i, contract, securitySymbol, comment), + () -> log.trace("didn't find DM*I by acc.id={} securitySymbol={} comment={}", + accId, securitySymbol, comment)); + } + + private void updateD__i(Registry d__i, String contract, String securitySymbol, String comment) { + d__i.setContract(contract); + d__i.setUpdated(Instant.now()); + try { + Long dbfId = Long.valueOf(contract); + SDf57 sdf57 = sDf57Imdg.getFirstObjectBySQL("dbfId=%d".formatted(dbfId)); + if (sdf57 != null) { + ImdgPredicateBuilder pb = statementImdg.predicateBuilder(); + ImdgPredicate stmtPrdct = pb.and( + pb.equals("inSDfId", sdf57.getId()), + pb.equals("inOutSDfType", InOutSDfType.type57.getKey()) + ); + Statement stmt = statementImdg.getFirstObjectByPredicate(stmtPrdct); + if (stmt != null && OperationStatus.Executed.equalsByKey(stmt.getOperationStatus())) { + d__i.setRegistryStatus(RegistryStatus.OK.getKey()); + } else { + log.warn("securitySymbol={} comment={} sdf57 dbfId={} found, stmt.id={} status {}. d**i status will not be updated", + securitySymbol, + comment, + dbfId, + stmt != null ? stmt.getId() : null, + stmt != null ? stmt.getOperationStatus() : null); + } + } + } catch (Throwable e) { + log.error("sdf57 search by {}: error {}", + contract, + ExceptionUtils.getStackTrace(e)); + } + rgsImdg.update(d__i); + log.trace("set {}.id={} contract {}", d__i.getRegistryCode(), d__i.getId(), contract); + } + /** * ищем D**I по tcrId/security/contract @@ -178,6 +191,20 @@ public class DmiService { return Optional.ofNullable(d__i); } + private Optional findDmiByCommentAcc(Long accId, String securitySymbol, String comment) { + ImdgPredicateBuilder pb = rgsImdg.predicateBuilder(); + RegistryTradingParams rgsCde = RegistryTradingParams.D__I; + ImdgPredicate prdct = pb.and( + pb.sql(RegistryCodeSqlBuilder.getInstance(rgsCde).build()), + pb.equals("accountId", accId), + pb.equals("securitySymbol", securitySymbol), + pb.equals("comment", comment) + ); + log.trace("searching D**I by {}", prdct); + Registry d__i = rgsImdg.getFirstObjectByPredicate(prdct); + return Optional.ofNullable(d__i); + } + public Optional createDmi(Long tcrId, String securitySymbol, BigDecimal summ) { diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/Sdf55ValidationRule.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/Sdf55ValidationRule.java index 58d8f2978..16a1b5d14 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/Sdf55ValidationRule.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/Sdf55ValidationRule.java @@ -104,9 +104,10 @@ public enum Sdf55ValidationRule implements IValidationRule> { + Fields() { + @Override + public Optional validate(ImdgValidationContext context) { + DmauRegistryRequest validatedObject = context.getValidatedObject(); + if (validatedObject.getAmount() == null || validatedObject.getAmount().compareTo(BigDecimal.ZERO) <= 0) { + return of(ClearingError.WrongField, "amount"); + } + if (validatedObject.getBalance() == null || validatedObject.getBalance().compareTo(validatedObject.getAmount()) < 0) { + return of(ClearingError.WrongField, "balance"); + } + if (validatedObject.getSenderId() == null) { + return of(ClearingError.WrongField, "senderId"); + } + return empty(); + } + }, + ; + private final static Logger log = LoggerFactory.getLogger(WithdrawalFundsValidationRule.class); + + @Override + public String ruleName() { + return "WithdrawalFundsValidationRule." + name(); + } +} \ No newline at end of file diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/WithdrawalFundsValidationStore.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/WithdrawalFundsValidationStore.java new file mode 100644 index 000000000..40298589b --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/validation/WithdrawalFundsValidationStore.java @@ -0,0 +1,9 @@ +package ru.spcex.clearing.service.validation; + +public enum WithdrawalFundsValidationStore { + CompanySymbol, + Company, + Account, + AssetTrio, + ; +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/util/LocaleUtil.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/util/LocaleUtil.java index 864b5a36d..344cf512c 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/util/LocaleUtil.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/util/LocaleUtil.java @@ -5,8 +5,8 @@ import ru.clearing.classes.statics.data.account.Account; import ru.spcex.platform.enumeration.CurrencyCode; public class LocaleUtil { - private static final Locale ru = new Locale("ru"); - private static final Locale eng = new Locale("eng"); + public static final Locale ru = new Locale("ru"); + public static final Locale eng = new Locale("eng"); public static boolean isRus(Locale locale) { return ru.equals(locale); diff --git a/clearing-parent/clearing-service/src/main/resources/messages/clearing_eng.properties b/clearing-parent/clearing-service/src/main/resources/messages/clearing_eng.properties index 7ddb3cb64..69a334b22 100644 --- a/clearing-parent/clearing-service/src/main/resources/messages/clearing_eng.properties +++ b/clearing-parent/clearing-service/src/main/resources/messages/clearing_eng.properties @@ -1,3 +1,4 @@ payment-instruction.purpose.withdrawal.tcr=Debt free cash withdrawal from TKR {0}. payment-instruction.purpose.withdrawal=Debt free cash withdrawal. -sdf54.urgent=urgent \ No newline at end of file +sdf54.urgent=urgent +withdrawal.funds.dmau=Withdrawal of funds from the register {0} of unidentified funds \ No newline at end of file diff --git a/clearing-parent/clearing-service/src/main/resources/messages/clearing_ru.properties b/clearing-parent/clearing-service/src/main/resources/messages/clearing_ru.properties index a411e49bb..b975e1cba 100644 --- a/clearing-parent/clearing-service/src/main/resources/messages/clearing_ru.properties +++ b/clearing-parent/clearing-service/src/main/resources/messages/clearing_ru.properties @@ -1,3 +1,4 @@ payment-instruction.purpose.withdrawal.tcr=Возврат средств, свободных от обязательств с ТКР {0}. payment-instruction.purpose.withdrawal=Вывод средств. -sdf54.urgent=срочно \ No newline at end of file +sdf54.urgent=срочно +withdrawal.funds.dmau=Вывод средств с регистра невыясненных денежных средств по {0} \ No newline at end of file diff --git a/clearing-parent/db-scripts/src/main/resources/db/DDL.sql b/clearing-parent/db-scripts/src/main/resources/db/DDL.sql index 1f8a0abba..a970ebed4 100644 --- a/clearing-parent/db-scripts/src/main/resources/db/DDL.sql +++ b/clearing-parent/db-scripts/src/main/resources/db/DDL.sql @@ -5343,7 +5343,9 @@ CREATE TABLE RISK_PARAMETER CURRENCY_PAIR_ID BIGINT, CREATED_AT timestamp, UPDATED_AT timestamp, - WORKFLOW_STATUS VARCHAR(4) + WORKFLOW_STATUS VARCHAR(4), + PENALTY_VALUE numeric(72,18), + QUOTE_VALUE numeric(72,18) ); COMMENT ON TABLE RISK_PARAMETER IS 'Риск-параметры'; COMMENT ON COLUMN RISK_PARAMETER.ID IS 'Идентификатор записи'; @@ -5354,6 +5356,8 @@ COMMENT ON COLUMN RISK_PARAMETER.CURRENCY_PAIR_ID IS 'Идентификатор COMMENT ON COLUMN RISK_PARAMETER.CREATED_AT IS 'Время создания записи'; COMMENT ON COLUMN RISK_PARAMETER.UPDATED_AT IS 'Время изменения записи'; COMMENT ON COLUMN RISK_PARAMETER.WORKFLOW_STATUS IS 'Статус'; +COMMENT ON COLUMN RISK_PARAMETER.QUOTE_VALUE IS 'Ставка переноса сопряженной валюты'; +COMMENT ON COLUMN RISK_PARAMETER.PENALTY_VALUE IS 'Ставка переноса при нехватке базовой валюты'; GRANT ALL PRIVILEGES ON TABLE RISK_PARAMETER TO clearing; -- rates - Расчетные курсы @@ -5579,7 +5583,8 @@ CREATE TABLE ORDER_CURRENCY COMMENT VARCHAR(255), INITIAL_ORDER_ID BIGINT, CREATED_AT TIMESTAMP, - UPDATED_AT TIMESTAMP + UPDATED_AT TIMESTAMP, + REGISTRY_ID BIGINT ); COMMENT ON TABLE ORDER_CURRENCY IS 'Заявки для заключения сделок переноса на валютном рынке'; @@ -5753,4 +5758,4 @@ GRANT ALL PRIVILEGES ON TABLE S_ORDERS TO clearing; -- Data types -INSERT INTO DB_VERSION(ID, VERSION) values (1, '3.22') ON CONFLICT (ID) DO UPDATE SET VERSION = EXCLUDED.VERSION \ No newline at end of file +INSERT INTO DB_VERSION(ID, VERSION) values (1, '3.23') ON CONFLICT (ID) DO UPDATE SET VERSION = EXCLUDED.VE RSION \ No newline at end of file diff --git a/clearing-parent/db-scripts/src/main/resources/db/MARKET_LCC.sql b/clearing-parent/db-scripts/src/main/resources/db/MARKET_LCC.sql index 8bbec5f24..5546656b4 100644 --- a/clearing-parent/db-scripts/src/main/resources/db/MARKET_LCC.sql +++ b/clearing-parent/db-scripts/src/main/resources/db/MARKET_LCC.sql @@ -7,5 +7,6 @@ /* Business objects */ INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (1000, 'SCND', 'CURR', 'RUB', 'UVAK', 'Режим непрерывных торгов (торги иностранной валютой с Провайдером ликвидности)', 1, 'Continuous trading mode (foreign currency trading with a Liquidity Provider)') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; -INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (1001, 'SCSP', 'CURR', 'RUB', 'TVAK', 'Технический режим переговорных сделок для сделок урегулирования', 1, 'Technical negotiated deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; -INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (1002, 'SCSW', 'CURR', 'RUB', 'TVWK', 'Технический режим витринных переговорных сделок для сделок урегулирования', 1, 'Technical negotiated showcase deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; \ No newline at end of file +INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (1001, 'SCND', 'CURR', 'RUB', 'NVAK', 'Режим анонимных переговорных сделок (торги иностранной валютой с Провайдером ликвидности)', 1, 'Anonimous negotiated deals mode (foreign currency trading with a Liquidity Provider)') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; +INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (2002, 'SCSP', 'CURR', 'RUB', 'TVAK', 'Технический режим переговорных сделок для сделок урегулирования', 1, 'Technical negotiated deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; +INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (2003, 'SCSW', 'CURR', 'RUB', 'TVWK', 'Технический режим витринных переговорных сделок для сделок урегулирования', 1, 'Technical negotiated showcase deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; \ No newline at end of file diff --git a/clearing-parent/db-scripts/src/main/resources/db/MARKET_efis.sql b/clearing-parent/db-scripts/src/main/resources/db/MARKET_efis.sql index 5bedca97f..e5f793482 100644 --- a/clearing-parent/db-scripts/src/main/resources/db/MARKET_efis.sql +++ b/clearing-parent/db-scripts/src/main/resources/db/MARKET_efis.sql @@ -7,5 +7,5 @@ /* Business objects */ INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (2000, 'SCND', 'CURR', 'RUB', 'UVAF', 'Режим непрерывных торгов (торги иностранной валютой с Провайдером ликвидности)', 1, 'Continuous trading mode (foreign currency trading with a Liquidity Provider)') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; -INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (2001, 'SCSP', 'CURR', 'RUB', 'TVAF', 'Технический режим переговорных сделок для сделок урегулирования', 1, 'Technical negotiated deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; -INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (2002, 'SCSW', 'CURR', 'RUB', 'TVWF', 'Технический режим витринных переговорных сделок для сделок урегулирования', 1, 'Technical negotiated showcase deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; +INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (2002, 'SCSP', 'CURR', 'RUB', 'TVAF', 'Технический режим переговорных сделок для сделок урегулирования', 1, 'Technical negotiated deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; +INSERT INTO MARKET(ID, MARKET_TYPE, SECTION, SETTLEMENT_CURRENCY, CODE, NAME, EXCHANGE_ID, DESCRIPTION) values (2003, 'SCSW', 'CURR', 'RUB', 'TVWF', 'Технический режим витринных переговорных сделок для сделок урегулирования', 1, 'Technical negotiated showcase deals mode for settlement') ON CONFLICT (ID) DO UPDATE SET MARKET_TYPE = EXCLUDED.MARKET_TYPE, SECTION = EXCLUDED.SECTION, SETTLEMENT_CURRENCY = EXCLUDED.SETTLEMENT_CURRENCY, CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, EXCHANGE_ID = EXCLUDED.EXCHANGE_ID, DESCRIPTION = EXCLUDED.DESCRIPTION; diff --git a/clearing-parent/db-scripts/src/main/resources/db/updateDDL_3.23.sql b/clearing-parent/db-scripts/src/main/resources/db/updateDDL_3.23.sql index 421745ac9..0ec389bcb 100644 --- a/clearing-parent/db-scripts/src/main/resources/db/updateDDL_3.23.sql +++ b/clearing-parent/db-scripts/src/main/resources/db/updateDDL_3.23.sql @@ -3,6 +3,8 @@ ALTER TABLE RISK_PARAMETER ADD COLUMN IF NOT EXISTS QUOTE_VALUE NUMERIC(72, 18); COMMENT ON COLUMN RISK_PARAMETER.QUOTE_VALUE IS 'Ставка переноса сопряженной валюты'; +ALTER TABLE RISK_PARAMETER ADD COLUMN IF NOT EXISTS PENALTY_VALUE NUMERIC(72, 18); +COMMENT ON COLUMN RISK_PARAMETER.PENALTY_VALUE IS 'Ставка переноса при нехватке базовой валюты'; ALTER TABLE ORDER_CURRENCY ADD COLUMN IF NOT EXISTS REGISTRY_ID BIGINT; COMMENT ON COLUMN ORDER_CURRENCY.REGISTRY_ID IS 'Идентификатор регистра'; diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/component/validation/OrderCurrencyCompanyNotChanged.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/component/validation/OrderCurrencyCompanyNotChanged.java new file mode 100644 index 000000000..f6836c56e --- /dev/null +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/component/validation/OrderCurrencyCompanyNotChanged.java @@ -0,0 +1,40 @@ +package ru.spcex.clearing.dflt.management.component.validation; + +import java.math.BigDecimal; +import java.util.Objects; +import java.util.Optional; +import ru.clearing.classes.statics.data.misc.OrderCurrency; +import ru.spcex.clearing.dflt.management.error.DefaultManagementErrors; +import ru.spcex.clearing.platform.messaging.domain.cud.order.OrderCompanyUpdateRequest; +import ru.spcex.platform.imdg.validation.ImdgValidationContext; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.validation.IValidationRule; + +public class OrderCurrencyCompanyNotChanged + implements IValidationRule> { + public static final OrderCurrencyCompanyNotChanged INSTANCE = new OrderCurrencyCompanyNotChanged(); + + @Override + public Optional validate(ImdgValidationContext ctx) { + OrderCompanyUpdateRequest validatedObject = ctx.getValidatedObject(); + OrderCurrency oc = ctx.getStoredObject(OrderCurrencyValidationStored.OrderCurrency); + + BigDecimal reqQuantityLog = Optional.ofNullable(validatedObject.getQuantityLot()) + .map(BigDecimal::new) + .orElse(BigDecimal.ZERO); + BigDecimal entityQuantityLog = Optional.ofNullable(oc.getQuantityLot()) + .orElse(BigDecimal.ZERO); + + if (Objects.equals(validatedObject.getCompanyId(), oc.getCompanyId()) + && Objects.equals(validatedObject.getTradingClearingRegistryId(), oc.getTradingClearingRegistryId()) + && Objects.equals(reqQuantityLog, entityQuantityLog)) { + return of(DefaultManagementErrors.RecordAlreadyPresent); + } + return empty(); + } + + @Override + public String ruleName() { + return "OrderCurrencyCompanyNotChanged"; + } +} diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/config/ValidationConfig.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/config/ValidationConfig.java index f84141bcf..cef013209 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/config/ValidationConfig.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/config/ValidationConfig.java @@ -17,6 +17,7 @@ import ru.clearing.platform.dictionary.OrderStatusDictionary; import ru.clearing.platform.dictionary.OvernightTypeDictionary; import ru.clearing.platform.dictionary.SideDictionary; import ru.spcex.clearing.dflt.management.component.validation.OrderCurrencyByCompanyAndTcrAlreadyPresent; +import ru.spcex.clearing.dflt.management.component.validation.OrderCurrencyCompanyNotChanged; import ru.spcex.clearing.dflt.management.component.validation.OrderCurrencyPLBLCheck; import ru.spcex.clearing.dflt.management.component.validation.OrderCurrencyValidationStored; import ru.spcex.clearing.dflt.management.component.validation.OvernightSettingAlreadyPresent; @@ -355,7 +356,10 @@ public class ValidationConfig { .builder(r -> { OrderCurrency oc = ctx.getStoredObject(OrderCurrencyValidationStored.OrderCurrency); ImdgPredicateBuilder pb = tcrImdg.predicateBuilder(); - return pb.equals("companyId", oc.getCounterPartyId()); + return pb.and( + pb.equals("companyId", oc.getCounterPartyId()), + pb.equals("code", oc.getCounterTradingCode()) + ); }) .mapName(IMDGDistributedNames.Map_TradingClearingRegistry) .errorProvider(r -> recordNotFound("counter (CK) TradingClearingRegistry")) @@ -434,6 +438,7 @@ public class ValidationConfig { companyPresentRule, tcrPresent, QuantityLotGreaterThanZero.INSTANCE, + OrderCurrencyCompanyNotChanged.INSTANCE, //OrderCurrencyByCompanyAndTcrAlreadyPresent.getInstance(OrderCompanyUpdateRequest.class), OrderCurrencyPLBLCheck.INSTANCE, counterOrderPresent); diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/OrderCurrencyListener.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/OrderCurrencyListener.java index a532f1084..6862a3e59 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/OrderCurrencyListener.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/OrderCurrencyListener.java @@ -38,7 +38,6 @@ import ru.spcex.clearing.platform.messaging.domain.cud.order.OrderCurrencyUpdate import ru.spcex.clearing.platform.messaging.service.QueueConsumer; import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; import ru.spcex.clearing.platform.messaging.service.Status; -import ru.spcex.platform.enumeration.MarketType; import ru.spcex.platform.enumeration.OrderStatus; import ru.spcex.platform.enumeration.OvernightType; import ru.spcex.platform.enumeration.SettleCode; @@ -183,7 +182,7 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing if (OvernightType.SWAP.equalsByKey(order.getOvernightType())) { Pair newSpotPair = swapToSpotSplit(order, rgs); log.info("deleting old OrderCurrency.id={}", order.getId()); - orderCurrencyImdg.delete(order); + reject(order); orderCurrencyImdg.insert(newSpotPair.getFirst()); orderCurrencyImdg.insert(newSpotPair.getSecond()); log.info("created OrderCurrency[{}, {}]", @@ -192,14 +191,20 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing OrderCurrency pairOrder = validator.getStored(OrderCurrencyValidationStored.OrderCurrencySpotPair); OrderCurrency newSwapOrder = spotToSwap(order, pairOrder, rgs); log.info("deleting old records OrderCurrency[{}, {}]", order.getId(), pairOrder.getId()); - orderCurrencyImdg.delete(order); - orderCurrencyImdg.delete(pairOrder); + reject(order); + reject(pairOrder); orderCurrencyImdg.insert(newSwapOrder); log.info("created OrderCurrency.id={}", newSwapOrder.getId()); } return null; } + private void reject(OrderCurrency o) { + o.setStatus(OrderStatus.REJECTED.getKey()); + o.setUpdated(Instant.now()); + orderCurrencyImdg.update(o); + } + private Pair swapToSpotSplit(OrderCurrency swap, Registry rgs) { OrderCurrency orderT0 = new OrderCurrency(); OrderCurrency orderT1 = new OrderCurrency(); @@ -216,26 +221,34 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing } Market market = imdgQueryService.getSpotMarket(); - Listing listing = imdgQueryService.findListing(rgs, market.getCode()); + Listing listingT0 = imdgQueryService.findListing(rgs, market.getCode(), SettleCode.T0.getKey()); + Listing listingT1 = imdgQueryService.findListing(rgs, market.getCode(), SettleCode.T1.getKey()); //мб возвращать вместе с листингом? - CurrencyPairDictionary currPair = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listing.getSecurityId()); - SCrossRate rate = imdgQueryService.getSCrossRates(currPair.getBaseCurrency(), LocalDate.now()); + CurrencyPairDictionary currPairT0 = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listingT0.getSecurityId()); + SCrossRate rateT0 = imdgQueryService.getSCrossRates(currPairT0.getBaseCurrency(), LocalDate.now()); + CurrencyPairDictionary currPairT1 = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listingT1.getSecurityId()); + SCrossRate rateT1 = imdgQueryService.getSCrossRates(currPairT1.getBaseCurrency(), LocalDate.now()); var pair = new Pair<>(orderT0, orderT1); Pair.forEach(pair, o -> { copyCommonFields(swap, o); - o.setSecurityId(listing.getSecurityId()); - o.setSecuritySymbol(listing.getSymbolCode()); - o.setListingId(listing.getId()); o.setOvernightType(OvernightType.SPOT.getKey()); - o.setMarket(MarketType.SCSP.getKey()); + o.setMarket(market.getCode()); o.setCreated(Instant.now()); }); - orderT0.setPrice(rate.getUnitRate()); - orderT1.setPrice(BigDecimalUtil.safeSumBD(swap.getPrice(), rate.getUnitRate())); + updateOrderFieldsByListing(orderT0, listingT0); + updateOrderFieldsByListing(orderT1, listingT1); + orderT0.setPrice(rateT0.getUnitRate()); + orderT1.setPrice(BigDecimalUtil.safeSumBD(swap.getPrice(), rateT1.getUnitRate())); return pair; } + private void updateOrderFieldsByListing(OrderCurrency order, Listing listing) { + order.setSecurityId(listing.getSecurityId()); + order.setSecuritySymbol(listing.getSymbolCode()); + order.setListingId(listing.getId()); + } + private OrderCurrency spotToSwap(OrderCurrency order1, OrderCurrency order2, Registry rgs) { OrderCurrency spotT0; OrderCurrency spotT1; @@ -257,14 +270,14 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing } Market market = imdgQueryService.getSwapMarket(); - Listing listing = imdgQueryService.findListing(rgs, market.getCode()); + Listing listing = imdgQueryService.findListing(rgs, market.getCode(), SettleCode.T1.getKey()); copyCommonFields(order1, swap); swap.setSecurityId(listing.getSecurityId()); swap.setSecuritySymbol(listing.getSymbolCode()); swap.setListingId(listing.getId()); swap.setOvernightType(OvernightType.SWAP.getKey()); - swap.setMarket(MarketType.SCSW.getKey()); + swap.setMarket(market.getCode()); swap.setPrice(safeBD(spotT1.getPrice()).subtract(safeBD(spotT0.getPrice()))); swap.setCreated(Instant.now()); return swap; @@ -380,8 +393,8 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing OrderCurrency counterOrder = validator.getStored(OrderCurrencyValidationStored.CounterOrderCurrency); log.info("deleting OrderCurrency[{}, {}]", order.getId(), counterOrder.getId()); - orderCurrencyImdg.delete(order); - orderCurrencyImdg.delete(counterOrder); + reject(order); + reject(counterOrder); return null; } @@ -406,6 +419,7 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing private void updateByFix(BaseRequest req) { OrderCurrencyStatusUpdateRequest payload = req.getRequestPayload(); for (Long orderId : payload.getIds()) { + log.debug("Process updating status by orderNum {}", orderId); SOrders sOrder = sOrdersImdg.getSingleObjectByPredicate( sOrdersImdg.predicateBuilder().equals("orderNum", orderId) ); @@ -416,7 +430,6 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing } orderCurrency.setStatus(status.getKey()); orderCurrency.setUpdated(Instant.now()); -// orderCurrency.setUpdated(sOrder.getTradeDate());//todo orderCurrencyImdg.update(orderCurrency); } } diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/TaskListener.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/TaskListener.java index 05468783f..91590fd68 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/TaskListener.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/listener/TaskListener.java @@ -151,7 +151,7 @@ public class TaskListener extends QueueConsumer implements InitializingBean { kafkaSender.sendRequestToQueue(Consts.NOTIFICATION_NEW, newReq); } - log.info("SDOR export and send command to fix, request={}", req); + log.info("export to tri..."); try { Collection orders = imdgQueryService.getClccOrderCurrencyIds(); String content = orderCurrencyTriExportService.exportToTri(orders); @@ -159,6 +159,7 @@ public class TaskListener extends QueueConsumer implements InitializingBean { LocalDateTime.now().format(formatter))); Files.writeString(fileName, content, StandardCharsets.UTF_8); if (SendOrdersTypes.FIX == sendOrdersTypes) { + log.info("send command to fix service..."); kafkaSender.sendRequestToQueue(Consts.SDOR_FIX_TASK, req.getRequestPayload()); } } catch (IOException e) { diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/model/FinalCalculationResult.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/model/FinalCalculationResult.java new file mode 100644 index 000000000..85844fca4 --- /dev/null +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/model/FinalCalculationResult.java @@ -0,0 +1,11 @@ +package ru.spcex.clearing.dflt.management.model; + +import java.math.BigDecimal; + +public record FinalCalculationResult( + BigDecimal price, + BigDecimal rate, + BigDecimal quantityLot, + CalculationResult calculationResult +) { +} \ No newline at end of file diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/ImdgQueryService.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/ImdgQueryService.java index 8f891adeb..e400d01ea 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/ImdgQueryService.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/ImdgQueryService.java @@ -209,7 +209,6 @@ public class ImdgQueryService { public BigDecimal defineTransferRate(Long currencyPairDictionaryId, String securitySymbol) { ImdgPredicateBuilder pb = riskParameterImdg.predicateBuilder(); boolean isRub = "RUB".equals(securitySymbol); - String baseCurrency = isRub ? "CNY" : securitySymbol; RiskParameter riskParameter = riskParameterImdg.getSingleObjectByPredicate( pb.and( @@ -217,7 +216,7 @@ public class ImdgQueryService { pb.equals("currencyPairId", currencyPairDictionaryId) ) ); - return isRub ? riskParameter.getValue() : riskParameter.getQuoteValue(); + return isRub ? riskParameter.getQuoteValue() : riskParameter.getPenaltyValue(); } public CurrencyPairDictionary getCurrencyPairDictionaryBySecurityId(Long securityId) { diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderCalculationService.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderCalculationService.java index 17c860266..e121a51c7 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderCalculationService.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderCalculationService.java @@ -8,12 +8,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.misc.Listing; -import ru.clearing.classes.statics.data.misc.OrderCurrency; import ru.clearing.classes.statics.data.misc.SCrossRate; import ru.clearing.classes.statics.data.registry.Registry; import ru.clearing.classes.statics.data.security.Rates; import ru.clearing.platform.dictionary.CurrencyPairDictionary; import ru.spcex.clearing.dflt.management.model.CalculationResult; +import ru.spcex.clearing.dflt.management.model.FinalCalculationResult; import ru.spcex.clearing.dflt.management.model.OrderCurrencyContext; import ru.spcex.platform.enumeration.CurrencyCode; import ru.spcex.platform.enumeration.SettleCode; @@ -32,10 +32,10 @@ public class OrderCalculationService { this.transferDateService = transferDateService; } - public void calcAndEnrichSwap(OrderCurrency order, + public FinalCalculationResult calcAndEnrichSwap(SettleCode settleCode, OrderCurrencyContext context) { Registry registry = context.getRegistry(); - Listing listing = imdgQueryService.findListing(registry, context.getMarket().getCode(), order.getSettleCode()); + Listing listing = imdgQueryService.findListing(registry, context.getMarket().getCode(), settleCode.getKey()); CurrencyPairDictionary pair = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listing.getSecurityId()); BigDecimal s = imdgQueryService.defineTransferRate(pair.getId(), registry.getSecuritySymbol()); @@ -46,16 +46,15 @@ public class OrderCalculationService { : calculationSums(registry, listing.getLotSize().intValue(), s, n); BigDecimal quantityLot = result.sum3().abs(); - BigDecimal price = swapPriceCalc(pair.getBaseCurrency(), s, n); + BigDecimal price = swapPriceCalc(pair.getBaseCurrency(), s, n, listing.getPrecision().intValue()); - order.setPrice(price); - order.setQuantityLot(quantityLot); + return new FinalCalculationResult(price, s, quantityLot, result); } - public void calcAndEnrichSpot(OrderCurrency order, - OrderCurrencyContext context) { + public FinalCalculationResult calcAndEnrichSpot(SettleCode settleCode, + OrderCurrencyContext context) { Registry registry = context.getRegistry(); - Listing listing = imdgQueryService.findListing(registry, context.getMarket().getCode(), order.getSettleCode()); + Listing listing = imdgQueryService.findListing(registry, context.getMarket().getCode(), settleCode.getKey()); CurrencyPairDictionary pair = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listing.getSecurityId()); BigDecimal s = imdgQueryService.defineTransferRate(pair.getId(), registry.getSecuritySymbol()); @@ -68,32 +67,28 @@ public class OrderCalculationService { SCrossRate rates = imdgQueryService.getSCrossRates(pair.getBaseCurrency(), LocalDate.now()); BigDecimal quantityLot; BigDecimal price; - if (SettleCode.T0.equalsByKey(order.getSettleCode())) { + if (SettleCode.T0 == settleCode) { quantityLot = result.sum3().abs(); - price = rates.getUnitRate(); + price = rates.getUnitRate().setScale(listing.getPrecision().intValue(), RoundingMode.HALF_UP); } else { quantityLot = result.sum5().abs(); - BigDecimal delta = swapPriceCalc(pair.getBaseCurrency(), s, n); - price = rates.getUnitRate().add(delta); + BigDecimal delta = swapPriceCalc(pair.getBaseCurrency(), s, n, listing.getPrecision().intValue()); + price = rates.getUnitRate().add(delta).setScale(listing.getPrecision().intValue(), RoundingMode.HALF_UP);; } - order.setPrice(price); - order.setQuantityLot(quantityLot); - order.setRate(s); + return new FinalCalculationResult(price, s, quantityLot, result); } private CalculationResult calculationSums(Registry registry, Integer lotSize, BigDecimal s, BigDecimal n) { BigDecimal planPosition = registry.getBalance(); BigDecimal sum1 = planPosition.abs(); - Registry rubRegistry = imdgQueryService.findAmtRegistry(registry, "RUB"); - BigDecimal planPositionRub = rubRegistry.getBalance(); - - Rates rates = imdgQueryService.getRate(registry.getSecuritySymbol(), LocalDate.now()); - BigDecimal rateValue = rates.getValue(); - - BigDecimal sum2 = planPositionRub.divide(rateValue, lotSize, RoundingMode.HALF_DOWN); - BigDecimal sum3 = sum1.setScale(lotSize, RoundingMode.CEILING); + SCrossRate sCrossRates = imdgQueryService.getSCrossRates(registry.getSecuritySymbol(), LocalDate.now()); + if (sCrossRates == null) { + throw new IllegalStateException(String.format("Not fond rate by security symbol: %s", CurrencyCode.CNY.getKey())); + } + BigDecimal rateValue = sCrossRates.getUnitRate(); + BigDecimal sum3 = mathCeil(sum1, lotSize); BigDecimal sum4 = sum3.multiply(rateValue, MC).negate(); BigDecimal sum5 = sum3.negate(); @@ -104,7 +99,7 @@ public class OrderCalculationService { BigDecimal sum6 = sum3.multiply(rateValue, MC).multiply(multiplier, MC); - return new CalculationResult(sum1, sum2, sum3, sum4, sum5, sum6); + return new CalculationResult(sum1, BigDecimal.ZERO, sum3, sum4, sum5, sum6); } private CalculationResult calculationRubSums(Registry registry, Integer lotSize, BigDecimal s, BigDecimal n) { @@ -115,13 +110,14 @@ public class OrderCalculationService { } BigDecimal rateValue = sCrossRates.getUnitRate(); - BigDecimal sum1 = planPosition.divide(rateValue, MC); + BigDecimal sum1 = planPosition.divide(rateValue, MC).abs(); - Registry cnyRegistry = imdgQueryService.findAmtRegistry(registry, "CNY"); - BigDecimal planPositionCny = cnyRegistry.getBalance(); + Registry amtRegistry = imdgQueryService.findAmtRegistry(registry, "CNY"); + BigDecimal planPositionCny = amtRegistry.getBalance(); - BigDecimal sum2 = planPositionCny.divide(rateValue, lotSize, RoundingMode.HALF_DOWN); - BigDecimal sum3 = sum1.setScale(lotSize, RoundingMode.CEILING).min(sum2).negate(); + BigDecimal sum2 = planPositionCny.setScale(lotSize, RoundingMode.HALF_DOWN); + BigDecimal y = sum1.min(sum2); + BigDecimal sum3 = mathCeil(y, lotSize).negate(); BigDecimal sum4 = sum3.multiply(rateValue, MC).negate(); BigDecimal sum5 = sum3.negate(); @@ -135,7 +131,7 @@ public class OrderCalculationService { return new CalculationResult(sum1, sum2, sum3, sum4, sum5, sum6); } - private BigDecimal swapPriceCalc(String baseCurrency, BigDecimal s, BigDecimal n) { + private BigDecimal swapPriceCalc(String baseCurrency, BigDecimal s, BigDecimal n, int precision) { Rates rate = imdgQueryService.getRate(baseCurrency, LocalDate.now()); BigDecimal p = BigDecimal.ONE.divide(n, MC); log.info("Calculation swap price: {}*{}*{}", s, n, p); @@ -143,10 +139,14 @@ public class OrderCalculationService { .multiply(s, MC) .multiply(n, MC) .multiply(p, MC) - .setScale(8, RoundingMode.HALF_UP); + .setScale(precision, RoundingMode.HALF_UP); } private boolean isRub(Registry registry) { return "RUB".equals(registry.getSecuritySymbol()); } + + private BigDecimal mathCeil(BigDecimal val, Integer multiplicity) { + return BigDecimal.valueOf(Math.ceil(val.doubleValue() / multiplicity)); + } } diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderTemplateBuilder.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderTemplateBuilder.java index 2af0fa2ef..bbc744c44 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderTemplateBuilder.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/OrderTemplateBuilder.java @@ -7,6 +7,7 @@ import ru.clearing.classes.statics.data.misc.Market; import ru.clearing.classes.statics.data.misc.OrderCurrency; import ru.clearing.classes.statics.data.registry.Registry; import ru.spcex.clearing.dflt.management.model.CounterpartyInfo; +import ru.spcex.clearing.dflt.management.model.FinalCalculationResult; import ru.spcex.clearing.dflt.management.model.OrderCurrencyContext; import ru.spcex.platform.enumeration.SettleCode; import static ru.spcex.platform.enumeration.SettleCode.T0; @@ -20,7 +21,9 @@ public class OrderTemplateBuilder { this.imdgQueryService = imdgQueryService; } - public OrderCurrency buildPrimaryTransfer(OrderCurrencyContext context, SettleCode settleCode) { + public OrderCurrency buildPrimaryTransfer(OrderCurrencyContext context, + SettleCode settleCode, + FinalCalculationResult finalCalculationResult) { OrderCurrency orderCurrency; switch (context.getOrderCategory()) { case "PL" -> orderCurrency = buildPrimaryTransferPl(context); @@ -28,11 +31,13 @@ public class OrderTemplateBuilder { default -> throw new IllegalArgumentException("Unsupported order category: " + context.getOrderCategory()); } Side side = resolvePrimaryTransferSide(context.getRegistry(), settleCode); - enrichOrder(orderCurrency, context, side, "overnight", settleCode); + enrichOrder(orderCurrency, context, side, "overnight", settleCode, finalCalculationResult); return orderCurrency; } - public OrderCurrency buildCounterTransfer(OrderCurrencyContext context, SettleCode settleCode) { + public OrderCurrency buildCounterTransfer(OrderCurrencyContext context, + SettleCode settleCode, + FinalCalculationResult finalCalculationResult) { OrderCurrency orderCurrency; switch (context.getOrderCategory()) { case "PL" -> orderCurrency = buildCounterTransferPl(context); @@ -41,11 +46,13 @@ public class OrderTemplateBuilder { } ; Side side = resolveCounterTransferSide(context.getRegistry(), settleCode); - enrichOrder(orderCurrency, context, side, "overnight", settleCode); + enrichOrder(orderCurrency, context, side, "overnight", settleCode, finalCalculationResult); return orderCurrency; } - public OrderCurrency buildPrimaryBalancing(OrderCurrencyContext context, SettleCode settleCode) { + public OrderCurrency buildPrimaryBalancing(OrderCurrencyContext context, + SettleCode settleCode, + FinalCalculationResult finalCalculationResult) { OrderCurrency orderCurrency; switch (context.getOrderCategory()) { case "PL" -> orderCurrency = buildPrimaryBalancingPl(context); @@ -54,11 +61,13 @@ public class OrderTemplateBuilder { } ; Side side = resolvePrimaryBalancingSide(context.getRegistry(), settleCode); - enrichOrder(orderCurrency, context, side, "balance", settleCode); + enrichOrder(orderCurrency, context, side, "balance", settleCode, finalCalculationResult); return orderCurrency; } - public OrderCurrency buildCounterBalancing(OrderCurrencyContext context, SettleCode settleCode) { + public OrderCurrency buildCounterBalancing(OrderCurrencyContext context, + SettleCode settleCode, + FinalCalculationResult finalCalculationResult) { OrderCurrency orderCurrency; switch (context.getOrderCategory()) { case "PL" -> orderCurrency = buildCounterBalancingPl(context); @@ -67,7 +76,7 @@ public class OrderTemplateBuilder { } ; Side side = resolveCounterBalancingSide(context.getRegistry(), settleCode); - enrichOrder(orderCurrency, context, side, "balance", settleCode); + enrichOrder(orderCurrency, context, side, "balance", settleCode, finalCalculationResult); return orderCurrency; } @@ -195,7 +204,8 @@ public class OrderTemplateBuilder { OrderCurrencyContext context, Side side, String comment, - SettleCode settleCode) { + SettleCode settleCode, + FinalCalculationResult finalCalculationResult) { Registry registry = context.getRegistry(); Market market = context.getMarket(); Listing listing = imdgQueryService.findListing(registry, market.getCode(), settleCode.getKey()); @@ -212,28 +222,32 @@ public class OrderTemplateBuilder { order.setComment(comment); order.setRegistryId(registry.getId()); order.setCreated(Instant.now()); + + order.setPrice(finalCalculationResult.price()); + order.setQuantityLot(finalCalculationResult.quantityLot()); + order.setRate(finalCalculationResult.rate()); } - private Side resolvePrimaryTransferSide(Registry registry, SettleCode settleCode) { - Side side = T0 == settleCode ? Side.BUY : Side.SELL; - return isRub(registry) ? side : side.revers(); - } - - private Side resolveCounterTransferSide(Registry registry, SettleCode settleCode) { + public Side resolvePrimaryTransferSide(Registry registry, SettleCode settleCode) { Side side = T0 == settleCode ? Side.SELL : Side.BUY; return isRub(registry) ? side : side.revers(); } - private Side resolvePrimaryBalancingSide(Registry registry, SettleCode settleCode) { - Side side = T0 == settleCode ? Side.SELL : Side.BUY; + public Side resolveCounterTransferSide(Registry registry, SettleCode settleCode) { + Side side = T0 == settleCode ? Side.BUY : Side.SELL; return isRub(registry) ? side : side.revers(); } - private Side resolveCounterBalancingSide(Registry registry, SettleCode settleCode) { + public Side resolvePrimaryBalancingSide(Registry registry, SettleCode settleCode) { Side side = T0 == settleCode ? Side.BUY : Side.SELL; return isRub(registry) ? side : side.revers(); } + public Side resolveCounterBalancingSide(Registry registry, SettleCode settleCode) { + Side side = T0 == settleCode ? Side.SELL : Side.BUY; + return isRub(registry) ? side : side.revers(); + } + private boolean isRub(Registry registry) { return "RUB".equals(registry.getSecuritySymbol()); } diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SpotOvernightOrderService.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SpotOvernightOrderService.java index 9b9991915..e1d27f8c7 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SpotOvernightOrderService.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SpotOvernightOrderService.java @@ -1,5 +1,6 @@ package ru.spcex.clearing.dflt.management.service; +import java.math.BigDecimal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -8,14 +9,17 @@ import ru.clearing.classes.statics.data.misc.OrderCurrency; import ru.clearing.classes.statics.data.registry.Registry; import ru.clearing.classes.statics.data.security.RiskParameter; import ru.spcex.clearing.dflt.management.model.CounterpartyInfo; +import ru.spcex.clearing.dflt.management.model.FinalCalculationResult; import ru.spcex.clearing.dflt.management.model.OrderCurrencyContext; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.platform.enumeration.CompanyRole; +import ru.spcex.platform.enumeration.CurrencyCode; import ru.spcex.platform.enumeration.OvernightType; import ru.spcex.platform.enumeration.RegistryStatus; import ru.spcex.platform.enumeration.SettleCode; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.utils.enumeration.IEnumKey; @Service public class SpotOvernightOrderService { @@ -46,6 +50,7 @@ public class SpotOvernightOrderService { CounterpartyInfo categoryP = imdgQueryService.getCompanyByClearingCategory("P"); CounterpartyInfo clcc = imdgQueryService.getCompanyByRole(CompanyRole.CLCC); CounterpartyInfo rgcm = imdgQueryService.getCompanyByRole(CompanyRole.RGCM); + CurrencyCode currency = IEnumKey.getEnumByKey(CurrencyCode.class, registry.getSecuritySymbol()); OrderCurrencyContext context = OrderCurrencyContext.builder() .registry(registry) @@ -58,25 +63,23 @@ public class SpotOvernightOrderService { .overnightType(OvernightType.SPOT) .build(); - OrderCurrency primaryTransferT0 = templateBuilder.buildPrimaryTransfer(context, SettleCode.T0); - OrderCurrency counterTransferT0 = templateBuilder.buildCounterTransfer(context, SettleCode.T0); - OrderCurrency primaryTransferT1 = templateBuilder.buildPrimaryTransfer(context, SettleCode.T1); - OrderCurrency counterTransferT1 = templateBuilder.buildCounterTransfer(context, SettleCode.T1); + FinalCalculationResult finalCalculationResultT0 = swapCalculationService.calcAndEnrichSpot(SettleCode.T0, context); + FinalCalculationResult finalCalculationResultT1 = swapCalculationService.calcAndEnrichSpot(SettleCode.T1, context); - OrderCurrency primaryBalancingT0 = templateBuilder.buildPrimaryBalancing(context, SettleCode.T0); - OrderCurrency counterBalancingT0 = templateBuilder.buildCounterBalancing(context, SettleCode.T0); - OrderCurrency primaryBalancingT1 = templateBuilder.buildPrimaryBalancing(context, SettleCode.T1); - OrderCurrency counterBalancingT1 = templateBuilder.buildCounterBalancing(context, SettleCode.T1); + if (CurrencyCode.RUB == currency && + finalCalculationResultT0.calculationResult().sum3().compareTo(BigDecimal.ZERO) >= 0) { + log.warn("QuantityLot greater than zero; skip forming order currency"); + return; + } + OrderCurrency primaryTransferT0 = templateBuilder.buildPrimaryTransfer(context, SettleCode.T0, finalCalculationResultT0); + OrderCurrency counterTransferT0 = templateBuilder.buildCounterTransfer(context, SettleCode.T0, finalCalculationResultT0); + OrderCurrency primaryTransferT1 = templateBuilder.buildPrimaryTransfer(context, SettleCode.T1, finalCalculationResultT1); + OrderCurrency counterTransferT1 = templateBuilder.buildCounterTransfer(context, SettleCode.T1, finalCalculationResultT1); - swapCalculationService.calcAndEnrichSpot(primaryTransferT0, context); - swapCalculationService.calcAndEnrichSpot(counterTransferT0, context); - swapCalculationService.calcAndEnrichSpot(primaryTransferT1, context); - swapCalculationService.calcAndEnrichSpot(counterTransferT1, context); - - swapCalculationService.calcAndEnrichSpot(primaryBalancingT0, context); - swapCalculationService.calcAndEnrichSpot(counterBalancingT0, context); - swapCalculationService.calcAndEnrichSpot(primaryBalancingT1, context); - swapCalculationService.calcAndEnrichSpot(counterBalancingT1, context); + OrderCurrency primaryBalancingT0 = templateBuilder.buildPrimaryBalancing(context, SettleCode.T0, finalCalculationResultT0); + OrderCurrency counterBalancingT0 = templateBuilder.buildCounterBalancing(context, SettleCode.T0, finalCalculationResultT0); + OrderCurrency primaryBalancingT1 = templateBuilder.buildPrimaryBalancing(context, SettleCode.T1, finalCalculationResultT1); + OrderCurrency counterBalancingT1 = templateBuilder.buildCounterBalancing(context, SettleCode.T1, finalCalculationResultT1); orderCurrencyImdg.insert(primaryTransferT0); orderCurrencyImdg.insert(counterTransferT0); diff --git a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SwapOvernightOrderService.java b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SwapOvernightOrderService.java index 67f95849c..1f9ecb8a6 100644 --- a/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SwapOvernightOrderService.java +++ b/clearing-parent/default-management/src/main/java/ru/spcex/clearing/dflt/management/service/SwapOvernightOrderService.java @@ -1,5 +1,6 @@ package ru.spcex.clearing.dflt.management.service; +import java.math.BigDecimal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -8,14 +9,17 @@ import ru.clearing.classes.statics.data.misc.OrderCurrency; import ru.clearing.classes.statics.data.registry.Registry; import ru.clearing.classes.statics.data.security.RiskParameter; import ru.spcex.clearing.dflt.management.model.CounterpartyInfo; +import ru.spcex.clearing.dflt.management.model.FinalCalculationResult; import ru.spcex.clearing.dflt.management.model.OrderCurrencyContext; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.platform.enumeration.CompanyRole; +import ru.spcex.platform.enumeration.CurrencyCode; import ru.spcex.platform.enumeration.OvernightType; import ru.spcex.platform.enumeration.RegistryStatus; import ru.spcex.platform.enumeration.SettleCode; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.utils.enumeration.IEnumKey; @Service public class SwapOvernightOrderService { @@ -46,7 +50,7 @@ public class SwapOvernightOrderService { CounterpartyInfo categoryP = imdgQueryService.getCompanyByClearingCategory("P"); CounterpartyInfo clcc = imdgQueryService.getCompanyByRole(CompanyRole.CLCC); CounterpartyInfo rgcm = imdgQueryService.getCompanyByRole(CompanyRole.RGCM); - + CurrencyCode currency = IEnumKey.getEnumByKey(CurrencyCode.class, registry.getSecuritySymbol()); OrderCurrencyContext context = OrderCurrencyContext.builder() .registry(registry) .orderCategory(orderCategory) @@ -58,15 +62,17 @@ public class SwapOvernightOrderService { .overnightType(OvernightType.SWAP) .build(); - OrderCurrency primaryTransfer = templateBuilder.buildPrimaryTransfer(context, SettleCode.T1); - OrderCurrency counterTransfer = templateBuilder.buildCounterTransfer(context, SettleCode.T1); - OrderCurrency primaryBalancing = templateBuilder.buildPrimaryBalancing(context, SettleCode.T1); - OrderCurrency counterBalancing = templateBuilder.buildCounterBalancing(context, SettleCode.T1); + FinalCalculationResult finalCalculationResult = swapCalculationService.calcAndEnrichSwap(SettleCode.T1, context); + if (CurrencyCode.RUB == currency && + finalCalculationResult.calculationResult().sum3().compareTo(BigDecimal.ZERO) >= 0) { + log.warn("QuantityLot greater than zero; skip forming order currency"); + return; + } - swapCalculationService.calcAndEnrichSwap(primaryTransfer, context); - swapCalculationService.calcAndEnrichSwap(counterTransfer, context); - swapCalculationService.calcAndEnrichSwap(primaryBalancing, context); - swapCalculationService.calcAndEnrichSwap(counterBalancing, context); + OrderCurrency primaryTransfer = templateBuilder.buildPrimaryTransfer(context, SettleCode.T1, finalCalculationResult); + OrderCurrency counterTransfer = templateBuilder.buildCounterTransfer(context, SettleCode.T1, finalCalculationResult); + OrderCurrency primaryBalancing = templateBuilder.buildPrimaryBalancing(context, SettleCode.T1, finalCalculationResult); + OrderCurrency counterBalancing = templateBuilder.buildCounterBalancing(context, SettleCode.T1, finalCalculationResult); orderCurrencyImdg.insert(primaryTransfer); orderCurrencyImdg.insert(counterTransfer); diff --git a/clearing-parent/default-management/src/test/java/ru/spcex/clearing/dflt/service/OrderTemplateBuilderTest.java b/clearing-parent/default-management/src/test/java/ru/spcex/clearing/dflt/service/OrderTemplateBuilderTest.java new file mode 100644 index 000000000..38bb174a2 --- /dev/null +++ b/clearing-parent/default-management/src/test/java/ru/spcex/clearing/dflt/service/OrderTemplateBuilderTest.java @@ -0,0 +1,32 @@ +package ru.spcex.clearing.dflt.service; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.spcex.clearing.dflt.management.service.OrderTemplateBuilder; +import ru.spcex.platform.enumeration.SettleCode; +import ru.spcex.platform.enumeration.Side; + +public class OrderTemplateBuilderTest { + + @Test + public void testSwapNotRubT1() { + OrderTemplateBuilder builder = new OrderTemplateBuilder(null); + Assertions.assertEquals(Side.SELL, builder.resolvePrimaryTransferSide(createNotRubRegistry(), SettleCode.T1)); + Assertions.assertEquals(Side.BUY, builder.resolvePrimaryTransferSide(createRubRegistry(), SettleCode.T1)); + Assertions.assertEquals(Side.BUY, builder.resolvePrimaryTransferSide(createNotRubRegistry(), SettleCode.T0)); + Assertions.assertEquals(Side.SELL, builder.resolvePrimaryTransferSide(createNotRubRegistry(), SettleCode.T1)); + } + + public static Registry createNotRubRegistry() { + Registry registry = new Registry(); + registry.setSecuritySymbol("CNY"); + return registry; + } + + public static Registry createRubRegistry() { + Registry registry = new Registry(); + registry.setSecuritySymbol("RUB"); + return registry; + } +} diff --git a/clearing-parent/fix-service/src/main/java/ru/spcex/clearing/fix/quickfix/Application.java b/clearing-parent/fix-service/src/main/java/ru/spcex/clearing/fix/quickfix/Application.java index af4d01e09..84f036ad1 100644 --- a/clearing-parent/fix-service/src/main/java/ru/spcex/clearing/fix/quickfix/Application.java +++ b/clearing-parent/fix-service/src/main/java/ru/spcex/clearing/fix/quickfix/Application.java @@ -1,13 +1,47 @@ package ru.spcex.clearing.fix.quickfix; +import java.math.BigDecimal; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import quickfix.*; -import quickfix.field.*; +import quickfix.ConfigError; +import quickfix.DoNotSend; +import quickfix.FieldNotFound; +import quickfix.IncorrectDataFormat; +import quickfix.IncorrectTagValue; +import quickfix.LogFactory; +import quickfix.Message; +import quickfix.MessageFactory; +import quickfix.MessageStoreFactory; +import quickfix.RejectLogon; +import quickfix.Session; +import quickfix.SessionID; +import quickfix.SessionNotFound; +import quickfix.SessionSettings; +import quickfix.SocketInitiator; +import quickfix.StringField; +import quickfix.UnsupportedMessageType; +import quickfix.field.Account; +import quickfix.field.ClOrdID; +import quickfix.field.ContraBroker; +import quickfix.field.ExDestination; +import quickfix.field.HandlInst; +import quickfix.field.OrdStatus; +import quickfix.field.OrdType; +import quickfix.field.OrderQty; +import quickfix.field.Price; +import quickfix.field.SecurityID; +import quickfix.field.SecurityIDSource; +import quickfix.field.SettlType; +import quickfix.field.Side; +import quickfix.field.Text; +import quickfix.field.TransactTime; import quickfix.fix44.ExecutionReport; import quickfix.fix44.MessageCracker; import quickfix.fix44.NewOrderSingle; @@ -21,11 +55,6 @@ import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; import ru.spcex.platform.imdg.api.ImdgTransaction; -import java.math.BigDecimal; -import java.time.ZoneOffset; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - @Service public class Application extends MessageCracker implements quickfix.Application, InitializingBean, DisposableBean { private static final Logger log = LoggerFactory.getLogger(Application.class); @@ -113,25 +142,19 @@ public class Application extends MessageCracker implements quickfix.Application, ImdgTransaction transaction = imdgProvider.newTransaction(); transaction.beginTransaction(); try { - if (executionReport.isSetField(OrderID.FIELD) && executionReport.isSetField(ExDestination.FIELD) && executionReport.isSetField(TransactTime.FIELD)) { - OrderID orderId = executionReport.getOrderID(); + if (executionReport.isSetField(ClOrdID.FIELD) && executionReport.isSetField(ExDestination.FIELD) && executionReport.isSetField(TransactTime.FIELD) && executionReport.isSetField(OrdStatus.FIELD)) { +// OrderID orderId = executionReport.getOrderID(); ExDestination exDestination = (ExDestination) executionReport.getField(new ExDestination()); TransactTime transactTime = executionReport.getTransactTime(); - SecurityID securityID = executionReport.getSecurityID(); - Price price = executionReport.getPrice(); - OrderQty orderQty = executionReport.getOrderQty(); - Account account = executionReport.getAccount(); - Side side = executionReport.getSide(); - Currency currency = executionReport.getCurrency(); - SettlType settlType = executionReport.getSettlType(); OrdStatus ordStatus = executionReport.getOrdStatus(); + ClOrdID clOrdID = executionReport.getClOrdID(); Pattern orderIdPattern = Pattern.compile("(\\d{6})-([A-Z]+)-(\\d+)"); - Matcher matcher = orderIdPattern.matcher(orderId.getValue()); - if (matcher.matches()) { +// Matcher matcher = orderIdPattern.matcher(orderId.getValue()); +// if (matcher.matches()) { // LocalDate tradeDate = LocalDate.parse(matcher.group(1), DateTimeFormatter.ofPattern("yyMMdd")); // String classCode = matcher.group(2); - Long orderNum = Long.parseLong(matcher.group(3)); + Long orderNum = Long.parseLong(clOrdID.getValue()); Imdg sOrdersImdg = transaction.getImdg(IMDGDistributedNames.Map_SOrders, SOrders.class); SOrders sOrders = sOrdersImdg.getSingleObjectByPredicate(sOrdersImdg.predicateBuilder().and( @@ -145,28 +168,36 @@ public class Application extends MessageCracker implements quickfix.Application, sOrders.setClassCode(exDestination.getValue()); sOrders.setTradeDate(transactTime.getValue().toLocalDate()); - sOrders.setSecCode(securityID.getValue()); - sOrders.setPrice(BigDecimal.valueOf(price.getValue())); - sOrders.setQty(BigDecimal.valueOf(orderQty.getValue())); - sOrders.setAccount(account.getValue()); - sOrders.setSettleCode(settlType.getValue()); - sOrders.setPriceCurrency(currency.getValue()); - sOrders.setState(String.valueOf(ordStatus.getValue())); + if (executionReport.isSetSecurityID()) + sOrders.setSecCode(executionReport.getSecurityID().getValue()); + if (executionReport.isSetPrice()) + sOrders.setPrice(BigDecimal.valueOf(executionReport.getPrice().getValue())); + if (executionReport.isSetOrderQty()) + sOrders.setQty(BigDecimal.valueOf(executionReport.getOrderQty().getValue())); + if (executionReport.isSetAccount()) + sOrders.setAccount(executionReport.getAccount().getValue()); + if (executionReport.isSetSettlType()) + sOrders.setSettleCode(executionReport.getSettlType().getValue()); + if (executionReport.isSetCurrency()) + sOrders.setPriceCurrency(executionReport.getCurrency().getValue()); + sOrders.setState(String.valueOf(ordStatus.getValue())); if (ordStatus.valueEquals(OrdStatus.NEW)) { sOrders.setOrderDateTime(transactTime.getValue().toInstant(ZoneOffset.UTC)); } else if (ordStatus.valueEquals(OrdStatus.CANCELED)) { sOrders.setWithdrawDateTime(transactTime.getValue().toInstant(ZoneOffset.UTC)); } - + sOrders.setCreated(Instant.now()); sOrdersImdg.insert(sOrders); } else { + sOrders.setState(String.valueOf(ordStatus.getValue())); if (ordStatus.valueEquals(OrdStatus.NEW)) { sOrders.setOrderDateTime(transactTime.getValue().toInstant(ZoneOffset.UTC)); } else if (ordStatus.valueEquals(OrdStatus.CANCELED)) { sOrders.setWithdrawDateTime(transactTime.getValue().toInstant(ZoneOffset.UTC)); } + sOrders.setUpdated(Instant.now()); sOrdersImdg.update(sOrders); } @@ -175,7 +206,7 @@ public class Application extends MessageCracker implements quickfix.Application, updateRequest.addId(orderNum); kafkaSender.sendRequestToQueue(Consts.ORDER_CURRENCY_STATUS_UPDATE_FIX, updateRequest); } - } +// } transaction.commitTransaction(); } catch (Exception e) { log.error(executionReport.toString(), e); @@ -200,7 +231,7 @@ public class Application extends MessageCracker implements quickfix.Application, newOrderSingle.setField(new ContraBroker(orderCurrency.getCounterTradingCode())); newOrderSingle.set(new SettlType(orderCurrency.getSettleCode())); newOrderSingle.setField(new StringField(5018, orderCurrency.getTradingCode())); // FirmId - + newOrderSingle.set(new HandlInst(HandlInst.AUTOMATED_EXECUTION_ORDER_PRIVATE_NO_BROKER_INTERVENTION)); boolean result = Session.sendToTarget(newOrderSingle, fixSessionID); } catch (SessionNotFound sessionNotFound) { log.error(orderCurrency.toString(), sessionNotFound); diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/security/CurrencyRate.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/security/CurrencyRate.java index c60b0e7b9..f8258a99a 100644 --- a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/security/CurrencyRate.java +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/security/CurrencyRate.java @@ -6,7 +6,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import ru.spcex.clearing.gatewayapi.config.deserializers.LocalDateDeserializer; -import ru.spcex.clearing.gatewayapi.config.deserializers.LocalDateTimeV2Deserializer; +import ru.spcex.clearing.gatewayapi.config.deserializers.LocalDateTimeDeserializer; public class CurrencyRate { @@ -32,11 +32,11 @@ public class CurrencyRate { private BigDecimal price; - @JsonDeserialize(using = LocalDateTimeV2Deserializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonProperty("update_date_time") private LocalDateTime updateDateTime; - @JsonDeserialize(using = LocalDateTimeV2Deserializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonProperty("source_date_time") private LocalDateTime sourceDateTime; diff --git a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/RiskParameterMapStore.java b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/RiskParameterMapStore.java index 3f0ed06b1..3e453fc06 100644 --- a/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/RiskParameterMapStore.java +++ b/clearing-parent/imdg/src/main/java/ru/spcex/clearing/imdg/object/RiskParameterMapStore.java @@ -31,7 +31,7 @@ public class RiskParameterMapStore extends TemplateMapStore { public String[] getFields() { return new String[]{ "ID", "VALUE", "SETTLEMENT_TYPE", "CURRENCY_PAIR", "CURRENCY_PAIR_ID", "CREATED_AT", "UPDATED_AT", "WORKFLOW_STATUS", - "QUOTE_VALUE" + "QUOTE_VALUE", "PENALTY_VALUE" }; } @@ -47,6 +47,7 @@ public class RiskParameterMapStore extends TemplateMapStore { object.setUpdated(getInstantFromTimestamp(resultSet, "UPDATED_AT")); object.setWorkflowStatus(resultSet.getObject("WORKFLOW_STATUS", String.class)); object.setQuoteValue(resultSet.getObject("QUOTE_VALUE", BigDecimal.class)); + object.setPenaltyValue(resultSet.getObject("PENALTY_VALUE", BigDecimal.class)); return object; } @@ -61,7 +62,8 @@ public class RiskParameterMapStore extends TemplateMapStore { TimeUtil.toDateFromInstant(object.getCreated()), TimeUtil.toDateFromInstant(object.getUpdated()), object.getWorkflowStatus(), - object.getQuoteValue() + object.getQuoteValue(), + object.getPenaltyValue() }; return args; } diff --git a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/facade/RiskParameterFacade.java b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/facade/RiskParameterFacade.java index dc0058614..b9b9c4ae8 100644 --- a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/facade/RiskParameterFacade.java +++ b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/facade/RiskParameterFacade.java @@ -42,6 +42,7 @@ public class RiskParameterFacade implements IClearingFacade { riskParameter.setSettlementType(request.getSettlementType()); riskParameter.setValue(request.getValue()); riskParameter.setQuoteValue(request.getQuoteValue()); + riskParameter.setPenaltyValue(request.getPenaltyValue()); riskParameter.setWorkflowStatus(request.getWorkflowStatus()); riskParameter.setCreated(Instant.now()); @@ -55,6 +56,7 @@ public class RiskParameterFacade implements IClearingFacade { riskParameterForUpdate.setValue(request.getValue()); riskParameterForUpdate.setQuoteValue(request.getQuoteValue()); + riskParameterForUpdate.setPenaltyValue(request.getPenaltyValue()); riskParameterForUpdate.setWorkflowStatus(request.getWorkflowStatus()); riskParameterImdg.update(riskParameterForUpdate); diff --git a/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/validation/rule/PresentByPredicate.java b/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/validation/rule/PresentByPredicate.java index 5269785bc..c38ec4be8 100644 --- a/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/validation/rule/PresentByPredicate.java +++ b/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/validation/rule/PresentByPredicate.java @@ -2,6 +2,8 @@ package ru.spcex.platform.imdg.validation.rule; import java.util.Optional; import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.spcex.platform.classes.base.SpcexObjectBase; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.predicate.ImdgPredicate; @@ -10,6 +12,7 @@ import ru.spcex.platform.utils.enumeration.EnumMessage; import ru.spcex.platform.utils.validation.IValidationRule; public class PresentByPredicate implements IValidationRule> { + private static final Logger log = LoggerFactory.getLogger(PresentByPredicate.class); private final String mapName; private final Function errorProvider; @@ -42,8 +45,10 @@ public class PresentByPredicate implements IValidationRule