diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/company/CompanyInfoController.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/company/CompanyInfoController.java index 6ac88b427..02ef05d01 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/company/CompanyInfoController.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/queue/company/CompanyInfoController.java @@ -14,6 +14,7 @@ import ru.spcex.clearing.backendapi.controller.request.cud.company.CompanyInfoUp import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse; import ru.spcex.clearing.backendapi.controller.response.cud.CudResponse; import ru.spcex.clearing.backendapi.controller.response.entity.CommonGetAllResponse; +import ru.spcex.clearing.backendapi.controller.response.entity.company.CompanyInfoBackendGetAll; import ru.spcex.clearing.backendapi.meta.GetResponseFactory; import ru.spcex.clearing.backendapi.service.IOperator; import ru.spcex.clearing.imdg.IMDGDistributedNames; @@ -53,19 +54,14 @@ public class CompanyInfoController extends AbstractQueueController { } @ApiOperation(value = "get all CompanyInfo's.") - @ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = CommonGetAllResponse.class)}) + @ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = CompanyInfoBackendGetAll.class)}) @RequestMapping(method = RequestMethod.GET) @ResponseBody - public CommonGetAllResponse getAll() { + public CompanyInfoBackendGetAll getAll() { Collection> all = new ArrayList<>(); Collection companies = companyImdg.getAllValues(); - for (Company company : companies) { - if (company.getProfile() != null && company.getProfile().getId() != null) { // пока возвращает "пустой" CompanyInfo если его нет для Company - all.add(responseFactory.responseFromObject(company.getProfile())); - } - } - CommonGetAllResponse response = new CommonGetAllResponse(); - response.fromEntity(all); + CompanyInfoBackendGetAll response = new CompanyInfoBackendGetAll(); + response.fromEntity(companies); return response; } } diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/company/CompanyInfoBackendGetAll.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/company/CompanyInfoBackendGetAll.java new file mode 100644 index 000000000..e134eb83a --- /dev/null +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/company/CompanyInfoBackendGetAll.java @@ -0,0 +1,48 @@ +package ru.spcex.clearing.backendapi.controller.response.entity.company; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import ru.clearing.classes.statics.data.company.Company; +import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@ApiModel(description = "Ответ при получении объектов CompanyInfos.") +public class CompanyInfoBackendGetAll extends BasicSpcexResponse { + + @JsonProperty + @ApiModelProperty(value = "Полезная нагрузка") + private CompanyInfoBackendPayload payload = new CompanyInfoBackendPayload(); + + private static class CompanyInfoBackendPayload { + private List items = new ArrayList<>(); + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + } + + public CompanyInfoBackendPayload getPayload() { + return payload; + } + + public void setPayload(CompanyInfoBackendPayload payload) { + this.payload = payload; + } + + public void fromEntity(Collection companys) { + var payload = this.getPayload(); + for (var company : companys) { + var singleItem = CompanyInfoBackendGetFields.fromEntity(company); + payload.getItems().add(singleItem); + } + } + +} \ No newline at end of file diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/company/CompanyInfoBackendGetFields.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/company/CompanyInfoBackendGetFields.java new file mode 100644 index 000000000..4e039a019 --- /dev/null +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/company/CompanyInfoBackendGetFields.java @@ -0,0 +1,224 @@ +package ru.spcex.clearing.backendapi.controller.response.entity.company; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.profile.CompanyInfo; + +public class CompanyInfoBackendGetFields { + @JsonProperty + @ApiModelProperty(value = "Идентификатор записи") + private Long id; + @JsonProperty + @ApiModelProperty(value = "Идентификатор компании") + private Long companyId; + @JsonProperty + @ApiModelProperty(value = "Код единоличного исполнительного органа") + private String corporationSoleType; + @JsonProperty + @ApiModelProperty(value = "Код юрисдикции") + private String countryCode; + @JsonProperty + @ApiModelProperty(value = "Описание компании") + private String description; + @JsonProperty + @ApiModelProperty(value = "Код признака профессионального участника") + private String professionalSign; + @JsonProperty + @ApiModelProperty(value = "Код вида субъекта") + private String legalKind; + @JsonProperty + @ApiModelProperty(value = "Код типа организации") + private String organizationType; + @JsonProperty + @ApiModelProperty(value = "Код резиденции") + private String residence; + @JsonProperty + @ApiModelProperty(value = "Краткое наименование компании") + private String shortName; + @JsonProperty + @ApiModelProperty(value = "Полное наименование компании") + private String fullName; + @JsonProperty + @ApiModelProperty(value = "Краткое наименование компании на английском") + private String shortNameEng; + @JsonProperty + @ApiModelProperty(value = "Краткое наименование компании на английском") + private String fullNameEng; + @JsonProperty + @ApiModelProperty(value = "Код участника торгов") + private String tradingCode; + @JsonProperty + @ApiModelProperty(value = "Код участника клиринга") + private String clearingCode; + @JsonProperty + @ApiModelProperty(value = "Регистрационный код участника") + private String registrationCode; + @JsonProperty + @ApiModelProperty(value = "Наименование статуса") + private String workflowStatus; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getCorporationSoleType() { + return corporationSoleType; + } + + public void setCorporationSoleType(String corporationSoleType) { + this.corporationSoleType = corporationSoleType; + } + + public String getCountryCode() { + return countryCode; + } + + public void setCountryCode(String countryCode) { + this.countryCode = countryCode; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getProfessionalSign() { + return professionalSign; + } + + public void setProfessionalSign(String professionalSign) { + this.professionalSign = professionalSign; + } + + public String getLegalKind() { + return legalKind; + } + + public void setLegalKind(String legalKind) { + this.legalKind = legalKind; + } + + public String getOrganizationType() { + return organizationType; + } + + public void setOrganizationType(String organizationType) { + this.organizationType = organizationType; + } + + public String getResidence() { + return residence; + } + + public void setResidence(String residence) { + this.residence = residence; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getShortNameEng() { + return shortNameEng; + } + + public void setShortNameEng(String shortNameEng) { + this.shortNameEng = shortNameEng; + } + + public String getFullNameEng() { + return fullNameEng; + } + + public void setFullNameEng(String fullNameEng) { + this.fullNameEng = fullNameEng; + } + + public String getTradingCode() { + return tradingCode; + } + + public void setTradingCode(String tradingCode) { + this.tradingCode = tradingCode; + } + + public String getClearingCode() { + return clearingCode; + } + + public void setClearingCode(String clearingCode) { + this.clearingCode = clearingCode; + } + + public String getRegistrationCode() { + return registrationCode; + } + + public void setRegistrationCode(String registrationCode) { + this.registrationCode = registrationCode; + } + + public String getWorkflowStatus() { + return workflowStatus; + } + + public void setWorkflowStatus(String workflowStatus) { + this.workflowStatus = workflowStatus; + } + + public static CompanyInfoBackendGetFields fromEntity(Company company) { + if (company == null || company.getProfile() == null) { + return null; + } + CompanyInfo info = company.getProfile(); + CompanyInfoBackendGetFields fields = new CompanyInfoBackendGetFields(); + fields.setId(info.getId()); + fields.setCompanyId(info.getCompanyId()); + fields.setCorporationSoleType(info.getCorporationSoleType()); + fields.setCountryCode(info.getCountryCode()); + fields.setDescription(info.getDescription()); + fields.setProfessionalSign(info.getProfessionalSign()); + fields.setLegalKind(info.getLegalKind()); + fields.setOrganizationType(info.getOrganizationType()); + fields.setResidence(info.getResidence()); + fields.setShortNameEng(info.getShortNameEng()); + fields.setFullNameEng(info.getFullNameEng()); + + fields.setShortName(company.getShortName()); + fields.setFullName(company.getFullName()); + fields.setTradingCode(company.getTradingCode()); + fields.setClearingCode(company.getClearingCode()); + fields.setRegistrationCode(company.getRegistrationCode()); + fields.setWorkflowStatus(company.getWorkflowStatus()); + + return fields; + } +} diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/registry/TradingClearingRegistryBackendGetFields.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/registry/TradingClearingRegistryBackendGetFields.java index 60495a336..d4822d731 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/registry/TradingClearingRegistryBackendGetFields.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/response/entity/registry/TradingClearingRegistryBackendGetFields.java @@ -36,10 +36,12 @@ public class TradingClearingRegistryBackendGetFields { @ApiModelProperty(value = "Наименование статуса", example = "ACTV") private String status; - @JsonProperty("Дата-время создания записи") + @JsonProperty + @ApiModelProperty(value = "Дата-время создания записи") @JsonSerialize(using = InstantSerializer.class) private Instant createdAt; - @JsonProperty("Дата-время изменения записи") + @JsonProperty + @ApiModelProperty(value = "Дата-время изменения записи") @JsonSerialize(using = InstantSerializer.class) private Instant updatedAt; diff --git a/clearing-parent/backend-api/src/main/resources/meta/data.xml b/clearing-parent/backend-api/src/main/resources/meta/data.xml index f3efd0b8d..e6c737fd9 100644 --- a/clearing-parent/backend-api/src/main/resources/meta/data.xml +++ b/clearing-parent/backend-api/src/main/resources/meta/data.xml @@ -80,7 +80,7 @@ - + @@ -198,7 +198,7 @@ - + diff --git a/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/IMDGMessageResolver.java b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/IMDGMessageResolver.java index 97dfbf8cf..3d64121d6 100644 --- a/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/IMDGMessageResolver.java +++ b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/IMDGMessageResolver.java @@ -1,5 +1,6 @@ package ru.spcex.clearing.util.services; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.clearing.platform.dictionary.ErrorCodeDictionary; @@ -25,13 +26,17 @@ public class IMDGMessageResolver implements IMessageResolver { @Override public String resolve(EnumMessage errorMessage) { if (errorMessage == null) return "null"; - - ErrorCodeDictionary errorDictionary = errorCodeDictionaryIMDG.getSingleObjectByID(errorMessage.getSubject().getId()); - if (errorDictionary == null) { - log.warn("ERROR_CODE_DICTIONARY not found fo id={}", errorMessage.getSubject().getId()); + try { + ErrorCodeDictionary errorDictionary = errorCodeDictionaryIMDG.getSingleObjectByID(errorMessage.getSubject().getId()); + if (errorDictionary == null) { + log.warn("ERROR_CODE_DICTIONARY not found fo id={}", errorMessage.getSubject().getId()); + return String.format("(%d) args %s", errorMessage.getSubject().getId(), Arrays.toString(errorMessage.getArgs())); + } + String textTemplate = errorDictionary.getName(); + return String.format(textTemplate, errorMessage.getArgs()); + } catch (Exception errFormatting) { // MissingFormatArgumentException + log.warn("Error in message resolver for error {}: {}", errorMessage.getSubject(), ExceptionUtils.getStackTrace(errFormatting)); return String.format("(%d) args %s", errorMessage.getSubject().getId(), Arrays.toString(errorMessage.getArgs())); } - String textTemplate = errorDictionary.getName(); - return String.format(textTemplate, errorMessage.getArgs()); } } diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/util/RequestHelper.java b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/RequestHelper.java similarity index 84% rename from clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/util/RequestHelper.java rename to clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/RequestHelper.java index f134ec949..04decd8a7 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/util/RequestHelper.java +++ b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/RequestHelper.java @@ -1,8 +1,7 @@ -package ru.spcex.clearing.company.util; +package ru.spcex.clearing.util.services; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ru.spcex.clearing.company.error.CompanyErrors; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; import ru.spcex.clearing.platform.messaging.service.Status; @@ -22,21 +21,28 @@ import java.util.Objects; * callback(CompanyNewRequest.class) * .setFunction( request -> requestHelper.requestFunction(this::createCompany, request)) * .forDestination(Consts.DESTINATION_COMPANY_NEW, callbacks::put); + * + * return requestHelper.makeErrorResponse(CompanyError.GeneralError, ...) */ public class RequestHelper { protected Logger log = LoggerFactory.getLogger(getClass()); protected final IMessageResolver messageResolver; + protected final IErrorEnumId generalError; - public RequestHelper(IMessageResolver messageResolver) { + public RequestHelper(IMessageResolver messageResolver, IErrorEnumId generalError) { Objects.requireNonNull(messageResolver); this.messageResolver = messageResolver; + this.generalError = generalError; + Objects.requireNonNull(generalError, "Required generalError enum"); } - public RequestHelper(Logger log, IMessageResolver messageResolver) { + public RequestHelper(Logger log, IMessageResolver messageResolver, IErrorEnumId generalError) { Objects.requireNonNull(log); Objects.requireNonNull(messageResolver); this.log = log; this.messageResolver = messageResolver; + this.generalError = generalError; + Objects.requireNonNull(generalError, "Required generalError enum"); } /** @@ -58,13 +64,14 @@ public class RequestHelper { String message = expectedE.getEnumMsg() == null ? expectedE.getMessage() : messageResolver.resolve(expectedE.getEnumMsg()); + log.info("Response: {}", message); return new RequestInfoUpdate() .setId(userRequest.getId()) .setStatus(Status.Error) .setMessage(message); } catch (Exception unexpectedE) { log.error("Error at {}: {} ", command, ExceptionUtils.getStackTrace(unexpectedE)); - String message = messageResolver.resolve(new EnumMessage(CompanyErrors.GeneralError)); + String message = messageResolver.resolve(new EnumMessage(generalError)); return new RequestInfoUpdate() .setId(userRequest.getId()) .setStatus(Status.Error) @@ -74,6 +81,7 @@ public class RequestHelper { public RequestInfoUpdate makeErrorResponse(BaseRequest request, EnumMessage msg) { String errorMsg = msg == null? "" : messageResolver.resolve(msg); + log.info("Response: {}", errorMsg); return new RequestInfoUpdate() .setId(request.getId()) .setStatus(Status.Error) diff --git a/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/exchangers/BiDirectionQueueExchanger.java b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/exchangers/BiDirectionQueueExchanger.java index f8aef7bf6..b90b1993e 100644 --- a/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/exchangers/BiDirectionQueueExchanger.java +++ b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/util/services/exchangers/BiDirectionQueueExchanger.java @@ -1,66 +1,216 @@ package ru.spcex.clearing.util.services.exchangers; +import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import ru.spcex.clearing.platform.messaging.domain.ActionType; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; +import ru.spcex.clearing.platform.messaging.domain.Consts; +import ru.spcex.clearing.platform.messaging.domain.cud.balance.AccountBalanceClearingRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.clearing.Sdf04Request; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonIdRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.schedule.LauncherCommandRequest; import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; +import ru.spcex.clearing.platform.messaging.service.Status; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.platform.enumeration.Task; +import ru.spcex.platform.utils.log.ExceptionUtils; + +import java.io.Closeable; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; /** * Синхронный обмен сообщениями с ассинхронным сервисом. - * Метод exchange() отправляет сообщение в очередь и дожидается ответа из другой очереди + * Метод exchange() отправляет сообщение в очередь и дожидается ответа из другой очереди. + * Использовать с осторожностью, т.к. нет возобновления ожидания в случае нештатного выключения модуля при ожидании ответа из kafka. + *

+ *

+ * Ожидает из очереди (CommonIdRequest) + * + * @param отправляется в очередь */ -public class BiDirectionQueueExchanger> extends QueueConsumer implements InitializingBean, DisposableBean { +public class BiDirectionQueueExchanger> extends QueueConsumer implements Closeable { protected final Logger log = LoggerFactory.getLogger(getClass()); - protected final Object sync = new Object(); protected String outQueue; protected String inQueue; - protected Class listenClass; protected long timeout; + private Producer producer; + + protected volatile boolean isTerminated; + protected Object syncObject; + protected Long lastSentRequestId; + protected volatile Long lastResponseId; + protected boolean ignoreOtherResponse = true; // Пропускать другие ID, пока не получит lastResponseId==lastSentRequestId /** * Синхронно-ассинхронный обмен сообщениями * - * @param kafkaQueue + * @param kafkaQueue уникальный kafka Consumer (Spring prototype). Нельзя переиспользовать существующие. * @param kafkaProducer * @param outQueue отправляет в очередь - * @param inQueue слушает очередь, ожидает ответов - * @param listenClass типы объектов из inQueue - * @param timeout - максимальное ожидание ответа, в миллисекундах, 0 - неограничено + * @param inQueue слушает очередь/топик, ожидает ответов. Пример: Consts.CONTINUE_CLEARING + * @param ignoreOtherResponse true для inQueue=Consts.REQUEST_INFO_UPDATE + * @param timeout - максимальное ожидание ответа, в миллисекундах, 0 - неограничено */ public BiDirectionQueueExchanger(Consumer kafkaQueue, Producer kafkaProducer, String outQueue, - String inQueue, Class listenClass, + String inQueue, + boolean ignoreOtherResponse, long timeout) { - super(kafkaQueue, kafkaProducer); + super(kafkaQueue); + this.producer = kafkaProducer; + if (kafkaQueue == null) { + throw new IllegalArgumentException("kafkaQueue is empty"); + } + if (kafkaProducer == null) { + throw new IllegalArgumentException("kafkaProducer is empty"); + } + if (StringUtils.isEmpty(outQueue)) { + throw new IllegalArgumentException("outQueue is empty"); + } this.outQueue = outQueue; + if (StringUtils.isEmpty(inQueue)) { + throw new IllegalArgumentException("inQueue is empty"); + } this.inQueue = inQueue; - this.listenClass = listenClass; + if (timeout < 0) { + timeout = 0; + } this.timeout = timeout; + this.ignoreOtherResponse = ignoreOtherResponse; + + initReplyListener(); } /** * Отправить сообщение message в outQueue и дождаться ответа из очереди inQueue * * @param message - * @return - * @throws InterruptedException + * @return request/reply id + * @throws InterruptedException или когда поток прерывают, или когда BiDirectionQueueExchanger.close() */ - public TOut exchange(TIn message) throws InterruptedException { - //todo impl BiDirectionQueueExchanger - return null; + public Long exchange(TOut message) throws InterruptedException { + if (syncObject == null) + throw new IllegalStateException("Listener queue not initialized"); + if (message.getId() == null) { + throw new IllegalArgumentException("Required message " + message.getClass().getSimpleName() + ".id is null"); + } + + //send request to kafka, wait for a reply + try { + synchronized (syncObject) { + Long sentRequestId = sendMessage(message); + log.trace("sent request to kafka, requestId: {}", sentRequestId); + this.lastSentRequestId = sentRequestId; + + long waitStart = System.currentTimeMillis(); + long waitTime = timeout; + do { + try { + if (timeout > 0) { + syncObject.wait(waitTime); + } else { + syncObject.wait(); + } + //если на этом месте произошла ошибка - непонятно как обрабатывать + //т.к. request на самом деле могла быть проблема с сетью/недоступностью кафки etc. + } catch (InterruptedException e) { + log.error(ExceptionUtils.getStackTrace(e)); + throw e; +// Thread.currentThread().interrupt(); +// throw new RuntimeException(e); + } + waitTime = timeout - (System.currentTimeMillis() - waitStart); + if (isTerminated && lastResponseId == null) { + log.warn("Terminated waiter {} kafka at thread {}", this, Thread.currentThread().getName()); + throw new InterruptedException(getClass().getSimpleName() + " closed"); + } + if (ignoreOtherResponse && !sentRequestId.equals(lastResponseId)) { + log.debug("Ignore response id={}, we waiting {}.", lastResponseId, sentRequestId); + } + } while (ignoreOtherResponse && !sentRequestId.equals(lastResponseId) && (timeout > 0 && waitTime > 0)); + if (!sentRequestId.equals(lastResponseId)) { + log.error("FATAL: last response from {} update from Kafka ID was {}, but sent ID was {}", + inQueue, lastResponseId, sentRequestId); + throw new IllegalStateException("Expected wait till requestId=" + sentRequestId + " but catch lastResponseId=" + lastResponseId); + } + return sentRequestId; + + } + } finally { + this.lastSentRequestId = null; + } } @Override - public void destroy() throws Exception { + public void close() { + isTerminated = true; + if (lastSentRequestId != null) { + log.warn("Finished before wait, lastSentRequestId={}", lastSentRequestId); + if (syncObject != null) { + synchronized (syncObject) { + lastResponseId = null; + syncObject.notifyAll(); + } + } + } + syncObject = null; log.debug("Listener {} for async exchange {}-{} close", this, outQueue, inQueue); } - @Override - public void afterPropertiesSet() throws Exception { + protected void initReplyListener() { + if (syncObject != null) { + throw new IllegalStateException("Already initialized"); + } + callback(CommonIdRequest.class) + .setConsumer(this::continueWaiting) + .forDestination(inQueue, callbacks::put); + init(); + + syncObject = new Object(); + isTerminated = false; + log.debug("Listener {} for async exchange {}-{} ready", this, outQueue, inQueue); } + + protected Long sendMessage(TOut message) throws InterruptedException { + Long sentRequestId = message.getId(); + if (sentRequestId == null) { + throw new IllegalArgumentException("Message " + message.getClass().getSimpleName() + " required id."); + } + try { + Future send = producer.send(new ProducerRecord<>(Consts.REQUEST_INFO_UPDATE, message)); + send.get(); + return sentRequestId; + } catch (InterruptedException e) { + log.error("Interrupt when send message, {}", ExceptionUtils.getStackTrace(e)); + Thread.currentThread().interrupt(); + throw e; + } catch (ExecutionException e) { + log.error("Error at send message, {} cause: {}", e.toString(), ExceptionUtils.getStackTrace(e.getCause())); + throw new RuntimeException("Send message error", e.getCause()); + } catch (Exception e) { + log.error("Unexpected exception when send message: {}", ExceptionUtils.getStackTrace(e)); + } + return null; + } + + protected void continueWaiting(BaseRequest event) { + synchronized (syncObject) { + CommonIdRequest requestPayload = event.getRequestPayload(); + log.debug("continueWaiting {} for id={}", inQueue, requestPayload.getId()); + lastResponseId = requestPayload.getId(); + syncObject.notifyAll(); + } + } } diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/BeanConfiguration.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/BeanConfiguration.java index d6248d708..fd44e1d0e 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/BeanConfiguration.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/BeanConfiguration.java @@ -5,7 +5,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import ru.spcex.clearing.company.error.CompanyErrors; -import ru.spcex.clearing.company.util.RequestHelper; +import ru.spcex.clearing.util.services.RequestHelper; import ru.spcex.clearing.util.security.UserRoleVerification; import ru.spcex.platform.enumeration.UserRole; import ru.spcex.platform.imdg.api.ImdgProvider; @@ -22,7 +22,7 @@ public class BeanConfiguration { @Bean @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public RequestHelper requestHelper(IMessageResolver messageResolver) { - return new RequestHelper(messageResolver); + return new RequestHelper(messageResolver, CompanyErrors.GeneralError); } @Bean diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ClientCodeValidationConfig.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ClientCodeValidationConfig.java new file mode 100644 index 000000000..c3b2776ae --- /dev/null +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ClientCodeValidationConfig.java @@ -0,0 +1,162 @@ +package ru.spcex.clearing.company.config.validation; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.account.ClientCode; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; +import ru.clearing.platform.dictionary.WorkflowStatusDictionary; +import ru.spcex.clearing.company.error.CompanyErrors; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.cud.account.ClientCodeNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.account.ClientCodeUpdateRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.validation.common.rules.DictionaryPresentRule; +import ru.spcex.clearing.validation.common.rules.FieldRequiredRule; +import ru.spcex.clearing.validation.common.rules.IdPresentRule; +import ru.spcex.platform.classes.base.SpcexObjectBase; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.validation.ImdgValidationContext; +import ru.spcex.platform.utils.validation.IValidator; +import ru.spcex.platform.utils.validation.ValidatorImpl; + +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +@Configuration +public class ClientCodeValidationConfig { + + @Bean("clientCodeNewRequestValidator") + public Function clientCodeNewRequestValidator(Map> imdgForValidation) { + return clientCodeUpdateRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(clientCodeUpdateRequest); + Consumer addImdg = (s) -> context.addImdg(s, imdgForValidation.get(s)); + addImdg.accept(IMDGDistributedNames.Map_Company); + addImdg.accept(IMDGDistributedNames.Map_Account); + addImdg.accept(IMDGDistributedNames.Map_TradingClearingRegistry); + addImdg.accept(IMDGDistributedNames.Map_WorkflowStatusDictionary); + return new ValidatorImpl<>(context, + FieldRequiredRule.instance("companyId", ClientCodeNewRequest::getCompanyId, CompanyErrors.RequiredFieldEmpty), + IdPresentRule.instance("companyId", + ClientCodeNewRequest::getCompanyId, + IMDGDistributedNames.Map_Company, + Company.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.CompanyNotFound), + + IdPresentRule.instance("moneyAccountId", + ClientCodeNewRequest::getMoneyAccountId, + IMDGDistributedNames.Map_Account, + Account.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.AccountNotFound, + false, + acc -> Objects.equals(context.getValidatedObject().getCompanyId(), acc.getCompanyId()) + ? null : CompanyErrors.AccountNotFound + ), + IdPresentRule.instance("depoAccountId", + ClientCodeNewRequest::getDepoAccountId, + IMDGDistributedNames.Map_Account, + Account.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.AccountNotFound, + false, + acc -> Objects.equals(context.getValidatedObject().getCompanyId(), acc.getCompanyId()) + ? null : CompanyErrors.AccountNotFound + ), + + + DictionaryPresentRule.instance("stauts", + ClientCodeNewRequest::getStatus, + IMDGDistributedNames.Map_WorkflowStatusDictionary, + WorkflowStatusDictionary.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.DictionaryNotFound, + false) + ); + }; + } + + @Bean("clientCodeUpdateRequestValidator") + public Function clientCodeUpdateRequestValidator(Map> imdgForValidation) { + return clientCodeUpdateRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(clientCodeUpdateRequest); + Consumer addImdg = (s) -> context.addImdg(s, imdgForValidation.get(s)); + addImdg.accept(IMDGDistributedNames.Map_ClientCode); + addImdg.accept(IMDGDistributedNames.Map_Company); + addImdg.accept(IMDGDistributedNames.Map_Account); + addImdg.accept(IMDGDistributedNames.Map_TradingClearingRegistry); + addImdg.accept(IMDGDistributedNames.Map_WorkflowStatusDictionary); + return new ValidatorImpl>(context, + IdPresentRule.instance("id", + ClientCodeUpdateRequest::getId, + IMDGDistributedNames.Map_ClientCode, + ClientCode.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.RecordNotFound + ), + + FieldRequiredRule.instance("companyId", ClientCodeUpdateRequest::getCompanyId, CompanyErrors.RequiredFieldEmpty), + IdPresentRule.instance("companyId", + ClientCodeUpdateRequest::getCompanyId, + IMDGDistributedNames.Map_Company, + Company.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.CompanyNotFound), + + IdPresentRule.instance("moneyAccountId", + ClientCodeUpdateRequest::getMoneyAccountId, + IMDGDistributedNames.Map_Account, + Account.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.AccountNotFound, + false, + acc -> Objects.equals(context.getValidatedObject().getCompanyId(), acc.getCompanyId()) + ? null : CompanyErrors.AccountNotFound + ), + IdPresentRule.instance("depoAccountId", + ClientCodeUpdateRequest::getDepoAccountId, + IMDGDistributedNames.Map_Account, + Account.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.AccountNotFound, + false, + acc -> Objects.equals(context.getValidatedObject().getCompanyId(), acc.getCompanyId()) + ? null : CompanyErrors.AccountNotFound + ), + + DictionaryPresentRule.instance("stauts", + ClientCodeUpdateRequest::getStatus, + IMDGDistributedNames.Map_WorkflowStatusDictionary, + WorkflowStatusDictionary.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.DictionaryNotFound, + false) + ); + }; + } + + @Bean("clientCodeDeleteRequestValidator") + public Function clientCodeDeleteRequestValidator(Map> imdgForValidation) { + return companyDeleteRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(companyDeleteRequest); + Consumer addImdg = (s) -> context.addImdg(s, imdgForValidation.get(s)); + addImdg.accept(IMDGDistributedNames.Map_ClientCode); + return new ValidatorImpl<>(context, +// FieldRequiredRule.instance("id", CommonDeleteRequest::getId, CompanyErrors.RequiredFieldEmpty), + IdPresentRule.instance("id", + CommonDeleteRequest::getId, + IMDGDistributedNames.Map_ClientCode, + ClientCode.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.RecordNotFound) + ); + }; + } +} diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ContactValidationConfig.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ContactValidationConfig.java index f92635d78..a69e7731c 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ContactValidationConfig.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ContactValidationConfig.java @@ -73,7 +73,7 @@ public class ContactValidationConfig { IMDGDistributedNames.Map_Contact, Contact.class, CompanyErrors.RequiredFieldEmpty, - CompanyErrors.CompanyNotFound, + CompanyErrors.ContactNotFound, contact -> { if (contactUpdateRequest.getContactType() == null) return null; if (!contactUpdateRequest.getContactType().equalsIgnoreCase(contact.getContactType())) { diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ProfileDocumentValidationConfig.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ProfileDocumentValidationConfig.java index 9fe82a927..5449f8f9e 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ProfileDocumentValidationConfig.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ProfileDocumentValidationConfig.java @@ -44,7 +44,7 @@ public class ProfileDocumentValidationConfig { Company.class, CompanyErrors.RequiredFieldEmpty, CompanyErrors.CompanyNotFound, - company -> WorkflowStatus.Active.getKey().equals(company.getWorkflowStatus()) ? null : CompanyErrors.CompanyDisabled), + company -> WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()) ? null : CompanyErrors.CompanyDisabled), DictionaryPresentRule.instance("documentType", ProfileDocumentNewRequest::getDocumentType, IMDGDistributedNames.Map_DocumentTypeDictionary, @@ -138,7 +138,15 @@ public class ProfileDocumentValidationConfig { IMDGDistributedNames.Map_ProfileDocument, ProfileDocument.class, CompanyErrors.RequiredFieldEmpty, - CompanyErrors.RecordNotFound) + CompanyErrors.RecordNotFound, + profileDoc -> { + Imdg companyImdg = context.obtainMap(IMDGDistributedNames.Map_Company, Company.class); + Company company = companyImdg.getSingleObjectByID(profileDoc.getCompanyId()); + if (company == null) { + return CompanyErrors.CompanyNotFound; + } + return null; + }) ); }; } diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/RelationValidationConfig.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/RelationValidationConfig.java new file mode 100644 index 000000000..959f62491 --- /dev/null +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/RelationValidationConfig.java @@ -0,0 +1,134 @@ +package ru.spcex.clearing.company.config.validation; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.company.relation.Relation; +import ru.clearing.platform.dictionary.ClearingCategoryDictionary; +import ru.clearing.platform.dictionary.WorkflowStatusDictionary; +import ru.spcex.clearing.company.error.CompanyErrors; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.company.RelationNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.company.RelationUpdateRequest; +import ru.spcex.clearing.validation.common.rules.DictionaryPresentRule; +import ru.spcex.clearing.validation.common.rules.IdPresentRule; +import ru.spcex.platform.classes.base.SpcexObjectBase; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.validation.ImdgValidationContext; +import ru.spcex.platform.utils.validation.IValidator; +import ru.spcex.platform.utils.validation.ValidatorImpl; + +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +@Configuration +public class RelationValidationConfig { + + @Bean("relationNewRequestValidation") + public Function relationNewRequestValidator( + Map> imdgForValidation + ) { + return relationNewRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(relationNewRequest); + Consumer addImdg = (s) -> context.addImdg(s, imdgForValidation.get(s)); + addImdg.accept(IMDGDistributedNames.Map_Company); + addImdg.accept(IMDGDistributedNames.Map_ClearingCategoryDictionary); + addImdg.accept(IMDGDistributedNames.Map_WorkflowStatusDictionary); + return new ValidatorImpl<>(context, + IdPresentRule.instance("companyId", + RelationNewRequest::getCompanyId, + IMDGDistributedNames.Map_Company, + Company.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.CompanyNotFound), + DictionaryPresentRule.instance("documentType", + RelationNewRequest::getClearingMemberCategory, + IMDGDistributedNames.Map_ClearingCategoryDictionary, + ClearingCategoryDictionary.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.WrongFieldValue), + DictionaryPresentRule.instance("serviceStatus", + RelationNewRequest::getServiceStatus, + IMDGDistributedNames.Map_WorkflowStatusDictionary, + WorkflowStatusDictionary.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.WrongFieldValue) + ); + }; + } + + @Bean("relationUpdateRequestValidation") + public Function relationUpdateRequestValidator( + Map> imdgForValidation + ) { + return relationUpdateRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(relationUpdateRequest); + Consumer addImdg = (s) -> context.addImdg(s, imdgForValidation.get(s)); + addImdg.accept(IMDGDistributedNames.Map_Relation); + addImdg.accept(IMDGDistributedNames.Map_Company); +// addImdg.accept(IMDGDistributedNames.Map_ClearingCategoryDictionary); + addImdg.accept(IMDGDistributedNames.Map_WorkflowStatusDictionary); + return new ValidatorImpl<>(context, + IdPresentRule.instance("id", + RelationUpdateRequest::getId, + IMDGDistributedNames.Map_Relation, + Relation.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.RelationNotFound, + relation -> { + if (relation.getConsumerId() == null) return null; + Imdg companyImdg = context.obtainMap(IMDGDistributedNames.Map_Company, Company.class); + Company company = companyImdg.getSingleObjectByID(relation.getConsumerId()); + if (company == null) { + return CompanyErrors.CompanyNotFound; + } else { + return null; + } + }), +// IdPresentRule.instance("companyId", +// RelationUpdateRequest::getCompanyId, +// IMDGDistributedNames.Map_Company, +// Company.class, +// CompanyErrors.RequiredFieldEmpty, +// CompanyErrors.CompanyNotFound), +// DictionaryPresentRule.instance("documentType", +// RelationUpdateRequest::getClearingMemberCategory, +// IMDGDistributedNames.Map_ClearingCategoryDictionary, +// ClearingCategoryDictionary.class, +// CompanyErrors.RequiredFieldEmpty, +// CompanyErrors.WrongFieldValue), + DictionaryPresentRule.instance("serviceStatus", + RelationUpdateRequest::getServiceStatus, + IMDGDistributedNames.Map_WorkflowStatusDictionary, + WorkflowStatusDictionary.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.WrongFieldValue) + ); + }; + } + + @Bean("relationDeleteRequestValidation") + public Function relationDeleteRequestValidator( + Map> imdgForValidation + ) { + return relationDeleteRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(relationDeleteRequest); + Consumer addImdg = (s) -> context.addImdg(s, imdgForValidation.get(s)); + addImdg.accept(IMDGDistributedNames.Map_Relation); + return new ValidatorImpl<>(context, + IdPresentRule.instance("id", + CommonDeleteRequest::getId, + IMDGDistributedNames.Map_Relation, + Relation.class, + CompanyErrors.RequiredFieldEmpty, + CompanyErrors.RelationNotFound + ) + ); + }; + } +} diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ValidationConfig.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ValidationConfig.java index 597365270..c586e2fd7 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ValidationConfig.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/config/validation/ValidationConfig.java @@ -2,11 +2,15 @@ package ru.spcex.clearing.company.config.validation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.account.ClientCode; import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.company.CompanySymbols; import ru.clearing.classes.statics.data.company.ClearingMemberCategory; +import ru.clearing.classes.statics.data.company.relation.Relation; import ru.clearing.classes.statics.data.profile.Contact; import ru.clearing.classes.statics.data.profile.ProfileDocument; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; import ru.clearing.platform.dictionary.*; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.validation.common.ValidationHelper; @@ -34,6 +38,7 @@ public class ValidationConfig { addImdg.accept(IMDGDistributedNames.Map_DocumentTypeDictionary, DocumentTypeDictionary.class); addImdg.accept(IMDGDistributedNames.Map_ContactTypeDictionary, ContactTypeDictionary.class); addImdg.accept(IMDGDistributedNames.Map_Contact, Contact.class); + addImdg.accept(IMDGDistributedNames.Map_Relation, Relation.class); addImdg.accept(IMDGDistributedNames.Map_WorkflowStatusDictionary, WorkflowStatusDictionary.class); addImdg.accept(IMDGDistributedNames.Map_CompanySymbols, CompanySymbols.class); @@ -44,6 +49,10 @@ public class ValidationConfig { addImdg.accept(IMDGDistributedNames.Map_LegalKindDictionary, LegalKindDictionary.class); addImdg.accept(IMDGDistributedNames.Map_OrganizationTypeDictionary, OrganizationTypeDictionary.class); + addImdg.accept(IMDGDistributedNames.Map_ClientCode, ClientCode.class); + addImdg.accept(IMDGDistributedNames.Map_Account, Account.class); + addImdg.accept(IMDGDistributedNames.Map_TradingClearingRegistry, TradingClearingRegistry.class); + return imdg; } diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/error/CompanyErrors.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/error/CompanyErrors.java index d36d2104f..1c5b42c49 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/error/CompanyErrors.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/error/CompanyErrors.java @@ -22,7 +22,12 @@ public enum CompanyErrors implements IErrorEnumId { CompanyAlreadyHasLiabilities(3018L), // У компании %s присутствуют обязательства. CompanyWithCompanySymbolAlreadyExist(3019L), // Компания с %s = %s уже создана" (где первый %s - companySymbol, второй %s - companySymbolValue) EditCompanySymbols(3020L), // Тип реквизита компании %s не может быть изменен. - EditContactType(3021L) // Тип контакта компании %s не может быть изменен. + EditContactType(3021L), // Тип контакта компании %s не может быть изменен. + TradingClearingRegistryNotFound(3022L), // ТКР с %s не найден + AccountNotFound(3023L), // Счет %s не найден + ClearingMemberCategoryOnMKRAlreadyExist(3024L), // Договорные отношения %s в секции МКР уже созданы. + ClearingMemberCategoryOnFONDAlreadyExist(3025L), // Договорные отношения %s на фондовой секции уже созданы. + RelationNotFound(3026L), // Запись о договорных отношениях не найдена ; private final Long id; diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/ClientCodeService.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/ClientCodeService.java new file mode 100644 index 000000000..183e6da6d --- /dev/null +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/ClientCodeService.java @@ -0,0 +1,413 @@ +package ru.spcex.clearing.company.service; + +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import ru.clearing.classes.statics.data.account.ClientCode; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; +import ru.spcex.clearing.company.error.CompanyErrors; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.ActionType; +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.ClientCodeNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.account.ClientCodeUpdateRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.registry.TradingClearingRegistryNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.registry.TradingClearingRegistryUpdateRequest; +import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.clearing.util.security.UserRoleVerification; +import ru.spcex.clearing.util.services.exchangers.BiDirectionQueueExchanger; +import ru.spcex.clearing.validation.common.ValidationHelper; +import ru.spcex.platform.enumeration.TradingClearingRegistryType; +import ru.spcex.platform.enumeration.WorkflowStatus; +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.imdg.iml.hazelcast.adapter.ImdgIdGeneratorHazelcast; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.enumeration.IMessageResolver; +import ru.spcex.platform.utils.error.ClearingBaseException; +import ru.spcex.platform.utils.log.ExceptionUtils; +import ru.spcex.platform.utils.validation.IValidator; + +import java.time.Instant; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.function.Function; + +@Service +public class ClientCodeService extends QueueConsumer implements InitializingBean, DisposableBean { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Producer kafkaProducer; + private final ImdgId idGenerator; + private final Imdg clientCodeMap; + private final Imdg companyMap; + private final Imdg tradingClearingRegistryMap; + + + private final Function clientCodeNewRequestValidator; + private final Function clientCodeUpdateRequestValidator; + private final Function clientCodeDeleteRequestValidator; + private final ValidationHelper validationHelper; + private final UserRoleVerification userRoleVerification; + private final IMessageResolver messageResolver; + + BiDirectionQueueExchanger> accountServiceExchanger; + + @Autowired + public ClientCodeService(Consumer kafkaQueue1, Consumer kafkaQueue2, + Producer kafkaProducer, + ImdgProvider imdgProvider, + ValidationHelper validationHelper, + IMessageResolver messageResolver, + UserRoleVerification userRoleVerification, + @Qualifier("clientCodeNewRequestValidator") Function clientCodeNewRequestValidator, + @Qualifier("clientCodeUpdateRequestValidator") Function clientCodeUpdateRequestValidator, + @Qualifier("clientCodeDeleteRequestValidator") Function clientCodeDeleteRequestValidator) { + super(kafkaQueue1, kafkaProducer); + this.kafkaProducer = kafkaProducer; + this.idGenerator = imdgProvider.getImdgIdGenerator(); + this.clientCodeMap = imdgProvider.getImdg(IMDGDistributedNames.Map_ClientCode, ClientCode.class); + this.companyMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + this.tradingClearingRegistryMap = imdgProvider.getImdg(IMDGDistributedNames.Map_TradingClearingRegistry, TradingClearingRegistry.class); + this.clientCodeNewRequestValidator = clientCodeNewRequestValidator; + this.clientCodeUpdateRequestValidator = clientCodeUpdateRequestValidator; + this.clientCodeDeleteRequestValidator = clientCodeDeleteRequestValidator; + this.validationHelper = validationHelper; + this.messageResolver = messageResolver; + this.userRoleVerification = userRoleVerification; + + this.accountServiceExchanger = new BiDirectionQueueExchanger<>(kafkaQueue2, kafkaProducer, + Consts.DESTINATION_TRADING_CLEARING_REGISTRY_NEW, + Consts.REQUEST_INFO_UPDATE, true, // REQUEST_INFO_UPDATE - стандартная очередь, для результатов всех реквестов. DESTINATION_TRADING_CLEARING_REGISTRY_REPLY, + 60000 + ); + } + + @Override + public void afterPropertiesSet() { + callback(ClientCodeNewRequest.class) + .setConsumer(this::clientCodeNew) + .forDestination(Consts.DESTINATION_CLIENT_CODE_NEW, callbacks::put); + callback(ClientCodeNewRequest.class) + .setConsumer(this::clientCodeNewFromApiUmCompany) + .forDestination(Consts.DESTINATION_CLIENT_CODE_NEW_UM_COMPANY, callbacks::put); + + callback(ClientCodeUpdateRequest.class) + .setConsumer(this::clientCodeUpdate) + .forDestination(Consts.DESTINATION_CLIENT_CODE_UPDATE, callbacks::put); + callback(CommonDeleteRequest.class) + .setConsumer(this::clientCodeDelete) + .forDestination(Consts.DESTINATION_CLIENT_CODE_DELETE, callbacks::put); + init(); + + } + + @Override + public void destroy() { + accountServiceExchanger.close(); + } + + protected RequestInfoUpdate clientCodeNew(BaseRequest userRequest) { + log.debug("ClientCodeNewRequest received {}", userRequest.getId()); + + RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); + if (requestInfoUpdate != null) return requestInfoUpdate; + + requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, clientCodeNewRequestValidator); + if (requestInfoUpdate != null) return requestInfoUpdate; + + ClientCodeNewRequest req = userRequest.getRequestPayload(); + + boolean doCreateTCR = checkNeedCreateTCR(req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + if (doCreateTCR) { + try { + createAndWaitTCR(userRequest.getId(), null, + req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + } catch (ClearingBaseException e) { + log.error("Can not wait creation of TCR. request id={};CompanyId={}, MoneyAccountId={}, DepoAccountId={}; {}", + userRequest.getId(), req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId(), + e.toString()); + return makeErrorResponse(userRequest, CompanyErrors.GeneralError, "Can not create TCR at this moment"); + } + } + + ClientCode newClientCode = buildClientCode(req); + clientCodeMap.insert(newClientCode); + log.debug("successfully processed, new clientCode id {}", newClientCode.getId()); + + return null; + } + + + protected RequestInfoUpdate clientCodeNewFromApiUmCompany(BaseRequest userRequest) { + log.debug("ClientCodeNewRequest received {}", userRequest.getId()); + + RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); + if (requestInfoUpdate != null) return requestInfoUpdate; + + requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, clientCodeNewRequestValidator); + if (requestInfoUpdate != null) return requestInfoUpdate; + + + ClientCodeNewRequest req = userRequest.getRequestPayload(); + + boolean doCreateTCR = checkNeedCreateTCR(req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + if (doCreateTCR) { + try { + createAndWaitTCR(userRequest.getId(), null, + req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + } catch (ClearingBaseException e) { + log.error("Can not wait creation of TCR. request id={};CompanyId={}, MoneyAccountId={}, DepoAccountId={}; {}", + userRequest.getId(), req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId(), + e.toString()); + return makeErrorResponse(userRequest, CompanyErrors.GeneralError, "Can not create TCR at this moment"); + } + } + + ClientCode newClientCode = buildClientCode(req); + clientCodeMap.insert(newClientCode); + log.debug("successfully processed, new clientCode id {}", newClientCode.getId()); + + return null; + } + + + protected RequestInfoUpdate clientCodeUpdate(BaseRequest userRequest) { + ClientCodeUpdateRequest req = userRequest.getRequestPayload(); + + RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); + if (requestInfoUpdate != null) return requestInfoUpdate; + + requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, clientCodeUpdateRequestValidator); + if (requestInfoUpdate != null) return requestInfoUpdate; + + + log.debug("ClientCodeUpdateRequest received, id={}", req.getId()); + ClientCode clientCode = clientCodeMap.getSingleObjectByID(req.getId()); + if (clientCode == null) { + return makeErrorResponse(userRequest, CompanyErrors.RecordNotFound, req.getId()); + } + + boolean doCreateTCR = checkNeedCreateTCR(req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + if (doCreateTCR) { + try { + createAndWaitTCR(userRequest.getId(), clientCode.getId(), + req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + } catch (ClearingBaseException e) { + log.error("Can not wait creation of TCR. request id={};CompanyId={}, MoneyAccountId={}, DepoAccountId={}; {}", + userRequest.getId(), req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId(), + e.toString()); + return makeErrorResponse(userRequest, CompanyErrors.GeneralError, "Can not create TCR at this moment"); + } + } + + updateClientCode(clientCode, req); + + clientCodeMap.update(clientCode); + log.debug("successfully processed update, id {}", clientCode.getId()); + return null; + } + + private RequestInfoUpdate makeErrorResponse(BaseRequest req, CompanyErrors err, Object... arg) { + String errMsg = messageResolver.resolve(new EnumMessage(err, arg)); + return new RequestInfoUpdate() + .setId(req.getId()) + .setStatus(ru.spcex.clearing.platform.messaging.service.Status.Error) + .setMessage(errMsg); + + } + + protected RequestInfoUpdate clientCodeDelete(BaseRequest userRequest) { + log.debug("CommonDeleteRequest received id = {}", userRequest.getId()); + CommonDeleteRequest req = userRequest.getRequestPayload(); + + RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); + if (requestInfoUpdate != null) return requestInfoUpdate; + + requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, clientCodeDeleteRequestValidator); + if (requestInfoUpdate != null) return requestInfoUpdate; + + ClientCode clientCode = clientCodeMap.getSingleObjectByID(req.getId()); + if (clientCode.getMoneyAccountId() != null) { + log.debug("Delete clientCode.id = {}: send message to account-service", clientCode.getId()); + sendBlockTCR(clientCode.getTradingClearingRegistryId(), clientCode.getMoneyAccountId()); + } + log.debug("Delete clientCode.id={}", clientCode.getId()); + clientCodeMap.delete(clientCode); + + return null; + } + + TradingClearingRegistry selectTradingClearingRegistry(Long companyId, Long moneyAccountId, Long depoAccountId) { + Map> query = new HashMap<>(); + query.put("companyId", companyId); + query.put("moneyAccountId", moneyAccountId); + query.put("tradingClearingRegistryType", TradingClearingRegistryType.Client_B.getKey()); + if (depoAccountId != null) { + query.put("depoAaccountId", depoAccountId); // todo опечатка в поле класса, см. meta.xml! + } + TradingClearingRegistry result = tradingClearingRegistryMap.getSingleObjectByFieldValues(query); + log.trace("TradingClearingRegistry by: {}; {}found", query, result == null ? "not " : ""); + return result; + } + + private boolean checkNeedCreateTCR(Long companyId, Long moneyAccountId, Long depoAccountId) { + // moneyAccountId обязателен, depoAccountId опционален + if (moneyAccountId == null) { + return false; + } + TradingClearingRegistry result = selectTradingClearingRegistry(companyId, moneyAccountId, depoAccountId); + if (result == null) { + return true; + } else { + return false; + } + } + + protected void createAndWaitTCR(Long reqId, Long clientCode, + Long companyId, Long moneyAccountId, Long depoAccountId) throws ClearingBaseException { + log.debug("For request {}, clientCode={} need create TCR: companyId={}, moneyAccountId={}, depoAccountId={}", + reqId, clientCode == null ? "new" : clientCode, + companyId, moneyAccountId, depoAccountId); + BaseRequest request = new BaseRequest<>(); + request.setId(idGenerator.nextId()); + request.setActionType(ActionType.NEW); + TradingClearingRegistryNewRequest requestPayload = new TradingClearingRegistryNewRequest(); + requestPayload.setCompanyId(companyId); + requestPayload.setMoneyAccountId(moneyAccountId); + requestPayload.setDepoAccountId(depoAccountId); +// requestPayload.setStatus(WorkflowStatus.Active.getKey()); + requestPayload.setTradingClearingRegistryType(TradingClearingRegistryType.Client_B.getKey()); + request.setRequestPayload(requestPayload); + try { + Long reply = accountServiceExchanger.exchange(request); + if (reply == null) { + throw new ClearingBaseException(CompanyErrors.GeneralError, "Waiting account-service timeout"); + } + // примечание: ошибка и сбой (непредвиденное завершение программы) не приведёт к необратимым последствиям, + // т.к. пользователь сможет повторить запрос, а созданный на предыдущем запросе ТКР уже будет создан и найдётся. + } catch (InterruptedException e) { + throw new ClearingBaseException(CompanyErrors.GeneralError, "Waiting account-service timeout"); + } + } + + protected void sendBlockTCR(Long tradingClearingRegistryId, Long moneyAccountId) { + if (tradingClearingRegistryId == null) { + throw new IllegalArgumentException("tradingClearingRegistryId was null"); + } + BaseRequest request = new BaseRequest<>(); + request.setId(idGenerator.nextId()); + request.setActionType(ActionType.UPDATE); + TradingClearingRegistryUpdateRequest requestPayload = new TradingClearingRegistryUpdateRequest(); + requestPayload.setId(tradingClearingRegistryId); + requestPayload.setStatus(WorkflowStatus.Blocked.getKey()); + request.setRequestPayload(requestPayload); + + try { + sendMessage(Consts.DESTINATION_TRADING_CLEARING_REGISTRY_UPDATE, request); + } catch (Exception e) { + log.error("Can not send block TCR message, {}", e.toString()); + throw e; + } + } + + + /** + * @param queue Consts.* + * @param message BaseRequest + * @return + */ + private Long sendMessage(String queue, BaseRequest message) { + Long sentRequestId = message.getId(); + if (sentRequestId == null) { + throw new IllegalArgumentException("Message " + message.getClass().getSimpleName() + " required id."); + } + log.debug("Send to \"{}\" message {} id={}", queue, message.getActionType(), sentRequestId); + try { + Future send = kafkaProducer.send(new ProducerRecord<>(queue, message)); + send.get(); + return sentRequestId; + } catch (InterruptedException e) { + log.error("Interrupt when send message, {}", ExceptionUtils.getStackTrace(e)); + Thread.currentThread().interrupt(); + throw new RuntimeException("Thread interrupted when send message to " + queue, e); + } catch (ExecutionException e) { + log.error("Error at send message, {} cause: {}", e.toString(), ExceptionUtils.getStackTrace(e.getCause())); + throw new RuntimeException("Send message error", e.getCause()); + } catch (Exception e) { + log.error("Unexpected exception when send message: {}", ExceptionUtils.getStackTrace(e)); + } + return null; + } + + /** + * @param req требуется заполнить tradingClearingRegistryId по tradingClearingRegistry.code + * @return + */ + private ClientCode buildClientCode(ClientCodeNewRequest req) { + ClientCode clientCode = new ClientCode(); +// clientCode.setId(idSequence.newId()); add in insert + clientCode.setCreated(Instant.now()); + clientCode.setUpdated(clientCode.getCreated()); + + clientCode.setCompanyId(req.getCompanyId()); + clientCode.setCode(req.getCode()); + + if (req.getMoneyAccountId() != null) { + TradingClearingRegistry tradingClearingRegistry = selectTradingClearingRegistry(req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + if (tradingClearingRegistry == null) { + log.warn("TCR not found: CompanyId {}, MoneyAccountId {}, DepoAccountId {}", + req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + } else { + clientCode.setTradingClearingRegistryId(tradingClearingRegistry.getId()); + } + } + + clientCode.setMoneyAccountId(req.getMoneyAccountId()); + clientCode.setDepoAccountId(req.getDepoAccountId()); + clientCode.setStatus(req.getStatus()); + + return clientCode; + } + + private void updateClientCode(ClientCode clientCode, ClientCodeUpdateRequest req) { + assert clientCode.getId() != null && clientCode.getId().equals(req.getId()); + + clientCode.setCompanyId(req.getCompanyId()); + clientCode.setCode(req.getCode()); + + if (req.getMoneyAccountId() != null) { + TradingClearingRegistry tradingClearingRegistry = selectTradingClearingRegistry(req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + if (tradingClearingRegistry == null) { + log.warn("TCR not found: CompanyId {}, MoneyAccountId {}, DepoAccountId {}", + req.getCompanyId(), req.getMoneyAccountId(), req.getDepoAccountId()); + } else { + clientCode.setTradingClearingRegistryId(tradingClearingRegistry.getId()); + } + } + + clientCode.setTradingClearingRegistryId(req.getTradingClearingRegistryId()); + clientCode.setMoneyAccountId(req.getMoneyAccountId()); + clientCode.setDepoAccountId(req.getDepoAccountId()); + clientCode.setStatus(req.getStatus()); + + clientCode.setUpdated(Instant.now()); + } + +} diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyInfoService.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyInfoService.java index 7dd8ba584..c3199aca8 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyInfoService.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyInfoService.java @@ -11,7 +11,7 @@ import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.profile.CompanyInfo; import ru.spcex.clearing.company.error.CompanyErrors; -import ru.spcex.clearing.company.util.RequestHelper; +import ru.spcex.clearing.util.services.RequestHelper; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.domain.Consts; @@ -22,14 +22,18 @@ import ru.spcex.clearing.util.security.UserRoleVerification; import ru.spcex.clearing.validation.common.ValidationHelper; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.imdg.api.ImdgTransaction; +import ru.spcex.platform.utils.error.ValidationException; import ru.spcex.platform.utils.validation.IValidator; import java.time.Instant; +import java.util.Objects; import java.util.function.Function; @Service public class CompanyInfoService extends QueueConsumer implements InitializingBean { private final Logger log = LoggerFactory.getLogger(getClass()); + private final ImdgProvider imdgProvider; private final Imdg companyMap; protected final CompanyService companyService; private final RequestHelper requestHelper; @@ -46,6 +50,7 @@ public class CompanyInfoService extends QueueConsumer implements InitializingBea Function companyInfoUpdateRequestValidator) { super(kafkaQueue, kafkaProducer); this.companyService = companyService; + this.imdgProvider = imdgProvider; this.companyMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); this.validationHelper = validationHelper; this.userRoleVerification = userRoleVerification; @@ -56,16 +61,16 @@ public class CompanyInfoService extends QueueConsumer implements InitializingBea @Override public void afterPropertiesSet() { callback(CompanyInfoUpdateRequest.class) - .setFunction(this::companyInfoUpdate) + .setFunction(request -> requestHelper.requestFunction(this::companyInfoUpdate, request)) .forDestination(Consts.DESTINATION_COMPANY_INFO_UPDATE, callbacks::put); callback(CompanyInfoUpdateRequest.class) - .setFunction(this::companyInfoUpdate) + .setFunction(request -> requestHelper.requestFunction(this::companyInfoUpdate, request)) .forDestination(Consts.DESTINATION_COMPANY_INFO_NEW, callbacks::put); // логика заполнения одинакова с companyInfoUpdate init(); } - public RequestInfoUpdate companyInfoUpdate(BaseRequest companyInfoReq) { + public RequestInfoUpdate companyInfoUpdate(BaseRequest companyInfoReq) throws ValidationException { CompanyInfoUpdateRequest req = companyInfoReq.getRequestPayload(); log.debug("{} received", req.getClass().getSimpleName()); { // Валидация, ValidationException @@ -81,6 +86,7 @@ public class CompanyInfoService extends QueueConsumer implements InitializingBea log.trace("Company {} not found", req.getId()); return requestHelper.makeErrorResponse(companyInfoReq, CompanyErrors.CompanyNotFound, companyInfoReq.getId()); } + company.setUpdated(Instant.now()); CompanyInfo companyInfo = company.getProfile(); companyInfo.setCorporationSoleType(req.getCorporationSoleType()); @@ -93,8 +99,39 @@ public class CompanyInfoService extends QueueConsumer implements InitializingBea companyInfo.setShortNameEng(req.getShortNameEng()); companyInfo.setFullNameEng(req.getFullNameEng()); - company.setUpdated(Instant.now()); - companyMap.update(company); + company.setShortName(req.getShortName()); + company.setFullName(req.getFullName()); + boolean simpleUpdate = true; + if (req.getWorkflowStatus() != null) { + String prevStatus = company.getWorkflowStatus(); + company.setWorkflowStatus(req.getWorkflowStatus()); + if (!Objects.equals(prevStatus, company.getWorkflowStatus())) { + simpleUpdate = false; + ImdgTransaction transaction = imdgProvider.newTransaction(); + transaction.beginTransaction(); + boolean txOk = false; + try { + companyService.relationService.onChangeWorkflowStatus(transaction, company, prevStatus, company.getWorkflowStatus()); + Imdg txCompanyMap = transaction.getImdg(IMDGDistributedNames.Map_Company, Company.class); + txCompanyMap.update(company); + log.trace("Company {} updated", company.getId()); + txOk = true; + } finally { + if (txOk) + transaction.commitTransaction(); + else + transaction.rollbackTransaction(); + } + } else { + log.trace("Status was not changed"); + } + } else { + log.trace("Null new WorkflowStatus"); + } + if (simpleUpdate) { + companyMap.update(company); + log.trace("Company {} updated", company.getId()); + } log.debug("successfully CompanyInfo processed, id {}", companyInfo.getId()); } return null; diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyRoleSetService.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyRoleSetService.java index 2af7a358c..ca4526b9b 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyRoleSetService.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyRoleSetService.java @@ -9,7 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.company.CompanyRoleSet; import ru.spcex.clearing.company.error.CompanyErrors; -import ru.spcex.clearing.company.util.RequestHelper; +import ru.spcex.clearing.util.services.RequestHelper; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.domain.Consts; diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyService.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyService.java index 3444b1243..f7182432d 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyService.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanyService.java @@ -11,7 +11,7 @@ import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.company.CompanySymbols; import ru.spcex.clearing.company.error.CompanyErrors; -import ru.spcex.clearing.company.util.RequestHelper; +import ru.spcex.clearing.util.services.RequestHelper; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.domain.Consts; @@ -29,13 +29,11 @@ import ru.spcex.platform.imdg.api.ImdgId; import ru.spcex.platform.imdg.api.ImdgProvider; import ru.spcex.platform.imdg.api.ImdgTransaction; import ru.spcex.platform.utils.enumeration.EnumMessage; -import ru.spcex.platform.utils.enumeration.IMessageResolver; import ru.spcex.platform.utils.error.ClearingBaseException; import ru.spcex.platform.utils.error.ValidationException; import ru.spcex.platform.utils.validation.IValidator; import java.time.Instant; -import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -56,7 +54,7 @@ public class CompanyService extends QueueConsumer implements InitializingBean { protected CompanySymbolService companySymbolService; protected AccountNotificationHelper accountNotification; - protected RelationHelper relationHelper; + protected RelationService relationService; @Autowired public CompanyService(Consumer kafkaQueue, Producer kafkaProducer, @@ -73,7 +71,7 @@ public class CompanyService extends QueueConsumer implements InitializingBean { CompanySymbolService companySymbolService, AccountNotificationHelper accountNotification, - RelationHelper relationHelper + RelationService relationService ) { super(kafkaQueue, kafkaProducer); this.imdgProvider = imdgProvider; @@ -89,7 +87,7 @@ public class CompanyService extends QueueConsumer implements InitializingBean { this.companySymbolService = companySymbolService; companySymbolService.setCompanyService(this); this.accountNotification = accountNotification; - this.relationHelper = relationHelper; + this.relationService = relationService; companyIMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); } @@ -145,9 +143,9 @@ public class CompanyService extends QueueConsumer implements InitializingBean { companyMap.insert(company); log.debug("company-new request processed, BaseRequest.id = {}, company.id={}", companyNewRequestBaseRequest.getId(), company.getId()); - relationHelper.createNewRelation(transaction, company); + relationService.createNewRelation(transaction, company); if (!(WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()))) { // Block - relationHelper.onChangeWorkflowStatus(transaction, company, null, company.getWorkflowStatus()); + relationService.onChangeWorkflowStatus(transaction, company, null, company.getWorkflowStatus()); } txOk = true; } finally { @@ -180,10 +178,10 @@ public class CompanyService extends QueueConsumer implements InitializingBean { log.trace("Company {} not found", updateRequest.getId()); return requestHelper.makeErrorResponse(companyUpdateRequestBaseRequest, CompanyErrors.CompanyNotFound, updateRequest.getId()); } - if (!WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus())) { - log.trace("Company {} not active: {}", company.getId(), company.getWorkflowStatus()); - return requestHelper.makeErrorResponse(companyUpdateRequestBaseRequest, CompanyErrors.CompanyDisabled, updateRequest.getId()); - } +// if (!WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus())) { +// log.trace("Company {} not active: {}", company.getId(), company.getWorkflowStatus()); +// return requestHelper.makeErrorResponse(companyUpdateRequestBaseRequest, CompanyErrors.CompanyDisabled, updateRequest.getId()); +// } company.setUpdated(Instant.now()); company.setShortName(updateRequest.getShortName()); @@ -199,7 +197,7 @@ public class CompanyService extends QueueConsumer implements InitializingBean { if (updateRequest.getWorkflowStatus() != null) { company.setWorkflowStatus(updateRequest.getWorkflowStatus()); if (!Objects.equals(prevStatus, company.getWorkflowStatus())) { - relationHelper.onChangeWorkflowStatus(transaction, company, prevStatus, company.getWorkflowStatus()); + relationService.onChangeWorkflowStatus(transaction, company, prevStatus, company.getWorkflowStatus()); } else { log.trace("Status was not changed"); } @@ -286,7 +284,7 @@ public class CompanyService extends QueueConsumer implements InitializingBean { String prevStatus = company.getWorkflowStatus(); company.setWorkflowStatus(WorkflowStatus.Blocked.getKey()); - relationHelper.onChangeWorkflowStatus(transaction, company, prevStatus, company.getWorkflowStatus()); + relationService.onChangeWorkflowStatus(transaction, company, prevStatus, company.getWorkflowStatus()); log.debug("Update company.id={}", company.getId()); companyMap.update(company); @@ -339,7 +337,7 @@ public class CompanyService extends QueueConsumer implements InitializingBean { company.setWorkflowStatus(WorkflowStatus.Blocked.getKey()); - relationHelper.onChangeWorkflowStatusOnlyRelationChange(transaction, company, company.getWorkflowStatus()); + relationService.onChangeWorkflowStatusOnlyRelationChange(transaction, company, company.getWorkflowStatus()); log.debug("Update company.id={}", company.getId()); companyMap.update(company); diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanySymbolService.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanySymbolService.java index 85c0469f8..cf534c0ea 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanySymbolService.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/CompanySymbolService.java @@ -10,7 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.company.CompanySymbols; -import ru.spcex.clearing.company.util.RequestHelper; +import ru.spcex.clearing.util.services.RequestHelper; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.domain.Consts; diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/ProfileDocumentService.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/ProfileDocumentService.java index 411cfd669..30eab291e 100644 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/ProfileDocumentService.java +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/ProfileDocumentService.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Service; import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.profile.ProfileDocument; import ru.spcex.clearing.company.error.CompanyErrors; -import ru.spcex.clearing.company.util.RequestHelper; +import ru.spcex.clearing.util.services.RequestHelper; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; import ru.spcex.clearing.platform.messaging.domain.Consts; @@ -21,7 +21,6 @@ import ru.spcex.clearing.platform.messaging.domain.cud.company.ProfileDocumentNe import ru.spcex.clearing.platform.messaging.domain.cud.company.ProfileDocumentUpdateRequest; import ru.spcex.clearing.platform.messaging.service.QueueConsumer; import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; -import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; import ru.spcex.clearing.util.security.UserRoleVerification; import ru.spcex.clearing.validation.common.ValidationHelper; import ru.spcex.platform.enumeration.DocumentTypes; @@ -174,8 +173,10 @@ public class ProfileDocumentService extends QueueConsumer implements Initializin ProfileDocument profileDocument = profileDocumentMap.getSingleObjectByID(profileDocumentUpdateRequest.getId()); String prevDocType = profileDocument.getDocumentType(); - if (profileDocumentUpdateRequest.getCompanyId() != null) - profileDocument.setCompanyId(profileDocumentUpdateRequest.getCompanyId()); + if (profileDocumentUpdateRequest.getCompanyId() != null && !profileDocumentUpdateRequest.getCompanyId().equals(profileDocument.getCompanyId())) { + log.warn("profileDocumentUpdateRequest[{}] CompanyId {} not match", profileDocumentUpdateRequest.getId(), profileDocumentUpdateRequest.getCompanyId()); + return requestHelper.makeErrorResponse(profileDocumentUpdateRequestBaseRequest, CompanyErrors.WrongFieldValue, "companyId"); + } if (profileDocumentUpdateRequest.getDocumentType() != null) profileDocument.setDocumentType(profileDocumentUpdateRequest.getDocumentType()); diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/RelationHelper.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/RelationHelper.java deleted file mode 100644 index c68595767..000000000 --- a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/RelationHelper.java +++ /dev/null @@ -1,97 +0,0 @@ -package ru.spcex.clearing.company.service; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import ru.clearing.classes.statics.data.company.Company; -import ru.clearing.classes.statics.data.company.relation.Relation; -import ru.spcex.clearing.company.error.CompanyErrors; -import ru.spcex.clearing.imdg.IMDGDistributedNames; -import ru.spcex.platform.enumeration.ServiceProduct; -import ru.spcex.platform.enumeration.ServiceStatus; -import ru.spcex.platform.enumeration.WorkflowStatus; -import ru.spcex.platform.imdg.api.Imdg; -import ru.spcex.platform.imdg.api.ImdgTransaction; -import ru.spcex.platform.utils.error.ValidationException; - -import java.time.Instant; -import java.util.Collection; -import java.util.Objects; - -@Service -public class RelationHelper { - protected static final Long SPVB_ID = 1L; // СПВБ - protected final Logger log = LoggerFactory.getLogger(getClass()); - - protected AccountNotificationHelper accountNotification; - - @Autowired - public RelationHelper(AccountNotificationHelper accountNotification) { - this.accountNotification = accountNotification; - } - - protected void createNewRelation(ImdgTransaction transaction, Company company) { - Relation relation = new Relation(); -// relation.setId(idSequence.nextId()); - relation.setCreated(Instant.now()); - relation.setUpdated(relation.getCreated()); - - relation.setConsumerId(company.getId()); - relation.setSupplierId(SPVB_ID); // 1 СПВБ - relation.setServiceStatus(WorkflowStatus.Active.getKey()); - relation.setService(ru.spcex.platform.enumeration.Service.MKR.getKey()); // MKR - relation.setServiceProduct(ServiceProduct.ZERO.getKey()); - log.debug("New Relation[{}] created.", relation.getId()); - Imdg relationMap = transaction.getImdg(IMDGDistributedNames.Map_Relation, Relation.class); - relationMap.insert(relation); - } - - /** - * @param company - * @param oldStatus - * @param newStatus WorkflowStatus.Active - при возобналвении; WorkflowStatus.Blocked - при блокировки/расторжении - */ - public void onChangeWorkflowStatus(ImdgTransaction transaction, Company company, String oldStatus, String newStatus) throws ValidationException { - log.debug("Company id={} status changed from {} to {}", - company.getId(), oldStatus, company.getWorkflowStatus()); - onChangeWorkflowStatusOnlyRelationChange(transaction, company, newStatus); - - if (WorkflowStatus.Blocked.equalsByKey(newStatus)) { - // IV - сообщения на добавления документа расторжения договора - CompanyErrors hasError = accountNotification.accountTerminationNotification(company.getId()); - if (hasError != null) { - log.warn("Can not block company, cause error {}", hasError); - throw new ValidationException(hasError); - } - } - } - - protected void onChangeWorkflowStatusOnlyRelationChange(ImdgTransaction transaction, Company company, String newStatus) throws ValidationException { - assert Objects.equals(newStatus, company.getWorkflowStatus()); - String query = String.format("consumerId=%s", company.getId()); - Imdg relationMap = transaction.getImdg(IMDGDistributedNames.Map_Relation, Relation.class); - Collection relations = relationMap.getCollectionObjectsBySQL(query); - log.trace("Selected {} Relation by query: {}", relations.size(), query); - Instant now = Instant.now(); - for (Relation relation : relations) { - boolean modified = false; - if (WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus())) { - if (!ServiceStatus.Reopened.equalsByKey(relation.getServiceStatus())) { - relation.setServiceStatus(ServiceStatus.Reopened.getKey()); - modified = true; - } - } else if (WorkflowStatus.Blocked.equalsByKey(company.getWorkflowStatus())) { - if (!ServiceStatus.Closed.equalsByKey(relation.getServiceStatus())) { - relation.setServiceStatus(ServiceStatus.Closed.getKey()); - modified = true; - } - } - if (modified) { - relation.setUpdated(now); - log.debug("Relation id={} updated", relation.getId()); - relationMap.update(relation); - } - } - } -} diff --git a/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/RelationService.java b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/RelationService.java new file mode 100644 index 000000000..d91417af6 --- /dev/null +++ b/clearing-parent/company-service/src/main/java/ru/spcex/clearing/company/service/RelationService.java @@ -0,0 +1,331 @@ +package ru.spcex.clearing.company.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.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.company.relation.Relation; +import ru.spcex.clearing.company.error.CompanyErrors; +import ru.spcex.clearing.util.services.RequestHelper; +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.company.*; +import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; +import ru.spcex.clearing.util.security.UserRoleVerification; +import ru.spcex.clearing.validation.common.ValidationHelper; +import ru.spcex.platform.enumeration.ClearingCategory; +import ru.spcex.platform.enumeration.ServiceProduct; +import ru.spcex.platform.enumeration.ServiceStatus; +import ru.spcex.platform.enumeration.WorkflowStatus; +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.imdg.api.ImdgTransaction; +import ru.spcex.platform.utils.error.ValidationException; +import ru.spcex.platform.utils.validation.IValidator; + +import java.time.Instant; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +@Service +public class RelationService extends QueueConsumer implements InitializingBean { + protected static final Long SPVB_ID = 1L; // СПВБ + protected final Logger log = LoggerFactory.getLogger(getClass()); + + protected ImdgProvider imdgProvider; + protected ImdgId idSequence; + protected Imdg relationMap; + protected Imdg companyMap; + protected AccountNotificationHelper accountNotification; + protected UserRoleVerification userRoleVerification; + protected RequestHelper requestHelper; + + protected ValidationHelper validationHelper; + private Function relationNewRequestValidator; + private Function relationUpdateRequestValidator; + private Function relationDeleteRequestValidator; + + @Autowired + public RelationService(Consumer kafkaQueue, + Producer kafkaProducer, + ImdgProvider imdgProvider, + RequestHelper requestHelper, + + UserRoleVerification userRoleVerification, + ValidationHelper validationHelper, + @Qualifier("relationNewRequestValidation") Function relationNewRequestValidator, + @Qualifier("relationUpdateRequestValidation") Function relationUpdateRequestValidator, + @Qualifier("relationDeleteRequestValidation") Function relationDeleteRequestValidator, + + AccountNotificationHelper accountNotification) { + super(kafkaQueue, kafkaProducer); + this.imdgProvider = imdgProvider; + this.idSequence = imdgProvider.getImdgIdGenerator(); + this.accountNotification = accountNotification; + this.userRoleVerification = userRoleVerification; + + this.validationHelper = validationHelper; + this.relationNewRequestValidator = relationNewRequestValidator; + this.relationUpdateRequestValidator = relationUpdateRequestValidator; + this.relationDeleteRequestValidator = relationDeleteRequestValidator; + + relationMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Relation, Relation.class); + companyMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + this.requestHelper = requestHelper.setLogger(log); + } + + // --- external API --- + @Override + public void afterPropertiesSet() { +// this.profileDocumentMap = imdgProvider.getImdg(IMDGDistributedNames.Map_ProfileDocument, ProfileDocument.class); +// this.companyMap = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + + callback(RelationNewRequest.class) + .setFunction(this::relationNew) + .forDestination(Consts.DESTINATION_RELATION_NEW, callbacks::put); + callback(RelationUpdateRequest.class) + .setFunction(this::relationUpdate) + .forDestination(Consts.DESTINATION_RELATION_UPDATE, callbacks::put); + callback(CommonDeleteRequest.class) + .setFunction(this::relationDelete) + .forDestination(Consts.DESTINATION_RELATION_DELETE, callbacks::put); + init(); + } + + private synchronized RequestInfoUpdate relationNew(BaseRequest relationNewBaseRequest) { + RelationNewRequest req = relationNewBaseRequest.getRequestPayload(); + log.debug("relation-new request received, BaseRequest.id = {}", relationNewBaseRequest.getId()); + { // Валидация + RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(relationNewBaseRequest); + if (requestInfoUpdate != null) return requestInfoUpdate; + + requestInfoUpdate = validationHelper.validateTillFirstError(relationNewBaseRequest, relationNewRequestValidator); + if (requestInfoUpdate != null) return requestInfoUpdate; + } + + + Company company = companyMap.getSingleObjectByID(req.getCompanyId()); + + Relation existRelation = searchRelation(req.getClearingMemberCategory(), req.getCompanyId()); + if (existRelation != null && !WorkflowStatus.Blocked.equalsByKey(existRelation.getServiceStatus())) { + CompanyErrors errorType; + if (ru.spcex.platform.enumeration.Service.MKR.equalsByKey(existRelation.getService())) { + errorType = CompanyErrors.ClearingMemberCategoryOnMKRAlreadyExist; + } else if (ru.spcex.platform.enumeration.Service.FOND.equalsByKey(existRelation.getService())) { + errorType = CompanyErrors.ClearingMemberCategoryOnFONDAlreadyExist; + } else { + log.warn("Unknown relation[{}] service={}", existRelation.getId(), existRelation.getService()); + errorType = CompanyErrors.GeneralError; + } + return requestHelper.makeErrorResponse(relationNewBaseRequest, errorType); + } + + Relation relation; + if (existRelation == null) { + relation = new Relation(); + relation.setId(idSequence.nextId()); + relation.setCreated(Instant.now()); + relation.setUpdated(relation.getCreated()); + } else { + log.info("Found exist relation id={}, it will be update.", existRelation.getId()); + relation = existRelation; + relation.setUpdated(Instant.now()); + } + + relation.setConsumerId(req.getCompanyId()); + relation.setSupplierId(SPVB_ID); // 1 СПВБ + if (req.getServiceStatus() != null) { + relation.setServiceStatus(req.getServiceStatus()); + } else { + if (existRelation != null) { + relation.setServiceStatus(ServiceStatus.Reopened.getKey()); + log.debug("Request has no status. Set status by resume: {}", relation.getServiceStatus()); + } else { + String toStatus = WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()) ? ServiceStatus.Active.getKey() : + WorkflowStatus.Blocked.equalsByKey(company.getWorkflowStatus()) ? ServiceStatus.Closed.getKey() : + null; + log.debug("Request has no status. Set status by company[{}].status={}. Relation[{}].serviceSStatus={}", + company.getId(), company.getWorkflowStatus(), toStatus); + relation.setServiceStatus(toStatus); + } + } + relation.setService(ru.spcex.platform.enumeration.Service.MKR.getKey()); // MKR + relation.setServiceProduct(ServiceProduct.ZERO.getKey()); + relation.setComment(req.getComment()); + + relationMap.insert(relation); + log.debug("New Relation[{}] created.", relation.getId()); + + return null; + } + + private Relation searchRelation(String clearingMemberCategory, Long consumerId) { + String svc; + if (ClearingCategory.B.equalsByKey(clearingMemberCategory) + || ClearingCategory.I.equalsByKey(clearingMemberCategory) + || ClearingCategory.V.equalsByKey(clearingMemberCategory) + ) { + svc = ru.spcex.platform.enumeration.Service.MKR.getKey(); + } else if (ClearingCategory.C.equalsByKey(clearingMemberCategory) + || ClearingCategory.F.equalsByKey(clearingMemberCategory) + ) { + svc = ru.spcex.platform.enumeration.Service.FOND.getKey(); + } else { + log.warn("Unexpected relation Relation={}", clearingMemberCategory); + return null; + } + Relation existRelation = relationMap.getSingleObjectByFieldValues(Map.of( + "consumerId", consumerId, + "service", svc + )); + return existRelation; + } + + private synchronized RequestInfoUpdate relationUpdate(BaseRequest relationUpdateBaseRequest) { + RelationUpdateRequest req = relationUpdateBaseRequest.getRequestPayload(); + log.debug("relation-update request received, BaseRequest.id = {}", relationUpdateBaseRequest.getId()); + { // Валидация + RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(relationUpdateBaseRequest); + if (requestInfoUpdate != null) return requestInfoUpdate; + + requestInfoUpdate = validationHelper.validateTillFirstError(relationUpdateBaseRequest, relationUpdateRequestValidator); + if (requestInfoUpdate != null) return requestInfoUpdate; + } + + + Relation relation = relationMap.getSingleObjectByID(req.getId()); + relation.setUpdated(relation.getCreated()); + + if (req.getServiceStatus() != null) { + log.debug("To relation {} set serivceStatus=\"{}\" by user request.", relation.getId(), req.getServiceStatus()); + relation.setServiceStatus(req.getServiceStatus()); + } else { + if (relation.getConsumerId() == null) { + log.warn("Relation[{}]. consumerId was null", relation.getId()); + } + Company company = companyMap.getSingleObjectByID(relation.getConsumerId()); + String toStatus = WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()) ? ServiceStatus.Active.getKey() : + WorkflowStatus.Blocked.equalsByKey(company.getWorkflowStatus()) ? ServiceStatus.Closed.getKey() : + null; + if (ServiceStatus.Active.equalsByKey(toStatus) && ServiceStatus.Closed.equalsByKey(relation.getServiceStatus())) { + relation.setServiceStatus(ServiceStatus.Reopened.getKey()); + log.debug("Set status by resume: {}", relation.getServiceStatus()); + } else { + relation.setServiceStatus(toStatus); + log.debug("Request has no status. Set status by company[{}].status={}. Relation[{}].serviceSStatus={}", + company.getId(), company.getWorkflowStatus(), toStatus, relation.getServiceStatus()); + } + } + + relation.setComment(req.getComment()); + + relationMap.insert(relation); + log.debug("Update Relation[{}] created.", relation.getId()); + + return null; + } + + + private synchronized RequestInfoUpdate relationDelete(BaseRequest relationDeleteBaseRequest) { + CommonDeleteRequest req = relationDeleteBaseRequest.getRequestPayload(); + log.debug("relation-delete request received, BaseRequest.id = {}", relationDeleteBaseRequest.getId()); + { // Валидация + RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(relationDeleteBaseRequest); + if (requestInfoUpdate != null) return requestInfoUpdate; + + requestInfoUpdate = validationHelper.validateTillFirstError(relationDeleteBaseRequest, relationDeleteRequestValidator); + if (requestInfoUpdate != null) return requestInfoUpdate; + } + + + Relation relation = relationMap.getSingleObjectByID(req.getId()); + if (WorkflowStatus.Blocked.equalsByKey(relation.getServiceStatus())) { + log.warn("Relation {} already blocked.", relation.getId()); + } else { + relation.setUpdated(relation.getCreated()); + relation.setServiceStatus(WorkflowStatus.Blocked.getKey()); + + relationMap.insert(relation); + log.debug("Relation[{}] block.", relation.getId()); + } + return null; + } + + + // --- internal API --- + + protected void createNewRelation(ImdgTransaction transaction, Company company) { + Relation relation = new Relation(); +// relation.setId(idSequence.nextId()); + relation.setCreated(Instant.now()); + relation.setUpdated(relation.getCreated()); + + relation.setConsumerId(company.getId()); + relation.setSupplierId(SPVB_ID); // 1 СПВБ + relation.setServiceStatus(WorkflowStatus.Active.getKey()); + relation.setService(ru.spcex.platform.enumeration.Service.MKR.getKey()); // MKR + relation.setServiceProduct(ServiceProduct.ZERO.getKey()); + log.debug("New Relation[{}] created.", relation.getId()); + Imdg relationMap = transaction.getImdg(IMDGDistributedNames.Map_Relation, Relation.class); + relationMap.insert(relation); + } + + /** + * @param company + * @param oldStatus + * @param newStatus WorkflowStatus.Active - при возобналвении; WorkflowStatus.Blocked - при блокировки/расторжении + */ + public void onChangeWorkflowStatus(ImdgTransaction transaction, Company company, String oldStatus, String newStatus) throws ValidationException { + log.debug("Company id={} status changed from {} to {}", + company.getId(), oldStatus, company.getWorkflowStatus()); + onChangeWorkflowStatusOnlyRelationChange(transaction, company, newStatus); + + if (WorkflowStatus.Blocked.equalsByKey(newStatus)) { + // IV - сообщения на добавления документа расторжения договора + CompanyErrors hasError = accountNotification.accountTerminationNotification(company.getId()); + if (hasError != null) { + log.warn("Can not block company, cause error {}", hasError); + throw new ValidationException(hasError); + } + } + } + + protected synchronized void onChangeWorkflowStatusOnlyRelationChange(ImdgTransaction transaction, Company company, String newStatus) throws ValidationException { + assert Objects.equals(newStatus, company.getWorkflowStatus()); + String query = String.format("consumerId=%s", company.getId()); + Imdg relationMap = transaction.getImdg(IMDGDistributedNames.Map_Relation, Relation.class); + Collection relations = relationMap.getCollectionObjectsBySQL(query); + log.trace("Selected {} Relation by query: {}", relations.size(), query); + Instant now = Instant.now(); + for (Relation relation : relations) { + boolean modified = false; + if (WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus())) { + if (!ServiceStatus.Reopened.equalsByKey(relation.getServiceStatus())) { + relation.setServiceStatus(ServiceStatus.Reopened.getKey()); + modified = true; + } + } else if (WorkflowStatus.Blocked.equalsByKey(company.getWorkflowStatus())) { + if (!ServiceStatus.Closed.equalsByKey(relation.getServiceStatus())) { + relation.setServiceStatus(ServiceStatus.Closed.getKey()); + modified = true; + } + } + if (modified) { + relation.setUpdated(now); + log.debug("Relation id={} updated", relation.getId()); + relationMap.update(relation); + } + } + } +} diff --git a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/ClientCodeServiceTest.java b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/ClientCodeServiceTest.java new file mode 100644 index 000000000..2599a0ec9 --- /dev/null +++ b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/ClientCodeServiceTest.java @@ -0,0 +1,367 @@ +package ru.spcex.clearing.company.service; + +import org.apache.kafka.clients.consumer.MockConsumer; +import org.apache.kafka.clients.producer.MockProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.account.ClientCode; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.profile.CompanyInfo; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; +import ru.clearing.platform.dictionary.*; +import ru.spcex.clearing.company.config.BeanConfiguration; +import ru.spcex.clearing.company.config.validation.ClientCodeValidationConfig; +import ru.spcex.clearing.company.config.validation.ValidationConfig; +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.account.ClientCodeNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.account.ClientCodeUpdateRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.test.MatcherFactory; +import ru.spcex.clearing.test.TestUtils; +import ru.spcex.clearing.test.config.ImdgTestConfig; +import ru.spcex.clearing.test.config.KafkaTestConfig; +import ru.spcex.platform.enumeration.TradingClearingRegistryType; +import ru.spcex.platform.enumeration.WorkflowStatus; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +import javax.annotation.PostConstruct; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static ru.spcex.clearing.test.MatcherFactory.usingIgnoringFieldsComparator; +import static ru.spcex.clearing.test.TestUtils.*; +import static ru.spcex.clearing.test.config.ImdgTestConfig.waitAvailableImdgProviderAndAddAdminWithDefaultId; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { + ClientCodeService.class, + ClientCodeValidationConfig.class, + + ValidationConfig.class, + BeanConfiguration.class, + + KafkaTestConfig.class, + ImdgTestConfig.class}) +class ClientCodeServiceTest { + + private static final int PARTITION = 0; + private static final Long ID = 4L; + public static final MatcherFactory.Matcher CLIENT_CODE_MATCHER = usingIgnoringFieldsComparator("created","updated"); + + private static final Long TCR_ID = 41L; + private static final Long COMPANY_ID = 42L; + + @Autowired + ClientCodeService clientCodeService; + @Autowired + @Qualifier("hazelcastServiceTest") + private ImdgProvider hazelcastServiceTest; + @Captor + private ArgumentCaptor producerRecord; + @SpyBean + private MockProducer mockProducer; + + private Imdg clientCodeImdg; + + + // ****************************-******************* + + @PostConstruct + private void init() { + waitAvailableImdgProviderAndAddAdminWithDefaultId(); + clientCodeImdg = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_ClientCode, ClientCode.class); + + // Словари для теста, применяются в ValidationConfig + putToDictionary(IMDGDistributedNames.Map_WorkflowStatusDictionary, new WorkflowStatusDictionary(), "ACTV"); + putToDictionary(IMDGDistributedNames.Map_CompanySymbolDictionary, new CompanySymbolDictionary(), "CLRC"); + putToDictionary(IMDGDistributedNames.Map_CorporationSoleTypeDictionary, new CorporationSoleTypeDictionary(), "GDIR"); + putToDictionary(IMDGDistributedNames.Map_CountryCodeDictionary, new CountryCodeDictionary(), "RUS"); + putToDictionary(IMDGDistributedNames.Map_AllowedDictionary, new AllowedDictionary(), "ALWD"); + putToDictionary(IMDGDistributedNames.Map_LegalKindDictionary, new LegalKindDictionary(), "JURD"); + putToDictionary(IMDGDistributedNames.Map_OrganizationTypeDictionary, new OrganizationTypeDictionary(), "NCRD"); + + + Imdg companyImdg = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_Company, Company.class); + Company company1 = new Company(); + company1.setId(COMPANY_ID); + company1.setWorkflowStatus(WorkflowStatus.Active.getKey()); + company1.setFullName("Company prime"); + company1.setShortName("Seizwell"); + company1.setProfile(new CompanyInfo()); + company1.getProfile().setCompanyId(COMPANY_ID); + company1.getProfile().setCountryCode("TLDI"); + company1.getProfile().setDescription("Big profit from TLD Company Prime."); + company1.getProfile().setLegalKind("TLDI"); + company1.getProfile().setResidence("TLDI"); + companyImdg.insert(company1); + + Imdg tcrImdg = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_TradingClearingRegistry, TradingClearingRegistry.class); + TradingClearingRegistry registry1 = new TradingClearingRegistry(); + registry1.setId(TCR_ID); + registry1.setCompanyId(COMPANY_ID); + registry1.setCode("code-120-101"); + registry1.setMoneyAccountId(131L); + registry1.setDepoAaccountId(132L); + registry1.setTradingClearingRegistryType(TradingClearingRegistryType.Client_B.getKey()); + registry1.setStatus(WorkflowStatus.Active.getKey()); + tcrImdg.insert(registry1); + + Imdg accounts = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_Account, Account.class); + Account moneyAccount=new Account(); + moneyAccount.setId(131L); + moneyAccount.setAccount("AAAA-4444"); + moneyAccount.setStatus("ACTV"); + moneyAccount.setCompanyId(COMPANY_ID); // для валидации принадлежности счёта + accounts.insert(moneyAccount); + Account depoAccount=new Account(); + depoAccount.setId(132L); + depoAccount.setAccount("AAAB-44654"); + depoAccount.setStatus("ACTV"); + depoAccount.setCompanyId(COMPANY_ID); // для валидации принадлежности счёта + accounts.insert(depoAccount); + + TestUtils.FutureRecordMetadata future = spy(TestUtils.FutureRecordMetadata.class); + doReturn(future).when(mockProducer).send(producerRecord.capture()); + } + + private void putToDictionary(String mapName, D object, String code) { + Imdg dMap = (Imdg) hazelcastServiceTest.getImdg(mapName, object.getClass()); + object.setId(2L); + object.setCode(code); + object.setName("name of " + code); + dMap.insert(object); + } + + @Test + void selectTradingClearingRegistry() { + TradingClearingRegistry tcr = clientCodeService.selectTradingClearingRegistry(COMPANY_ID, 131L, 132L); + assertNotNull(tcr); + assertEquals(41L, tcr.getId()); + + tcr = clientCodeService.selectTradingClearingRegistry(COMPANY_ID, 131L, null); + assertNotNull(tcr); + assertEquals(41L, tcr.getId()); + + assertNull(clientCodeService.selectTradingClearingRegistry(0L, 131L, 132L)); + assertNull(clientCodeService.selectTradingClearingRegistry(COMPANY_ID, 0L, 132L)); + assertNull(clientCodeService.selectTradingClearingRegistry(COMPANY_ID, 131L, 0L)); + } + + + /** + * {@link ClientCodeService#clientCodeUpdate(BaseRequest)}
+ * Тест проверяет создание {@link ClientCode} в IMDG при передаче из Apache Kafka (очередь 1).
+ * Входной запрос {@link ru.spcex.clearing.platform.messaging.domain.cud.account.ClientCodeNewRequest}:
+ **/ + @Test + void clientCodeNew1() { + //ARRANGE + final String ccCode = "Lucky planet"; + ClientCodeNewRequest clientCodeNewRequest = new ClientCodeNewRequest(); + clientCodeNewRequest.setCompanyId(COMPANY_ID); + clientCodeNewRequest.setCode(ccCode); + clientCodeNewRequest.setTradingClearingRegistryId(TCR_ID); + clientCodeNewRequest.setDepoAccountId(null); + clientCodeNewRequest.setMoneyAccountId(null); + clientCodeNewRequest.setStatus("ACTV"); + + ClientCode predictableClientCode = new ClientCode(); + predictableClientCode.setCode(ccCode); + predictableClientCode.setStatus("ACTV"); + predictableClientCode.setCompanyId(COMPANY_ID); +// predictableClientCode.setMoneyAccountId(131L); +// predictableClientCode.setDepoAccountId(132L); +// predictableClientCode.setTradingClearingRegistryId(TCR_ID); + + //ACT + String jsonString = getJsonStringForNew(clientCodeNewRequest, ID); + + addRecordToKafka((MockConsumer) clientCodeService.getConsumer(), Consts.DESTINATION_CLIENT_CODE_NEW, PARTITION, 0, jsonString); + + //ASSERT + waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); + ClientCode resultNew = clientCodeImdg.getSingleObjectBySQL(String.format("code = '%s'", ccCode)); + predictableClientCode.setId(resultNew.getId()); + CLIENT_CODE_MATCHER.assertMatch(resultNew, predictableClientCode); + assertNotNull(resultNew.getCreated()); + } + + /** + * {@link ClientCodeService#clientCodeUpdate(BaseRequest)}
+ * Тест проверяет создание {@link ClientCode} в IMDG при передаче из Apache Kafka (очередь 2).
+ * Входной запрос {@link ru.spcex.clearing.platform.messaging.domain.cud.account.ClientCodeNewRequest}:
+ **/ + @Test + void clientCodeNew2() { + //ARRANGE + final String ccCode = "Lucky planet"; + ClientCodeNewRequest clientCodeNewRequest = new ClientCodeNewRequest(); + clientCodeNewRequest.setCompanyId(COMPANY_ID); + clientCodeNewRequest.setCode(ccCode); + clientCodeNewRequest.setTradingClearingRegistryId(TCR_ID); + clientCodeNewRequest.setDepoAccountId(null); + clientCodeNewRequest.setMoneyAccountId(null); + clientCodeNewRequest.setStatus("ACTV"); + + ClientCode predictableClientCode = new ClientCode(); + predictableClientCode.setCode(ccCode); + predictableClientCode.setStatus("ACTV"); + predictableClientCode.setCompanyId(COMPANY_ID); +// predictableClientCode.setMoneyAccountId(131L); +// predictableClientCode.setDepoAccountId(132L); +// predictableClientCode.setTradingClearingRegistryId(TCR_ID); + + //ACT + String jsonString = getJsonStringForNew(clientCodeNewRequest, ID); + + addRecordToKafka((MockConsumer) clientCodeService.getConsumer(), Consts.DESTINATION_CLIENT_CODE_NEW_UM_COMPANY, PARTITION, 0, jsonString); + + //ASSERT + waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); + ClientCode resultNew = clientCodeImdg.getSingleObjectBySQL(String.format("code = '%s'", ccCode)); + predictableClientCode.setId(resultNew.getId()); + CLIENT_CODE_MATCHER.assertMatch(resultNew, predictableClientCode); + assertNotNull(resultNew.getCreated()); + } + + //todo добавить тест NEW заполненными MoneyAccountId(131L), DepoAccountId(132L); - от этого направляется дополнительное сообщение в очередь и используется ожидание ответа. + + /** + * {@link ClientCodeService#clientCodeUpdate(BaseRequest)}
+ * Тест проверяет обновление сущности {@link ClientCode} в IMDG при передаче из Apache Kafka.
+ * Входной запрос {@link ru.spcex.clearing.platform.messaging.domain.cud.account.ClientCodeUpdateRequest}:
+ **/ + @Test + void clientCodeUpdate() { + //ARRANGE + ClientCode existsClientCode = new ClientCode(); + existsClientCode.setId(ID); + existsClientCode.setCompanyId(COMPANY_ID); + existsClientCode.setCode("0000"); + existsClientCode.setTradingClearingRegistryId(TCR_ID); + existsClientCode.setMoneyAccountId(131L); + existsClientCode.setDepoAccountId(132L); + existsClientCode.setStatus("ACTV"); + clientCodeImdg.insert(existsClientCode); + + ClientCodeUpdateRequest clientCodeUpdateRequest = new ClientCodeUpdateRequest(); + clientCodeUpdateRequest.setId(ID); + clientCodeUpdateRequest.setCompanyId(COMPANY_ID); + clientCodeUpdateRequest.setCode("1111"); + clientCodeUpdateRequest.setTradingClearingRegistryId(TCR_ID); + clientCodeUpdateRequest.setMoneyAccountId(131L); + clientCodeUpdateRequest.setDepoAccountId(132L); + clientCodeUpdateRequest.setStatus("ACTV"); + + ClientCode predictableClientCode = new ClientCode(); + predictableClientCode.setId(ID); + predictableClientCode.setCompanyId(COMPANY_ID); + predictableClientCode.setCode("1111"); + predictableClientCode.setTradingClearingRegistryId(TCR_ID); + predictableClientCode.setMoneyAccountId(131L); + predictableClientCode.setDepoAccountId(132L); + predictableClientCode.setStatus("ACTV"); + + //ACT + String jsonString = getJsonStringForUpdate(clientCodeUpdateRequest, ID); + + addRecordToKafka((MockConsumer) clientCodeService.getConsumer(), Consts.DESTINATION_CLIENT_CODE_UPDATE, PARTITION, 0, jsonString); + + //ASSERT + waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); + + ClientCode resultUpdating = clientCodeImdg.getSingleObjectByID(ID); + CLIENT_CODE_MATCHER.assertMatch(resultUpdating, predictableClientCode); + assertNotNull(resultUpdating.getUpdated()); + } + + + /** + * {@link ClientCodeService#clientCodeUpdate(BaseRequest)}
+ * Тест проверяет удаление {@link ClientCode} из IMDG при передаче из Apache Kafka.
+ * Входной запрос {@link ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest}:
+ **/ + @Test + void clientCodeDelete1() { + //ARRANGE + ClientCode existsClientCode = new ClientCode(); + existsClientCode.setId(ID); + existsClientCode.setCompanyId(COMPANY_ID); + existsClientCode.setCode("0000"); + // Если следующие поля заполнить, то дополнительно отправит сообщение в trading-clearing-registry-update: + existsClientCode.setTradingClearingRegistryId(null); + existsClientCode.setMoneyAccountId(null); + existsClientCode.setDepoAccountId(null); + existsClientCode.setStatus("ACTV"); + + clientCodeImdg.insert(existsClientCode); + + CommonDeleteRequest clientCodeDeleteRequest = new CommonDeleteRequest(); + clientCodeDeleteRequest.setId(ID); + + Assertions.assertNotNull(clientCodeImdg.getSingleObjectByID(ID)); // verify test data + + //ACT + String jsonString = getJsonStringForUpdate(clientCodeDeleteRequest, ID); + + addRecordToKafka((MockConsumer) clientCodeService.getConsumer(), Consts.DESTINATION_CLIENT_CODE_DELETE, PARTITION, 0, jsonString); + + //ASSERT + + waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); + ClientCode resultUpdate = clientCodeImdg.getSingleObjectByID(ID); + Assertions.assertNull(resultUpdate); + } + +// /** +// * {@link ClientCodeService#clientCodeUpdate(BaseRequest)}
+// * Тест проверяет удаление {@link ClientCode} из IMDG при передаче из Apache Kafka.
+// * У ClientCode заполнены MoneyAccountId, DepoAccountId - по этому при удалении должно направиться дополнительное сообщение в очередь DESTINATION_TRADING_CLEARING_REGISTRY_UPDATE
+// * Входной запрос {@link ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest}:
+// **/ +// @Test +// void clientCodeDelete2() { +// //ARRANGE +// ClientCode existsClientCode = new ClientCode(); +// existsClientCode.setId(ID); +// existsClientCode.setCompanyId(COMPANY_ID); +// existsClientCode.setCode("0000"); +// // Если следующие поля заполнить, то дополнительно отправит сообщение в trading-clearing-registry-update: +// existsClientCode.setTradingClearingRegistryId(TCR_ID); +// existsClientCode.setMoneyAccountId(131L); +// existsClientCode.setDepoAccountId(132L); +// existsClientCode.setStatus("ACTV"); +// +// clientCodeImdg.insert(existsClientCode); +// +// CommonDeleteRequest clientCodeDeleteRequest = new CommonDeleteRequest(); +// clientCodeDeleteRequest.setId(ID); +// +// Assertions.assertNotNull(clientCodeImdg.getSingleObjectByID(ID)); // verify test data +// +// //ACT +// String jsonString = getJsonStringForUpdate(clientCodeDeleteRequest, ID); +// +// addRecordToKafka((MockConsumer) clientCodeService.getConsumer(), Consts.DESTINATION_CLIENT_CODE_DELETE, PARTITION, 0, jsonString); +// +// //ASSERT +// +// waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); +// ClientCode resultUpdate = clientCodeImdg.getSingleObjectByID(ID); +// Assertions.assertNull(resultUpdate); +// } + +} \ No newline at end of file diff --git a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyInfoServiceTest.java b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyInfoServiceTest.java index d823d4ab6..2735eff4c 100644 --- a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyInfoServiceTest.java +++ b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyInfoServiceTest.java @@ -18,6 +18,7 @@ import ru.clearing.platform.dictionary.*; import ru.spcex.clearing.company.config.BeanConfiguration; import ru.spcex.clearing.company.config.validation.CompanySymbolValidationConfig; import ru.spcex.clearing.company.config.validation.CompanyValidationConfig; +import ru.spcex.clearing.company.config.validation.RelationValidationConfig; import ru.spcex.clearing.company.config.validation.ValidationConfig; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; @@ -50,7 +51,8 @@ import static ru.spcex.clearing.test.config.ImdgTestConfig.waitAvailableImdgProv ValidationConfig.class, CompanySymbolService.class, AccountNotificationHelper.class, - RelationHelper.class, + RelationService.class, + RelationValidationConfig.class, BeanConfiguration.class, KafkaTestConfig.class, diff --git a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyServiceTest.java b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyServiceTest.java index b80446af5..e6ec57333 100644 --- a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyServiceTest.java +++ b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanyServiceTest.java @@ -21,6 +21,7 @@ import ru.clearing.platform.dictionary.WorkflowStatusDictionary; import ru.spcex.clearing.company.config.BeanConfiguration; import ru.spcex.clearing.company.config.validation.CompanySymbolValidationConfig; import ru.spcex.clearing.company.config.validation.CompanyValidationConfig; +import ru.spcex.clearing.company.config.validation.RelationValidationConfig; import ru.spcex.clearing.company.config.validation.ValidationConfig; import ru.spcex.clearing.company.error.CompanyErrors; import ru.spcex.clearing.imdg.IMDGDistributedNames; @@ -54,7 +55,7 @@ import static ru.spcex.clearing.test.config.ImdgTestConfig.waitAvailableImdgProv ValidationConfig.class, CompanySymbolService.class, AccountNotificationHelper.class, - RelationHelper.class, + RelationService.class, RelationValidationConfig.class, CompanySymbolService.class, CompanySymbolValidationConfig.class, diff --git a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanySymbolServiceTest.java b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanySymbolServiceTest.java index e247fb26a..f9dee4531 100644 --- a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanySymbolServiceTest.java +++ b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/CompanySymbolServiceTest.java @@ -12,11 +12,13 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; +import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.company.CompanySymbols; import ru.clearing.platform.dictionary.CompanySymbolDictionary; import ru.spcex.clearing.company.config.BeanConfiguration; import ru.spcex.clearing.company.config.validation.CompanySymbolValidationConfig; import ru.spcex.clearing.company.config.validation.CompanyValidationConfig; +import ru.spcex.clearing.company.config.validation.RelationValidationConfig; import ru.spcex.clearing.company.config.validation.ValidationConfig; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; @@ -49,7 +51,7 @@ import static ru.spcex.clearing.test.config.ImdgTestConfig.waitAvailableImdgProv CompanyService.class, CompanyValidationConfig.class, AccountNotificationHelper.class, - RelationHelper.class, + RelationService.class, RelationValidationConfig.class, KafkaTestConfig.class, ImdgTestConfig.class}) @@ -92,6 +94,14 @@ class CompanySymbolServiceTest { cioSymbol.setName(CompanySymbol.CIO.getKey()); companySymbolDictionaryImdg.insert(cioSymbol); } + Imdg companyImdg = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_Company, Company.class); + { + Company company = new Company(); + company.setId(ID); + company.setShortName("TST"); + company.setWorkflowStatus("ACTV"); + companyImdg.insert(company); + } TestUtils.FutureRecordMetadata future = spy(TestUtils.FutureRecordMetadata.class); diff --git a/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/RelationServiceTest.java b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/RelationServiceTest.java new file mode 100644 index 000000000..8bebb24f7 --- /dev/null +++ b/clearing-parent/company-service/src/test/java/ru/spcex/clearing/company/service/RelationServiceTest.java @@ -0,0 +1,214 @@ +package ru.spcex.clearing.company.service; + +import org.apache.kafka.clients.consumer.MockConsumer; +import org.apache.kafka.clients.producer.MockProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.company.relation.Relation; +import ru.clearing.platform.dictionary.ClearingCategoryDictionary; +import ru.clearing.platform.dictionary.WorkflowStatusDictionary; +import ru.spcex.clearing.company.config.BeanConfiguration; +import ru.spcex.clearing.company.config.validation.RelationValidationConfig; +import ru.spcex.clearing.company.config.validation.ValidationConfig; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +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.company.RelationNewRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.company.RelationUpdateRequest; +import ru.spcex.clearing.test.MatcherFactory; +import ru.spcex.clearing.test.TestUtils; +import ru.spcex.clearing.test.config.ImdgTestConfig; +import ru.spcex.clearing.test.config.KafkaTestConfig; +import ru.spcex.platform.enumeration.ClearingCategory; +import ru.spcex.platform.enumeration.WorkflowStatus; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +import javax.annotation.PostConstruct; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static ru.spcex.clearing.test.MatcherFactory.usingIgnoringFieldsComparator; +import static ru.spcex.clearing.test.TestUtils.*; +import static ru.spcex.clearing.test.config.ImdgTestConfig.currentID; +import static ru.spcex.clearing.test.config.ImdgTestConfig.waitAvailableImdgProviderAndAddAdminWithDefaultId; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { + BeanConfiguration.class, + ValidationConfig.class, + RelationService.class, + AccountNotificationHelper.class, + RelationValidationConfig.class, + KafkaTestConfig.class, + ImdgTestConfig.class}) +class RelationServiceTest { + + public static final MatcherFactory.Matcher RELATION_MATCHER = usingIgnoringFieldsComparator("created", "updated"); + private static final int PARTITION = 0; + + private static final Long ID = currentID.getAndIncrement(); + @Autowired + RelationService relationService; + @Autowired + @Qualifier("hazelcastServiceTest") + private ImdgProvider hazelcastServiceTest; + private Imdg relationMap; + private Imdg companyMap; + + @Captor + private ArgumentCaptor producerRecord; + @SpyBean + private MockProducer mockProducer; + + private long COMPANY_ID; + + private final long RELATION_ID = ID; + + @PostConstruct + private void init() { + waitAvailableImdgProviderAndAddAdminWithDefaultId(); + this.relationMap = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_Relation, Relation.class); + this.companyMap = hazelcastServiceTest.getImdg(IMDGDistributedNames.Map_Company, Company.class); + + Company testCompany = new Company(); + testCompany.setWorkflowStatus(WorkflowStatus.Active.getKey()); + COMPANY_ID = companyMap.insert(testCompany); + + Imdg workflowStatusDictionaryImdg = hazelcastServiceTest.getImdg( + IMDGDistributedNames.Map_WorkflowStatusDictionary, + WorkflowStatusDictionary.class + ); + WorkflowStatusDictionary workflowStatusDictionary = new WorkflowStatusDictionary(); + workflowStatusDictionary.setId(1L); + workflowStatusDictionary.setCode(WorkflowStatus.Active.getKey()); + workflowStatusDictionary.setName("active"); + workflowStatusDictionaryImdg.insert(workflowStatusDictionary); + workflowStatusDictionary = new WorkflowStatusDictionary(); + workflowStatusDictionary.setId(2L); + workflowStatusDictionary.setCode(WorkflowStatus.Blocked.getKey()); + workflowStatusDictionary.setName("not active"); + workflowStatusDictionaryImdg.insert(workflowStatusDictionary); + + Imdg clearingCategoryDictionaryImdg = hazelcastServiceTest.getImdg( + IMDGDistributedNames.Map_ClearingCategoryDictionary, + ClearingCategoryDictionary.class + ); + ClearingCategoryDictionary clearingCategoryDictionary = new ClearingCategoryDictionary(); + clearingCategoryDictionary.setId(1L); + clearingCategoryDictionary.setCode(ClearingCategory.I.getKey()); + clearingCategoryDictionary.setName("I"); + clearingCategoryDictionaryImdg.insert(clearingCategoryDictionary); + + TestUtils.FutureRecordMetadata future = spy(TestUtils.FutureRecordMetadata.class); + doReturn(future).when(mockProducer).send(producerRecord.capture()); + } + + + @Test + void newRelation() { + //ARRANGE + RelationNewRequest relationNewRequest = new RelationNewRequest(); + relationNewRequest.setCompanyId(COMPANY_ID); + relationNewRequest.setServiceStatus("ACTV"); + relationNewRequest.setClearingMemberCategory("I"); + relationNewRequest.setComment(null); + + Relation predictableRelation = new Relation(); + predictableRelation.setId(RELATION_ID); + predictableRelation.setConsumerId(relationNewRequest.companyId); + predictableRelation.setService("MKR"); + predictableRelation.setServiceProduct("ZERO"); + predictableRelation.setSupplierId(1L); + predictableRelation.setServiceStatus("ACTV"); + + //ACT + String jsonString = getJsonStringForNew(relationNewRequest, ID); + + addRecordToKafka((MockConsumer) relationService.getConsumer(), Consts.DESTINATION_RELATION_NEW, PARTITION, 0, jsonString); + + //ASSERT + waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); + Relation resultNew = relationMap.getSingleObjectBySQL("serviceStatus=ACTV"); // String.format("supplierId = %d", COMPANY_ID)); + predictableRelation.setId(resultNew.getId()); + RELATION_MATCHER.assertMatch(resultNew, predictableRelation); + } + + @Test + void updateRelation() { + //ARRANGE + Relation existsRelation = new Relation(); + existsRelation.setId(ID); + existsRelation.setConsumerId(COMPANY_ID); + existsRelation.setSupplierId(1L); + existsRelation.setComment("Test ONE"); + existsRelation.setServiceStatus("ACTV"); + existsRelation.setServiceProduct("PROD"); + existsRelation.setService("MKR"); + relationMap.insert(existsRelation); + + Relation predictableRelation = new Relation(); + predictableRelation.setId(ID); + predictableRelation.setConsumerId(COMPANY_ID); + predictableRelation.setSupplierId(1L); + predictableRelation.setComment("Another Me - ONE"); + predictableRelation.setServiceStatus("ACTV"); + predictableRelation.setServiceProduct("PROD"); + predictableRelation.setService("MKR"); + + RelationUpdateRequest relationUpdateRequest = new RelationUpdateRequest(); + relationUpdateRequest.setId(RELATION_ID); + relationUpdateRequest.setComment("Another Me - ONE"); + relationUpdateRequest.setServiceStatus("ACTV"); + + //ACT + String jsonString = getJsonStringForUpdate(relationUpdateRequest, ID); + + addRecordToKafka((MockConsumer) relationService.getConsumer(), Consts.DESTINATION_RELATION_UPDATE, PARTITION, 0, jsonString); + + //ASSERT + waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); + Relation resultUpdate = relationMap.getSingleObjectBySQL(String.format("id = %d", RELATION_ID)); +// predictableProfileDocument.setId(resultUpdate.getId()); + RELATION_MATCHER.assertMatch(resultUpdate, predictableRelation); + } + + + @Test + void deleteProfileDocument() { + //ARRANGE + Relation existsRelation = new Relation(); + existsRelation.setId(ID); + existsRelation.setConsumerId(COMPANY_ID); + existsRelation.setSupplierId(1L); + existsRelation.setComment("Test ONE"); + existsRelation.setServiceStatus("ACTV"); + existsRelation.setServiceProduct("PROD"); + existsRelation.setService("MKR"); + relationMap.insert(existsRelation); + + CommonDeleteRequest profileDocumentDeleteRequest = new CommonDeleteRequest(); + profileDocumentDeleteRequest.setId(RELATION_ID); + + //ACT + String jsonString = getJsonStringForUpdate(profileDocumentDeleteRequest, ID); + + addRecordToKafka((MockConsumer) relationService.getConsumer(), Consts.DESTINATION_RELATION_DELETE, PARTITION, 0, jsonString); + + //ASSERT + waitingWhenAddedRecordAndCheckIt(ID, mockProducer, producerRecord); + Relation resultUpdate = relationMap.getSingleObjectByID(RELATION_ID); +// Assertions.assertNull(resultUpdate); + assertEquals(WorkflowStatus.Blocked.getKey(), resultUpdate.getServiceStatus()); + } +} \ No newline at end of file diff --git a/clearing-parent/db-scripts/src/main/resources/db/DATA.sql b/clearing-parent/db-scripts/src/main/resources/db/DATA.sql index 20a64eaaa..63716ecd2 100644 --- a/clearing-parent/db-scripts/src/main/resources/db/DATA.sql +++ b/clearing-parent/db-scripts/src/main/resources/db/DATA.sql @@ -1,4 +1,4 @@ --- DB version: 3.5.0.20 +-- DB version: 3.5.0.21 -- DATA version: 3.5.0.4 /* Dictionaries */ @@ -160,7 +160,7 @@ INSERT INTO COMPANY_SYMBOL_DICTIONARY(ID, CODE, NAME, SHORTNAME) values (17, 'RG INSERT INTO COMPANY_SYMBOL_DICTIONARY(ID, CODE, NAME, SHORTNAME) values (18, 'UUID', 'Идентификатор во внешней системе', 'Внешний идентификатор') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, SHORTNAME = EXCLUDED.SHORTNAME; -INSERT INTO COMPANY_SYMBOL_DICTIONARY(ID, CODE, NAME, SHORTNAME) values (19, 'RDPZ', 'Требуется получение документа о подтверждении открытия депозитного счета', 'Подтверждение депозитного счета') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, SHORTNAME = EXCLUDED.SHORTNAME; +INSERT INTO COMPANY_SYMBOL_DICTIONARY(ID, CODE, NAME, SHORTNAME) values (19, 'LICM', 'Лицензия на управление инвестиционными фондами, паевыми инвестиционными фондами, негосударственными пенсионными фондами', 'Лицензия на управление фондами') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME, SHORTNAME = EXCLUDED.SHORTNAME; INSERT INTO COMPANY_ROLE_DICTIONARY(ID, CODE, NAME) values (1, 'RPRT', 'Отчетная организация') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; @@ -394,7 +394,7 @@ INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (12, 'CMBA', 'Формиро INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (14, 'GCMR', 'Формирование реестра участников клиринга') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; -INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (15, 'GBRR', 'Формирование реестра остатков денежных средств ') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; +INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (15, 'GBRR', 'Формирование реестра остатков денежных средств') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (16, 'GORR', 'Формирование реестра распоряжений, направленных расчетной организации') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; @@ -414,9 +414,9 @@ INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (36, 'SPRC', 'Запуск п INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (37, 'SPOC', 'Запуск постклиринга') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; -INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (38, 'LIMM', 'Выгрузка в торговую систему остатков секции МКР') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; +INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (38, 'LIMM', 'Выгрузка в торговую систему остатков по деньгам') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; -INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (39, 'LIMF', 'Выгрузка в торговую систему остатков Фондовой секции') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; +INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (39, 'LIMS', 'Выгрузка в торговую систему остатков по бумагам') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; INSERT INTO TASK_DICTIONARY(ID, CODE, NAME) values (40, 'LIQU', 'Ликвидационная сессия по обязательтсвам участника') ON CONFLICT (ID) DO UPDATE SET CODE = EXCLUDED.CODE, NAME = EXCLUDED.NAME; diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/SchedulerServiceImdgConfig.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/ImdgConfig.java similarity index 97% rename from clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/SchedulerServiceImdgConfig.java rename to clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/ImdgConfig.java index f9d5da568..69fea82d7 100644 --- a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/SchedulerServiceImdgConfig.java +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/ImdgConfig.java @@ -10,7 +10,7 @@ import ru.spcex.platform.imdg.api.ImdgProvider; import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService; @Configuration -public class SchedulerServiceImdgConfig { +public class ImdgConfig { private static ThreadPoolTaskExecutor createThreadPoolTaskExecutor(int maxPoolSz, boolean waitForCompletion) { ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor(); if (maxPoolSz > 2) { diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/SchedulerServiceConfig.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/SchedulerServiceConfig.java new file mode 100644 index 000000000..4e9c619c0 --- /dev/null +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/SchedulerServiceConfig.java @@ -0,0 +1,17 @@ +package ru.spcex.clearing.scheduler.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +@Configuration +public class SchedulerServiceConfig { + @Bean(name = "taskScheduler") + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); + threadPoolTaskScheduler.setPoolSize(5); + threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler"); + return threadPoolTaskScheduler; + } +} diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/ClearingCalendarValidationConfig.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/ClearingCalendarValidationConfig.java index 3d7327714..237040622 100644 --- a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/ClearingCalendarValidationConfig.java +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/ClearingCalendarValidationConfig.java @@ -43,6 +43,7 @@ public class ClearingCalendarValidationConfig { IMDGDistributedNames.Map_Company, Company.class, ValidationError.CompanyNotFound, + false, company -> WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()) ? null : ValidationError.CompanyNotActive), DictionaryPresentRule.instance("dayStatus", ClearingCalendarNewRequest::getDayStatus, @@ -74,6 +75,7 @@ public class ClearingCalendarValidationConfig { IMDGDistributedNames.Map_Company, Company.class, ValidationError.CompanyNotFound, + false, company -> WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()) ? null : ValidationError.CompanyNotActive), DictionaryPresentRule.instance("dayStatus", ClearingCalendarUpdateRequest::getDayStatus, diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerTemplateValidationConfig.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerTemplateValidationConfig.java index e0390eab6..77adaefcc 100644 --- a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerTemplateValidationConfig.java +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerTemplateValidationConfig.java @@ -45,7 +45,9 @@ public class PlannerTemplateValidationConfig { IMDGDistributedNames.Map_TaskDictionary, TaskDictionary.class), TimeNotBeforeRule.instance("taskTime", - PlannerTemplateNewRequest::getTaskTime), + PlannerTemplateNewRequest::getTaskTime, + true, + false), DictionaryPresentRule.instance("taskStatus", PlannerTemplateNewRequest::getTaskStatus, IMDGDistributedNames.Map_TaskStatusDictionary, @@ -55,12 +57,14 @@ public class PlannerTemplateValidationConfig { IMDGDistributedNames.Map_Company, Company.class, ValidationError.CompanyNotFound, + false, company -> WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()) ? null : ValidationError.CompanyNotActive), IdPresentRule.instance("securityId", PlannerTemplateNewRequest::getSecurityId, IMDGDistributedNames.Map_Security, Security.class, ValidationError.SecurityNotFound, + false, security -> WorkflowStatus.Active.equalsByKey(security.getWorkflowStatus()) ? null : ValidationError.SecurityNotActive) ); }; @@ -90,6 +94,7 @@ public class PlannerTemplateValidationConfig { false), TimeNotBeforeRule.instance("taskTime", PlannerTemplateUpdateRequest::getTaskTime, + false, false), DictionaryPresentRule.instance("taskStatus", PlannerTemplateUpdateRequest::getTaskStatus, diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerValidationConfig.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerValidationConfig.java index c60e0d628..fa6e186af 100644 --- a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerValidationConfig.java +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/config/validation/PlannerValidationConfig.java @@ -62,12 +62,14 @@ public class PlannerValidationConfig { IMDGDistributedNames.Map_Company, Company.class, ValidationError.CompanyNotFound, + false, company -> WorkflowStatus.Active.equalsByKey(company.getWorkflowStatus()) ? null : ValidationError.CompanyNotActive), IdPresentRule.instance("securityId", PlannerNewRequest::getSecurityId, IMDGDistributedNames.Map_Security, Security.class, ValidationError.SecurityNotFound, + false, security -> WorkflowStatus.Active.equalsByKey(security.getWorkflowStatus()) ? null : ValidationError.SecurityNotActive) ); }; diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TaskManager.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TaskManager.java index 9aed3335e..2f0719507 100644 --- a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TaskManager.java +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TaskManager.java @@ -48,7 +48,7 @@ public class TaskManager implements InitializingBean, AutoCloseable { private final ExecutorService outputExecutor; @Autowired - TaskManager(TaskScheduler taskScheduler, + TaskManager(@Qualifier("taskScheduler") TaskScheduler taskScheduler, ImdgProvider imdgProvider, LauncherSender launcherSender, @Qualifier("plannerQueue") BlockingQueue> plannerQueue) { diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/validation/rules/common/EnumPresentRule.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/validation/rules/common/EnumPresentRule.java index a8ab3cd4f..39e53a19a 100644 --- a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/validation/rules/common/EnumPresentRule.java +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/validation/rules/common/EnumPresentRule.java @@ -55,6 +55,6 @@ public record EnumPresentRule(String fieldName, for (E enumValue : enumValues) { if (enumValue.getKey().equals(enumCode)) return empty(); } - return of(ValidationError.WrongEnumValue, fieldName); + return of(ValidationError.WrongDictionaryValue, fieldName); } } diff --git a/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/AbstractServiceTest.java b/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/AbstractServiceTest.java index c2a6b2173..51e4f577f 100644 --- a/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/AbstractServiceTest.java +++ b/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/AbstractServiceTest.java @@ -19,7 +19,7 @@ import ru.clearing.platform.dictionary.TaskStatusDictionary; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.scheduler.config.ErrorResolverConfig; import ru.spcex.clearing.scheduler.config.PlannerQueueConfig; -import ru.spcex.clearing.scheduler.config.SchedulerTestConfig; +import ru.spcex.clearing.scheduler.config.SchedulerServiceConfig; import ru.spcex.clearing.scheduler.config.validation.ClearingCalendarValidationConfig; import ru.spcex.clearing.scheduler.config.validation.PlannerTemplateValidationConfig; import ru.spcex.clearing.scheduler.config.validation.PlannerValidationConfig; @@ -60,7 +60,7 @@ import static ru.spcex.platform.enumeration.Market.mkrs; LauncherService.class, ErrorResolverConfig.class, PlannerQueueConfig.class, - SchedulerTestConfig.class, + SchedulerServiceConfig.class, ImdgTestConfig.class, KafkaTestConfig.class}) public abstract class AbstractServiceTest { diff --git a/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/validation/EnumPresentRuleTest.java b/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/validation/EnumPresentRuleTest.java index 5d039d6aa..cfc803a26 100644 --- a/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/validation/EnumPresentRuleTest.java +++ b/clearing-parent/scheduler-service/src/test/java/ru/spcex/clearing/scheduler/validation/EnumPresentRuleTest.java @@ -85,7 +85,7 @@ class EnumPresentRuleTest extends AbstractServiceTest { setter.accept(record, "BAD_VALUE"); enumMessages = strictValidator.validateAll(); assertEquals(1, enumMessages.size()); - assertEquals(ValidationError.WrongEnumValue, enumMessages.iterator().next().getSubject()); + assertEquals(ValidationError.WrongDictionaryValue, enumMessages.iterator().next().getSubject()); } } \ No newline at end of file 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 c6c9f0d28..2d0f0cae6 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 @@ -72,18 +72,22 @@ public interface Consts { @Deprecated String ACCOUNT_NEW_SDF01 = "account-new-sdf01"; + String DESTINATION_RELATION_NEW = "relation-new"; String DESTINATION_RELATION_UPDATE = "relation-update"; + String DESTINATION_RELATION_DELETE = "relation-delete"; String DESTINATION_PROFILE_DOCUMENT_NEW = "profile-document-new"; String DESTINATION_PROFILE_DOCUMENT_UPDATE = "profile-document-update"; String DESTINATION_PROFILE_DOCUMENT_DELETE = "profile-document-delete"; String DESTINATION_CLIENT_CODE_NEW = "client-code-new"; + String DESTINATION_CLIENT_CODE_NEW_UM_COMPANY = "client-code-new-from-api-um-company"; String DESTINATION_CLIENT_CODE_UPDATE = "client-code-update"; String DESTINATION_CLIENT_CODE_DELETE = "client-code-delete"; String DESTINATION_TRADING_CLEARING_REGISTRY_NEW = "trading-clearing-registry-new"; String DESTINATION_TRADING_CLEARING_REGISTRY_UPDATE = "trading-clearing-registry-update"; String DESTINATION_TRADING_CLEARING_REGISTRY_DELETE = "trading-clearing-registry-delete"; +// String DESTINATION_TRADING_CLEARING_REGISTRY_REPLY = "trading-clearing-registry"; String DESTINATION_SDF08_NEW = "s-df-08-new"; String DESTINATION_SDF02_NEW = "s-df-02-new"; diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/company/RelationNewRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/company/RelationNewRequest.java new file mode 100644 index 000000000..9e630303a --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/company/RelationNewRequest.java @@ -0,0 +1,46 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.company; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class RelationNewRequest { + @JsonProperty + public Long companyId; + @JsonProperty + private String serviceStatus; + @JsonProperty + private String clearingMemberCategory; + @JsonProperty + private String comment; + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getServiceStatus() { + return serviceStatus; + } + + public void setServiceStatus(String serviceStatus) { + this.serviceStatus = serviceStatus; + } + + public String getClearingMemberCategory() { + return clearingMemberCategory; + } + + public void setClearingMemberCategory(String clearingMemberCategory) { + this.clearingMemberCategory = clearingMemberCategory; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } +} diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/registry/TradingClearingRegistryNewRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/registry/TradingClearingRegistryNewRequest.java index 75041b81e..c68eed1ba 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/registry/TradingClearingRegistryNewRequest.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/registry/TradingClearingRegistryNewRequest.java @@ -21,6 +21,8 @@ public class TradingClearingRegistryNewRequest { private Long depoAccountId; @JsonProperty private String status; + @JsonProperty + private String tradingClearingRegistryType; public Long getCompanyId() { return companyId; @@ -50,6 +52,14 @@ public class TradingClearingRegistryNewRequest { return status; } + public String getTradingClearingRegistryType() { + return tradingClearingRegistryType; + } + + public void setTradingClearingRegistryType(String tradingClearingRegistryType) { + this.tradingClearingRegistryType = tradingClearingRegistryType; + } + public void setStatus(String status) { this.status = status; } diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/STradesImportedRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/STradesImportedRequest.java index 348527aae..30383c910 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/STradesImportedRequest.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/STradesImportedRequest.java @@ -1,4 +1,16 @@ package ru.spcex.clearing.platform.messaging.domain.cud.utilities; +import com.fasterxml.jackson.annotation.JsonProperty; + public class STradesImportedRequest { + @JsonProperty + private Long tradeNum; + + public Long getTradeNum() { + return tradeNum; + } + + public void setTradeNum(Long tradeNum) { + this.tradeNum = tradeNum; + } } diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/service/QueueConsumer.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/service/QueueConsumer.java index 0b26d5c03..380250d37 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/service/QueueConsumer.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/service/QueueConsumer.java @@ -74,6 +74,7 @@ public class QueueConsumer implements AutoCloseable { public void init() { inputExecutor.submit(() -> { try { + log.debug("Using kafka consumer {} for subscribe on \"{}\"", consumer, callbacks.keySet()); if (supportStartOffsetTimeWindow) { consumer.subscribe(callbacks.keySet(), new OffsetChanger(consumer, callbacks.keySet())); } else { @@ -82,9 +83,11 @@ public class QueueConsumer implements AutoCloseable { Object o = null; int lastErrors = 0; while (!closed.get()) { + String lastTopic = null; try { ConsumerRecords records = consumer.poll(Duration.of(10, ChronoUnit.SECONDS)); for (ConsumerRecord next : records) { + lastTopic = next.topic(); ConsumerSpecificClass callback = callbacks.get(next.topic()); Class clazz = callback.getClazz(); JavaType payloadType = json.getTypeFactory().constructParametricType(BaseRequest.class, clazz); @@ -98,7 +101,8 @@ public class QueueConsumer implements AutoCloseable { } lastErrors = 0; } catch (Throwable e) { - log.error(ExceptionUtils.getStackTrace(e)); + log.error("Listener {} last topic \"{}\", error: {}", + QueueConsumer.this.getClass().getName(), lastTopic, ExceptionUtils.getStackTrace(e)); if (producer != null && o != null) { sendErrorResponse((BaseRequest) o); }