clearing-service http://jira.mfd.msk:8088/browse/CLS-200 TODO оделать сверку когда уточнят ТЗ и доабвить класс VerificationResult
This commit is contained in:
parent
ade57fd62a
commit
d36794b95d
4 changed files with 342 additions and 2 deletions
|
|
@ -2,6 +2,7 @@ package ru.spcex.clearing.service;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
|
@ -12,16 +13,19 @@ import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
public class ClearingService {
|
public class ClearingService implements DisposableBean {
|
||||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final SdfCreatorBySTLDPayment sdfCreator;
|
private final SdfCreatorBySTLDPayment sdfCreator;
|
||||||
private final PaymentUpdateBySdf04 paymentUpdater;
|
private final PaymentUpdateBySdf04 paymentUpdater;
|
||||||
|
private final VerificationResultComponent verificationResultComponent;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ClearingService(SdfCreatorBySTLDPayment sdfCreator, PaymentUpdateBySdf04 paymentUpdater) {
|
public ClearingService(SdfCreatorBySTLDPayment sdfCreator, PaymentUpdateBySdf04 paymentUpdater,
|
||||||
|
VerificationResultComponent verificationResultComponent) {
|
||||||
this.sdfCreator = sdfCreator;
|
this.sdfCreator = sdfCreator;
|
||||||
this.paymentUpdater = paymentUpdater;
|
this.paymentUpdater = paymentUpdater;
|
||||||
|
this.verificationResultComponent = verificationResultComponent;
|
||||||
this.executor = Executors.newSingleThreadExecutor();
|
this.executor = Executors.newSingleThreadExecutor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,4 +39,14 @@ public class ClearingService {
|
||||||
log.info("updating payment.transactionStatus by sdf04 task added to queue");
|
log.info("updating payment.transactionStatus by sdf04 task added to queue");
|
||||||
executor.execute(() -> paymentUpdater.updatePayments(sdf04GroupId));
|
executor.execute(() -> paymentUpdater.updatePayments(sdf04GroupId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void executeVerification() {
|
||||||
|
log.info("execute verification");
|
||||||
|
executor.execute(() -> verificationResultComponent.executeRevision());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() throws Exception {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ public class EventsReceiver extends QueueConsumer implements InitializingBean {
|
||||||
callback(LauncherCommandRequest.class)
|
callback(LauncherCommandRequest.class)
|
||||||
.setConsumer(event -> clearingService.sdfCreate())
|
.setConsumer(event -> clearingService.sdfCreate())
|
||||||
.forDestination(Task.createOrder.topic(), callbacks::put);
|
.forDestination(Task.createOrder.topic(), callbacks::put);
|
||||||
|
callback(LauncherCommandRequest.class)
|
||||||
|
.setConsumer(event -> clearingService.executeVerification())
|
||||||
|
.forDestination(Task.getVerification.topic(), callbacks::put);
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<SDf01> sdf01Imdg;
|
||||||
|
private Imdg<AccountBalance> accountBalanceImdg;
|
||||||
|
private Imdg<Account> 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<SDf01> allSDf01 = sdf01Imdg.getAllValues();
|
||||||
|
Collection<AccountBalance> allAccountBalance = accountBalanceImdg.getAllValues();
|
||||||
|
Collection<Account> allAccount = accountImdg.getAllValues();
|
||||||
|
executeRevision(allSDf01, allAccountBalance, allAccount);
|
||||||
|
|
||||||
|
//todo результат logic... см. как было в корпе?
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeRevision(Collection<SDf01> allSDf01, Collection<AccountBalance> allAccountBalance, Collection<Account> allAccount) {
|
||||||
|
// 1. Сумму по остаткам
|
||||||
|
List<Object/*verificationResult*/> vResLst = new ArrayList<>();
|
||||||
|
/* todo use:
|
||||||
|
<verificationResult name="Результаты сверки" destination="verification-results" class="com.spicex.Static." table="verification_result">
|
||||||
|
<clearingCode type="2" length="255" name="Код участника клиринга" shortname="Клиринговый код" searchable="true" sortable="true" visible="true"/>
|
||||||
|
<accountId type="1" name="Счет УК, по которому проводится сверка" shortname="Счет УК" searchable="true" sortable="true"/>
|
||||||
|
<inSum type="11" name="Входящая сумма остатков" shortname="Остатки" visible="true" searchable="true" sortable="true"/>
|
||||||
|
<outIntSum type="11" name="Исходящая сумма остатков, полученная в КС" shortname="Остатки, полученные в КС" visible="true" searchable="true" sortable="true"/>
|
||||||
|
<outExtSum type="11" name="Исходящая сумма остатков из отчета ПРЦ" shortname="Остатки, полученные из ПРЦ" visible="true" searchable="true" sortable="true"/>
|
||||||
|
<diffSum type="11" name="Сумма расхождений" shortname="Сумма расхождений" visible="true" searchable="true" sortable="true"/>
|
||||||
|
<generationId type="1" name="Идентификатор взаимодействия" shortname="ID взаимодействия" searchable="true" sortable="true"/>
|
||||||
|
<generationStatus type="12" name="Общий статус сверки" shortname="Статус" searchable="true" sortable="true" visible="true" link="resultStatus"/>
|
||||||
|
<resultStatus type="12" name="Статус сверки" shortname="Статус" searchable="true" sortable="true" visible="true" link="resultStatus"/>
|
||||||
|
<id type="1" name="Идентификатор записи" shortname="ID" searchable="true" sortable="true"/>
|
||||||
|
<createdAt type="4" name="Дата и время создания записи" shortname="Создано" searchable="true" sortable="true" ignore="true"/>
|
||||||
|
<updatedAt type="4" name="Дата и время изменения записи" shortname="Изменено" searchable="true" sortable="true" ignore="true"/>
|
||||||
|
</verificationResult>
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
Map<String, SDf01> 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<String, SDf01> indexSDf01 = index(allSDf01, SDf01::getDeal);
|
||||||
|
Set<String> sDf01Keys = allSDf01.stream()
|
||||||
|
.filter(sdf01 -> sdf01.getDeal() != null)
|
||||||
|
.map(sdf01 -> sdf01.getDeal())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Set<String> 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]
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
<K, V extends WithId> Map<K, V> index(Collection<V> from, Function<V, K> indexFieldExtractor) {
|
||||||
|
Map<K, V> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue