Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
ialbert 2026-04-16 16:07:33 +03:00
commit c9fc25d7db
11 changed files with 276 additions and 14 deletions

View file

@ -14,7 +14,7 @@ public class DefaultManagementSettings {
private HazelcastClientParams hazelcast;
private KafkaConsumerSettings kafkaConsumer;
private KafkaProducerSettings kafkaProducer;
private String clearingSystem;
private String fileStoragePath;
public HazelcastClientParams getHazelcast() {
return hazelcast;
@ -40,11 +40,11 @@ public class DefaultManagementSettings {
this.kafkaProducer = kafkaProducer;
}
public String getClearingSystem() {
return clearingSystem;
public String getFileStoragePath() {
return fileStoragePath;
}
public void setClearingSystem(String clearingSystem) {
this.clearingSystem = clearingSystem;
public void setFileStoragePath(String fileStoragePath) {
this.fileStoragePath = fileStoragePath;
}
}

View file

@ -3,6 +3,7 @@ package ru.spcex.clearing.dflt.management.listener;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import org.apache.kafka.clients.consumer.Consumer;
@ -18,6 +19,7 @@ import ru.clearing.classes.statics.data.misc.Listing;
import ru.clearing.classes.statics.data.misc.Market;
import ru.clearing.classes.statics.data.misc.OrderCurrency;
import ru.clearing.classes.statics.data.misc.OvernightSettings;
import ru.clearing.classes.statics.data.misc.SOrders;
import ru.clearing.classes.statics.data.registry.Registry;
import ru.clearing.classes.statics.data.registry.TradingClearingRegistry;
import ru.clearing.classes.statics.data.security.Rates;
@ -31,12 +33,14 @@ 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.order.OrderCompanyNewRequest;
import ru.spcex.clearing.platform.messaging.domain.cud.order.OrderCompanyUpdateRequest;
import ru.spcex.clearing.platform.messaging.domain.cud.order.OrderCurrencyStatusUpdateRequest;
import ru.spcex.clearing.platform.messaging.domain.cud.order.OrderCurrencyUpdateRequest;
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.clearing.util.security.UserRoleVerification;
import ru.spcex.platform.enumeration.MarketType;
import ru.spcex.platform.enumeration.OrderStatus;
import ru.spcex.platform.enumeration.OvernightType;
import ru.spcex.platform.enumeration.SettleCode;
import ru.spcex.platform.enumeration.Side;
@ -55,6 +59,7 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing
private final Imdg<Registry> registryImdg;
private final Imdg<OvernightSettings> overnightSettingsImdg;
private final Imdg<OrderCurrency> orderCurrencyImdg;
private final Imdg<SOrders> sOrdersImdg;
private final Function<OrderCurrencyUpdateRequest, IValidator> orderCurrencyUpdateValidator;
private final Function<OrderCurrencyUpdateRequest, IValidator> overnightTypeUpdateValidator;
private final Function<OrderCompanyNewRequest, IValidator> orderCompanyNewValidator;
@ -81,6 +86,7 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing
super(kafkaQueue, kafkaProducer);
this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class);
this.overnightSettingsImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_OvernightSettings, OvernightSettings.class);
this.sOrdersImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SOrders, SOrders.class);;
this.orderCurrencyUpdateValidator = orderCurrencyUpdateValidator;
this.overnightTypeUpdateValidator = overnightTypeUpdateValidator;
this.orderCompanyNewValidator = orderCompanyNewValidator;
@ -110,6 +116,12 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing
callback(CommonDeleteRequest.class)
.setFunction(this::deleteCompany)
.forDestination(Consts.ORDER_CURRENCY_COMPANY_DELETE, callbacks::put);
callback(OrderCurrencyStatusUpdateRequest.class)
.setConsumer(this::updateByImporter)
.forDestination(Consts.ORDER_CURRENCY_STATUS_UPDATE_IMPORTER, callbacks::put);
callback(OrderCurrencyStatusUpdateRequest.class)
.setConsumer(this::updateByFix)
.forDestination(Consts.ORDER_CURRENCY_STATUS_UPDATE_FIX, callbacks::put);
init();
}
@ -179,7 +191,7 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing
Market market = imdgQueryService.getSpotMarket();
Listing listing = imdgQueryService.findListing(rgs, market.getCode());
//мб возвращать вместе с листингом?
CurrencyPairDictionary currPair = imdgQueryService.getCurrencyPairDictionaryByListing(listing);
CurrencyPairDictionary currPair = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listing.getSecurityId());
Rates rate = imdgQueryService.getRate(currPair.getBaseCurrency(), LocalDate.now());
var pair = new Pair<>(orderT0, orderT1);
@ -335,4 +347,54 @@ public class OrderCurrencyListener extends QueueConsumer implements Initializing
orderCurrencyImdg.delete(counterOrder);
return null;
}
private void updateByImporter(BaseRequest<OrderCurrencyStatusUpdateRequest> req) {
OrderCurrencyStatusUpdateRequest payload = req.getRequestPayload();
for (Long orderId : payload.getIds()) {
SOrders sOrder = sOrdersImdg.getSingleObjectByID(orderId);
OrderCurrency orderCurrency = orderCurrencyImdg.getSingleObjectByID(orderId);
OrderStatus status = resolveStatusByImportState(sOrder.getState());
if (status == null) {
continue;
}
orderCurrency.setStatus(status.getKey());
orderCurrencyImdg.update(orderCurrency);
}
}
private void updateByFix(BaseRequest<OrderCurrencyStatusUpdateRequest> req) {
OrderCurrencyStatusUpdateRequest payload = req.getRequestPayload();
for (Long orderId : payload.getIds()) {
SOrders sOrder = sOrdersImdg.getSingleObjectByID(orderId);
OrderCurrency orderCurrency = orderCurrencyImdg.getSingleObjectByID(orderId);
OrderStatus status = resolveStatusByFixState(sOrder.getState());
if (status == null) {
continue;
}
orderCurrency.setStatus(status.getKey());
orderCurrencyImdg.update(orderCurrency);
}
}
private OrderStatus resolveStatusByImportState(String state) {
if (Arrays.asList("0", "1", "15").contains(state)) {
return null;
}
if (state.equals("3")) {
return OrderStatus.NEW;
} else {
return OrderStatus.REJECTED;
}
}
private OrderStatus resolveStatusByFixState(String state) {
return switch (state) {
case "0" -> OrderStatus.NEW;
case "1" -> OrderStatus.PTFL;
case "2" -> OrderStatus.FILL;
case "4" -> OrderStatus.CNCL;
case "5" -> OrderStatus.REPL;
case "8" -> OrderStatus.REJECTED;
default -> null;
};
}
}

View file

@ -1,5 +1,11 @@
package ru.spcex.clearing.dflt.management.listener;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Optional;
import org.apache.kafka.clients.consumer.Consumer;
@ -9,20 +15,26 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.clearing.classes.statics.data.misc.OrderCurrency;
import ru.clearing.classes.statics.data.misc.OvernightSettings;
import ru.clearing.classes.statics.data.registry.Registry;
import ru.spcex.clearing.dflt.management.config.settings.DefaultManagementSettings;
import ru.spcex.clearing.dflt.management.service.ImdgQueryService;
import ru.spcex.clearing.dflt.management.service.OrderCurrencyTriExportService;
import ru.spcex.clearing.dflt.management.service.SpotOvernightOrderService;
import ru.spcex.clearing.dflt.management.service.SwapOvernightOrderService;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.domain.cud.schedule.LauncherCommandRequest;
import ru.spcex.clearing.platform.messaging.service.QueueConsumer;
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
import ru.spcex.platform.enumeration.OvernightType;
import ru.spcex.platform.enumeration.Task;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgProvider;
import ru.spcex.platform.utils.enumeration.IEnumKey;
import ru.spcex.platform.utils.log.ExceptionUtils;
@Service
public class TaskListener extends QueueConsumer implements InitializingBean {
@ -31,6 +43,10 @@ public class TaskListener extends QueueConsumer implements InitializingBean {
private final Imdg<Registry> registryImdg;
private final SwapOvernightOrderService swapOvernightOrderService;
private final SpotOvernightOrderService spotOvernightOrderService;
private final OrderCurrencyTriExportService orderCurrencyTriExportService;
private final KafkaSender kafkaSender;
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyymmddHHmmssSSS");
private final Path storagePath;
@Autowired
public TaskListener(Consumer<String, Object> kafkaQueue,
@ -38,12 +54,18 @@ public class TaskListener extends QueueConsumer implements InitializingBean {
ImdgQueryService imdgQueryService,
ImdgProvider imdgProvider,
SwapOvernightOrderService swapOvernightOrderService,
SpotOvernightOrderService spotOvernightOrderService) {
SpotOvernightOrderService spotOvernightOrderService,
OrderCurrencyTriExportService orderCurrencyTriExportService,
KafkaSender kafkaSender,
DefaultManagementSettings defaultManagementSettings) {
super(kafkaQueue, kafkaProducer);
this.imdgQueryService = imdgQueryService;
this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class);
this.swapOvernightOrderService = swapOvernightOrderService;
this.spotOvernightOrderService = spotOvernightOrderService;
this.orderCurrencyTriExportService = orderCurrencyTriExportService;
this.kafkaSender = kafkaSender;
this.storagePath = Path.of(defaultManagementSettings.getFileStoragePath());
}
@Override
@ -51,6 +73,9 @@ public class TaskListener extends QueueConsumer implements InitializingBean {
callback(LauncherCommandRequest.class)
.setConsumer(this::createOrder)
.forDestination(Task.CROR.topic(), callbacks::put);
callback(LauncherCommandRequest.class)
.setConsumer(this::exportToTriAndSendToFix)
.forDestination(Task.SDOR.topic(), callbacks::put);
init();
}
@ -88,6 +113,19 @@ public class TaskListener extends QueueConsumer implements InitializingBean {
log.info("CROR createOrder finished");
}
private void exportToTriAndSendToFix(BaseRequest<LauncherCommandRequest> req) {
try {
Collection<OrderCurrency> orders = imdgQueryService.getClccOrderCurrencyIds();
String content = orderCurrencyTriExportService.exportToTri(orders);
Path fileName = storagePath.resolve(String.format("KS_TS_ORDERS_%s.tri",
LocalTime.now().format(formatter)));
Files.writeString(fileName, content, StandardCharsets.UTF_8);
kafkaSender.sendRequestToQueue(Consts.SDOR_FIX_TASK, req.getRequestPayload());
} catch (IOException e) {
log.error("Error while import order currency to tri file: {}", ExceptionUtils.getStackTrace(e));
}
}
private OvernightType resolveOvernightType(Registry registry) {
Optional<OvernightSettings> settingsOpt =

View file

@ -10,6 +10,7 @@ import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.company.CompanyRoleSet;
import ru.clearing.classes.statics.data.misc.Listing;
import ru.clearing.classes.statics.data.misc.Market;
import ru.clearing.classes.statics.data.misc.OrderCurrency;
import ru.clearing.classes.statics.data.misc.OvernightSettings;
import ru.clearing.classes.statics.data.registry.Registry;
import ru.clearing.classes.statics.data.registry.TradingClearingRegistry;
@ -42,6 +43,7 @@ public class ImdgQueryService {
private final Imdg<Rates> ratesImdg;
private final Imdg<Registry> registryImdg;
private final Imdg<OvernightSettings> overnightSettingsImdg;
private final Imdg<OrderCurrency> orderCurrencyImdg;
public ImdgQueryService(ImdgProvider imdgProvider) {
@ -57,6 +59,7 @@ public class ImdgQueryService {
this.ratesImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Rates, Rates.class);
this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class);
this.overnightSettingsImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_OvernightSettings, OvernightSettings.class);
this.orderCurrencyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_OrderCurrency, OrderCurrency.class);
}
public Collection<Long> getCurrencyRegistry() {
ImdgPredicateBuilder builder = registryImdg.predicateBuilder();
@ -68,6 +71,23 @@ public class ImdgQueryService {
return registryImdg.getCollectionIdsByPredicate(registryPredicate);
}
public Collection<OrderCurrency> getClccOrderCurrencyIds() {
ImdgPredicateBuilder builder = orderCurrencyImdg.predicateBuilder();
CompanyRoleSet companyRoleSet = companyRoleSetImdg.getFirstObjectByPredicate(
builder.equals("companyRole", CompanyRole.CLCC.getKey())
);
ImdgPredicate clccOrderPredicate = builder.and(
builder.equals("status", "CRET"),
builder.or(
builder.equals("companyId", companyRoleSet.getCompanyId()),
builder.equals("counterPartyId", companyRoleSet.getCompanyId())
)
);
return orderCurrencyImdg.getCollectionObjectsByPredicate(clccOrderPredicate);
}
public Optional<OvernightSettings> findOvernightSettingsByCompany(Long companyId) {
ImdgPredicateBuilder builder = overnightSettingsImdg.predicateBuilder();
ImdgPredicate predicate = builder.and(
@ -170,8 +190,8 @@ public class ImdgQueryService {
return riskParameter.getQuoteValue();
}
public CurrencyPairDictionary getCurrencyPairDictionaryByListing(Listing listing) {
CurrencyPairSecurity security = currencyPairSecurityImdg.getSingleObjectByID(listing.getSecurityId());
public CurrencyPairDictionary getCurrencyPairDictionaryBySecurityId(Long securityId) {
CurrencyPairSecurity security = currencyPairSecurityImdg.getSingleObjectByID(securityId);
return currencyPairDictionaryImdg.getSingleObjectByID(security.getCurrencyPairId());
}

View file

@ -32,7 +32,7 @@ public class OrderCalculationService {
Registry registry = context.getRegistry();
Listing listing = context.getListing();
CurrencyPairDictionary pair = imdgQueryService.getCurrencyPairDictionaryByListing(listing);
CurrencyPairDictionary pair = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listing.getSecurityId());
BigDecimal s = imdgQueryService.defineTransferRate(pair.getId(), registry.getSecuritySymbol());
BigDecimal n = transferDateService.numberOfDaysOfTransfer();
@ -52,7 +52,7 @@ public class OrderCalculationService {
Registry registry = context.getRegistry();
Listing listing = context.getListing();
CurrencyPairDictionary pair = imdgQueryService.getCurrencyPairDictionaryByListing(listing);
CurrencyPairDictionary pair = imdgQueryService.getCurrencyPairDictionaryBySecurityId(listing.getSecurityId());
BigDecimal s = imdgQueryService.defineTransferRate(pair.getId(), registry.getSecuritySymbol());
BigDecimal n = transferDateService.numberOfDaysOfTransfer();

View file

@ -0,0 +1,99 @@
package ru.spcex.clearing.dflt.management.service;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;
import org.springframework.stereotype.Service;
import ru.clearing.classes.statics.data.misc.OrderCurrency;
import ru.clearing.classes.statics.data.security.Rates;
import ru.clearing.platform.dictionary.CurrencyPairDictionary;
import ru.spcex.platform.enumeration.OvernightType;
import ru.spcex.platform.enumeration.Side;
@Service
public class OrderCurrencyTriExportService {
private final ImdgQueryService imdgQueryService;
private static final String ACTION = "Ввод внебиржевой заявки";
public OrderCurrencyTriExportService(
ImdgQueryService imdgQueryService) {
this.imdgQueryService = imdgQueryService;
}
/**
* Возвращает содержимое tri-файла в виде строки.
*/
public String exportToTri(Collection<OrderCurrency> orders) {
return orders.stream()
.map(this::toTriLine)
.reduce((a, b) -> a + System.lineSeparator() + b)
.orElse("");
}
/**
* Возвращает содержимое tri-файла в байтах, чтобы можно было отдать как файл.
*/
public byte[] exportToTriBytes(List<OrderCurrency> orders) {
return exportToTri(orders).getBytes(StandardCharsets.UTF_8);
}
private String toTriLine(OrderCurrency orderCurrency) {
StringJoiner joiner = new StringJoiner(";", "", ";");
joiner.add("TRANS_ID=" + nullSafe(orderCurrency.getId()));
joiner.add("CLASSCODE=" + nullSafe(orderCurrency.getMarket()));
joiner.add("ACTION=" + ACTION);
joiner.add("Торговый счет=" + nullSafe(orderCurrency.getAccount()));
joiner.add("К/П=" + mapSide(orderCurrency.getSide()));
joiner.add("Класс=" + nullSafe(orderCurrency.getMarket()));
joiner.add("Инструмент=" + nullSafe(orderCurrency.getSecuritySymbol()));
joiner.add("Контрагент=" + nullSafe(orderCurrency.getCounterTradingCode()));
joiner.add("Цена=" + formatDecimal(orderCurrency.getPrice()));
joiner.add("Количество=" + formatDecimal(orderCurrency.getQuantityLot()));
joiner.add("Примечание=" + nullSafe(orderCurrency.getAccount()));
joiner.add("Ссылка=");
joiner.add("Код расчетов=" + nullSafe(orderCurrency.getSettleCode()));
joiner.add("На заявку №=");
joiner.add("Базовый курс=" + resolveBaseRate(orderCurrency));
return joiner.toString();
}
private String mapSide(String side) {
if (Side.BUY.equalsByKey(side)) {
return "Покупка";
}
if (Side.SELL.equalsByKey(side)) {
return "Продажа";
}
throw new IllegalArgumentException("Неизвестное значение side: " + side);
}
private String resolveBaseRate(OrderCurrency orderCurrency) {
if (OvernightType.SPOT.equalsByKey(orderCurrency.getOvernightType())) {
return "";
}
CurrencyPairDictionary currencyPairDictionary = imdgQueryService.getCurrencyPairDictionaryBySecurityId(orderCurrency.getSecurityId());
String baseCurrency = currencyPairDictionary.getBaseCurrency();
Rates rates = imdgQueryService.getRate(currencyPairDictionary.getBaseCurrency(), LocalDate.now());
if (rates == null || rates.getValue() == null) {
throw new IllegalStateException(
"Не найден активный курс для валюты " + baseCurrency + " на дату " + LocalDate.now()
);
}
return formatDecimal(rates.getValue());
}
private String formatDecimal(BigDecimal value) {
return value == null ? "" : value.toPlainString();
}
private String nullSafe(Object value) {
return value == null ? "" : String.valueOf(value);
}
}

View file

@ -1,4 +1,6 @@
spring.main.web-application-type=none
default-management.file-storage-path=/opt/storage/csv-impoter/
default-management.hazelcast.cluster-members=127.0.0.1:5701
default-management.hazelcast.login=dev
default-management.hazelcast.password=dev-pass

View file

@ -0,0 +1,18 @@
package ru.spcex.platform.enumeration;
import ru.spcex.platform.utils.enumeration.IEnumKey;
public enum OrderStatus implements IEnumKey {
NEW("NEW"), PTFL("PTFL"), FILL("FILL"), CNCL("CNCL"), REPL("REPL"), REJECTED("RJCT");
private final String key;
OrderStatus(String key) {
this.key = key;
}
@Override
public String getKey() {
return key;
}
}

View file

@ -62,8 +62,9 @@ public enum Task implements IEnumKey {
makeFiles_MTCR("MTCR"), // Формирование файлов с МТКР
CX84("CX84"), // Формирование отчета CCX84
GVTF("GVTF"), // Проведение сверки по регистрам AMAT и AMAF
LORT("LORT"), // Загрузка курсов валют
CROR("CROR"), // Загрузка курсов валют
LORT("LORT"),
CROR("CROR"),
SDOR("SDOR"),
;
private final String key;

View file

@ -147,7 +147,9 @@ public interface Consts {
String ORDER_CURRENCY_COMPANY_NEW = "order-currency-company-new";
String ORDER_CURRENCY_COMPANY_UPDATE = "order-currency-company-update";
String ORDER_CURRENCY_COMPANY_DELETE = "order-currency-company-delete";
String ORDER_CURRENCY_STATUS_UPDATE_IMPORTER = "order-currency-status-update-importer";
String ORDER_CURRENCY_STATUS_UPDATE_FIX = "order-currency-status-update-fix";
String SDOR_FIX_TASK = "sdor-fix-task";
String DESTINATION_SDF08_NEW = "s-df-08-new";
String DESTINATION_SDF02_NEW = "s-df-02-new";

View file

@ -0,0 +1,20 @@
package ru.spcex.clearing.platform.messaging.domain.cud.order;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
public class OrderCurrencyStatusUpdateRequest {
@JsonProperty
private List<Long> ids = new ArrayList<>();
public List<Long> getIds() {
return ids;
}
public void addId(Long id) {
if (id != null) {
ids.add(id);
}
}
}