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 a2c9487a8..a1df6934e 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 @@ -2,6 +2,7 @@ package ru.spcex.clearing.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; @@ -12,16 +13,19 @@ import java.util.concurrent.Executors; @Service @EnableScheduling -public class ClearingService { +public class ClearingService implements DisposableBean { private final Logger log = LoggerFactory.getLogger(getClass()); private final ExecutorService executor; private final SdfCreatorBySTLDPayment sdfCreator; private final PaymentUpdateBySdf04 paymentUpdater; + private final VerificationResultComponent verificationResultComponent; @Autowired - public ClearingService(SdfCreatorBySTLDPayment sdfCreator, PaymentUpdateBySdf04 paymentUpdater) { + public ClearingService(SdfCreatorBySTLDPayment sdfCreator, PaymentUpdateBySdf04 paymentUpdater, + VerificationResultComponent verificationResultComponent) { this.sdfCreator = sdfCreator; this.paymentUpdater = paymentUpdater; + this.verificationResultComponent = verificationResultComponent; this.executor = Executors.newSingleThreadExecutor(); } @@ -35,4 +39,14 @@ public class ClearingService { log.info("updating payment.transactionStatus by sdf04 task added to queue"); executor.execute(() -> paymentUpdater.updatePayments(sdf04GroupId)); } + + public void executeVerification() { + log.info("execute verification"); + executor.execute(() -> verificationResultComponent.executeRevision()); + } + + @Override + public void destroy() throws Exception { + executor.shutdown(); + } } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java index bd4bfef11..f7911379f 100644 --- a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/EventsReceiver.java @@ -28,6 +28,9 @@ public class EventsReceiver extends QueueConsumer implements InitializingBean { callback(LauncherCommandRequest.class) .setConsumer(event -> clearingService.sdfCreate()) .forDestination(Task.createOrder.topic(), callbacks::put); + callback(LauncherCommandRequest.class) + .setConsumer(event -> clearingService.executeVerification()) + .forDestination(Task.getVerification.topic(), callbacks::put); init(); } } diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/VerificationResultComponent.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/VerificationResultComponent.java new file mode 100644 index 000000000..09df1cc26 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/service/VerificationResultComponent.java @@ -0,0 +1,301 @@ +package ru.spcex.clearing.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import ru.clearing.classes.objects.BusinessObject; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.sdf.SDf01; +import ru.clearing.classes.statics.data.account.AccountBalance; +import ru.clearing.classes.statics.data.sdf.SDf03; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.platform.classes.base.interfaces.WithId; +import ru.spcex.platform.enumeration.AccountType; +import ru.spcex.platform.enumeration.ResultStatuses; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +import java.math.BigDecimal; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 1.70. verificationResult - Результаты сверки + * Раздел I - Функционал сверок + */ +@Component +public class VerificationResultComponent { + private final Logger log = LoggerFactory.getLogger(getClass()); + + // private final ImdgProvider imdgProvider; + private Imdg sdf01Imdg; + private Imdg accountBalanceImdg; + private Imdg accountImdg; + + + @Autowired + public VerificationResultComponent(ImdgProvider imdgProvider + ) { +// this.imdgProvider = imdgProvider; + this.sdf01Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf01, SDf01.class); + this.accountBalanceImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_AccountBalance, AccountBalance.class); + this.accountImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class); + + //this.idGenerator = imdgProvider.getImdgIdGenerator(); + //this.kafkaSender = KafkaSender kafkaSender + } + + protected void executeRevision() { + Collection allSDf01 = sdf01Imdg.getAllValues(); + Collection allAccountBalance = accountBalanceImdg.getAllValues(); + Collection allAccount = accountImdg.getAllValues(); + executeRevision(allSDf01, allAccountBalance, allAccount); + + //todo результат logic... см. как было в корпе? + } + + protected void executeRevision(Collection allSDf01, Collection allAccountBalance, Collection allAccount) { + // 1. Сумму по остаткам + List vResLst = new ArrayList<>(); + /* todo use: + + + + + + + + + + + + + + + */ + { + Map indexSDf01 = index(allSDf01, SDf01::getAccount); + for (AccountBalance account : allAccountBalance) { + VerificationResult vResult = new VerificationResult(); + vResult.setAccountId(account.getAccountId()); + + SDf01 document = indexSDf01.get(account.getAccount()); + if (document == null) { + log.warn("s_df01 not found by AccountBalance[{}].account=\"{}\"", account.getId(), account.getAccount()); + } else { + BigDecimal remainder = BigDecimal.ZERO; + if (document.getRemainder() != null) { + try { + remainder = new BigDecimal(document.getRemainder()); + } catch (NumberFormatException nfe) { + log.warn("SDf01[{}].remainder=\"{}\" is not parseable: {}", + document.getId(), document.getRemainder(), nfe.getMessage()); + } + } + BigDecimal balance = account.getCloseBalanceAmount() == null ? BigDecimal.ZERO : account.getCloseBalanceAmount(); + //todo сравнить правильно, remainder там без точек. remainder==balance + BigDecimal diff = balance.subtract(remainder).abs(); + vResult.setDiffSum(diff); + if (BigDecimal.ZERO.compareTo(diff) == 0) { + vResult.setResultStatus(ResultStatuses.success.getKey()); + } else { + vResult.setResultStatus(ResultStatuses.notSuccess.getKey()); + } + } + } + } + + // 2.1 Номера торговых/клиринговых счетов, соответствующие загруженным в КС + // -sDf01.deal = account.account & accountType=CLRN & accountStatus=UNBL + { +// Map indexSDf01 = index(allSDf01, SDf01::getDeal); + Set sDf01Keys = allSDf01.stream() + .filter(sdf01 -> sdf01.getDeal() != null) + .map(sdf01 -> sdf01.getDeal()) + .collect(Collectors.toSet()); + Set accountKeys = allAccount.stream() + .filter(account -> AccountType.Clrn.equalsByKey(account.getAccountType()) && "UNBL".equals(account.getAccountStatus())) + .map(sdf01 -> sdf01.getAccount()) + .collect(Collectors.toSet()); + boolean isOk = sDf01Keys.size() == accountKeys.size() && + sDf01Keys.containsAll(accountKeys) && accountKeys.containsAll(sDf01Keys); +// for (Account account:allAccount) { +// if (AccountType.Clrn.equalsByKey(account.getAccountType()) & "UNBL".equals(account.getAccountStatus())) { +// // todo use enum/const ? +// SDf01 document = indexSDf01.get(account.getAccount()); +// if (document == null) { +// //todo найдено несоответствие. +// } else { +// } +// } +// } + } + + // 2.2 Количество торговых/клиринговых счетов, соответствующие загруженным в КС - COUNT[sDf01.deal] = COUNT [account.account] + { + long countSDf01 = allSDf01.stream().filter(sDf01 -> sDf01.getDeal() != null).count(); + long countAccount = allAccount.stream().filter(account -> account.getAccount() != null).count(); + log.info("countSDf01={}, countAccount={}", countSDf01 ,countAccount); + //todo countSDf01 != countAccount + } + + // 2.3 Суммарный остаток по счетам + { + //todo не понял условия [sDf01.account = accountBalance.account] + long summSDf01 = 0; + for (SDf01 document : allSDf01) { + if (StringUtils.isEmpty(document.getRemainder())) { + try { + long value = Long.parseLong(document.getRemainder()); + summSDf01 += value; + } catch (NumberFormatException nfe) { + log.warn("SDf01[{}].remainder=\"{}\" is not parseable: {}", + document.getId(), document.getRemainder(), nfe.getMessage()); + } + } else { + log.debug("SDf01[{}].remainder is null or empty", document.getId()); + } + } + long summAccount = 0; + for (AccountBalance account : allAccountBalance) { + if (account.getCloseBalanceAmount() != null) { + long value = account.getCloseBalanceAmount().longValue(); + summAccount += value; + } + } + // todo summSDf01==summAccount теперь с точностью и погрешностью сравнений проблема. + } + + // 3. Сверка остатков по клиринговым счетам СПВБ + { + //todo не понял [sDf01.account = accountBalance.account & accountType=INFO] + } + } + + + protected void onSuccessRevisionResult() { + //todo 17.1 - Сообщение пользователю об успешной сверке + } + + protected void onDetectDiffRevisionResult() { + //todo 17.2 - Сообщение пользователю в случае выявления расхождений + } + + /* + Сумму по остаткам в бизнес-объекте accountBalance - sDf01.remainder = accountBalance.closeBalanceAmount [sDf01.account = accountBalance.account] + 2. Расчет контрольных сумм: + 2.1 Номера торговых/клиринговых счетов, соответствующие загруженным в КС -sDf01.deal = account.account & accountType=CLRN & accountStatus=UNBL + 2.2 Количество торговых/клиринговых счетов, соответствующие загруженным в КС - COUNT[sDf01.deal] = COUNT [account.account] + 2.3 Суммарный остаток по счетам - ∑sDf01.remainder = ∑accountBalance.closeBalanceAmount[sDf01.account = accountBalance.account] + 3. Сверка остатков по клиринговым счетам СПВБ в бизнес-объекте accountBalance - sDf01.remainder = accountBalance.closeBalanceAmount [sDf01.account = accountBalance.account & accountType=INFO] + */ + + + Map index(Collection from, Function indexFieldExtractor) { + Map index = new HashMap<>(); + for (V obj : from) { + K key = indexFieldExtractor.apply(obj); + V exist = index.put(key, obj); + if (exist != null) { + log.warn("{} with id={},{} has duplicate Account=\"{}\"", + exist.getClass().getSimpleName(), exist.getId(), obj.getId(), key); + } + } + return index; + } + + //todo to classes: VerificationResult! + static class VerificationResult extends BusinessObject { + private String clearingCode; // type="2" + private Long accountId; // type="1" + private BigDecimal inSum; // type="11" + private BigDecimal outIntSum; // type="11" + private BigDecimal outExtSum; // type="11" + private BigDecimal diffSum; // type="11" + private Long generationId; // type="1" + private String generationStatus; // type="12" + private String resultStatus; // type="12" + + public String getClearingCode() { + return clearingCode; + } + + public void setClearingCode(String clearingCode) { + this.clearingCode = clearingCode; + } + + public Long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public BigDecimal getInSum() { + return inSum; + } + + public void setInSum(BigDecimal inSum) { + this.inSum = inSum; + } + + public BigDecimal getOutIntSum() { + return outIntSum; + } + + public void setOutIntSum(BigDecimal outIntSum) { + this.outIntSum = outIntSum; + } + + public BigDecimal getOutExtSum() { + return outExtSum; + } + + public void setOutExtSum(BigDecimal outExtSum) { + this.outExtSum = outExtSum; + } + + public BigDecimal getDiffSum() { + return diffSum; + } + + public void setDiffSum(BigDecimal diffSum) { + this.diffSum = diffSum; + } + + public Long getGenerationId() { + return generationId; + } + + public void setGenerationId(Long generationId) { + this.generationId = generationId; + } + + public String getGenerationStatus() { + return generationStatus; + } + + public void setGenerationStatus(String generationStatus) { + this.generationStatus = generationStatus; + } + + public String getResultStatus() { + return resultStatus; + } + + public void setResultStatus(String resultStatus) { + this.resultStatus = resultStatus; + } + } + +} diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ResultStatuses.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ResultStatuses.java new file mode 100644 index 000000000..54660b5ce --- /dev/null +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ResultStatuses.java @@ -0,0 +1,22 @@ +package ru.spcex.platform.enumeration; + +import ru.spcex.platform.utils.enumeration.IEnumKey; + +/** + * ResultStatusDictionary + */ +public enum ResultStatuses implements IEnumKey { + notSuccess("NACK"), // Неуспешно + success("ACK"); // Успешно + + private final String key; + + ResultStatuses(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } +}