Merge branch 'dev' into dev_xml-test

This commit is contained in:
Ivan Nikolaev-Axenov 2026-04-30 17:37:30 +03:00
commit c6eb287095
43 changed files with 837 additions and 224 deletions

View file

@ -72,7 +72,7 @@ public class BankAccountValidationConfig {
AccountError.DictionaryNotFound,
false),
ValidateSymbolsRule.notRequired("БИК",
BankAccountUpdateRequest::getBankIdentificationCode,
BankAccountNewRequest::getBankIdentificationCode,
AccountError.BadSymbol),
ValidateSymbolsRule.required("Наименование компании",
BankAccountNewRequest::getBankName,

View file

@ -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)

View file

@ -12,9 +12,12 @@ public class RiskParameterNewAction implements IAction<RiskParameterNewRequest>
@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>
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<RiskParameterNewRequest>
public void setWorkflowStatus(String workflowStatus) {
this.workflowStatus = workflowStatus;
}
public void setPenaltyValue(BigDecimal penaltyValue) {
this.penaltyValue = penaltyValue;
}
}

View file

@ -15,9 +15,12 @@ public class RiskParameterUpdateAction implements IAction<RiskParameterUpdateReq
@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 workflowStatus;
@ -28,6 +31,7 @@ public class RiskParameterUpdateAction implements IAction<RiskParameterUpdateReq
request.setId(this.id);
request.setValue(this.value);
request.setQuoteValue(this.quoteValue);
request.setPenaltyValue(this.penaltyValue);
request.setWorkflowStatus(this.workflowStatus);
return request;
}
@ -61,4 +65,12 @@ public class RiskParameterUpdateAction implements IAction<RiskParameterUpdateReq
public void setWorkflowStatus(String workflowStatus) {
this.workflowStatus = workflowStatus;
}
public void setQuoteValue(BigDecimal quoteValue) {
this.quoteValue = quoteValue;
}
public void setPenaltyValue(BigDecimal penaltyValue) {
this.penaltyValue = penaltyValue;
}
}

View file

@ -1,6 +1,6 @@
{
"version": "3.23.295.165",
"version": "3.23.295.169",
"enums": {
@ -6389,17 +6389,6 @@
"name": "Отправка заявок в Торговую систему",
"fields": []
}
,
{"method":"post",
"destination": "ORST",
"group": "Обмен с Торговой системой",
"name": "Получение статуса заявок из Торговой системы",
"fields": []
}
]
@ -11089,9 +11078,13 @@
{"code": "value",
"name": "Ставка","shortname": "Ставка","type": 10
}
,
{"code": "penaltyValue",
"name": "Ставка переноса при нехватке валюты","shortname": "Ставка переноса при нехватке валюты","type": 10
}
,
{"code": "quoteValue",
"name": "Ставка сопряженной валюты","shortname": "Ставка сопряженной валюты","type": 10
"name": "Ставка переноса при нехватке рублей","shortname": "Ставка переноса при нехватке рублей","type": 10
}
,
{"code": "workflowStatus",
@ -11103,7 +11096,7 @@
"name": "Добавление параметров риска",
"confirmation": "value,quoteValue,settlementType,currencyPairId,workflowStatus",
"confirmation": "value,penaltyValue,quoteValue,settlementType,currencyPairId,workflowStatus",
"class": "ru.spcex.clearing.backendapi.controller.request.cud.securities.RiskParameterNewAction",
@ -11119,9 +11112,13 @@
{"code": "value",
"name": "Ставка","shortname": "Ставка","type": 10
}
,
{"code": "penaltyValue",
"name": "Ставка переноса при нехватке валюты","shortname": "Ставка переноса при нехватке валюты","type": 10
}
,
{"code": "quoteValue",
"name": "Ставка сопряженной валюты","shortname": "Ставка сопряженной валюты","type": 10
"name": "Ставка переноса при нехватке рублей","shortname": "Ставка переноса при нехватке рублей","type": 10
}
,
{"code": "workflowStatus",
@ -11134,7 +11131,7 @@
"name": "Изменение параметров риска",
"confirmation": "value,quoteValue,workflowStatus",
"confirmation": "value,penaltyValue,quoteValue,workflowStatus",
"class": "ru.spcex.clearing.backendapi.controller.request.cud.securities.RiskParameterUpdateAction",
@ -11146,9 +11143,13 @@
{"code": "value",
"name": "Ставка","shortname": "Ставка","type": 10
}
,
{"code": "penaltyValue",
"name": "Ставка переноса при нехватке валюты","shortname": "Ставка переноса при нехватке валюты","type": 10
}
,
{"code": "quoteValue",
"name": "Ставка сопряженной валюты","shortname": "Ставка сопряженной валюты","type": 10
"name": "Ставка переноса при нехватке рублей","shortname": "Ставка переноса при нехватке рублей","type": 10
}
,
{"code": "workflowStatus",
@ -11743,7 +11744,7 @@
}
,
{"code": "market",
"type": 12,"name": "Код режима торгов","shortname": "Режим торгов","visible": true,"searchable": true,"sortable": true,"link": "market","linkCode": "code"
"type": 12,"name": "Код режима торгов","shortname": "Режим торгов","visible": true,"searchable": true,"sortable": true
}
,
{"code": "status",
@ -11831,7 +11832,7 @@
}
,
{"code": "market",
"type": 12,"name": "Код режима торгов","shortname": "Режим торгов","link": "market","linkCode": "code","enabled": false,"required": true
"type": 12,"name": "Код режима торгов","shortname": "Режим торгов","enabled": false,"required": true
}
,
{"code": "price",

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--?xml-stylesheet type="text/xsl" href="\..\corp-reports\src\data\meta\meta.server.xslt"?-->
<meta version="3.23.295.165">
<meta version="3.23.295.169">
<!-- _xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" _xsi:noNamespaceSchemaLocation="file:///E:/d/projects/meta/from/meta.xsd" -->
<!--Здесь словари-->
<enums>
@ -1478,8 +1478,6 @@
</post>
<post destination="SDOR" group="Обмен с Торговой системой" name="Отправка заявок в Торговую систему">
</post>
<post destination="ORST" group="Обмен с Торговой системой" name="Получение статуса заявок из Торговой системы">
</post>
</actions>
</launcher>
<session name="Клиринговые сессии" destination="sessions" historyDestination="history" class="ru.clearing.classes.statics.data.misc.Session" logUpdates="true" table="session">
@ -2570,20 +2568,23 @@
<currencyPairId name="Идентификатор валютной пары" shortname="Валютная пара" type="1"/>
<settlementType name="Код расчета" shortname="Код расчета" type="2" length="4"/>
<value name="Ставка" shortname="Ставка" type="10"/>
<quoteValue name="Ставка сопряженной валюты" shortname="Ставка сопряженной валюты" type="10"/>
<penaltyValue name="Ставка переноса при нехватке валюты" shortname="Ставка переноса при нехватке валюты" type="10"/>
<quoteValue name="Ставка переноса при нехватке рублей" shortname="Ставка переноса при нехватке рублей" type="10"/>
<workflowStatus name="Статус" shortname="Статус" type="2" length="4" link="workflowStatus"/>
<actions>
<post name="Добавление параметров риска" confirmation="value,quoteValue,settlementType,currencyPairId,workflowStatus" class="ru.spcex.clearing.backendapi.controller.request.cud.securities.RiskParameterNewAction">
<post name="Добавление параметров риска" confirmation="value,penaltyValue,quoteValue,settlementType,currencyPairId,workflowStatus" class="ru.spcex.clearing.backendapi.controller.request.cud.securities.RiskParameterNewAction">
<currencyPairId name="Идентификатор валютной пары" shortname="Валютная пара" type="1" link="currencyPair" linkCode="code" linkKeyCode="id"/>
<settlementType name="Код расчёта" shortname="Код расчёта" type="12" link="currencySettlementType" linkCode="code"/>
<value name="Ставка" shortname="Ставка" type="10"/>
<quoteValue name="Ставка сопряженной валюты" shortname="Ставка сопряженной валюты" type="10"/>
<penaltyValue name="Ставка переноса при нехватке валюты" shortname="Ставка переноса при нехватке валюты" type="10"/>
<quoteValue name="Ставка переноса при нехватке рублей" shortname="Ставка переноса при нехватке рублей" type="10"/>
<workflowStatus name="Статус" shortname="Статус" type="2" length="4" link="workflowStatus"/>
</post>
<put name="Изменение параметров риска" confirmation="value,quoteValue,workflowStatus" class="ru.spcex.clearing.backendapi.controller.request.cud.securities.RiskParameterUpdateAction">
<put name="Изменение параметров риска" confirmation="value,penaltyValue,quoteValue,workflowStatus" class="ru.spcex.clearing.backendapi.controller.request.cud.securities.RiskParameterUpdateAction">
<id type="1" name="Идентификатор записи" shortname="ID" link="riskParameter" linkCode="id" required="true"/>
<value name="Ставка" shortname="Ставка" type="10"/>
<quoteValue name="Ставка сопряженной валюты" shortname="Ставка сопряженной валюты" type="10"/>
<penaltyValue name="Ставка переноса при нехватке валюты" shortname="Ставка переноса при нехватке валюты" type="10"/>
<quoteValue name="Ставка переноса при нехватке рублей" shortname="Ставка переноса при нехватке рублей" type="10"/>
<workflowStatus name="Статус" shortname="Статус" type="2" length="4" link="workflowStatus"/>
</put>
</actions>
@ -2726,7 +2727,7 @@
<account type="2" length="50" name="Торговый счет" shortname="Счет" searchable="true" sortable="true" visible="true"/>
<companyId type="1" dbname="Идентификатор участника" name="Наименование участника" shortname="Участник" searchable="true" sortable="true" visible="true" link="company" linkCode="shortName"/>
<tradingCode type="2" length="255" name="Код участника торгов" shortname="Код УТ" searchable="true" sortable="true" visible="true"/>
<market type="12" name="Код режима торгов" shortname="Режим торгов" visible="true" searchable="true" sortable="true" link="market" linkCode="code"/>
<market type="12" name="Код режима торгов" shortname="Режим торгов" visible="true" searchable="true" sortable="true"/>
<status type="2" length="4" name="Статус заявки" shortname="Статус" link="orderStatus" searchable="true" sortable="true" visible="true"/>
<price type="10" name="Цена заявки" shortname="Цена" searchable="true" sortable="true" visible="true"/>
<quantityLot type="3" name="Объем заявки в лотах" shortname="Объем заявки в лотах" searchable="true" sortable="true" visible="false"/>
@ -2748,7 +2749,7 @@
<put name="Изменение заявки" class="ru.spcex.clearing.backendapi.controller.request.cud.order.OrderCurrencyUpdateAction">
<id type="1" name="Идентификатор записи" shortname="ID" link="orderCurrency" linkCode="id" required="true"/>
<securityId type="1" name="Наименование инструмента" shortname="Инструмент" link="security" linkCode="shortName" enabled="false" required="true"/>
<market type="12" name="Код режима торгов" shortname="Режим торгов" link="market" linkCode="code" enabled="false" required="true"/>
<market type="12" name="Код режима торгов" shortname="Режим торгов" enabled="false" required="true"/>
<price type="10" name="Цена заявки" shortname="Цена" required="true"/>
<quantityLot type="3" name="Объем заявки в лотах" shortname="Объем заявки" required="true"/>
<side type="12" name="Направление заявки" shortname="Направление" required="true" link="side"/>

View file

@ -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;
}
}

View file

@ -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<PaymentInstruction> pmtImdg;
Imdg<ClearingMemberCategory> 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<DmauRegistryRequest, IValidator> withdrawalFundsValidator() {
return identificationFundsRequest -> {
ImdgValidationContext<DmauRegistryRequest> 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<DmauRegistryRequest, ImdgPredicate>) 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<ImdgValidationContext<DmauRegistryRequest>>() {
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<DmauRegistryRequest> ctx) {
Company cmp = ctx.getStoredObject(WithdrawalFundsValidationStore.Company);
Account acc = ctx.getStoredObject(WithdrawalFundsValidationStore.Account);
Optional<AssetTrio> 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<RegistrySplitDepositActionRequest, IValidator> splitDepositValidator() {
return refundDateRequest -> {

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

@ -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(),

View file

@ -453,6 +453,7 @@ public class Sdf57Executor extends AbstractExecutor<SDf57> {
Company cmp = stmtCmpAcc.company();
Account acc = stmtCmpAcc.account();
Optional<AssetTrio> 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<SDf57> {
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<SDf57> {
.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<SDf57> {
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());

View file

@ -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<DmauRegistryRequest, IValidator> withdrawalFundsVal;
private final Sdf54Creator sdf54Creator;
private final Imdg<SDf54> sdf54Imdg;
private final Imdg<PaymentInstruction> pmtImdg;
private final Imdg<Registry> 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<DmauRegistryRequest, IValidator> 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<DmauRegistryRequest> 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<EnumMessage> 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<DmauRegistryRequest> 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());
}
}
}

View file

@ -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<Registry> 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<Registry> createDmi(Long tcrId,
String securitySymbol,
BigDecimal summ) {

View file

@ -104,9 +104,10 @@ public enum Sdf55ValidationRule implements IValidationRule<ImdgValidationContext
}
if (tcr == null) {
return of(ClearingError.RecordNotFound, errMsg);
log.debug("{}: TCR was not found; account will be used instead.", ruleName());
} else {
context.storeObject(ValidationStored.Sdf55Tcr, tcr);
}
context.storeObject(ValidationStored.Sdf55Tcr, tcr);
return empty();
}
},

View file

@ -0,0 +1,37 @@
package ru.spcex.clearing.service.validation;
import java.math.BigDecimal;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.spcex.clearing.error.ClearingError;
import ru.spcex.clearing.platform.messaging.domain.cud.registry.DmauRegistryRequest;
import ru.spcex.platform.imdg.validation.ImdgValidationContext;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.validation.IValidationRule;
public enum WithdrawalFundsValidationRule implements IValidationRule<ImdgValidationContext<DmauRegistryRequest>> {
Fields() {
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<DmauRegistryRequest> 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();
}
}

View file

@ -0,0 +1,9 @@
package ru.spcex.clearing.service.validation;
public enum WithdrawalFundsValidationStore {
CompanySymbol,
Company,
Account,
AssetTrio,
;
}

View file

@ -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);

View file

@ -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
sdf54.urgent=urgent
withdrawal.funds.dmau=Withdrawal of funds from the register {0} of unidentified funds

View file

@ -1,3 +1,4 @@
payment-instruction.purpose.withdrawal.tcr=Возврат средств, свободных от обязательств с ТКР {0}.
payment-instruction.purpose.withdrawal=Вывод средств.
sdf54.urgent=срочно
sdf54.urgent=срочно
withdrawal.funds.dmau=Вывод средств с регистра невыясненных денежных средств по {0}

View file

@ -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
INSERT INTO DB_VERSION(ID, VERSION) values (1, '3.23') ON CONFLICT (ID) DO UPDATE SET VERSION = EXCLUDED.VE RSION

View file

@ -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;
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;

View file

@ -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;

View file

@ -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 'Идентификатор регистра';

View file

@ -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<ImdgValidationContext<OrderCompanyUpdateRequest>> {
public static final OrderCurrencyCompanyNotChanged INSTANCE = new OrderCurrencyCompanyNotChanged();
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<OrderCompanyUpdateRequest> 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";
}
}

View file

@ -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);

View file

@ -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<OrderCurrency, OrderCurrency> 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<OrderCurrency, OrderCurrency> 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<OrderCurrencyStatusUpdateRequest> 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);
}
}

View file

@ -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<OrderCurrency> 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) {

View file

@ -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
) {
}

View file

@ -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) {

View file

@ -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));
}
}

View file

@ -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());
}

View file

@ -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);

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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<SOrders> 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);

View file

@ -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;

View file

@ -31,7 +31,7 @@ public class RiskParameterMapStore extends TemplateMapStore<RiskParameter> {
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<RiskParameter> {
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<RiskParameter> {
TimeUtil.toDateFromInstant(object.getCreated()),
TimeUtil.toDateFromInstant(object.getUpdated()),
object.getWorkflowStatus(),
object.getQuoteValue()
object.getQuoteValue(),
object.getPenaltyValue()
};
return args;
}

View file

@ -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);

View file

@ -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<T> implements IValidationRule<ImdgValidationContext<T>> {
private static final Logger log = LoggerFactory.getLogger(PresentByPredicate.class);
private final String mapName;
private final Function<T, EnumMessage> errorProvider;
@ -42,8 +45,10 @@ public class PresentByPredicate<T> implements IValidationRule<ImdgValidationCont
if (obj == null) {
EnumMessage errMsg = errorProvider.apply(validatedObject);
log.debug("object not found by predicate: {}", predicate);
return Optional.of(errMsg);
}
log.debug("object found by predicate [{}]: {}.id={}", predicate, mapName, obj.getId());
if (storeEnabled != null) {
context.storeObject(storeEnabled, obj);

View file

@ -10,6 +10,8 @@ public class RiskParameterNewRequest {
@JsonProperty
private BigDecimal quoteValue;
@JsonProperty
private BigDecimal penaltyValue;
@JsonProperty
private String settlementType;
@JsonProperty
private Long currencyPairId;
@ -55,4 +57,12 @@ public class RiskParameterNewRequest {
public void setWorkflowStatus(String status) {
this.workflowStatus = status;
}
public BigDecimal getPenaltyValue() {
return penaltyValue;
}
public void setPenaltyValue(BigDecimal penaltyValue) {
this.penaltyValue = penaltyValue;
}
}

View file

@ -12,6 +12,8 @@ public class RiskParameterUpdateRequest {
@JsonProperty
private BigDecimal quoteValue;
@JsonProperty
private BigDecimal penaltyValue;
@JsonProperty
private String workflowStatus;
public Long getId() {
@ -45,4 +47,12 @@ public class RiskParameterUpdateRequest {
public void setWorkflowStatus(String status) {
this.workflowStatus = status;
}
public BigDecimal getPenaltyValue() {
return penaltyValue;
}
public void setPenaltyValue(BigDecimal penaltyValue) {
this.penaltyValue = penaltyValue;
}
}