From 48c843e5c15e17e30d128186d8ea422ebf3cef43 Mon Sep 17 00:00:00 2001 From: AKurakin Date: Tue, 23 Apr 2024 15:05:03 +0300 Subject: [PATCH] gateway-api http://jira.mfd.msk:8088/browse/CLS-663 CURR(currency-pair) --- .../config/RestTemplateErrorHandler.java | 7 +- .../controller/GatewayController.java | 24 ++ .../listing/curr/mkr/CurrListingsRequest.java | 22 ++ .../listing/curr/mkr/CurrencyInstrument.java | 342 ++++++++++++++++++ .../gatewayapi/service/GatewayService.java | 16 +- .../CurrencyPairSecurityRequestAdapter.java | 33 ++ .../processor/ListingCurrProcessor.java | 70 ++++ .../controller/GatewayControllerTest.java | 97 +++++ .../cud/CurrencyPairSecurityService.java | 5 + .../platform/messaging/domain/Consts.java | 1 + ...CurrencyPairSecurityNewGatewayRequest.java | 26 ++ .../CurrencyPairSecurityNewRequest.java | 10 + 12 files changed, 650 insertions(+), 3 deletions(-) create mode 100644 clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrListingsRequest.java create mode 100644 clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrencyInstrument.java create mode 100644 clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/adapter/CurrencyPairSecurityRequestAdapter.java create mode 100644 clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/processor/ListingCurrProcessor.java create mode 100644 platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewGatewayRequest.java diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/config/RestTemplateErrorHandler.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/config/RestTemplateErrorHandler.java index ab506cc81..c6000dc0c 100644 --- a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/config/RestTemplateErrorHandler.java +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/config/RestTemplateErrorHandler.java @@ -87,8 +87,13 @@ public class RestTemplateErrorHandler implements ResponseErrorHandler { case ON_DEMAND -> { if (Section.MKR == section) { prefix = "Ошибка загрузки инструментов"; - } else { + } else if (Section.FOND == section) { prefix = "Ошибка загрузки биржевых инструментов"; + } else if (Section.CURR == section) { + prefix = "Ошибка загрузки биржевых валютных инструментов"; + } else {//never + prefix = "Ошибка загрузки " + section + " инструментов"; + log.error("No text for section={}", section); } } case FILL_LIMITS -> prefix = "Ошибка выгрузки лимитов"; diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/GatewayController.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/GatewayController.java index 39d5e65f0..7a47cbfd6 100644 --- a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/GatewayController.java +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/GatewayController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.*; import ru.spcex.clearing.gatewayapi.controller.inbound.request.CommonRequest; import ru.spcex.clearing.gatewayapi.controller.inbound.request.company.CompaniesRequest; import ru.spcex.clearing.gatewayapi.controller.inbound.request.limit.LimitRequest; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrListingsRequest; import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.fond.FondListingsRequest; import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.mkr.MMListingsRequest; import ru.spcex.clearing.gatewayapi.controller.inbound.request.operations.OperationsRequest; @@ -20,6 +21,7 @@ import ru.spcex.clearing.gatewayapi.controller.inbound.response.CommonResponse; import ru.spcex.clearing.gatewayapi.exception.GatewayException; import ru.spcex.clearing.gatewayapi.service.OperationService; import ru.spcex.clearing.gatewayapi.service.processor.CompanyProcessor; +import ru.spcex.clearing.gatewayapi.service.processor.ListingCurrProcessor; import ru.spcex.clearing.gatewayapi.service.processor.ListingFondProcessor; import ru.spcex.clearing.gatewayapi.service.processor.ListingMMProcessor; import ru.spcex.platform.utils.enumeration.EnumMessage; @@ -40,6 +42,7 @@ public class GatewayController { private final CompanyProcessor companiesProcessor; private final ListingFondProcessor listingFondProcessor; private final ListingMMProcessor listingMMProcessor; + private final ListingCurrProcessor listingCurrProcessor; private final ExecutorService executor; private final OperationService operationService; @@ -49,12 +52,14 @@ public class GatewayController { CompanyProcessor companiesProcessor, ListingFondProcessor listingFondProcessor, ListingMMProcessor listingMMProcessor, + ListingCurrProcessor listingCurrProcessor, @Qualifier("gatewayExecutor") ExecutorService executor, OperationService operationService) { this.messageResolver = messageResolver; this.companiesProcessor = companiesProcessor; this.listingFondProcessor = listingFondProcessor; this.listingMMProcessor = listingMMProcessor; + this.listingCurrProcessor = listingCurrProcessor; this.executor = executor; this.operationService = operationService; } @@ -98,6 +103,25 @@ public class GatewayController { } + @ApiOperation(value = "Загрузка биржевого валютного инструмента в Клиринговую систему") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", response = CommonResponse.class), + @ApiResponse(code = 400, message = "Ошибка валидации", response = CommonResponse.class) + }) + @RequestMapping( + path = "/listing_curr", + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseBody + public CommonResponse listingsCurr(@RequestBody CurrListingsRequest request) { +// request.validate(validTypes, List.of(Section.CURR)); + executor.submit(() -> listingCurrProcessor.process(request)); + return createResponse(request, true); + } + + @ApiOperation(value = "Экспорт участников в клиринговую систему") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = CommonResponse.class), diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrListingsRequest.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrListingsRequest.java new file mode 100644 index 000000000..0a7e57409 --- /dev/null +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrListingsRequest.java @@ -0,0 +1,22 @@ +package ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.CommonRequest; + +import java.util.ArrayList; +import java.util.List; + +public class CurrListingsRequest extends CommonRequest { + @JsonProperty("currency_specification") + @ApiModelProperty(value = "Список инструментов") + private List currencyInstrumentList = new ArrayList<>(); + + public List getCurrencyInstrumentList() { + return currencyInstrumentList; + } + + public void setCurrencyInstrumentList(List currencyInstrumentList) { + this.currencyInstrumentList = currencyInstrumentList; + } +} diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrencyInstrument.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrencyInstrument.java new file mode 100644 index 000000000..5a7745312 --- /dev/null +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/controller/inbound/request/listing/curr/mkr/CurrencyInstrument.java @@ -0,0 +1,342 @@ +package ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.annotations.ApiModelProperty; +import ru.spcex.clearing.gatewayapi.config.deserializers.LocalDateDeserializer; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.WithMapId; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; + +public class CurrencyInstrument extends WithMapId { + @JsonProperty("id") + @ApiModelProperty( + value = "id", + example = "28406b73-7eab-4e21-9295-2ae8ee5c4eb5" + ) + private UUID id; + + @JsonProperty("ticker") + @ApiModelProperty( + value = "Код биржевого валютного инструмента", + example = "CNYRUB_TOD_N" + ) + private String ticker; + + @JsonProperty("name") + @ApiModelProperty( + value = "Наименование биржевого валютного инструмента", + example = "Спот-инструмент китайский юань с расчетами сегодня с клирингом в АО СПВБ" + ) + private String name; + + @JsonProperty("exchange_offexchange") + @ApiModelProperty( + value = """ + Биржевой\\Внебиржевой + «EXCHANGE»\\«OFF_EXCHANGE» + """, + example = "EXCHANGE" + ) + private String exchangeOffexchange; + + @JsonProperty("min_step") + @ApiModelProperty( + value = "Шаг изменения цены, значение из listing.dir_price_min_step_precision.min_step", + example = "1" + ) + private BigDecimal minStep; + + @JsonProperty("precision") + @ApiModelProperty( + value = "Точность цены, значение из listing.dir_price_min_step_precision.precision, соответствующее значению из listing.dir_price_min_step_precision.min_step", + example = "2" + ) + private BigDecimal precision; + + @JsonProperty("settle_code") + @ApiModelProperty( + value = "Код (условия) расчетов: заполняется значением согласно справочнику «Код сектора в клиринговой системе»", + example = "T0" + ) + private String settleCode; + + @JsonProperty("value_date") + @ApiModelProperty( + value = "«Код (условия) расчетов» - заполняется значением системного справочника ", + example = "TOD" + ) + private String valueDate; + + @JsonProperty("symbol_trading_mode") + @ApiModelProperty( + value = """ + Тип заявок (адресные/безадресные) - данные из системного справочника. + Y – адресные + N – безадресные + """, + example = "N" + ) + private String symbolTradingMode; + + @JsonProperty("lot_currency_letter_code") + @ApiModelProperty( + value = "Значение поля «Валюта лота»", + example = "CNY" + ) + private String lotCurrencyLetterCode; + + @JsonProperty("matched_currency_letter_code") + @ApiModelProperty( + value = "Значение поля «Сопряженная валюта»", + example = "RUB" + ) + private String matchedCurrencyLetterCode; + + @JsonProperty("trading_mode") + @ApiModelProperty( + value = "Доступные значения: из справочника «Режимы торгов Секция ДВР» - значение поля Спецификации «Режим торгов»", + example = "NVAD" + ) + private String tradingMode; + + @JsonProperty("lot_size") + @ApiModelProperty( + value = "Размер лота", + example = "1" + ) + private BigDecimal lotSize; + + @JsonProperty("clearing_organization") + @ApiModelProperty( + value = "Клиринговая организация", + example = "АО СПВБ" + ) + private String clearingOrganization; + + + @JsonProperty("settlement_organization") + @ApiModelProperty( + value = "Расчетная организация", + example = "НКО АО ПРЦ" + ) + private String settlementOrganization; + + + @JsonProperty("matched_settlement_organization") + @ApiModelProperty( + value = "Расчетная организация", + example = "НКО АО ПРЦ" + ) + private String matchedSettlementOrganization; + + @JsonProperty("number_of_lot_currency") + @ApiModelProperty( + value = "Размер лота", + example = "0" + ) + private BigDecimal numberOfLotCurrency; + + + @JsonProperty("start_trading_date") + @JsonDeserialize(using = LocalDateDeserializer.class) + @ApiModelProperty( + value = "Дата начала торгов инструментом", + example = "21.02.2024" + ) + private LocalDate startTradingDate; + + @JsonProperty("specification_approval_date") + @JsonDeserialize(using = LocalDateDeserializer.class) + @ApiModelProperty( + value = "Дата принятия Решения об утверждении Спецификации биржевого инструмента", + example = "21.02.2024" + ) + private LocalDate specificationApprovalDate; + + @JsonProperty("specification_approval_number") + @ApiModelProperty( + value = "Номер принятия Решения об утверждении Спецификации биржевого инструмента", + example = "125" + ) + private String specificationApprovalNumber; + + @JsonProperty("workflow_status") + @ApiModelProperty( + value = """ + «ACTV»|«BLKD» + """, + example = "ACTV" + ) + private String workflowStatus; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTicker() { + return ticker; + } + + public void setTicker(String ticker) { + this.ticker = ticker; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getExchangeOffexchange() { + return exchangeOffexchange; + } + + public void setExchangeOffexchange(String exchangeOffexchange) { + this.exchangeOffexchange = exchangeOffexchange; + } + + public BigDecimal getMinStep() { + return minStep; + } + + public void setMinStep(BigDecimal minStep) { + this.minStep = minStep; + } + + public BigDecimal getPrecision() { + return precision; + } + + public void setPrecision(BigDecimal precision) { + this.precision = precision; + } + + public String getSettleCode() { + return settleCode; + } + + public void setSettleCode(String settleCode) { + this.settleCode = settleCode; + } + + public String getValueDate() { + return valueDate; + } + + public void setValueDate(String valueDate) { + this.valueDate = valueDate; + } + + public String getSymbolTradingMode() { + return symbolTradingMode; + } + + public void setSymbolTradingMode(String symbolTradingMode) { + this.symbolTradingMode = symbolTradingMode; + } + + public String getLotCurrencyLetterCode() { + return lotCurrencyLetterCode; + } + + public void setLotCurrencyLetterCode(String lotCurrencyLetterCode) { + this.lotCurrencyLetterCode = lotCurrencyLetterCode; + } + + public String getMatchedCurrencyLetterCode() { + return matchedCurrencyLetterCode; + } + + public void setMatchedCurrencyLetterCode(String matchedCurrencyLetterCode) { + this.matchedCurrencyLetterCode = matchedCurrencyLetterCode; + } + + public String getTradingMode() { + return tradingMode; + } + + public void setTradingMode(String tradingMode) { + this.tradingMode = tradingMode; + } + + public BigDecimal getLotSize() { + return lotSize; + } + + public void setLotSize(BigDecimal lotSize) { + this.lotSize = lotSize; + } + + public String getClearingOrganization() { + return clearingOrganization; + } + + public void setClearingOrganization(String clearingOrganization) { + this.clearingOrganization = clearingOrganization; + } + + public String getSettlementOrganization() { + return settlementOrganization; + } + + public void setSettlementOrganization(String settlementOrganization) { + this.settlementOrganization = settlementOrganization; + } + + public String getMatchedSettlementOrganization() { + return matchedSettlementOrganization; + } + + public void setMatchedSettlementOrganization(String matchedSettlementOrganization) { + this.matchedSettlementOrganization = matchedSettlementOrganization; + } + + public BigDecimal getNumberOfLotCurrency() { + return numberOfLotCurrency; + } + + public void setNumberOfLotCurrency(BigDecimal numberOfLotCurrency) { + this.numberOfLotCurrency = numberOfLotCurrency; + } + + public LocalDate getStartTradingDate() { + return startTradingDate; + } + + public void setStartTradingDate(LocalDate startTradingDate) { + this.startTradingDate = startTradingDate; + } + + public LocalDate getSpecificationApprovalDate() { + return specificationApprovalDate; + } + + public void setSpecificationApprovalDate(LocalDate specificationApprovalDate) { + this.specificationApprovalDate = specificationApprovalDate; + } + + public String getSpecificationApprovalNumber() { + return specificationApprovalNumber; + } + + public void setSpecificationApprovalNumber(String specificationApprovalNumber) { + this.specificationApprovalNumber = specificationApprovalNumber; + } + + public String getWorkflowStatus() { + return workflowStatus; + } + + public void setWorkflowStatus(String workflowStatus) { + this.workflowStatus = workflowStatus; + } +} \ No newline at end of file diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/GatewayService.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/GatewayService.java index 3bbe6f446..c94e63ac4 100644 --- a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/GatewayService.java +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/GatewayService.java @@ -148,20 +148,32 @@ public class GatewayService extends QueueConsumer implements InitializingBean { .type(OutboundRequestType.ON_DEMAND.getKey()) .build(); + OutboundRequest outboundCurrRequest = OutboundRequestBuilder.builder() + .section(Section.CURR.getKey()) + .type(OutboundRequestType.ON_DEMAND.getKey()) + .build(); + HttpEntity requestFond = makeDefaultRequest(outboundFondRequest); HttpEntity requestMKR = makeDefaultRequest(outboundMkrRequest); + HttpEntity requestCURR = makeDefaultRequest(outboundCurrRequest); outboundRequestByUuid.put(outboundFondRequest.getId(), outboundFondRequest); outboundRequestByUuid.put(outboundMkrRequest.getId(), outboundMkrRequest); + outboundRequestByUuid.put(outboundCurrRequest.getId(), outboundCurrRequest); ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestFond, SuccessResponse.class); SuccessResponse bodyResponse = response.getBody(); if (bodyResponse != null) { - log.debug("LOSC task 1/2 complete (FOND section), success response: {}", bodyResponse); + log.debug("LOSC task 1/3 complete (FOND section), success response: {}", bodyResponse); } response = restTemplate.exchange(url, HttpMethod.POST, requestMKR, SuccessResponse.class); bodyResponse = response.getBody(); if (bodyResponse != null) { - log.debug("LOSC task 2/2 complete (MKR section), success response: {}", bodyResponse); + log.debug("LOSC task 2/3 complete (MKR section), success response: {}", bodyResponse); + } + response = restTemplate.exchange(url, HttpMethod.POST, requestCURR, SuccessResponse.class); + bodyResponse = response.getBody(); + if (bodyResponse != null) { + log.debug("LOSC task 3/3 complete (CURR section), success response: {}", bodyResponse); } } diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/adapter/CurrencyPairSecurityRequestAdapter.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/adapter/CurrencyPairSecurityRequestAdapter.java new file mode 100644 index 000000000..53cab3d15 --- /dev/null +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/adapter/CurrencyPairSecurityRequestAdapter.java @@ -0,0 +1,33 @@ +package ru.spcex.clearing.gatewayapi.service.adapter; + +import org.springframework.stereotype.Service; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrencyInstrument; +import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewGatewayRequest; +import ru.spcex.platform.enumeration.InstrumentType; + +@Service +public class CurrencyPairSecurityRequestAdapter { + + public CurrencyPairSecurityNewGatewayRequest toCurrencySecurityNewRequest(CurrencyInstrument currencyInstrument) { + CurrencyPairSecurityNewGatewayRequest req = new CurrencyPairSecurityNewGatewayRequest(); + + CurrencyPairSecurityNewRequest securityNewRequest = new CurrencyPairSecurityNewRequest(); + securityNewRequest.setInstrumentType(InstrumentType.CRNC.getKey()); + securityNewRequest.setSecuritySymbol(currencyInstrument.getTicker()); + securityNewRequest.setFullName(currencyInstrument.getName()); + securityNewRequest.setBaseUnitSize(currencyInstrument.getNumberOfLotCurrency()); + securityNewRequest.setSettlementType(currencyInstrument.getSettleCode()); + securityNewRequest.setClearingOrganization(currencyInstrument.getClearingOrganization()); + securityNewRequest.setWorkflowStatus(currencyInstrument.getWorkflowStatus()); + + //to listing + securityNewRequest.setMinStep(currencyInstrument.getMinStep()); + securityNewRequest.setPrecision(currencyInstrument.getPrecision()); + securityNewRequest.setLotSize(currencyInstrument.getLotSize()); + + req.setCurrencySecurityNewRequest(securityNewRequest); +// req.setUuid(currencyInstrument.getInitiatorId() != null ? currencyInstrument.getInitiatorId().toString() : null); + return req; + } +} diff --git a/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/processor/ListingCurrProcessor.java b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/processor/ListingCurrProcessor.java new file mode 100644 index 000000000..d4d0b4b57 --- /dev/null +++ b/clearing-parent/gateway-api/src/main/java/ru/spcex/clearing/gatewayapi/service/processor/ListingCurrProcessor.java @@ -0,0 +1,70 @@ +package ru.spcex.clearing.gatewayapi.service.processor; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrListingsRequest; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrencyInstrument; +import ru.spcex.clearing.gatewayapi.service.CodeStatusComparator; +import ru.spcex.clearing.gatewayapi.service.adapter.CurrencyPairSecurityRequestAdapter; +import ru.spcex.clearing.platform.messaging.domain.Consts; +import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewGatewayRequest; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.platform.utils.log.ExceptionUtils; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class ListingCurrProcessor { + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final CurrencyPairSecurityRequestAdapter currencyPairSecurityRequestAdapter; + private final KafkaSender kafkaSender; + + public ListingCurrProcessor(CurrencyPairSecurityRequestAdapter currencyPairSecurityRequestAdapter, + KafkaSender kafkaSender) { + this.currencyPairSecurityRequestAdapter = currencyPairSecurityRequestAdapter; + this.kafkaSender = kafkaSender; + } + + public void process(CurrListingsRequest request) { + List currencyInstrumentList = request.getCurrencyInstrumentList(); + CodeStatusComparator comparatorByCodeAndWorkflowStatus = CodeStatusComparator.create( + CurrencyInstrument::getTicker, + CurrencyInstrument::getWorkflowStatus); + currencyInstrumentList = currencyInstrumentList.stream() + .filter(ei -> { + if (StringUtils.isEmpty(ei.getTicker())) { + log.warn("CurrencyInstrument {}, ignore 1002: required field 'ticker' was empty", ei.getId()); + return false; + } + if ("EXCHANGE".equalsIgnoreCase(StringUtils.trim(ei.getExchangeOffexchange()))) { + return true; + } else { + log.trace("CurrencyInstrument {} with ticker={} ignore: not allow exchange_offexchange={}", + ei.getId(), ei.getTicker(), ei.getExchangeOffexchange()); + return false; + } + }) + .sorted(comparatorByCodeAndWorkflowStatus).collect(Collectors.toList()); + for (CurrencyInstrument currencyInstrument : currencyInstrumentList) { + UUID currencyInstrumentId = null; + try { + currencyInstrumentId = currencyInstrument.getId(); + log.debug("Process currencyInstrumentId : {}", currencyInstrumentId); +// if (StringUtils.isEmpty(currencyInstrument.getCode()) || currencyInstrument.getCode().length() < 6) { +// log.warn("Incorrect code : {}, skip record", currencyInstrument.getCode()); +// continue; +// } + CurrencyPairSecurityNewGatewayRequest currencySecurityNewRequest = currencyPairSecurityRequestAdapter + .toCurrencySecurityNewRequest(currencyInstrument); + kafkaSender.sendRequestToQueue(Consts.DESTINATION_GATEWAY_CURRENCY_PAIR_SECURITY, currencySecurityNewRequest); + } catch (Throwable e) { + log.error("currencyInstrumentId={}: {}", currencyInstrumentId, ExceptionUtils.getStackTrace(e)); + } + } + } +} diff --git a/clearing-parent/gateway-api/src/test/java/ru/spcex/clearing/gatewayapi/controller/GatewayControllerTest.java b/clearing-parent/gateway-api/src/test/java/ru/spcex/clearing/gatewayapi/controller/GatewayControllerTest.java index 02f93ba98..7bcb4d499 100644 --- a/clearing-parent/gateway-api/src/test/java/ru/spcex/clearing/gatewayapi/controller/GatewayControllerTest.java +++ b/clearing-parent/gateway-api/src/test/java/ru/spcex/clearing/gatewayapi/controller/GatewayControllerTest.java @@ -1,14 +1,19 @@ package ru.spcex.clearing.gatewayapi.controller; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrListingsRequest; +import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrencyInstrument; import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.fond.FondListingsRequest; import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.fond.FondSecurity; import ru.spcex.clearing.gatewayapi.controller.inbound.response.CommonResponse; +import ru.spcex.clearing.test.json.JsonUtil; import java.math.BigDecimal; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -20,6 +25,7 @@ import static ru.spcex.clearing.test.json.JsonUtil.writeValue; class GatewayControllerTest extends AbstractControllerTest{ public static final String LISTING_FOND_URL = "/listing_fond/"; + public static final String LISTING_CURR_URL = "/listing_curr/"; @Test void listingsFond() throws Exception { @@ -356,4 +362,95 @@ class GatewayControllerTest extends AbstractControllerTest{ "\t\t}\n" + "\t]\n" + "}"; + + @Test + void listingsCurr() throws Exception { + CommonResponse expected = new CommonResponse(); + expected.setCode(0L); + expected.setMessage("success"); + CurrListingsRequest request = new CurrListingsRequest(); + List instruments = new ArrayList<>(); + CurrencyInstrument cInst = new CurrencyInstrument(); +// cInst.setInstrumentType("CURR"); + cInst.setId(UUID.fromString("28406b73-7eab-4e21-9295-2ae8ee5c4eb5")); + cInst.setTicker("CNYRUB_TOD_N"); + cInst.setName("Спот-инструмент китайский юань с расчетами сегодня с клирингом в АО СПВБ"); + cInst.setExchangeOffexchange("EXCHANGE"); + cInst.setMinStep(BigDecimal.valueOf(1)); + cInst.setPrecision(BigDecimal.valueOf(2)); + cInst.setSettleCode("T0"); + cInst.setValueDate("TOD"); + cInst.setSymbolTradingMode("N"); + cInst.setLotCurrencyLetterCode("CNY"); + cInst.setMatchedCurrencyLetterCode("RUB"); + cInst.setTradingMode("NVAD"); + cInst.setLotSize(BigDecimal.valueOf(1)); + cInst.setClearingOrganization("АО СПВБ"); + cInst.setSettlementOrganization("НКО АО ПРЦ"); + cInst.setMatchedSettlementOrganization("НКО АО ПРЦ"); + cInst.setNumberOfLotCurrency(BigDecimal.valueOf(0)); + cInst.setStartTradingDate(LocalDate.of(2024,2,21)); + cInst.setSpecificationApprovalDate(LocalDate.of(2024,2,21)); + cInst.setSpecificationApprovalNumber("125"); + cInst.setWorkflowStatus("ACTV"); + instruments.add(cInst); + request.setCurrencyInstrumentList(instruments); + String req1 = writeValue(request); + Assertions.assertEquals("{\"id\":null,\"type\":null,\"datetime\":null,\"section\":null,\"currency_specification\":[{\"id\":\"28406b73-7eab-4e21-9295-2ae8ee5c4eb5\",\"ticker\":\"CNYRUB_TOD_N\",\"name\":\"Спот-инструмент китайский юань с расчетами сегодня с клирингом в АО СПВБ\",\"exchange_offexchange\":\"EXCHANGE\",\"min_step\":1,\"precision\":2,\"settle_code\":\"T0\",\"value_date\":\"TOD\",\"symbol_trading_mode\":\"N\",\"lot_currency_letter_code\":\"CNY\",\"matched_currency_letter_code\":\"RUB\",\"trading_mode\":\"NVAD\",\"lot_size\":1,\"clearing_organization\":\"АО СПВБ\",\"settlement_organization\":\"НКО АО ПРЦ\",\"matched_settlement_organization\":\"НКО АО ПРЦ\",\"number_of_lot_currency\":0,\"start_trading_date\":\"2024-02-21\",\"specification_approval_date\":\"2024-02-21\",\"specification_approval_number\":\"125\",\"workflow_status\":\"ACTV\"}]}", + req1); + final String CURR_REQ = """ + { + "id": "1126de52-59ba-4118-a055-a6ac9b214ccd", + "type": "DAY_START", + "section": "CURR", + "datetime": "29.02.2024 15:57:44:506", + "currency_specification": + [ + { + "id": "28406b73-7eab-4e21-9295-2ae8ee5c4eb5", + "ticker": "CNYRUB_TOD_N", + "name": "Спот-инструмент китайский юань с расчетами сегодня с клирингом в АО СПВБ", + "exchange_offexchange": "EXCHANGE", + "min_step": 1, + "precision": 2, + "settle_code": "T0", + "value_date": "TOD", + "symbol_trading_mode": "N", + "lot_currency_letter_code": "CNY", + "matched_currency_letter_code": "RUB", + "trading_mode": "NVAD", + "lot_size": 1, + "clearing_organization": "АО СПВБ", + "settlement_organization": "НКО АО ПРЦ", + "matched_settlement_organization": "НКО АО ПРЦ", + "number_of_lot_currency": 0, + "start_trading_date": "21.02.2024", + "specification_approval_date": "21.02.2024", + "specification_approval_number": "125", + "workflow_status": "ACTV" + } + ] + } + """; + + //ACT + MvcResult mvcResult = perform(MockMvcRequestBuilders.post(LISTING_FOND_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(CURR_REQ)) + .andDo(print())//output to the log request and response + //ASSERT + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + //.andExpect(content().json(writeValue(expected))) + .andReturn(); + + String resp = mvcResult.getResponse().getContentAsString(); + String expResp = "{\"id\":\"1126de52-59ba-4118-a055-a6ac9b214ccd\",\"type\":\"DAY_START\",\"datetime\":\"29.02.2024 15:57:44:506\",\"section\":\"CURR\",\"code\":0,\"message\":\"success\"}"; + resp = resp.replaceAll("\"datetime\":\"[0-9\\\\.: ]+\",", "__DATE_TIME_"); + expResp = expResp.replaceAll("\"datetime\":\"[0-9\\\\.: ]+\",", "__DATE_TIME_"); + Assertions.assertEquals(expResp, resp); +// CudResponseTest cudResponseTest = JsonUtil.readValue(mvcResult.getResponse().getContentAsString(), CudResponseTest.class); +// expected.setPayload(new QueueSuccessResponse(ActionType.NEW, cudResponseTest.getPayload().getId())); +// CUD_RESPONSE_MATCHER.assertMatch(cudResponseTest, expected); + } } \ No newline at end of file diff --git a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/CurrencyPairSecurityService.java b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/CurrencyPairSecurityService.java index 21cfa3cd0..5fb66e203 100644 --- a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/CurrencyPairSecurityService.java +++ b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/CurrencyPairSecurityService.java @@ -15,6 +15,7 @@ import ru.spcex.clearing.platform.messaging.domain.Consts; import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewRequest; import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityUpdateRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewGatewayRequest; import ru.spcex.clearing.platform.messaging.service.QueueConsumer; import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; import ru.spcex.clearing.securities.validation.ValidationProvider; @@ -73,6 +74,10 @@ public class CurrencyPairSecurityService extends QueueConsumer implements Initia callback(CommonDeleteRequest.class) .setFunction(this::deleteCurrencyPairSecurity) .forDestination(Consts.DESTINATION_CURRENCY_PAIR_SECURITIES_DELETE, callbacks::put); + //todo DESTINATION_GATEWAY_CURRENCY_PAIR_SECURITY see CLS-612, CLS-663 +// callback(CurrencyPairSecurityNewGatewayRequest.class) +// .setFunction(this::newCurrencyPairSecurity) +// .forDestination(Consts.DESTINATION_GATEWAY_CURRENCY_PAIR_SECURITY, callbacks::put); imdgProvider.waitAvailable(); init(); } diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java index 48cdf4822..7e3f0be11 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java @@ -187,6 +187,7 @@ public interface Consts { String DESTINATION_TRADING_CLEARING_REGISTRY_ON_TCRLIST_NEW = "registry-on-trading_clearing_registry_list-new"; + String DESTINATION_GATEWAY_CURRENCY_PAIR_SECURITY = "currency-pair-securities-gateway-new"; String DESTINATION_CURRENCY_PAIR_SECURITIES_NEW = "currency-pair-securities-new"; String DESTINATION_CURRENCY_PAIR_SECURITIES_UPDATE = "currency-pair-securities-update"; String DESTINATION_CURRENCY_PAIR_SECURITIES_DELETE = "currency-pair-securities-delete"; diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewGatewayRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewGatewayRequest.java new file mode 100644 index 000000000..6bd10ac13 --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewGatewayRequest.java @@ -0,0 +1,26 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.security; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CurrencyPairSecurityNewGatewayRequest { + @JsonProperty + private String uuid; + @JsonProperty + private CurrencyPairSecurityNewRequest currencySecurityNewRequest; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public CurrencyPairSecurityNewRequest getCurrencySecurityNewRequest() { + return currencySecurityNewRequest; + } + + public void setCurrencySecurityNewRequest(CurrencyPairSecurityNewRequest currencySecurityNewRequest) { + this.currencySecurityNewRequest = currencySecurityNewRequest; + } +} diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewRequest.java index 7510ed572..422ce55ea 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewRequest.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/security/CurrencyPairSecurityNewRequest.java @@ -23,6 +23,8 @@ public class CurrencyPairSecurityNewRequest implements WithSecuritySymbol, WithI @JsonProperty private BigDecimal minStep; @JsonProperty + private BigDecimal precision; + @JsonProperty private BigDecimal baseUnitSize; @JsonProperty private String settlementType; @@ -89,6 +91,14 @@ public class CurrencyPairSecurityNewRequest implements WithSecuritySymbol, WithI this.minStep = minStep; } + public BigDecimal getPrecision() { + return precision; + } + + public void setPrecision(BigDecimal precision) { + this.precision = precision; + } + public BigDecimal getBaseUnitSize() { return baseUnitSize; }