From ce767ceae121be27612c7603abebb98e9001da49 Mon Sep 17 00:00:00 2001 From: ialbert Date: Tue, 29 Nov 2022 19:21:56 +0300 Subject: [PATCH 1/5] http://jira.mfd.msk:8088/browse/CLS-130 clearing-service --- clearing-parent/clearing-service/pom.xml | 72 +++++++++ .../clearing/ClearingServiceApplication.java | 12 ++ .../clearing/config/ClearingImdgConfig.java | 47 ++++++ .../ru/spcex/clearing/config/KafkaConfig.java | 49 +++++++ .../config/MessageResolverConfig.java | 18 +++ .../spcex/clearing/config/MessagesConfig.java | 29 ++++ .../element/ClearingServiceSettings.java | 41 ++++++ .../spcex/clearing/error/ClearingError.java | 21 +++ .../clearing/service/ClearingService.java | 77 ++++++++++ .../clearing/service/PaymentBatchInfo.java | 46 ++++++ .../service/PaymentBatchProcessor.java | 138 ++++++++++++++++++ .../src/main/resources/application.properties | 22 +++ clearing-parent/pom.xml | 1 + .../platform/enumeration/AccountType.java | 2 +- .../enumeration/ClearingMemberCategoryD.java | 18 +++ .../enumeration/TransactionStatus.java | 18 +++ 16 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 clearing-parent/clearing-service/pom.xml create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/ClearingServiceApplication.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ClearingImdgConfig.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/KafkaConfig.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessageResolverConfig.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessagesConfig.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/element/ClearingServiceSettings.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchProcessor.java create mode 100644 clearing-parent/clearing-service/src/main/resources/application.properties create mode 100644 platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ClearingMemberCategoryD.java create mode 100644 platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java diff --git a/clearing-parent/clearing-service/pom.xml b/clearing-parent/clearing-service/pom.xml new file mode 100644 index 000000000..2c40979e9 --- /dev/null +++ b/clearing-parent/clearing-service/pom.xml @@ -0,0 +1,72 @@ + + + + clearing-parent + ru.spcex.clearing + SPCEX-1.0.0.0 + + 4.0.0 + + clearing-service + + + 17 + 17 + + + + + ru.spcex.platform + platform-messaging + + + ru.spcex.platform + platform-imdg-api-hazelcast-impl + + + ru.spcex.clearing + classes + + + org.springframework.boot + spring-boot-starter + + + com.fasterxml.jackson.core + jackson-databind + + + ru.spcex.platform + platform-enum + + + + + + src/main/resources + + application.properties + + false + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + ${project.artifactId} + + + + + \ No newline at end of file diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/ClearingServiceApplication.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/ClearingServiceApplication.java new file mode 100644 index 000000000..3a4c724f8 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/ClearingServiceApplication.java @@ -0,0 +1,12 @@ +package ru.spcex.clearing; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ClearingServiceApplication { + public static void main(String[] args) { + SpringApplication app = new SpringApplication(ClearingServiceApplication.class); + app.run(args); + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ClearingImdgConfig.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ClearingImdgConfig.java new file mode 100644 index 000000000..670a536a7 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/ClearingImdgConfig.java @@ -0,0 +1,47 @@ +package ru.spcex.clearing.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import ru.spcex.clearing.config.element.ClearingServiceSettings; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService; + +@Configuration +public class ClearingImdgConfig { + private static ThreadPoolTaskExecutor createThreadPoolTaskExecutor(int maxPoolSz, boolean waitForCompletion) { + ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor(); + if (maxPoolSz > 2) { + pool.setKeepAliveSeconds(60); + pool.setAllowCoreThreadTimeOut(true); + } + pool.setCorePoolSize(maxPoolSz); + pool.setWaitForTasksToCompleteOnShutdown(waitForCompletion); + return pool; + } + + @Bean(name = "taskExecutorHazelcastClientInitializer") + public ThreadPoolTaskExecutor taskExecutorHazelcastClientInitializer() { + return createThreadPoolTaskExecutor(1, true); + } + + @Bean(name = "taskExecutorIdGeneratorAwaiter") + public ThreadPoolTaskExecutor taskExecutorIdGeneratorAwaiter() { + return createThreadPoolTaskExecutor(1, false); + } + + @Autowired + @Bean + public ImdgProvider imdgProvider( + @Qualifier("taskExecutorHazelcastClientInitializer") ThreadPoolTaskExecutor taskExecutorHazelcastClientInitializer, + @Qualifier("taskExecutorIdGeneratorAwaiter") ThreadPoolTaskExecutor taskExecutorIdGeneratorAwaiter, + ClearingServiceSettings settings + ) { + return new HazelcastService(taskExecutorHazelcastClientInitializer, + taskExecutorIdGeneratorAwaiter, + settings.getHazelcast()); + } + +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/KafkaConfig.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/KafkaConfig.java new file mode 100644 index 000000000..0a281b5e1 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/KafkaConfig.java @@ -0,0 +1,49 @@ +package ru.spcex.clearing.config; + +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.producer.Producer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import ru.spcex.clearing.config.element.ClearingServiceSettings; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.config.KafkaConsumerFactory; +import ru.spcex.clearing.platform.messaging.config.KafkaProducerFactory; +import ru.spcex.clearing.platform.messaging.service.RequestInfo; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgId; +import ru.spcex.platform.imdg.api.ImdgProvider; + +@Configuration +public class KafkaConfig { + @Autowired + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + @Bean + public Consumer createConsumer(ClearingServiceSettings settings) { + return KafkaConsumerFactory.consumer(settings.getKafkaConsumer()); + } + + @Autowired + @Bean + public Producer createProducer(ClearingServiceSettings settings) { + return KafkaProducerFactory.producer(settings.getKafkaProducer()); + } + + @Autowired + @Bean + public KafkaSender kafkaSender(Producer kafkaProducer, ImdgProvider imdgProvider) { + ImdgId imdgIdGenerator = imdgProvider.getImdgIdGenerator(); + return KafkaSender + .setup() + .producer(kafkaProducer) + .idGenerator(imdgIdGenerator::nextId) + .imdgProvider(s -> { + Imdg imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_RequestInfo, RequestInfo.class); + return imdg::insert; + }) + .build(); + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessageResolverConfig.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessageResolverConfig.java new file mode 100644 index 000000000..09157e14d --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessageResolverConfig.java @@ -0,0 +1,18 @@ +package ru.spcex.clearing.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.spcex.platform.utils.enumeration.IMessageResolver; + +import java.util.Arrays; + +@Configuration +public class MessageResolverConfig { + @Bean + public IMessageResolver messageResolver() { + return errorMessage -> { + if (errorMessage == null) return "null"; + return String.format("(%d) args %s", errorMessage.getSubject().getId(), Arrays.toString(errorMessage.getArgs())); + }; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessagesConfig.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessagesConfig.java new file mode 100644 index 000000000..38d52587e --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/MessagesConfig.java @@ -0,0 +1,29 @@ +package ru.spcex.clearing.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.ResourceBundleMessageSource; +import ru.spcex.platform.utils.enumeration.IMessageResolver; +import ru.spcex.platform.utils.enumeration.SpringPropertiesMessageResolver; + +import java.util.Locale; + +//@Configuration +public class MessagesConfig { + @Bean("validation-error-messages") + public ResourceBundleMessageSource messages() { + ResourceBundleMessageSource source = new ResourceBundleMessageSource(); + source.setBasenames("messages/error"); + source.setUseCodeAsDefaultMessage(true); + source.setDefaultEncoding("utf8"); + source.setDefaultLocale(Locale.ROOT); + return source; + } + + @Bean + public IMessageResolver errorResolver(@Qualifier("validation-error-messages") ResourceBundleMessageSource messageBundle) { + SpringPropertiesMessageResolver resolver = new SpringPropertiesMessageResolver(messageBundle); + resolver.setLocale("ru"); + return resolver; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/element/ClearingServiceSettings.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/element/ClearingServiceSettings.java new file mode 100644 index 000000000..d42e5fcc3 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/config/element/ClearingServiceSettings.java @@ -0,0 +1,41 @@ +package ru.spcex.clearing.config.element; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; +import ru.spcex.clearing.platform.messaging.config.element.KafkaConsumerSettings; +import ru.spcex.clearing.platform.messaging.config.element.KafkaProducerSettings; +import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams; + +@Component +@PropertySource("file:${spring.config.location}/application.properties") +@ConfigurationProperties("clearing-service") +public class ClearingServiceSettings { + private HazelcastClientParams hazelcast; + private KafkaConsumerSettings kafkaConsumer; + private KafkaProducerSettings kafkaProducer; + + public HazelcastClientParams getHazelcast() { + return hazelcast; + } + + public void setHazelcast(HazelcastClientParams hazelcast) { + this.hazelcast = hazelcast; + } + + public KafkaConsumerSettings getKafkaConsumer() { + return kafkaConsumer; + } + + public void setKafkaConsumer(KafkaConsumerSettings kafkaConsumer) { + this.kafkaConsumer = kafkaConsumer; + } + + public KafkaProducerSettings getKafkaProducer() { + return kafkaProducer; + } + + public void setKafkaProducer(KafkaProducerSettings kafkaProducer) { + this.kafkaProducer = kafkaProducer; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java new file mode 100644 index 000000000..9e30eb31a --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java @@ -0,0 +1,21 @@ +package ru.spcex.clearing.error; + +import ru.spcex.platform.utils.enumeration.IEnumId; + +public enum ClearingError implements IEnumId { + CompanyCreditCheck(10012L), + CompanyDebitCheck(10013L), + //if ever happens, ask to add + ClearingMemberCategoryUnknown(19999L), + ; + private final Long id; + + ClearingError(Long id) { + this.id = id; + } + + @Override + public Long getId() { + return id; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java new file mode 100644 index 000000000..64eabf711 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java @@ -0,0 +1,77 @@ +package ru.spcex.clearing.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.platform.enumeration.TransactionStatus; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgId; +import ru.spcex.platform.imdg.api.ImdgProvider; + +import java.util.AbstractMap; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +@Service +@EnableScheduling +public class ClearingService { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Imdg paymentImdgs; + private final ExecutorService executor; + private final ImdgId idGenerator; + private final PaymentBatchProcessor senderGroupProcessor; + + @Autowired + public ClearingService(ImdgProvider imdgProvider, PaymentBatchProcessor senderGroupProcessor) { + this.paymentImdgs = imdgProvider.getImdg(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class); + this.idGenerator = imdgProvider.getImdgIdGenerator(); + this.senderGroupProcessor = senderGroupProcessor; + this.executor = Executors.newSingleThreadExecutor(); + } + + @Scheduled(cron = "${clearing-service.scheduler.check-payment-instruction}") + public void run() { + executor.execute(this::createSdfFromPaymentInstructionSTLD); + } + + private void createSdfFromPaymentInstructionSTLD() { + //выгружаем PaymentInstructions с нужным статусом, группируем по компаниям + Map> pmtInsBySender = paymentImdgs.getCollectionObjectsByFieldValues( + Map.of("transactionStatus", TransactionStatus.stld.getKey())) + .stream() + .sorted(Comparator.comparing(PaymentInstruction::getSenderId)) + .collect(Collectors.groupingBy(PaymentInstruction::getSenderId)); + //generationId для созадаваемых Sdf03/Sdf11 + Long generationId = idGenerator.nextId(); + //результатом работы senderGroupProcessor будет Map PaymentBatchInfo> + //PaymentBatchInfo содержит возможную ошибку, при необходимости отсортированные Payment + //тип ClearingMemberCategory + Map processResults = pmtInsBySender + .entrySet() + .stream() + .map(entry -> { + Long senderId = entry.getKey(); + List pmtInstrcs = entry.getValue(); + return new AbstractMap.SimpleEntry<>(senderId, senderGroupProcessor.processSingleCompanyPayments(generationId, senderId, pmtInstrcs)); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + //todo export + //set status + + } + + private void processSingleCompanyPayments(Long generationId, Long senderId, List payments) { + log.info("processing PaymentInstruction's generationId={} senderId={} size={}", generationId, senderId, payments.size()); + + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java new file mode 100644 index 000000000..77ba4ad1a --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java @@ -0,0 +1,46 @@ +package ru.spcex.clearing.service; + +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.spcex.platform.enumeration.ClearingMemberCategoryD; +import ru.spcex.platform.utils.enumeration.EnumMessage; + +import java.util.List; + +public class PaymentBatchInfo { + private List fromClearingToBank; + private List fromBankToClearing; + private ClearingMemberCategoryD categoryD; + private EnumMessage error; + + public List getFromClearingToBank() { + return fromClearingToBank; + } + + public void setFromClearingToBank(List fromClearingToBank) { + this.fromClearingToBank = fromClearingToBank; + } + + public List getFromBankToClearing() { + return fromBankToClearing; + } + + public void setFromBankToClearing(List fromBankToClearing) { + this.fromBankToClearing = fromBankToClearing; + } + + public ClearingMemberCategoryD getCategoryD() { + return categoryD; + } + + public void setCategoryD(ClearingMemberCategoryD categoryD) { + this.categoryD = categoryD; + } + + public EnumMessage getError() { + return error; + } + + public void setError(EnumMessage error) { + this.error = error; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchProcessor.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchProcessor.java new file mode 100644 index 000000000..bdd1b5dcb --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchProcessor.java @@ -0,0 +1,138 @@ +package ru.spcex.clearing.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.generated.ClearingMemberCategory; +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.spcex.clearing.error.ClearingError; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.platform.enumeration.AccountType; +import ru.spcex.platform.enumeration.ClearingMemberCategoryD; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.enumeration.IEnumKey; +import ru.spcex.platform.utils.enumeration.IMessageResolver; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@Component +public class PaymentBatchProcessor { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Imdg clrngMmbrImdg; + private final Imdg accImdg; + private final IMessageResolver messageResolver; + + @Autowired + public PaymentBatchProcessor(ImdgProvider imdgProvider, IMessageResolver messageResolver) { + this.clrngMmbrImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ClearingMemberCategory, ClearingMemberCategory.class); + this.accImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class); + + this.messageResolver = messageResolver; + } + + PaymentBatchInfo processSingleCompanyPayments(Long generationId, Long senderId, List payments) { + PaymentBatchInfo batchInfo = new PaymentBatchInfo(); + log.info("processing PaymentInstruction's generationId={} senderId={} size={}", generationId, senderId, payments.size()); + ClearingMemberCategory category = clrngMmbrImdg.getSingleObjectByFieldValues(Map.of("companyId", senderId)); + ClearingMemberCategoryD categoryValue; + if (category == null || (categoryValue = + IEnumKey.getEnumByKey(ClearingMemberCategoryD.class, category.getClearingMemberCategory())) == null) { + log.warn("processing PaymentInstructions generationId={} senderId={}: " + + "ClearingMemberCategory {}, ClearingMemberCategory.clearingMemberCategory {}", + generationId, senderId, + category == null ? "null" : "is not null", + category == null ? "null" : category.getClearingMemberCategory() + ); + batchInfo.setError(new EnumMessage(ClearingError.ClearingMemberCategoryUnknown)); + return batchInfo; + } + batchInfo.setCategoryD(categoryValue); + if (ClearingMemberCategoryD.I.equals(categoryValue)) { + List aList = new ArrayList<>(); + List bList = new ArrayList<>(); + for (PaymentInstruction payment : payments) { + Account creditLegAcc = accImdg.getSingleObjectByID(payment.getCreditLegAccountId()); + Account debitLegAcc = accImdg.getSingleObjectByID(payment.getDebitLegAccountId()); + if (creditLegAcc == null || debitLegAcc == null) { + log.warn("generationId={}, senderId={} payment.id={} cannot find account CreditLegAccountId/DebitLegAccountId {}/{}", + generationId, senderId, payment.getId(), payment.getCreditLegAccountId(), payment.getDebitLegAccountId()); + continue; + } + if (AccountType.Clrn.equalsByKey(creditLegAcc.getAccountType()) + && AccountType.Bank.equalsByKey(debitLegAcc.getAccountType())) { + aList.add(payment); + } else if (AccountType.Bank.equalsByKey(creditLegAcc.getAccountType()) + && AccountType.Clrn.equalsByKey(debitLegAcc.getAccountType())) { + bList.add(payment); + } else { + log.warn("generationId={} senderId={} cannot sort payment.id={}, creditLegAccount.type={}, debitLegAccount.type={}", + generationId, senderId, payment.getId(),creditLegAcc.getAccountType(), debitLegAcc.getAccountType()); + continue; + } + } + Function, Long> creditAmountSum = paymentInstructions -> paymentInstructions + .stream() + .map(PaymentInstruction::getCreditLegAmount) + .reduce(0L, Long::sum); + Function, Long> debitAmountSum = paymentInstructions -> paymentInstructions + .stream() + .map(PaymentInstruction::getCreditLegAmount) + .reduce(0L, Long::sum); + Long fromClearingToBankCreditAmount = creditAmountSum.apply(aList); + Long fromBankToClearingCreditAmount = creditAmountSum.apply(bList); + Long fromClearingToBankDebitAmount = debitAmountSum.apply(aList); + Long fromBankToClearingDebitAmount = debitAmountSum.apply(bList); + if (!fromClearingToBankCreditAmount.equals(fromBankToClearingCreditAmount)) { + log.info("processing PaymentInstruction's generationId={} senderId={} [fromClearingToBankCreditAmount={}, " + + "fromBankToClearingCreditAmount={}, " + + "fromClearingToBankDebitAmount={}, " + + "fromBankToClearingDebitAmount={}] error {}", generationId, senderId, + fromClearingToBankCreditAmount, + fromBankToClearingCreditAmount, + fromClearingToBankDebitAmount, + fromBankToClearingDebitAmount, + messageResolver.resolve(new EnumMessage(ClearingError.CompanyCreditCheck, senderId.toString()))); + batchInfo.setError(new EnumMessage(ClearingError.CompanyCreditCheck, senderId.toString())); + return batchInfo; + } else if (!fromClearingToBankDebitAmount.equals(fromBankToClearingDebitAmount)) { + log.info("processing PaymentInstruction's generationId={} senderId={} [fromClearingToBankCreditAmount={}, " + + "fromBankToClearingCreditAmount={}, " + + "fromClearingToBankDebitAmount={}, " + + "fromBankToClearingDebitAmount={}] error {}", generationId, senderId, + fromClearingToBankCreditAmount, + fromBankToClearingCreditAmount, + fromClearingToBankDebitAmount, + fromBankToClearingDebitAmount, + messageResolver.resolve(new EnumMessage(ClearingError.CompanyDebitCheck, senderId.toString()))); + batchInfo.setError(new EnumMessage(ClearingError.CompanyDebitCheck, senderId.toString())); + return batchInfo; + } + log.debug("processing PaymentInstruction's generationId={} senderId={} [fromClearingToBankCreditAmount={}, " + + "fromBankToClearingCreditAmount={}, " + + "fromClearingToBankDebitAmount={}, " + + "fromBankToClearingDebitAmount={}]", generationId, senderId, + fromClearingToBankCreditAmount, + fromBankToClearingCreditAmount, + fromClearingToBankDebitAmount, + fromBankToClearingDebitAmount); + batchInfo.setFromClearingToBank(aList); + batchInfo.setFromBankToClearing(bList); + return batchInfo; + } else if (ClearingMemberCategoryD.B.equals(categoryValue)) { + return batchInfo; + } else { + log.warn("PaymentInstructions generationId={} senderId={} fail - unknown category ", + generationId, senderId); + batchInfo.setError(new EnumMessage(ClearingError.ClearingMemberCategoryUnknown)); + return batchInfo; + } + } +} diff --git a/clearing-parent/clearing-service/src/main/resources/application.properties b/clearing-parent/clearing-service/src/main/resources/application.properties new file mode 100644 index 000000000..af4ebcf34 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/resources/application.properties @@ -0,0 +1,22 @@ +spring.main.web-application-type=none + +clearing-service.hazelcast.cluster-members=127.0.0.1:5701 +clearing-service.hazelcast.login=dev +clearing-service.hazelcast.password=dev-pass + +clearing-service.kafka-consumer.bootstrap-servers=localhost:9092 +clearing-service.kafka-consumer.group-id=dev-group-clearing-service +clearing-service.kafka-consumer.enable-auto-commit=false +clearing-service.kafka-consumer.session-timeout-ms=30000 +clearing-service.kafka-consumer.auto-offset-reset=latest +clearing-service.kafka-consumer.linger-ms=1 +clearing-service.kafka-consumer.buffer-memory=33554432 + +clearing-service.kafka-producer.bootstrap-servers=localhost:9092 +clearing-service.kafka-producer.acks=all +clearing-service.kafka-producer.retries=0 +clearing-service.kafka-producer.batch-size=16384 +clearing-service.kafka-producer.linger-ms=1 +clearing-service.kafka-producer.buffer-memory=33554432 + +clearing-service.scheduler.check-payment-instruction=*/5 * * * * * \ No newline at end of file diff --git a/clearing-parent/pom.xml b/clearing-parent/pom.xml index 08cc8557b..85fb2a0a6 100644 --- a/clearing-parent/pom.xml +++ b/clearing-parent/pom.xml @@ -30,6 +30,7 @@ account-service balance-service scheduler-service + clearing-service diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java index 71385a733..bf0f5f50b 100644 --- a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/AccountType.java @@ -3,7 +3,7 @@ package ru.spcex.platform.enumeration; import ru.spcex.platform.utils.enumeration.IEnumKey; public enum AccountType implements IEnumKey { - Clrn("CLRN"); + Clrn("CLRN"), Bank("BANK"); private final String key; diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ClearingMemberCategoryD.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ClearingMemberCategoryD.java new file mode 100644 index 000000000..bc94d111c --- /dev/null +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ClearingMemberCategoryD.java @@ -0,0 +1,18 @@ +package ru.spcex.platform.enumeration; + +import ru.spcex.platform.utils.enumeration.IEnumKey; + +public enum ClearingMemberCategoryD implements IEnumKey { + I("I"), B("B"); + + private final String key; + + ClearingMemberCategoryD(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } +} diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java new file mode 100644 index 000000000..812d075bd --- /dev/null +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java @@ -0,0 +1,18 @@ +package ru.spcex.platform.enumeration; + +import ru.spcex.platform.utils.enumeration.IEnumKey; + +public enum TransactionStatus implements IEnumKey { + stld("STLD"); + + private final String key; + + TransactionStatus(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } +} From fa0b7ba7878b64335617f19f58304a4fbfd8d387 Mon Sep 17 00:00:00 2001 From: ialbert Date: Thu, 1 Dec 2022 11:15:46 +0300 Subject: [PATCH 2/5] http://jira.mfd.msk:8088/browse/CLS-130 sdf03 sdf11 --- clearing-parent/clearing-service/pom.xml | 10 +++ .../spcex/clearing/error/ClearingError.java | 2 - .../clearing/service/ClearingService.java | 86 ++++++++++++++----- .../clearing/service/PaymentBatchInfo.java | 23 +++++ ...sor.java => PaymentInstructionSorter.java} | 86 ++++++++----------- .../spcex/clearing/service/Sdf03Builder.java | 53 ++++++++++++ .../spcex/clearing/service/Sdf11Builder.java | 53 ++++++++++++ .../ru/spcex/clearing/service/SpecifUtil.java | 21 +++++ .../clearing/service/SpecifUtilTest.java | 76 ++++++++++++++++ .../enumeration/TransactionStatus.java | 2 +- .../platform/messaging/domain/Consts.java | 2 + .../cud/clearing/SdfClearingRequest.java | 16 ++++ 12 files changed, 355 insertions(+), 75 deletions(-) rename clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/{PaymentBatchProcessor.java => PaymentInstructionSorter.java} (55%) create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf03Builder.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf11Builder.java create mode 100644 clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/SpecifUtil.java create mode 100644 clearing-parent/clearing-service/src/test/java/ru/spcex/clearing/service/SpecifUtilTest.java create mode 100644 platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/SdfClearingRequest.java diff --git a/clearing-parent/clearing-service/pom.xml b/clearing-parent/clearing-service/pom.xml index 2c40979e9..aa206dcf4 100644 --- a/clearing-parent/clearing-service/pom.xml +++ b/clearing-parent/clearing-service/pom.xml @@ -41,6 +41,16 @@ ru.spcex.platform platform-enum + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java index 9e30eb31a..24106521d 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/error/ClearingError.java @@ -5,8 +5,6 @@ import ru.spcex.platform.utils.enumeration.IEnumId; public enum ClearingError implements IEnumId { CompanyCreditCheck(10012L), CompanyDebitCheck(10013L), - //if ever happens, ask to add - ClearingMemberCategoryUnknown(19999L), ; private final Long id; diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java index 64eabf711..1103e95a6 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/ClearingService.java @@ -7,7 +7,12 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.clearing.classes.statics.data.sdf.SDf03; +import ru.clearing.classes.statics.data.sdf.SDf11; import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.Consts; +import ru.spcex.clearing.platform.messaging.domain.cud.clearing.SdfClearingRequest; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; import ru.spcex.platform.enumeration.TransactionStatus; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgId; @@ -28,13 +33,19 @@ public class ClearingService { private final Imdg paymentImdgs; private final ExecutorService executor; private final ImdgId idGenerator; - private final PaymentBatchProcessor senderGroupProcessor; + private final PaymentInstructionSorter senderGroupSorter; + private final Imdg sdf03Imdg; + private final Imdg sdf11Imdg; + private final KafkaSender kafkaSender; @Autowired - public ClearingService(ImdgProvider imdgProvider, PaymentBatchProcessor senderGroupProcessor) { + public ClearingService(ImdgProvider imdgProvider, PaymentInstructionSorter senderGroupSorter, KafkaSender kafkaSender) { this.paymentImdgs = imdgProvider.getImdg(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class); + this.sdf03Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf03, SDf03.class); + this.sdf11Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf11, SDf11.class); this.idGenerator = imdgProvider.getImdgIdGenerator(); - this.senderGroupProcessor = senderGroupProcessor; + this.senderGroupSorter = senderGroupSorter; + this.kafkaSender = kafkaSender; this.executor = Executors.newSingleThreadExecutor(); } @@ -44,34 +55,69 @@ public class ClearingService { } private void createSdfFromPaymentInstructionSTLD() { - //выгружаем PaymentInstructions с нужным статусом, группируем по компаниям - Map> pmtInsBySender = paymentImdgs.getCollectionObjectsByFieldValues( - Map.of("transactionStatus", TransactionStatus.stld.getKey())) - .stream() - .sorted(Comparator.comparing(PaymentInstruction::getSenderId)) - .collect(Collectors.groupingBy(PaymentInstruction::getSenderId)); + final boolean[] anyError = {false}; //generationId для созадаваемых Sdf03/Sdf11 Long generationId = idGenerator.nextId(); - //результатом работы senderGroupProcessor будет Map PaymentBatchInfo> - //PaymentBatchInfo содержит возможную ошибку, при необходимости отсортированные Payment - //тип ClearingMemberCategory - Map processResults = pmtInsBySender + //выгружаем PaymentInstructions с нужным статусом + Map paymentBySender = paymentImdgs.getCollectionObjectsByFieldValues( + Map.of("transactionStatus", TransactionStatus.stld.getKey())) + .stream() + //группируем по компаниям (fixme sorted убрать?) + .sorted(Comparator.comparing(PaymentInstruction::getSenderId)) + .collect(Collectors.groupingBy(PaymentInstruction::getSenderId)) .entrySet() .stream() + //результатом работы senderGroupSorter будет Map PaymentBatchInfo> + //PaymentBatchInfo содержит возможную ошибку, при необходимости отсортированные Payment + //тип ClearingMemberCategory .map(entry -> { Long senderId = entry.getKey(); List pmtInstrcs = entry.getValue(); - return new AbstractMap.SimpleEntry<>(senderId, senderGroupProcessor.processSingleCompanyPayments(generationId, senderId, pmtInstrcs)); + PaymentBatchInfo senderInfo = senderGroupSorter.sortCompanyPayments(generationId, senderId, pmtInstrcs); + if (senderInfo.getError() != null) { + anyError[0] = true; + } + return new AbstractMap.SimpleEntry<>(senderId, senderInfo); }) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - //todo export - //set status + //save SDF03/SDF11 + for (var entry : paymentBySender.entrySet()) { + PaymentBatchInfo senderPayments = entry.getValue(); + saveSdfAnSendToKafka(senderPayments, generationId); + } + //update PaymentInstruction.transactionStatus + for (var entry : paymentBySender.entrySet()) { + PaymentBatchInfo batch = entry.getValue(); + //все PaymentInstruction.transactionStatus в batch с error != null + //уже проапдейтились в методе sortCompanyPayments + if (batch.getError() == null) { + TransactionStatus stat = anyError[0] ? TransactionStatus.notSent : TransactionStatus.sent; + batch.getOrderedPaymentInstructions() + .forEach(paymentInstruction -> { + paymentInstruction.setTransactionStatus(stat.getKey()); + paymentImdgs.update(paymentInstruction); + }); + } + } } - private void processSingleCompanyPayments(Long generationId, Long senderId, List payments) { - log.info("processing PaymentInstruction's generationId={} senderId={} size={}", generationId, senderId, payments.size()); - + private void saveSdfAnSendToKafka(PaymentBatchInfo batch, Long generationId) { + SdfClearingRequest kafkaMessage = new SdfClearingRequest(); + kafkaMessage.setGroupId(generationId); + switch (batch.getCategoryD()) { + case I -> { + batch.getOrderedPaymentInstructions() + .map(paymentInstruction -> Sdf03Builder.buildSdf03(paymentInstruction, generationId)) + .forEach(sdf03Imdg::insert); + kafkaSender.sendRequestToQueue(Consts.SDF03_PROCESS, kafkaMessage); + } + case B -> { + batch.getOrderedPaymentInstructions() + .map(paymentInstruction -> Sdf11Builder.buildSdf11(paymentInstruction, generationId)) + .forEach(sdf11Imdg::insert); + kafkaSender.sendRequestToQueue(Consts.SDF11_PROCESS, kafkaMessage); + } + } } } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java index 77ba4ad1a..fff82f55c 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchInfo.java @@ -4,14 +4,29 @@ import ru.clearing.classes.statics.data.payment.PaymentInstruction; import ru.spcex.platform.enumeration.ClearingMemberCategoryD; import ru.spcex.platform.utils.enumeration.EnumMessage; +import java.util.Collection; import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; public class PaymentBatchInfo { + private List initialOrder; private List fromClearingToBank; private List fromBankToClearing; private ClearingMemberCategoryD categoryD; private EnumMessage error; + public Stream getOrderedPaymentInstructions() { + Function, Stream> safeStream + = paymentInstructions -> paymentInstructions != null ? paymentInstructions.stream() : Stream.empty(); + if (categoryD.equals(ClearingMemberCategoryD.B)) { + return safeStream.apply(initialOrder); + } else { + return Stream.concat(safeStream.apply(fromClearingToBank), safeStream.apply(fromBankToClearing)); + } + } + + public List getFromClearingToBank() { return fromClearingToBank; } @@ -43,4 +58,12 @@ public class PaymentBatchInfo { public void setError(EnumMessage error) { this.error = error; } + + public List getInitialOrder() { + return initialOrder; + } + + public void setInitialOrder(List initialOrder) { + this.initialOrder = initialOrder; + } } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchProcessor.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentInstructionSorter.java similarity index 55% rename from clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchProcessor.java rename to clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentInstructionSorter.java index bdd1b5dcb..c618bad8f 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentBatchProcessor.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentInstructionSorter.java @@ -11,6 +11,7 @@ import ru.spcex.clearing.error.ClearingError; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.platform.enumeration.AccountType; import ru.spcex.platform.enumeration.ClearingMemberCategoryD; +import ru.spcex.platform.enumeration.TransactionStatus; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; import ru.spcex.platform.utils.enumeration.EnumMessage; @@ -23,47 +24,39 @@ import java.util.Map; import java.util.function.Function; @Component -public class PaymentBatchProcessor { +public class PaymentInstructionSorter { private final Logger log = LoggerFactory.getLogger(getClass()); private final Imdg clrngMmbrImdg; private final Imdg accImdg; private final IMessageResolver messageResolver; + private final Imdg pmtInstrctnsImdg; @Autowired - public PaymentBatchProcessor(ImdgProvider imdgProvider, IMessageResolver messageResolver) { + public PaymentInstructionSorter(ImdgProvider imdgProvider, IMessageResolver messageResolver) { this.clrngMmbrImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ClearingMemberCategory, ClearingMemberCategory.class); + this.pmtInstrctnsImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_PaymentInstruction, PaymentInstruction.class); this.accImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class); - this.messageResolver = messageResolver; } - PaymentBatchInfo processSingleCompanyPayments(Long generationId, Long senderId, List payments) { + PaymentBatchInfo sortCompanyPayments(Long generationId, Long senderId, List payments) { PaymentBatchInfo batchInfo = new PaymentBatchInfo(); log.info("processing PaymentInstruction's generationId={} senderId={} size={}", generationId, senderId, payments.size()); ClearingMemberCategory category = clrngMmbrImdg.getSingleObjectByFieldValues(Map.of("companyId", senderId)); - ClearingMemberCategoryD categoryValue; - if (category == null || (categoryValue = - IEnumKey.getEnumByKey(ClearingMemberCategoryD.class, category.getClearingMemberCategory())) == null) { - log.warn("processing PaymentInstructions generationId={} senderId={}: " + - "ClearingMemberCategory {}, ClearingMemberCategory.clearingMemberCategory {}", - generationId, senderId, - category == null ? "null" : "is not null", - category == null ? "null" : category.getClearingMemberCategory() - ); - batchInfo.setError(new EnumMessage(ClearingError.ClearingMemberCategoryUnknown)); - return batchInfo; - } + ClearingMemberCategoryD categoryValue = IEnumKey.getEnumByKey(ClearingMemberCategoryD.class, category.getClearingMemberCategory()); batchInfo.setCategoryD(categoryValue); if (ClearingMemberCategoryD.I.equals(categoryValue)) { + EnumMessage error = null; List aList = new ArrayList<>(); List bList = new ArrayList<>(); for (PaymentInstruction payment : payments) { Account creditLegAcc = accImdg.getSingleObjectByID(payment.getCreditLegAccountId()); Account debitLegAcc = accImdg.getSingleObjectByID(payment.getDebitLegAccountId()); if (creditLegAcc == null || debitLegAcc == null) { - log.warn("generationId={}, senderId={} payment.id={} cannot find account CreditLegAccountId/DebitLegAccountId {}/{}", - generationId, senderId, payment.getId(), payment.getCreditLegAccountId(), payment.getDebitLegAccountId()); + String message = String.format("cannot find account CreditLegAccountId/DebitLegAccountId %d/%d", payment.getCreditLegAccountId(), payment.getDebitLegAccountId()); + log.error("generationId={}, senderId={} payment.id={} {}", + generationId, senderId, payment.getId(), message); continue; } if (AccountType.Clrn.equalsByKey(creditLegAcc.getAccountType()) @@ -73,9 +66,10 @@ public class PaymentBatchProcessor { && AccountType.Clrn.equalsByKey(debitLegAcc.getAccountType())) { bList.add(payment); } else { - log.warn("generationId={} senderId={} cannot sort payment.id={}, creditLegAccount.type={}, debitLegAccount.type={}", - generationId, senderId, payment.getId(),creditLegAcc.getAccountType(), debitLegAcc.getAccountType()); - continue; + String message = String.format("cannot sort creditLegAccount.type=%s, debitLegAccount.type=%s", creditLegAcc.getAccountType(), debitLegAcc.getAccountType()); + log.error("generationId={}, senderId={} payment.id={} {}", + generationId, senderId, payment.getId(), message); + //continue; } } Function, Long> creditAmountSum = paymentInstructions -> paymentInstructions @@ -84,55 +78,43 @@ public class PaymentBatchProcessor { .reduce(0L, Long::sum); Function, Long> debitAmountSum = paymentInstructions -> paymentInstructions .stream() - .map(PaymentInstruction::getCreditLegAmount) + .map(PaymentInstruction::getDebitLegAmount) .reduce(0L, Long::sum); Long fromClearingToBankCreditAmount = creditAmountSum.apply(aList); Long fromBankToClearingCreditAmount = creditAmountSum.apply(bList); Long fromClearingToBankDebitAmount = debitAmountSum.apply(aList); Long fromBankToClearingDebitAmount = debitAmountSum.apply(bList); if (!fromClearingToBankCreditAmount.equals(fromBankToClearingCreditAmount)) { - log.info("processing PaymentInstruction's generationId={} senderId={} [fromClearingToBankCreditAmount={}, " + - "fromBankToClearingCreditAmount={}, " + - "fromClearingToBankDebitAmount={}, " + - "fromBankToClearingDebitAmount={}] error {}", generationId, senderId, - fromClearingToBankCreditAmount, - fromBankToClearingCreditAmount, - fromClearingToBankDebitAmount, - fromBankToClearingDebitAmount, - messageResolver.resolve(new EnumMessage(ClearingError.CompanyCreditCheck, senderId.toString()))); - batchInfo.setError(new EnumMessage(ClearingError.CompanyCreditCheck, senderId.toString())); - return batchInfo; + error = new EnumMessage(ClearingError.CompanyCreditCheck, senderId.toString()); } else if (!fromClearingToBankDebitAmount.equals(fromBankToClearingDebitAmount)) { - log.info("processing PaymentInstruction's generationId={} senderId={} [fromClearingToBankCreditAmount={}, " + + error = new EnumMessage(ClearingError.CompanyDebitCheck, senderId.toString()); + } + log.info("processing PaymentInstruction's generationId={} senderId={} [fromClearingToBankCreditAmount={}, " + "fromBankToClearingCreditAmount={}, " + "fromClearingToBankDebitAmount={}, " + - "fromBankToClearingDebitAmount={}] error {}", generationId, senderId, + "fromBankToClearingDebitAmount={}] {}", generationId, senderId, fromClearingToBankCreditAmount, fromBankToClearingCreditAmount, fromClearingToBankDebitAmount, fromBankToClearingDebitAmount, - messageResolver.resolve(new EnumMessage(ClearingError.CompanyDebitCheck, senderId.toString()))); - batchInfo.setError(new EnumMessage(ClearingError.CompanyDebitCheck, senderId.toString())); + error != null ? ("error " + messageResolver.resolve(error)) : "ok"); + if (error != null) { + batchInfo.setError(error); + payments.forEach(pmt -> { + pmt.setTransactionStatus(TransactionStatus.cher.getKey()); + pmtInstrctnsImdg.update(pmt); + }); + return batchInfo; + } else { + batchInfo.setFromClearingToBank(aList); + batchInfo.setFromBankToClearing(bList); return batchInfo; } - log.debug("processing PaymentInstruction's generationId={} senderId={} [fromClearingToBankCreditAmount={}, " + - "fromBankToClearingCreditAmount={}, " + - "fromClearingToBankDebitAmount={}, " + - "fromBankToClearingDebitAmount={}]", generationId, senderId, - fromClearingToBankCreditAmount, - fromBankToClearingCreditAmount, - fromClearingToBankDebitAmount, - fromBankToClearingDebitAmount); - batchInfo.setFromClearingToBank(aList); - batchInfo.setFromBankToClearing(bList); - return batchInfo; } else if (ClearingMemberCategoryD.B.equals(categoryValue)) { + batchInfo.setInitialOrder(payments); return batchInfo; } else { - log.warn("PaymentInstructions generationId={} senderId={} fail - unknown category ", - generationId, senderId); - batchInfo.setError(new EnumMessage(ClearingError.ClearingMemberCategoryUnknown)); - return batchInfo; + throw new IllegalStateException("unknown clearing member category"); } } } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf03Builder.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf03Builder.java new file mode 100644 index 000000000..fc32df0e2 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf03Builder.java @@ -0,0 +1,53 @@ +package ru.spcex.clearing.service; + +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.clearing.classes.statics.data.sdf.SDf03; +import ru.spcex.platform.enumeration.Sender; +import ru.spcex.platform.utils.time.TimeUtil; + +import java.time.Instant; +import java.time.LocalDate; + +public class Sdf03Builder { + public static SDf03 buildSdf03(PaymentInstruction paymentInstruction, Long generationId) { + SDf03 sDf03 = new SDf03(); + sDf03.setSeg_type("S"); + sDf03.setDoc_type("002"); + sDf03.setDocnm_ref(paymentInstruction.getId().toString()); + sDf03.setPriority("9"); + sDf03.setSbankcode(Sender.Prc.equalsById(paymentInstruction.getSenderId()) ? "@09999" : "@0"); + sDf03.setC_acc_deb(paymentInstruction.getDebitLegAccount()); + sDf03.setSbanknam1(paymentInstruction.getPayeeBankName()); + // =@00100, если ?; + // =@00000, если ?. + sDf03.setRbankcode(Sender.Prc.equalsById(paymentInstruction.getSenderId()) ? "@09999" : "@0"); + sDf03.setC_acc_cred(paymentInstruction.getCreditLegAccount()); + sDf03.setRbanknam1(paymentInstruction.getAddresseeBankName()); + sDf03.setPay_date(LocalDate.now().format(TimeUtil.PROPERTY_DATE_FORMATTER)); + sDf03.setPay_val("RUR"); + sDf03.setSum_deb(paymentInstruction.getDebitLegAmount() != null ? paymentInstruction.getDebitLegAmount().toString() : null); + //37 sp_code varchar(2) Код назначения платежа + String[] splitPaymentPurpose = SpecifUtil.splitPaymentPurpose(paymentInstruction.getPaymentPurpose()); + for (int i = 0; i < splitPaymentPurpose.length; i++) { + String specif = splitPaymentPurpose[i]; + if (i == 0) { + sDf03.setSpecif_1(specif); + } else if (i == 1) { + sDf03.setSpecif_2(specif); + } else if (i == 2) { + sDf03.setSpecif_3(specif); + } else if (i == 3) { + sDf03.setSpecif_4(specif); + } else if (i == 4) { + sDf03.setSpecif_5(specif); + } else if (i == 5) { + sDf03.setSpecif_6(specif); + } + } + sDf03.setGenerationTime(Instant.now()); + sDf03.setGenerationId(generationId); + //todo +// sDf03.setPaymentInstructionId(paymentInstruction.getId()); + return sDf03; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf11Builder.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf11Builder.java new file mode 100644 index 000000000..2d37e878b --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/Sdf11Builder.java @@ -0,0 +1,53 @@ +package ru.spcex.clearing.service; + +import ru.clearing.classes.statics.data.payment.PaymentInstruction; +import ru.clearing.classes.statics.data.sdf.SDf11; +import ru.spcex.platform.enumeration.Sender; +import ru.spcex.platform.utils.time.TimeUtil; + +import java.time.Instant; +import java.time.LocalDate; + +public class Sdf11Builder { + public static SDf11 buildSdf11(PaymentInstruction paymentInstruction, Long generationId) { + SDf11 sDf11 = new SDf11(); + sDf11.setSeg_type("S"); + sDf11.setDoc_type("002"); + sDf11.setDocnm_ref(paymentInstruction.getId().toString()); + sDf11.setPriority("9"); + sDf11.setSbankcode(Sender.Prc.equalsById(paymentInstruction.getSenderId()) ? "@09999" : "@0"); + sDf11.setC_acc_deb(paymentInstruction.getDebitLegAccount()); + sDf11.setSbanknam1(paymentInstruction.getPayeeBankName()); + // =@00100, если ?; + // =@00000, если ?. + sDf11.setRbankcode(Sender.Prc.equalsById(paymentInstruction.getSenderId()) ? "@09999" : "@0"); + sDf11.setC_acc_cred(paymentInstruction.getCreditLegAccount()); + sDf11.setRbanknam1(paymentInstruction.getAddresseeBankName()); + sDf11.setPay_date(LocalDate.now().format(TimeUtil.PROPERTY_DATE_FORMATTER)); + sDf11.setPay_val("RUR"); + sDf11.setSum_deb(paymentInstruction.getDebitLegAmount() != null ? paymentInstruction.getDebitLegAmount().toString() : null); + //37 sp_code varchar(2) Код назначения платежа + String[] splitPaymentPurpose = SpecifUtil.splitPaymentPurpose(paymentInstruction.getPaymentPurpose()); + for (int i = 0; i < splitPaymentPurpose.length; i++) { + String specif = splitPaymentPurpose[i]; + if (i == 0) { + sDf11.setSpecif_1(specif); + } else if (i == 1) { + sDf11.setSpecif_2(specif); + } else if (i == 2) { + sDf11.setSpecif_3(specif); + } else if (i == 3) { + sDf11.setSpecif_4(specif); + } else if (i == 4) { + sDf11.setSpecif_5(specif); + } else if (i == 5) { + sDf11.setSpecif_6(specif); + } + } + sDf11.setGenerationTime(Instant.now()); + sDf11.setGenerationId(generationId); + //todo +// sDf03.setPaymentInstructionId(paymentInstruction.getId()); + return sDf11; + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/SpecifUtil.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/SpecifUtil.java new file mode 100644 index 000000000..79ac236da --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/SpecifUtil.java @@ -0,0 +1,21 @@ +package ru.spcex.clearing.service; + +public class SpecifUtil { + public static String[] splitPaymentPurpose(String paymentPurpose) { + if (paymentPurpose == null || paymentPurpose.length() < 1) return new String[0]; + int specifSize = divisionRoundUp(paymentPurpose.length(), 35); + specifSize = Math.min(specifSize, 6); + String[] res = new String[specifSize]; + for (int i = 0; i < res.length; i++) { + int startIndex = i * 35; + int endIndex = Math.min(35 * (i + 1), paymentPurpose.length()); + res[i] = paymentPurpose.substring(startIndex, endIndex); + } + return res; + } + + public static int divisionRoundUp(int a, int b) { + int ostatok = a % b; + return a / b + (ostatok > 0 ? 1 : 0); + } +} diff --git a/clearing-parent/clearing-service/src/test/java/ru/spcex/clearing/service/SpecifUtilTest.java b/clearing-parent/clearing-service/src/test/java/ru/spcex/clearing/service/SpecifUtilTest.java new file mode 100644 index 000000000..a53305668 --- /dev/null +++ b/clearing-parent/clearing-service/src/test/java/ru/spcex/clearing/service/SpecifUtilTest.java @@ -0,0 +1,76 @@ +package ru.spcex.clearing.service; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SpecifUtilTest { + @Test + public void testDivideRoundUp() { + Assertions.assertEquals(2, SpecifUtil.divisionRoundUp(36, 35)); + Assertions.assertEquals(2, SpecifUtil.divisionRoundUp(70, 35)); + Assertions.assertEquals(3, SpecifUtil.divisionRoundUp(71, 35)); + } + + @Test + public void testSplit() { + { + //40 symbols + String[] splitted = SpecifUtil.splitPaymentPurpose("0123456789012345678901234567890123456789"); + Assertions.assertEquals(2, splitted.length); + Assertions.assertEquals("01234567890123456789012345678901234", splitted[0]); + Assertions.assertEquals("56789", splitted[1]); + } + + { + //40 symbols + String[] splitted = SpecifUtil.splitPaymentPurpose("01234567890123456789012345678901234"); + Assertions.assertEquals(1, splitted.length); + Assertions.assertEquals("01234567890123456789012345678901234", splitted[0]); + } + + { + //10 symbols + String[] splitted = SpecifUtil.splitPaymentPurpose("0123456789"); + Assertions.assertEquals(1, splitted.length); + Assertions.assertEquals("0123456789", splitted[0]); + } + + { + //0 symbols + String[] splitted = SpecifUtil.splitPaymentPurpose(""); + Assertions.assertEquals(0, splitted.length); + } + + { + //80 symbols + String[] splitted = SpecifUtil.splitPaymentPurpose("0123456789012345678901234567890123456789" + + "0123456789012345678901234567890123456789"); + Assertions.assertEquals(3, splitted.length); + Assertions.assertEquals("01234567890123456789012345678901234", splitted[0]); + Assertions.assertEquals("56789012345678901234567890123456789", splitted[1]); + Assertions.assertEquals("0123456789", splitted[2]); + } + + { + //maximum symbols + String[] splitted = SpecifUtil.splitPaymentPurpose("01234567890123456789012345678912345" + + "01234567890123456789012345678912345" + + "01234567890123456789012345678912345" + + "01234567890123456789012345678912345" + + "01234567890123456789012345678912345" + + "01234567890123456789012345678912345" + + "outside of scope" + ); + Assertions.assertEquals(6, splitted.length); + for (String s : splitted) { + Assertions.assertEquals("01234567890123456789012345678912345", s); + } + } + + + +// ""; + } + + +} \ No newline at end of file diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java index 812d075bd..2d97429b9 100644 --- a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/TransactionStatus.java @@ -3,7 +3,7 @@ package ru.spcex.platform.enumeration; import ru.spcex.platform.utils.enumeration.IEnumKey; public enum TransactionStatus implements IEnumKey { - stld("STLD"); + stld("STLD"), notSent("NSNT"), cher("CHER"), sent("SENT"); private final String key; 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 df5ac80b5..baf2b5080 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 @@ -35,6 +35,8 @@ public interface Consts { //todo String STATEMENT_PROCESS = "statement-process"; String SDF04_PROCESS = "sdf04-process"; + String SDF03_PROCESS = "sdf03-process"; + String SDF11_PROCESS = "sdf11-process"; String EXPORT_PROCESS = "export-process"; String ACCOUNT_NEW = "account-new"; String BALANCE_ACCOUNT_NEW = "balance-account-new"; diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/SdfClearingRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/SdfClearingRequest.java new file mode 100644 index 000000000..164e9c15d --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/SdfClearingRequest.java @@ -0,0 +1,16 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.clearing; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class SdfClearingRequest { + @JsonProperty + private Long groupId; + + public Long getGroupId() { + return groupId; + } + + public void setGroupId(Long groupId) { + this.groupId = groupId; + } +} \ No newline at end of file From 6d14c4399465c59d65f6fec6d602c4723720194d Mon Sep 17 00:00:00 2001 From: aalehin Date: Thu, 1 Dec 2022 12:27:57 +0300 Subject: [PATCH 3/5] scheduler set up --- .../scheduler/service/SchedulerService.java | 74 +++++++++--------- .../scheduler/service/TimetableService.java | 75 +++++++++++++++++++ .../service/TradingCalendarService.java | 75 +++++++++++++++++++ .../platform/messaging/domain/Consts.java | 8 ++ .../cud/schedule/SchedulerNewRequest.java | 30 +++----- .../cud/schedule/SchedulerUpdateRequest.java | 37 ++++----- .../cud/schedule/TimetableNewRequest.java | 45 +++++++++++ .../cud/schedule/TimetableUpdateRequest.java | 56 ++++++++++++++ .../schedule/TradingCalendarNewRequest.java | 44 +++++++++++ .../TradingCalendarUpdateRequest.java | 55 ++++++++++++++ 10 files changed, 424 insertions(+), 75 deletions(-) create mode 100644 clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TimetableService.java create mode 100644 clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TradingCalendarService.java create mode 100644 platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableNewRequest.java create mode 100644 platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableUpdateRequest.java create mode 100644 platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarNewRequest.java create mode 100644 platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarUpdateRequest.java diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/SchedulerService.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/SchedulerService.java index effaaf686..b07db3270 100644 --- a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/SchedulerService.java +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/SchedulerService.java @@ -7,73 +7,75 @@ 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.KeyRate; +import ru.clearing.classes.statics.data.scheduler.Scheduler; 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.common.CommonDeleteRequest; -import ru.spcex.clearing.platform.messaging.domain.cud.utilities.KeyRateNewRequest; -import ru.spcex.clearing.platform.messaging.domain.cud.utilities.KeyRateUpdateRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.schedule.SchedulerNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.schedule.SchedulerUpdateRequest; import ru.spcex.clearing.platform.messaging.service.QueueConsumer; -import ru.spcex.platform.enumeration.Status; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; @Service public class SchedulerService extends QueueConsumer implements InitializingBean { private final Logger log = LoggerFactory.getLogger(getClass()); - private final Imdg keyRateMap; + private final Imdg schedulerMap; @Autowired public SchedulerService(Consumer kafkaQueue, Producer kafkaProducer, ImdgProvider imdgProvider) { super(kafkaQueue, kafkaProducer); - this.keyRateMap = imdgProvider.getImdg(IMDGDistributedNames.Map_KeyRate, KeyRate.class); + this.schedulerMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Scheduler, Scheduler.class); } @Override public void afterPropertiesSet() { - callback(KeyRateNewRequest.class) - .setConsumer(this::newKeyRate) - .forDestination(Consts.DESTINATION_KEY_RATE_NEW, callbacks::put); - callback(KeyRateUpdateRequest.class) - .setConsumer(this::updateKeyRate) - .forDestination(Consts.DESTINATION_KEY_RATE_UPDATE, callbacks::put); + callback(SchedulerNewRequest.class) + .setConsumer(this::newScheduler) + .forDestination(Consts.DESTINATION_SCHEDULER_NEW, callbacks::put); + callback(SchedulerUpdateRequest.class) + .setConsumer(this::updateScheduler) + .forDestination(Consts.DESTINATION_SCHEDULER_UPDATE, callbacks::put); callback(CommonDeleteRequest.class) - .setConsumer(this::deleteKeyRate) - .forDestination(Consts.DESTINATION_KEY_RATE_DELETE, callbacks::put); + .setConsumer(this::deleteScheduler) + .forDestination(Consts.DESTINATION_SCHEDULER_DELETE, callbacks::put); init(); } - private void newKeyRate(BaseRequest userRequest) { - KeyRateNewRequest req = userRequest.getRequestPayload(); - log.debug("MoneyMarketSecurityNewRequest received"); - KeyRate keyRate = new KeyRate(); - keyRate.setEndDate(req.getEndDate()); - keyRate.setDocument(req.getDocument()); - keyRate.setRate(req.getKeyRate()); - keyRate.setStartDate(req.getStartDate()); - keyRate.setWorkflowStatus(Status.Active.getKey()); - keyRateMap.insert(keyRate); - log.debug("successfully processed, new id {}", keyRate.getId()); + private void newScheduler(BaseRequest userRequest) { + SchedulerNewRequest req = userRequest.getRequestPayload(); + log.debug("SchedulerNewRequest received"); + Scheduler scheduler = new Scheduler(); + scheduler.setTask(req.getTask()); + scheduler.setTaskTime(req.getTaskTime()); + scheduler.setClearingDate(req.getClearingDate()); + scheduler.setMarket(req.getMarket()); + scheduler.setTaskStatus(req.getTaskStatus()); + scheduler.setSecurityId(req.getSecurityId()); + schedulerMap.insert(scheduler); + log.debug("successfully processed, new id {}", scheduler.getId()); } - private void updateKeyRate(BaseRequest userRequest) { - KeyRateUpdateRequest req = userRequest.getRequestPayload(); + private void updateScheduler(BaseRequest userRequest) { + SchedulerUpdateRequest req = userRequest.getRequestPayload(); log.debug("MoneyMarketSecurityUpdateRequest received id = {}", req.getId()); - KeyRate keyRate = keyRateMap.getSingleObjectByID(req.getId()); - keyRate.setEndDate(req.getEndDate()); - keyRate.setDocument(req.getDocument()); - keyRate.setRate(req.getKeyRate()); - keyRate.setStartDate(req.getStartDate()); - keyRateMap.update(keyRate); + Scheduler scheduler = schedulerMap.getSingleObjectByID(req.getId()); + scheduler.setTask(req.getTask()); + scheduler.setTaskTime(req.getTaskTime()); + scheduler.setClearingDate(req.getClearingDate()); + scheduler.setMarket(req.getMarket()); + scheduler.setTaskStatus(req.getTaskStatus()); + scheduler.setSecurityId(req.getSecurityId()); + schedulerMap.update(scheduler); } - private void deleteKeyRate(BaseRequest userRequest) { + private void deleteScheduler(BaseRequest userRequest) { CommonDeleteRequest req = userRequest.getRequestPayload(); log.debug("CommonDeleteRequest received id = {}", req.getId()); - KeyRate keyRate = keyRateMap.getSingleObjectByID(req.getId()); - keyRateMap.delete(keyRate); + Scheduler scheduler = schedulerMap.getSingleObjectByID(req.getId()); + schedulerMap.delete(scheduler); } } diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TimetableService.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TimetableService.java new file mode 100644 index 000000000..bc77439c0 --- /dev/null +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TimetableService.java @@ -0,0 +1,75 @@ +package ru.spcex.clearing.scheduler.service; + +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.producer.Producer; +import org.slf4j.Logger; +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.scheduler.Timetable; +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.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.schedule.TimetableNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.schedule.TimetableUpdateRequest; +import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +@Service +public class TimetableService extends QueueConsumer implements InitializingBean { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Imdg timetableMap; + + @Autowired + public TimetableService(Consumer kafkaQueue, Producer kafkaProducer, + ImdgProvider imdgProvider) { + super(kafkaQueue, kafkaProducer); + this.timetableMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Timetable, Timetable.class); + } + + @Override + public void afterPropertiesSet() { + callback(TimetableNewRequest.class) + .setConsumer(this::newTimetable) + .forDestination(Consts.DESTINATION_TIMETABLE_NEW, callbacks::put); + callback(TimetableUpdateRequest.class) + .setConsumer(this::updateTimetable) + .forDestination(Consts.DESTINATION_TIMETABLE_UPDATE, callbacks::put); + callback(CommonDeleteRequest.class) + .setConsumer(this::deleteTimetable) + .forDestination(Consts.DESTINATION_TIMETABLE_DELETE, callbacks::put); + init(); + } + + private void newTimetable(BaseRequest userRequest) { + TimetableNewRequest req = userRequest.getRequestPayload(); + log.debug("TimetableNewRequest received"); + Timetable timetable = new Timetable(); + timetable.setTask(req.getTask()); + timetable.setTaskTime(req.getTaskTime()); + timetable.setTaskStatus(req.getTaskStatus()); + timetableMap.insert(timetable); + log.debug("successfully processed, new id {}", timetable.getId()); + } + + private void updateTimetable(BaseRequest userRequest) { + TimetableUpdateRequest req = userRequest.getRequestPayload(); + log.debug("TimetableNewRequest received"); + Timetable timetable = timetableMap.getSingleObjectByID(req.getId()); + timetable.setTask(req.getTask()); + timetable.setTaskTime(req.getTaskTime()); + timetable.setTaskStatus(req.getTaskStatus()); + timetableMap.update(timetable); + log.debug("successfully processed, new id {}", timetable.getId()); + } + + private void deleteTimetable(BaseRequest userRequest) { + CommonDeleteRequest req = userRequest.getRequestPayload(); + log.debug("CommonDeleteRequest received id = {}", req.getId()); + Timetable timetable = timetableMap.getSingleObjectByID(req.getId()); + timetableMap.delete(timetable); + } +} diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TradingCalendarService.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TradingCalendarService.java new file mode 100644 index 000000000..baf9cbda6 --- /dev/null +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TradingCalendarService.java @@ -0,0 +1,75 @@ +package ru.spcex.clearing.scheduler.service; + +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.producer.Producer; +import org.slf4j.Logger; +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.scheduler.TradingCalendar; +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.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.schedule.TradingCalendarNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.schedule.TradingCalendarUpdateRequest; +import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +@Service +public class TradingCalendarService extends QueueConsumer implements InitializingBean { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Imdg tradingCalendarMap; + + @Autowired + public TradingCalendarService(Consumer kafkaQueue, Producer kafkaProducer, + ImdgProvider imdgProvider) { + super(kafkaQueue, kafkaProducer); + this.tradingCalendarMap = imdgProvider.getImdg(IMDGDistributedNames.Map_TradingCalendar, TradingCalendar.class); + } + + @Override + public void afterPropertiesSet() { + callback(TradingCalendarNewRequest.class) + .setConsumer(this::newTradingCalendar) + .forDestination(Consts.DESTINATION_TRADING_CALENDAR_NEW, callbacks::put); + callback(TradingCalendarUpdateRequest.class) + .setConsumer(this::updateTradingCalendar) + .forDestination(Consts.DESTINATION_TRADING_CALENDAR_UPDATE, callbacks::put); + callback(CommonDeleteRequest.class) + .setConsumer(this::deleteTradingCalendar) + .forDestination(Consts.DESTINATION_TRADING_CALENDAR_DELETE, callbacks::put); + init(); + } + + private void newTradingCalendar(BaseRequest userRequest) { + TradingCalendarNewRequest req = userRequest.getRequestPayload(); + log.debug("TradingCalendarNewRequest received"); + TradingCalendar tradingCalendar = new TradingCalendar(); + tradingCalendar.setClearingDate(req.getClearingDate()); + tradingCalendar.setCompanyId(req.getCompanyId()); + tradingCalendar.setTradingStatus(req.getTradingStatus()); + tradingCalendarMap.insert(tradingCalendar); + log.debug("successfully processed, new id {}", tradingCalendar.getId()); + } + + private void updateTradingCalendar(BaseRequest userRequest) { + TradingCalendarUpdateRequest req = userRequest.getRequestPayload(); + log.debug("TradingCalendarUpdateRequest received"); + TradingCalendar tradingCalendar = tradingCalendarMap.getSingleObjectByID(req.getId()); + tradingCalendar.setClearingDate(req.getClearingDate()); + tradingCalendar.setCompanyId(req.getCompanyId()); + tradingCalendar.setTradingStatus(req.getTradingStatus()); + tradingCalendarMap.update(tradingCalendar); + log.debug("successfully processed, new id {}", tradingCalendar.getId()); + } + + private void deleteTradingCalendar(BaseRequest userRequest) { + CommonDeleteRequest req = userRequest.getRequestPayload(); + log.debug("CommonDeleteRequest received id = {}", req.getId()); + TradingCalendar tradingCalendar = tradingCalendarMap.getSingleObjectByID(req.getId()); + tradingCalendarMap.delete(tradingCalendar); + } +} 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 baf2b5080..2e4b2721b 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 @@ -9,6 +9,14 @@ public interface Consts { String DESTINATION_KEY_RATE_UPDATE = "key-rate-update"; String DESTINATION_KEY_RATE_DELETE = "key-rate-delete"; + String DESTINATION_TIMETABLE_NEW = "timetable-new"; + String DESTINATION_TIMETABLE_UPDATE = "timetable-update"; + String DESTINATION_TIMETABLE_DELETE = "timetable-delete"; + + String DESTINATION_TRADING_CALENDAR_NEW = "timetable-new"; + String DESTINATION_TRADING_CALENDAR_UPDATE = "timetable-update"; + String DESTINATION_TRADING_CALENDAR_DELETE = "timetable-delete"; + String DESTINATION_SCHEDULER_NEW = "scheduler-new"; String DESTINATION_SCHEDULER_UPDATE = "scheduler-update"; String DESTINATION_SCHEDULER_DELETE = "scheduler-delete"; diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerNewRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerNewRequest.java index 405d98f59..9b72792d3 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerNewRequest.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerNewRequest.java @@ -4,23 +4,26 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import ru.spcex.clearing.platform.messaging.domain.json.deserialize.LocalDateDeserializer; +import ru.spcex.clearing.platform.messaging.domain.json.deserialize.LocalTimeDeserializer; import ru.spcex.clearing.platform.messaging.domain.json.serialize.LocalDateSerializer; +import ru.spcex.clearing.platform.messaging.domain.json.serialize.LocalTimeSerializer; import java.time.LocalDate; import java.time.LocalTime; public class SchedulerNewRequest { - @JsonProperty - @JsonSerialize(using = LocalDateSerializer.class) - @JsonDeserialize(using = LocalDateDeserializer.class) - public LocalTime taskTime; @JsonProperty public String task; + @JsonSerialize(using = LocalTimeSerializer.class) + @JsonDeserialize(using = LocalTimeDeserializer.class) @JsonProperty + public LocalTime taskTime; + @JsonSerialize(using = LocalDateSerializer.class) @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonProperty public LocalDate clearingDate; @JsonProperty @@ -32,13 +35,12 @@ public class SchedulerNewRequest { @JsonProperty public Long securityId; - public SchedulerNewRequest(LocalTime taskTime, String task, LocalDate clearingDate, String market, String taskStatus, Long securityId) { - this.taskTime = taskTime; + public String getTask() { + return task; + } + + public void setTask(String task) { this.task = task; - this.clearingDate = clearingDate; - this.market = market; - this.taskStatus = taskStatus; - this.securityId = securityId; } public LocalTime getTaskTime() { @@ -49,14 +51,6 @@ public class SchedulerNewRequest { this.taskTime = taskTime; } - public String getTask() { - return task; - } - - public void setTask(String task) { - this.task = task; - } - public LocalDate getClearingDate() { return clearingDate; } diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerUpdateRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerUpdateRequest.java index abdf25d5a..6c46bee95 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerUpdateRequest.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/SchedulerUpdateRequest.java @@ -12,30 +12,33 @@ import java.time.LocalDate; import java.time.LocalTime; public class SchedulerUpdateRequest { - @JsonProperty - private String task; + @JsonProperty + public Long id; + @JsonProperty + public String task; @JsonSerialize(using = LocalTimeSerializer.class) @JsonDeserialize(using = LocalTimeDeserializer.class) @JsonProperty - private LocalTime taskTime; - + public LocalTime taskTime; @JsonSerialize(using = LocalDateSerializer.class) @JsonDeserialize(using = LocalDateDeserializer.class) @JsonProperty - private LocalDate clearingDate; - + public LocalDate clearingDate; @JsonProperty - private String market; - + public String market; @JsonProperty - private String taskStatus; - + public String taskStatus; @JsonProperty - private Long securityId; + public Long securityId; - @JsonProperty - private Long id; + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } public String getTask() { return task; @@ -84,12 +87,4 @@ public class SchedulerUpdateRequest { public void setSecurityId(Long securityId) { this.securityId = securityId; } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } } diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableNewRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableNewRequest.java new file mode 100644 index 000000000..ec178490d --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableNewRequest.java @@ -0,0 +1,45 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.schedule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import ru.spcex.clearing.platform.messaging.domain.json.deserialize.LocalTimeDeserializer; +import ru.spcex.clearing.platform.messaging.domain.json.serialize.LocalTimeSerializer; + +import java.time.LocalTime; + +public class TimetableNewRequest { + @JsonProperty + public String task; + + @JsonSerialize(using = LocalTimeSerializer.class) + @JsonDeserialize(using = LocalTimeDeserializer.class) + @JsonProperty + public LocalTime taskTime; + @JsonProperty + public String taskStatus; + + public String getTask() { + return task; + } + + public void setTask(String task) { + this.task = task; + } + + public LocalTime getTaskTime() { + return taskTime; + } + + public void setTaskTime(LocalTime taskTime) { + this.taskTime = taskTime; + } + + public String getTaskStatus() { + return taskStatus; + } + + public void setTaskStatus(String taskStatus) { + this.taskStatus = taskStatus; + } +} diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableUpdateRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableUpdateRequest.java new file mode 100644 index 000000000..210869118 --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TimetableUpdateRequest.java @@ -0,0 +1,56 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.schedule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import ru.spcex.clearing.platform.messaging.domain.json.deserialize.LocalTimeDeserializer; +import ru.spcex.clearing.platform.messaging.domain.json.serialize.LocalTimeSerializer; + +import java.time.LocalTime; + +public class TimetableUpdateRequest { + @JsonProperty + public Long id; + + @JsonProperty + public String task; + + @JsonSerialize(using = LocalTimeSerializer.class) + @JsonDeserialize(using = LocalTimeDeserializer.class) + @JsonProperty + public LocalTime taskTime; + @JsonProperty + public String taskStatus; + + public String getTask() { + return task; + } + + public void setTask(String task) { + this.task = task; + } + + public LocalTime getTaskTime() { + return taskTime; + } + + public void setTaskTime(LocalTime taskTime) { + this.taskTime = taskTime; + } + + public String getTaskStatus() { + return taskStatus; + } + + public void setTaskStatus(String taskStatus) { + this.taskStatus = taskStatus; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarNewRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarNewRequest.java new file mode 100644 index 000000000..2472babb8 --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarNewRequest.java @@ -0,0 +1,44 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.schedule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import ru.spcex.clearing.platform.messaging.domain.json.deserialize.LocalDateDeserializer; +import ru.spcex.clearing.platform.messaging.domain.json.serialize.LocalDateSerializer; + +import java.time.LocalDate; + +public class TradingCalendarNewRequest { + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonProperty + public LocalDate clearingDate; + @JsonProperty + public Long companyId; + @JsonProperty + public String tradingStatus; + + public LocalDate getClearingDate() { + return clearingDate; + } + + public void setClearingDate(LocalDate clearingDate) { + this.clearingDate = clearingDate; + } + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getTradingStatus() { + return tradingStatus; + } + + public void setTradingStatus(String tradingStatus) { + this.tradingStatus = tradingStatus; + } +} diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarUpdateRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarUpdateRequest.java new file mode 100644 index 000000000..7fb496a9a --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/schedule/TradingCalendarUpdateRequest.java @@ -0,0 +1,55 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.schedule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import ru.spcex.clearing.platform.messaging.domain.json.deserialize.LocalDateDeserializer; +import ru.spcex.clearing.platform.messaging.domain.json.serialize.LocalDateSerializer; + +import java.time.LocalDate; + +public class TradingCalendarUpdateRequest { + @JsonProperty + public Long id; + + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonProperty + public LocalDate clearingDate; + @JsonProperty + public Long companyId; + @JsonProperty + public String tradingStatus; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public LocalDate getClearingDate() { + return clearingDate; + } + + public void setClearingDate(LocalDate clearingDate) { + this.clearingDate = clearingDate; + } + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getTradingStatus() { + return tradingStatus; + } + + public void setTradingStatus(String tradingStatus) { + this.tradingStatus = tradingStatus; + } +} From 11ed1ff66b6daf54abfa4c19a244ace0a01a3c08 Mon Sep 17 00:00:00 2001 From: ialbert Date: Thu, 1 Dec 2022 16:22:08 +0300 Subject: [PATCH 4/5] http://jira.mfd.msk:8088/browse/CLS-130 sdf03 sdf11 --- .../ru/spcex/clearing/service/PaymentInstructionSorter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentInstructionSorter.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentInstructionSorter.java index c618bad8f..f5f59d15e 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentInstructionSorter.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/PaymentInstructionSorter.java @@ -114,7 +114,8 @@ public class PaymentInstructionSorter { batchInfo.setInitialOrder(payments); return batchInfo; } else { - throw new IllegalStateException("unknown clearing member category"); + throw new IllegalStateException("unknown clearing member category for generationId=" + + generationId + " companyId=" + senderId + ""); } } } From aa23776d288ad662977ffe95ada41ef216da8f58 Mon Sep 17 00:00:00 2001 From: ialbert Date: Fri, 2 Dec 2022 14:01:33 +0300 Subject: [PATCH 5/5] http://jira.mfd.msk:8088/browse/CLS-80 --- .../src/main/resources/meta/meta.xml | 463 ++++++++++-------- 1 file changed, 262 insertions(+), 201 deletions(-) diff --git a/clearing-parent/backend-api/src/main/resources/meta/meta.xml b/clearing-parent/backend-api/src/main/resources/meta/meta.xml index 2c066a2c6..0902df3d3 100644 --- a/clearing-parent/backend-api/src/main/resources/meta/meta.xml +++ b/clearing-parent/backend-api/src/main/resources/meta/meta.xml @@ -1,6 +1,6 @@ - + @@ -29,32 +29,32 @@ - + - + - + - - + + - + - - + + @@ -240,9 +240,14 @@ + + + + + - + @@ -254,14 +259,14 @@ - + - + @@ -274,7 +279,7 @@ - + @@ -288,7 +293,7 @@ - + @@ -302,41 +307,41 @@ - + - + - - + + - + - + - + - + - + - - + + @@ -347,7 +352,7 @@ - + @@ -355,8 +360,8 @@ - - + + @@ -364,12 +369,12 @@ - - + + - - + + @@ -377,10 +382,10 @@ - - - - + + + + @@ -394,9 +399,10 @@ + - - + + @@ -407,12 +413,12 @@ - + - + @@ -446,7 +452,7 @@ - + @@ -464,7 +470,7 @@ - + @@ -477,7 +483,7 @@ - + @@ -491,8 +497,24 @@ + + + + + + + + + + + + + + + + - + @@ -540,7 +562,7 @@ - + @@ -566,12 +588,12 @@ - + - + @@ -581,7 +603,7 @@ - + @@ -591,8 +613,15 @@ + + + + + + + - + @@ -635,18 +664,18 @@ - + - + - + @@ -659,19 +688,19 @@ - + - + - + @@ -682,7 +711,7 @@ - + @@ -692,7 +721,7 @@ - + @@ -702,7 +731,7 @@ - + @@ -714,7 +743,7 @@ - + @@ -725,7 +754,7 @@ - + @@ -768,7 +797,7 @@ - + @@ -794,7 +823,7 @@ - + @@ -811,7 +840,7 @@ - + @@ -827,105 +856,124 @@ - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + @@ -975,8 +1023,9 @@ + - + @@ -994,8 +1043,9 @@ + - + @@ -1018,7 +1068,7 @@ - + @@ -1064,7 +1114,7 @@ - + @@ -1075,14 +1125,14 @@ - + - + @@ -1151,7 +1201,7 @@ - + @@ -1160,7 +1210,7 @@ - + @@ -1178,7 +1228,7 @@ - + @@ -1197,7 +1247,7 @@ - + @@ -1245,10 +1295,11 @@ - - + + + - + @@ -1297,26 +1348,26 @@ - + - +
- + - + - + @@ -1325,10 +1376,10 @@ - + - + @@ -1341,7 +1392,7 @@ - + @@ -1388,10 +1439,11 @@ - + + - + @@ -1400,7 +1452,7 @@ - + @@ -1447,10 +1499,10 @@ - + - + @@ -1461,10 +1513,10 @@ - + - + @@ -1479,7 +1531,7 @@ - + @@ -1489,28 +1541,29 @@ - - + + + - - + + - + - - - - - - - - + + + + + + + + @@ -1527,13 +1580,21 @@ - + + + + + + + + + @@ -1577,16 +1638,16 @@ - + - + - + @@ -1595,7 +1656,7 @@ - + @@ -1604,7 +1665,7 @@ - + @@ -1630,7 +1691,7 @@ - + @@ -1673,6 +1734,6 @@ - +