ialbert 2023-02-01 20:52:25 +03:00
parent 8fb186f564
commit 75c4e7aeae
6 changed files with 194 additions and 9 deletions

View file

@ -5,13 +5,18 @@ import org.springframework.stereotype.Component;
import ru.clearing.classes.statics.data.account.Account;
import ru.clearing.classes.statics.data.account.AccountBalance;
import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.generated.ClearingMemberCategory;
import ru.spcex.clearing.balance.validation.AccountBalanceValidation;
import ru.spcex.clearing.balance.validation.ValidationStored;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.cud.balance.AccountBalanceClearingRequest;
import ru.spcex.platform.enumeration.AccountType;
import ru.spcex.platform.enumeration.BalanceAccountType;
import ru.spcex.platform.enumeration.ClearingCategory;
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.validation.IValidator;
import java.math.BigDecimal;
@ -26,12 +31,14 @@ public class AccountBalanceService {
private final ImdgProvider imdgProvider;
private final Imdg<AccountBalance> accountBalanceImdg;
private final Function<AccountBalanceValidation, IValidator> validationFactory;
private final Imdg<ClearingMemberCategory> clearingCategoryImdg;
public AccountBalanceService(ImdgProvider imdgProvider,
@Qualifier("accountBalanceValidator") Function<AccountBalanceValidation, IValidator> validationFactory) {
this.imdgProvider = imdgProvider;
this.validationFactory = validationFactory;
this.accountBalanceImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_AccountBalance, AccountBalance.class);
this.clearingCategoryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ClearingMemberCategory, ClearingMemberCategory.class);
}
public AccountResult createAccountBalance(Long addresseeId, Long accountId, BigDecimal amount,
@ -85,4 +92,88 @@ public class AccountBalanceService {
if (b == null) return a;
return a.add(b);
}
public void updateAccountBalanceByClearing(AccountBalanceClearingRequest req) {
ClearingCategory category = getClearingCategoryByCompanyId(req.getCompanyId());
if (category.equals(ClearingCategory.I)) {
updateAccountCategoryIClrn(req.getAccountId(), req.getCompanyId(), req.getFirstLegAmount());
updateAccountCategoryITran(req.getFirstLegAmount());
} else if (category.equals(ClearingCategory.V)) {
updateAccountCategoryVInfo(req.getAccountId(), req.getCompanyId(), req.getFirstLegAmount());
updateAccountCategoryVAnlt(req.getFirstLegAmount());
} else if (category.equals(ClearingCategory.B)) {
updateAccountCategoryBClrn(req.getAccountId(), req.getCompanyId(), req.getFirstLegAmount());
}
//todo send message success to Kafka
}
private ClearingCategory getClearingCategoryByCompanyId(Long companyId) {
ClearingMemberCategory category = clearingCategoryImdg.getSingleObjectByFieldValues(
Map.of("companyId", companyId));
return IEnumKey.getEnumByKey(ClearingCategory.class,
category.getClearingMemberCategory());
}
private void updateAccountCategoryIClrn(Long accountId, Long companyId, BigDecimal firstLegAmount) {
AccountBalance accountBalance = loadAccountBalance(accountId, companyId, AccountType.Clrn);
BigDecimal previousFreeBalanceAmount = accountBalance.getFreeBalanceAmount();
BigDecimal resultFreeBalanceAmount = previousFreeBalanceAmount.subtract(firstLegAmount);
accountBalance.setFreeBalanceAmount(resultFreeBalanceAmount);
accountBalance.setChangeBalanceAmount(resultFreeBalanceAmount);
accountBalance.setBalanceAmount(resultFreeBalanceAmount);
accountBalance.setUpdated(Instant.now());
accountBalanceImdg.update(accountBalance);
}
private void updateAccountCategoryITran(BigDecimal firstLegAmount) {
AccountBalance accountBalance = loadAccountBalance(AccountType.Tran);
BigDecimal freeBalanceAmount = accountBalance.getFreeBalanceAmount();
accountBalance.setFreeBalanceAmount(freeBalanceAmount.add(firstLegAmount.abs()));
accountBalance.setUpdated(Instant.now());
accountBalanceImdg.update(accountBalance);
}
private void updateAccountCategoryVInfo(Long accountId, Long companyId, BigDecimal firstLegAmount) {
AccountBalance accountBalance = loadAccountBalance(accountId, companyId, AccountType.Info);
BigDecimal previousFreeBalanceAmount = accountBalance.getFreeBalanceAmount();
BigDecimal resultFreeBalanceAmount = previousFreeBalanceAmount.subtract(firstLegAmount);
accountBalance.setFreeBalanceAmount(resultFreeBalanceAmount);
accountBalance.setChangeBalanceAmount(resultFreeBalanceAmount);
accountBalance.setBalanceAmount(resultFreeBalanceAmount);
accountBalance.setUpdated(Instant.now());
accountBalanceImdg.update(accountBalance);
}
private void updateAccountCategoryVAnlt(BigDecimal firstLegAmount) {
AccountBalance accountBalance = loadAccountBalance(AccountType.Anlt);
BigDecimal previousFreeBalanceAmount = accountBalance.getFreeBalanceAmount();
BigDecimal resultFreeBalanceAmount = previousFreeBalanceAmount.subtract(firstLegAmount);
accountBalance.setFreeBalanceAmount(resultFreeBalanceAmount);
accountBalance.setChangeBalanceAmount(resultFreeBalanceAmount);
accountBalance.setBalanceAmount(resultFreeBalanceAmount);
accountBalance.setUpdated(Instant.now());
accountBalanceImdg.update(accountBalance);
}
private void updateAccountCategoryBClrn(Long accountId, Long companyId, BigDecimal firstLegAmount) {
AccountBalance accountBalance = loadAccountBalance(accountId, companyId, AccountType.Clrn);
BigDecimal freeBalanceAmount = accountBalance.getFreeBalanceAmount();
accountBalance.setDebitAmount(plus(freeBalanceAmount, firstLegAmount.abs()));
accountBalance.setUpdated(Instant.now());
accountBalanceImdg.update(accountBalance);
}
private AccountBalance loadAccountBalance(Long accountId, Long companyId, AccountType type) {
return accountBalanceImdg.getSingleObjectByFieldValues(
Map.of("accountId", accountId, "companyId", companyId,
"accountType", type.getKey())
);
}
private AccountBalance loadAccountBalance(AccountType type) {
return accountBalanceImdg.getSingleObjectByFieldValues(
Map.of("companyId", 1L,
"accountType", type.getKey())
);
}
}

View file

@ -15,6 +15,7 @@ import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.domain.cud.account.sdf01.AccountSdf01Request;
import ru.spcex.clearing.platform.messaging.domain.cud.account.sdf01.AccountSdfRequestPart;
import ru.spcex.clearing.platform.messaging.domain.cud.balance.AccountBalanceClearingRequest;
import ru.spcex.clearing.platform.messaging.domain.cud.balance.ExportToFileRequest;
import ru.spcex.clearing.platform.messaging.domain.cud.balance.StatementRequest;
import ru.spcex.clearing.platform.messaging.service.QueueConsumer;
@ -38,14 +39,16 @@ public class StatementService extends QueueConsumer implements InitializingBean
private final KafkaSender kafkaReqProducer;
private final Map<SdfTable, Imdg<? extends WithAccount>> sdfImdgs;
private final Map<SdfTable, AbstractExecutor<? extends WithAccount>> executorsMap;
private final AccountBalanceService accountBalanceService;
@Autowired
public StatementService(Consumer<String, Object> kafkaQueue,
ImdgProvider imdgProvider,
KafkaSender kafkaReqProducer,
@Qualifier("sdfExecutors") Map<SdfTable, AbstractExecutor<? extends WithAccount>> executorsMap) {
@Qualifier("sdfExecutors") Map<SdfTable, AbstractExecutor<? extends WithAccount>> executorsMap, AccountBalanceService accountBalanceService) {
super(kafkaQueue);
this.imdgProvider = imdgProvider;
this.accountBalanceService = accountBalanceService;
this.sdfImdgs = new EnumMap<>(SdfTable.class);
this.kafkaReqProducer = kafkaReqProducer;
this.executorsMap = executorsMap;
@ -59,9 +62,16 @@ public class StatementService extends QueueConsumer implements InitializingBean
callback(StatementRequest.class)
.setConsumer(this::process)
.forDestination(Consts.STATEMENT_PROCESS, callbacks::put);
callback(AccountBalanceClearingRequest.class)
.setConsumer(this::accountBalanceClearingUpdate)
.forDestination(Consts.BALANCE_ACCOUNT_UPDATE, callbacks::put);
init();
}
private void accountBalanceClearingUpdate(BaseRequest<AccountBalanceClearingRequest> updateAccBalanceReq) {
accountBalanceService.updateAccountBalanceByClearing(updateAccBalanceReq.getRequestPayload());
}
private void process(BaseRequest<StatementRequest> systemRequest) {
StatementRequest statementRequest = systemRequest.getRequestPayload();
Collection<? extends WithAccount> sdfGroup;

View file

@ -12,15 +12,13 @@ import ru.spcex.clearing.service.order.ExecutionDepositSorter;
import ru.spcex.platform.enumeration.Allowed;
import ru.spcex.platform.enumeration.ClearingCategory;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgId;
import ru.spcex.platform.imdg.api.ImdgProvider;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.validation.IValidator;
import java.time.Instant;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.BiFunction;
@Service
@ -30,6 +28,13 @@ public class Clearing {
private final Imdg<ClearingMemberCategory> clearingCategoryImdg;
private final ExecutionDepositSorter sorter;
private final BiFunction<ClearingCategory, ExecutionDeposit, IValidator> validation;
private final ImdgId idProvider;
//при прохождении по выгруженным ExecutionDeposit, ошибочные статусы проставляются для
//контр сделок. В таком случае, в коллекции хранятся не синхронизированные с IMDG ExecutionDeposit
//для которых статус должен быть DENIED
//************ !!! NOT THREAD SAFE !!! ************
private final Set<Long> deniedIds = new HashSet<>();
private Long clearingSessionId;
@Autowired
public Clearing(ImdgProvider imdgProvider,
@ -37,11 +42,13 @@ public class Clearing {
BiFunction<ClearingCategory, ExecutionDeposit, IValidator> validation) {
this.executionDepositImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ExecutionDeposit, ExecutionDeposit.class);
this.clearingCategoryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ClearingMemberCategory, ClearingMemberCategory.class);
this.idProvider = imdgProvider.getImdgIdGenerator();
this.sorter = sorter;
this.validation = validation;
}
public void startClearing() {
clearingSessionId = idProvider.nextId();
//выгружаем ExecutionDeposit с пустым sessionId
Collection<ExecutionDeposit> execDeposits = executionDepositImdg.getCollectionObjectsBySQL("sessionId = null");
//делаем группировку ExecutionDeposit по категории и компании, делаем сортировку
@ -49,15 +56,29 @@ public class Clearing {
executionDeposits.forEach((category, companies) -> {
for (List<ExecutionDeposit> companyExecDeposits : companies) {
processSingleCompany(category, companyExecDeposits);
runChecksSetStatus(category, companyExecDeposits);
}
});
executionDeposits.forEach((category, companies) -> {
for (List<ExecutionDeposit> companyExecDeposits : companies) {
processAllowedExecutionDeposit(category, companyExecDeposits);
}
});
}
private void processSingleCompany(ClearingCategory category, List<ExecutionDeposit> executionDeposits) {
log.info("Start clearing for category: {}, company: {}", category, executionDeposits.get(0).getCompanyId());
//кладем в порядке I инициатор, V внутренний, B банк (ответная категория)
//получается I -> B; V -> B
//
//1) ТОЛЬКО ДЛЯ V: если DENIED по любым причинам то для всех следующих сделок этой компании DENIED
//2) DENIED сделка всегда встречная сделка тоже делается DENIED
//3) DENIED может быть для сделки которая уже прошла обработку и стала ALLOWED
private void runChecksSetStatus(ClearingCategory category, List<ExecutionDeposit> executionDeposits) {
log.info("setting statuses for category: {}, company: {}", category, executionDeposits.get(0).getCompanyId());
boolean financialError = false;
for (ExecutionDeposit execDeposit : executionDeposits) {
if (deniedIds.contains(execDeposit.getId())) {
continue;
}
if (financialError) {
updateDenied(execDeposit);
continue;
@ -80,6 +101,17 @@ public class Clearing {
execDeposit.setCoverageStatus(Allowed.DENIED.getKey());
execDeposit.setUpdated(Instant.now());
executionDepositImdg.update(execDeposit);
//также необходимо установить статус DEND встречной сделке контрагента этой компании, которая выбирается из executionDeposit по ключу:
//securityId И чтобы сделка была компании категории clearingMemberCategory.clearingMemberCategory той категории, сделка которой обрабатывается в настоящий момент.
ExecutionDeposit matchedExecDeposit = executionDepositImdg.getSingleObjectBySQL(
"securityId = " + execDeposit.getSecurityId()
+ " AND companyId != " + execDeposit.getCompanyId());
if (matchedExecDeposit != null) {
matchedExecDeposit.setCoverageStatus(Allowed.DENIED.getKey());
matchedExecDeposit.setUpdated(Instant.now());
executionDepositImdg.update(matchedExecDeposit);
deniedIds.add(matchedExecDeposit.getId());
}
}
private void updateAllowed(ExecutionDeposit execDeposit) {
@ -87,4 +119,17 @@ public class Clearing {
execDeposit.setUpdated(Instant.now());
executionDepositImdg.update(execDeposit);
}
private void processAllowedExecutionDeposit(ClearingCategory category, List<ExecutionDeposit> executionDeposits) {
log.info("running clearing for category: {}, company: {}", category, executionDeposits.get(0).getCompanyId());
for (ExecutionDeposit execDeposit : executionDeposits) {
if (deniedIds.contains(execDeposit.getId())) {
continue;
}
if (!Allowed.ALLOWED.getKey().equals(execDeposit.getCoverageStatus())) {
continue;
}
}
}
}

View file

@ -3,7 +3,7 @@ package ru.spcex.platform.enumeration;
import ru.spcex.platform.utils.enumeration.IEnumKey;
public enum AccountType implements IEnumKey {
Clrn("CLRN"), Bank("BANK"), Info("INFO");
Clrn("CLRN"), Bank("BANK"), Info("INFO"), Tran("TRAN"), Anlt("ANLT");
private final String key;

View file

@ -50,6 +50,7 @@ public interface Consts {
String EXPORT_PROCESS = "export-process";
String ACCOUNT_NEW = "account-new";
String BALANCE_ACCOUNT_NEW = "balance-account-new";
String BALANCE_ACCOUNT_UPDATE = "balance-account-update";
String LAUNCHER_NEW = "launcher-new";
String NOTIFICATION_NEW = "notification-new";

View file

@ -0,0 +1,38 @@
package ru.spcex.clearing.platform.messaging.domain.cud.balance;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
public class AccountBalanceClearingRequest {
@JsonProperty
private Long accountId;
@JsonProperty
private Long companyId;
@JsonProperty
private BigDecimal firstLegAmount;
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public Long getCompanyId() {
return companyId;
}
public void setCompanyId(Long companyId) {
this.companyId = companyId;
}
public BigDecimal getFirstLegAmount() {
return firstLegAmount;
}
public void setFirstLegAmount(BigDecimal firstLegAmount) {
this.firstLegAmount = firstLegAmount;
}
}