FINERACT-1972 Custom Snapshot Event Triggered by COB
This commit is contained in:
committed by
Peter Bagrij
parent
4cb8946bc1
commit
8b379695d7
+2
@@ -23,5 +23,7 @@ public final class FineractProfiles {
|
|||||||
public static final String LIQUIBASE_ONLY = "liquibase-only";
|
public static final String LIQUIBASE_ONLY = "liquibase-only";
|
||||||
public static final String DIAGNOSTICS = "diagnostics";
|
public static final String DIAGNOSTICS = "diagnostics";
|
||||||
|
|
||||||
|
public static final String TEST = "test";
|
||||||
|
|
||||||
private FineractProfiles() {}
|
private FineractProfiles() {}
|
||||||
}
|
}
|
||||||
|
|||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.infrastructure.event.external.api;
|
||||||
|
|
||||||
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.DELETE;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.InternalExternalEventService;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Profile(FineractProfiles.TEST)
|
||||||
|
@Component
|
||||||
|
@Path("/v1/internal/externalevents")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class InternalExternalEventsApiResource implements InitializingBean {
|
||||||
|
|
||||||
|
private final InternalExternalEventService internalExternalEventService;
|
||||||
|
private final DefaultToApiJsonSerializer<List<ExternalEventDTO>> jsonSerializer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
log.warn("------------------------------------------------------------");
|
||||||
|
log.warn(" ");
|
||||||
|
log.warn("DO NOT USE THIS IN PRODUCTION!");
|
||||||
|
log.warn("Internal client services mode is enabled");
|
||||||
|
log.warn("DO NOT USE THIS IN PRODUCTION!");
|
||||||
|
log.warn(" ");
|
||||||
|
log.warn("------------------------------------------------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
public String getAllExternalEvents(@QueryParam("idempotencyKey") final String idempotencyKey, @QueryParam("type") final String type,
|
||||||
|
@QueryParam("category") final String category, @QueryParam("aggregateRootId") final Long aggregateRootId) {
|
||||||
|
log.debug("getAllExternalEvents called with params idempotencyKey:{}, type:{}, category:{}, aggregateRootId:{} ", idempotencyKey,
|
||||||
|
type, category, aggregateRootId);
|
||||||
|
List<ExternalEventDTO> allExternalEvents = internalExternalEventService.getAllExternalEvents(idempotencyKey, type, category,
|
||||||
|
aggregateRootId);
|
||||||
|
return jsonSerializer.serialize(allExternalEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
public void deleteAllExternalEvents() {
|
||||||
|
log.debug("deleteAllExternalEvents called");
|
||||||
|
internalExternalEventService.deleteAllExternalEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-1
@@ -26,11 +26,12 @@ import org.apache.fineract.infrastructure.event.external.repository.domain.Exter
|
|||||||
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventView;
|
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventView;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
import org.springframework.data.jpa.repository.Modifying;
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
public interface ExternalEventRepository extends JpaRepository<ExternalEvent, Long> {
|
public interface ExternalEventRepository extends JpaRepository<ExternalEvent, Long>, JpaSpecificationExecutor<ExternalEvent> {
|
||||||
|
|
||||||
List<ExternalEventView> findByStatusOrderById(ExternalEventStatus status, Pageable batchSize);
|
List<ExternalEventView> findByStatusOrderById(ExternalEventStatus status, Pageable batchSize);
|
||||||
|
|
||||||
|
|||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.infrastructure.event.external.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.avro.BulkMessageItemV1;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Profile(FineractProfiles.TEST)
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class InternalExternalEventService {
|
||||||
|
|
||||||
|
private final ExternalEventRepository externalEventRepository;
|
||||||
|
|
||||||
|
public void deleteAllExternalEvents() {
|
||||||
|
externalEventRepository.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ExternalEventDTO> getAllExternalEvents(String idempotencyKey, String type, String category, Long aggregateRootId) {
|
||||||
|
List<Specification<ExternalEvent>> specifications = new ArrayList<>();
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(idempotencyKey)) {
|
||||||
|
specifications.add(hasIdempotencyKey(idempotencyKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(type)) {
|
||||||
|
specifications.add(hasType(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(category)) {
|
||||||
|
specifications.add(hasCategory(category));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregateRootId != null) {
|
||||||
|
specifications.add(hasAggregateRootId(aggregateRootId));
|
||||||
|
}
|
||||||
|
|
||||||
|
Specification<ExternalEvent> reducedSpecification = specifications.stream().reduce(Specification::and)
|
||||||
|
.orElse((Specification<ExternalEvent>) (root, query, criteriaBuilder) -> null);
|
||||||
|
List<ExternalEvent> externalEvents = externalEventRepository.findAll(reducedSpecification);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return convertToReadableFormat(externalEvents);
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException
|
||||||
|
| JsonProcessingException e) {
|
||||||
|
throw new RuntimeException("Error while converting external events to readable format", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Specification<ExternalEvent> hasIdempotencyKey(String idempotencyKey) {
|
||||||
|
return (root, query, cb) -> cb.equal(root.get("idempotencyKey"), idempotencyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Specification<ExternalEvent> hasType(String type) {
|
||||||
|
return (root, query, cb) -> cb.equal(root.get("type"), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Specification<ExternalEvent> hasCategory(String category) {
|
||||||
|
return (root, query, cb) -> cb.equal(root.get("category"), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Specification<ExternalEvent> hasAggregateRootId(Long aggregateRootId) {
|
||||||
|
return (root, query, cb) -> cb.equal(root.get("aggregateRootId"), aggregateRootId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ExternalEventDTO> convertToReadableFormat(List<ExternalEvent> externalEvents) throws ClassNotFoundException,
|
||||||
|
NoSuchMethodException, InvocationTargetException, IllegalAccessException, JsonProcessingException {
|
||||||
|
List<ExternalEventDTO> eventMessages = new ArrayList<>();
|
||||||
|
for (ExternalEvent externalEvent : externalEvents) {
|
||||||
|
Class<?> payLoadClass = Class.forName(externalEvent.getSchema());
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.wrap(externalEvent.getData());
|
||||||
|
Method method = payLoadClass.getMethod("fromByteBuffer", ByteBuffer.class);
|
||||||
|
Object payLoad = method.invoke(null, byteBuffer);
|
||||||
|
if (externalEvent.getType().equalsIgnoreCase("BulkBusinessEvent")) {
|
||||||
|
Method methodToGetDatas = payLoad.getClass().getMethod("getDatas", (Class<?>) null);
|
||||||
|
List<BulkMessageItemV1> bulkMessages = (List<BulkMessageItemV1>) methodToGetDatas.invoke(payLoad);
|
||||||
|
StringBuilder bulkMessagePayload = new StringBuilder();
|
||||||
|
for (BulkMessageItemV1 bulkMessage : bulkMessages) {
|
||||||
|
ExternalEventDTO bulkMessageData = retrieveBulkMessage(bulkMessage, externalEvent);
|
||||||
|
bulkMessagePayload.append(bulkMessageData);
|
||||||
|
bulkMessagePayload.append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
eventMessages.add(new ExternalEventDTO(externalEvent.getId(), externalEvent.getType(), externalEvent.getCategory(),
|
||||||
|
externalEvent.getCreatedAt(), toJsonMap(bulkMessagePayload.toString()), externalEvent.getBusinessDate(),
|
||||||
|
externalEvent.getSchema(), externalEvent.getAggregateRootId()));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
eventMessages.add(new ExternalEventDTO(externalEvent.getId(), externalEvent.getType(), externalEvent.getCategory(),
|
||||||
|
externalEvent.getCreatedAt(), toJsonMap(payLoad.toString()), externalEvent.getBusinessDate(),
|
||||||
|
externalEvent.getSchema(), externalEvent.getAggregateRootId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExternalEventDTO retrieveBulkMessage(BulkMessageItemV1 messageItem, ExternalEvent externalEvent) throws ClassNotFoundException,
|
||||||
|
InvocationTargetException, IllegalAccessException, NoSuchMethodException, JsonProcessingException {
|
||||||
|
Class<?> messageBulkMessagePayLoad = Class.forName(messageItem.getDataschema());
|
||||||
|
Method methodForPayLoad = messageBulkMessagePayLoad.getMethod("fromByteBuffer", ByteBuffer.class);
|
||||||
|
Object payLoadBulkItem = methodForPayLoad.invoke(null, messageItem.getData());
|
||||||
|
return new ExternalEventDTO((long) messageItem.getId(), messageItem.getType(), messageItem.getCategory(),
|
||||||
|
externalEvent.getCreatedAt(), toJsonMap(payLoadBulkItem.toString()), externalEvent.getBusinessDate(),
|
||||||
|
externalEvent.getSchema(), externalEvent.getAggregateRootId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> toJsonMap(String json) throws JsonProcessingException {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
return objectMapper.readValue(json, new TypeReference<>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.infrastructure.event.external.service.validation;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class ExternalEventDTO {
|
||||||
|
|
||||||
|
private final Long eventId;
|
||||||
|
private final String type;
|
||||||
|
private final String category;
|
||||||
|
private final OffsetDateTime createdAt;
|
||||||
|
private final Map<String, Object> payLoad;
|
||||||
|
private final LocalDate businessDate;
|
||||||
|
private final String schema;
|
||||||
|
private final Long aggregateRootId;
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.infrastructure.event.business.domain.loan;
|
||||||
|
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
|
||||||
|
public class LoanAccountCustomSnapshotBusinessEvent extends LoanBusinessEvent {
|
||||||
|
|
||||||
|
private static final String TYPE = "LoanAccountCustomSnapshotBusinessEvent";
|
||||||
|
|
||||||
|
public LoanAccountCustomSnapshotBusinessEvent(Loan value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -36,4 +36,5 @@
|
|||||||
<include relativeToChangelogFile="true" file="parts/1011_add_delinquency_actions_table.xml"/>
|
<include relativeToChangelogFile="true" file="parts/1011_add_delinquency_actions_table.xml"/>
|
||||||
<include relativeToChangelogFile="true" file="parts/1012_introduce_loan_schedule_processing_type_configuration.xml"/>
|
<include relativeToChangelogFile="true" file="parts/1012_introduce_loan_schedule_processing_type_configuration.xml"/>
|
||||||
<include relativeToChangelogFile="true" file="parts/1013_add_loan_account_delinquency_pause_changed_event.xml"/>
|
<include relativeToChangelogFile="true" file="parts/1013_add_loan_account_delinquency_pause_changed_event.xml"/>
|
||||||
|
<include relativeToChangelogFile="true" file="parts/1014_add_loan_account_custom_snapshot_event.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<insert tableName="m_external_event_configuration">
|
||||||
|
<column name="type" value="LoanAccountCustomSnapshotBusinessEvent"/>
|
||||||
|
<column name="enabled" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+2
-1
@@ -36,6 +36,7 @@ import org.apache.fineract.cob.loan.LoanCOBConstant;
|
|||||||
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
|
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
@@ -43,7 +44,7 @@ import org.springframework.beans.factory.InitializingBean;
|
|||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Profile("test")
|
@Profile(FineractProfiles.TEST)
|
||||||
@Component
|
@Component
|
||||||
@Path("/v1/internal/cob")
|
@Path("/v1/internal/cob")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
+2
-1
@@ -35,13 +35,14 @@ import org.apache.fineract.cob.domain.LoanAccountLock;
|
|||||||
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
|
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
|
||||||
import org.apache.fineract.cob.domain.LockOwner;
|
import org.apache.fineract.cob.domain.LockOwner;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
@Profile("test")
|
@Profile(FineractProfiles.TEST)
|
||||||
@Component
|
@Component
|
||||||
@Path("/v1/internal/loans")
|
@Path("/v1/internal/loans")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.cob.loan;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountCustomSnapshotBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CheckDueInstallmentsBusinessStep implements LoanCOBBusinessStep {
|
||||||
|
|
||||||
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loan execute(Loan loan) {
|
||||||
|
log.debug("start processing custom snapshot event trigger business step loan for loan with id [{}]", loan.getId());
|
||||||
|
|
||||||
|
if (loan.getRepaymentScheduleInstallments() != null && loan.getRepaymentScheduleInstallments().size() > 0) {
|
||||||
|
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
||||||
|
boolean shouldPostCustomSnapshotBusinessEvent = false;
|
||||||
|
for (int i = 0; i < loan.getRepaymentScheduleInstallments().size(); i++) {
|
||||||
|
if (loan.getRepaymentScheduleInstallments().get(i).getDueDate().equals(currentDate)
|
||||||
|
&& loan.getRepaymentScheduleInstallments().get(i).isNotFullyPaidOff()) {
|
||||||
|
shouldPostCustomSnapshotBusinessEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldPostCustomSnapshotBusinessEvent) {
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountCustomSnapshotBusinessEvent(loan));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("end processing custom snapshot event trigger business step for loan with id [{}]", loan.getId());
|
||||||
|
return loan;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEnumStyledName() {
|
||||||
|
return "CHECK_DUE_INSTALLMENTS";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHumanReadableName() {
|
||||||
|
return "Check Due Installments";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-1
@@ -33,12 +33,13 @@ import jakarta.ws.rs.core.MediaType;
|
|||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Profile("test")
|
@Profile(FineractProfiles.TEST)
|
||||||
@Component
|
@Component
|
||||||
@Path("/v1/instance-mode")
|
@Path("/v1/instance-mode")
|
||||||
@Tag(name = "Instance Mode", description = "Instance mode changing API")
|
@Tag(name = "Instance Mode", description = "Instance mode changing API")
|
||||||
|
|||||||
+2
-1
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.s3;
|
|||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
import org.apache.poi.util.StringUtil;
|
import org.apache.poi.util.StringUtil;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
@@ -28,7 +29,7 @@ import software.amazon.awssdk.services.s3.S3ClientBuilder;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Profile("test")
|
@Profile(FineractProfiles.TEST)
|
||||||
public class LocalstackS3ClientCustomizer implements S3ClientCustomizer {
|
public class LocalstackS3ClientCustomizer implements S3ClientCustomizer {
|
||||||
|
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
|
|||||||
+2
-1
@@ -37,6 +37,7 @@ import java.util.Map;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
@@ -45,7 +46,7 @@ import org.springframework.beans.factory.InitializingBean;
|
|||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Profile("test")
|
@Profile(FineractProfiles.TEST)
|
||||||
@Component
|
@Component
|
||||||
@Path("/v1/internal/client")
|
@Path("/v1/internal/client")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
+2
-1
@@ -38,6 +38,7 @@ import java.util.Map;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
@@ -50,7 +51,7 @@ import org.springframework.beans.factory.InitializingBean;
|
|||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Profile("test")
|
@Profile(FineractProfiles.TEST)
|
||||||
@Component
|
@Component
|
||||||
@Path("/v1/internal/loan")
|
@Path("/v1/internal/loan")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.cob.loan;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountCustomSnapshotBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class CheckDueInstallmentsBusinessStepTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CheckDueInstallmentsBusinessStep underTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup context before each test.
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||||
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
|
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.parse("2024-01-16"),
|
||||||
|
BusinessDateType.COB_DATE, LocalDate.parse("2024-01-15"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
ThreadLocalContextUtil.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoRepaymentScheduleInLoan() {
|
||||||
|
// given
|
||||||
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.execute(loan);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Mockito.verifyNoInteractions(businessEventNotifierService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInstallmentDueDateIsNotMatchingWithCurrentBusinessDate() {
|
||||||
|
// given
|
||||||
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class)));
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-17"));
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.execute(loan);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Mockito.verifyNoInteractions(businessEventNotifierService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleInstallmentDueDateIsMatchingWithCurrentBusinessDateAndNotFullyPayed() {
|
||||||
|
// given
|
||||||
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class)));
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).isNotFullyPaidOff()).thenReturn(true);
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.execute(loan);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Mockito.verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(businessEventArgumentCaptor.capture());
|
||||||
|
BusinessEvent<?> rawEvent = businessEventArgumentCaptor.getValue();
|
||||||
|
Assertions.assertInstanceOf(LoanAccountCustomSnapshotBusinessEvent.class, rawEvent);
|
||||||
|
LoanAccountCustomSnapshotBusinessEvent event = (LoanAccountCustomSnapshotBusinessEvent) rawEvent;
|
||||||
|
Assertions.assertEquals(loan, event.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleInstallmentDueDateIsMatchingWithCurrentBusinessDateAndFullyPayed() {
|
||||||
|
// given
|
||||||
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class)));
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).isNotFullyPaidOff()).thenReturn(false);
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.execute(loan);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Mockito.verifyNoInteractions(businessEventNotifierService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleInstallmentDueDateIsMatchingWithCurrentBusinessDateAndNotFullyPayedButSingleEventIsGenerated() {
|
||||||
|
// given
|
||||||
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(
|
||||||
|
List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class), Mockito.mock(LoanRepaymentScheduleInstallment.class)));
|
||||||
|
// first one is a down payment installment
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).isNotFullyPaidOff()).thenReturn(true);
|
||||||
|
Mockito.lenient().when(loan.getRepaymentScheduleInstallments().get(0).isDownPayment()).thenReturn(true);
|
||||||
|
// this one is a real installment
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(1).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
|
||||||
|
Mockito.when(loan.getRepaymentScheduleInstallments().get(1).isNotFullyPaidOff()).thenReturn(true);
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.execute(loan);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Mockito.verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(businessEventArgumentCaptor.capture());
|
||||||
|
BusinessEvent<?> rawEvent = businessEventArgumentCaptor.getValue();
|
||||||
|
Assertions.assertInstanceOf(LoanAccountCustomSnapshotBusinessEvent.class, rawEvent);
|
||||||
|
LoanAccountCustomSnapshotBusinessEvent event = (LoanAccountCustomSnapshotBusinessEvent) rawEvent;
|
||||||
|
Assertions.assertEquals(loan, event.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -98,7 +98,7 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"LoanChargeOffPostBusinessEvent", "LoanUndoChargeOffBusinessEvent", "LoanAccrualTransactionCreatedBusinessEvent",
|
"LoanChargeOffPostBusinessEvent", "LoanUndoChargeOffBusinessEvent", "LoanAccrualTransactionCreatedBusinessEvent",
|
||||||
"LoanRescheduledDueAdjustScheduleBusinessEvent", "LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
|
"LoanRescheduledDueAdjustScheduleBusinessEvent", "LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
|
||||||
"LoanTransactionDownPaymentPostBusinessEvent", "LoanTransactionDownPaymentPreBusinessEvent",
|
"LoanTransactionDownPaymentPostBusinessEvent", "LoanTransactionDownPaymentPreBusinessEvent",
|
||||||
"LoanAccountDelinquencyPauseChangedBusinessEvent");
|
"LoanAccountDelinquencyPauseChangedBusinessEvent", "LoanAccountCustomSnapshotBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
@@ -178,7 +178,7 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"LoanUndoChargeOffBusinessEvent", "LoanAccrualTransactionCreatedBusinessEvent",
|
"LoanUndoChargeOffBusinessEvent", "LoanAccrualTransactionCreatedBusinessEvent",
|
||||||
"LoanRescheduledDueAdjustScheduleBusinessEvent", "LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
|
"LoanRescheduledDueAdjustScheduleBusinessEvent", "LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
|
||||||
"LoanTransactionDownPaymentPostBusinessEvent", "LoanTransactionDownPaymentPreBusinessEvent",
|
"LoanTransactionDownPaymentPostBusinessEvent", "LoanTransactionDownPaymentPreBusinessEvent",
|
||||||
"LoanAccountDelinquencyPauseChangedBusinessEvent");
|
"LoanAccountDelinquencyPauseChangedBusinessEvent", "LoanAccountCustomSnapshotBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
|
|||||||
+5
-3
@@ -74,6 +74,8 @@ import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
||||||
|
|
||||||
protected final ResponseSpecification responseSpec = createResponseSpecification(200);
|
protected final ResponseSpecification responseSpec = createResponseSpecification(Matchers.is(200));
|
||||||
protected final RequestSpecification requestSpec = createRequestSpecification();
|
protected final RequestSpecification requestSpec = createRequestSpecification();
|
||||||
|
|
||||||
protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec);
|
protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec);
|
||||||
@@ -261,8 +263,8 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResponseSpecification createResponseSpecification(int statusCode) {
|
protected static ResponseSpecification createResponseSpecification(Matcher<Integer> statusCodeMatcher) {
|
||||||
return new ResponseSpecBuilder().expectStatusCode(statusCode).build();
|
return new ResponseSpecBuilder().expectStatusCode(statusCodeMatcher).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void verifyUndoLastDisbursalShallFail(Long loanId, String expectedError) {
|
protected void verifyUndoLastDisbursalShallFail(Long loanId, String expectedError) {
|
||||||
|
|||||||
+326
@@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.client.models.BusinessDateRequest;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
|
||||||
|
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.ExternalEventConfigurationHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@ExtendWith({ LoanTestLifecycleExtension.class, ExternalEventsExtension.class })
|
||||||
|
public class CustomSnapshotEventIntegrationTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
|
private SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(this.requestSpec);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSnapshotEventGenerationWhenLoanInstallmentIsNotPayed() {
|
||||||
|
runAt("31 January 2023", () -> {
|
||||||
|
// Enable Business Step
|
||||||
|
enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
|
||||||
|
"CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
|
||||||
|
"EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
|
||||||
|
|
||||||
|
enableLoanAccountCustomSnapshotBusinessEvent();
|
||||||
|
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
// Create Loan Product
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
|
||||||
|
InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
|
||||||
|
loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
// Apply and Approve Loan
|
||||||
|
Long loanId = applyAndApproveLoan(clientId, loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
|
||||||
|
|
||||||
|
// Disburse Loan
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
|
||||||
|
|
||||||
|
// Verify Repayment Schedule and Due Dates
|
||||||
|
verifyRepaymentSchedule(loanId, //
|
||||||
|
installment(0, null, "01 January 2023"), //
|
||||||
|
installment(313.0, false, "31 January 2023"), //
|
||||||
|
installment(313.0, false, "02 March 2023"), //
|
||||||
|
installment(313.0, false, "01 April 2023"), //
|
||||||
|
installment(311.0, false, "01 May 2023") //
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete all external events
|
||||||
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
|
// run cob
|
||||||
|
updateBusinessDateAndExecuteCOBJob("01 February 2023");
|
||||||
|
|
||||||
|
// verify external events
|
||||||
|
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||||
|
Assertions.assertEquals(1, allExternalEvents.size());
|
||||||
|
Assertions.assertEquals("LoanAccountCustomSnapshotBusinessEvent", allExternalEvents.get(0).getType());
|
||||||
|
Assertions.assertEquals(loanId, allExternalEvents.get(0).getAggregateRootId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoSnapshotEventGenerationWhenLoanInstallmentIsPayed() {
|
||||||
|
runAt("31 January 2023", () -> {
|
||||||
|
// Enable Business Step
|
||||||
|
enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
|
||||||
|
"CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
|
||||||
|
"EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
|
||||||
|
|
||||||
|
enableLoanAccountCustomSnapshotBusinessEvent();
|
||||||
|
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
// Create Loan Product
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
|
||||||
|
InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
|
||||||
|
loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
// Apply and Approve Loan
|
||||||
|
Long loanId = applyAndApproveLoan(clientId, loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
|
||||||
|
|
||||||
|
// Disburse Loan
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
|
||||||
|
|
||||||
|
// Verify Repayment Schedule and Due Dates
|
||||||
|
verifyRepaymentSchedule(loanId, //
|
||||||
|
installment(0, null, "01 January 2023"), //
|
||||||
|
installment(313.0, false, "31 January 2023"), //
|
||||||
|
installment(313.0, false, "02 March 2023"), //
|
||||||
|
installment(313.0, false, "01 April 2023"), //
|
||||||
|
installment(311.0, false, "01 May 2023") //
|
||||||
|
);
|
||||||
|
|
||||||
|
addRepaymentForLoan(loanId, 313.0, "31 January 2023");
|
||||||
|
|
||||||
|
// Verify Repayment Schedule and Due Dates
|
||||||
|
verifyRepaymentSchedule(loanId, //
|
||||||
|
installment(0, null, "01 January 2023"), //
|
||||||
|
installment(313.0, true, "31 January 2023"), //
|
||||||
|
installment(313.0, false, "02 March 2023"), //
|
||||||
|
installment(313.0, false, "01 April 2023"), //
|
||||||
|
installment(311.0, false, "01 May 2023") //
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete all external events
|
||||||
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
|
// run cob
|
||||||
|
updateBusinessDateAndExecuteCOBJob("01 February 2023");
|
||||||
|
|
||||||
|
// verify external events
|
||||||
|
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||||
|
Assertions.assertEquals(0, allExternalEvents.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoSnapshotEventGenerationWhenWhenCustomSnapshotEventCOBTaskIsNotActive() {
|
||||||
|
runAt("31 January 2023", () -> {
|
||||||
|
// Enable Business Step
|
||||||
|
enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
|
||||||
|
"CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
|
||||||
|
"EXTERNAL_ASSET_OWNER_TRANSFER");
|
||||||
|
|
||||||
|
enableLoanAccountCustomSnapshotBusinessEvent();
|
||||||
|
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
// Create Loan Product
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
|
||||||
|
InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
|
||||||
|
loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
// Apply and Approve Loan
|
||||||
|
Long loanId = applyAndApproveLoan(clientId, loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
|
||||||
|
|
||||||
|
// Disburse Loan
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
|
||||||
|
|
||||||
|
// Verify Repayment Schedule and Due Dates
|
||||||
|
verifyRepaymentSchedule(loanId, //
|
||||||
|
installment(0, null, "01 January 2023"), //
|
||||||
|
installment(313.0, false, "31 January 2023"), //
|
||||||
|
installment(313.0, false, "02 March 2023"), //
|
||||||
|
installment(313.0, false, "01 April 2023"), //
|
||||||
|
installment(311.0, false, "01 May 2023") //
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete all external events
|
||||||
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
|
// run cob
|
||||||
|
updateBusinessDateAndExecuteCOBJob("01 February 2023");
|
||||||
|
|
||||||
|
// verify external events
|
||||||
|
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||||
|
Assertions.assertEquals(0, allExternalEvents.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoSnapshotEventGenerationWhenCOBDateIsNotMatchingWithInstallmentDueDate() {
|
||||||
|
runAt("30 January 2023", () -> {
|
||||||
|
// Enable Business Step
|
||||||
|
enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
|
||||||
|
"CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
|
||||||
|
"EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
|
||||||
|
|
||||||
|
enableLoanAccountCustomSnapshotBusinessEvent();
|
||||||
|
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
// Create Loan Product
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
|
||||||
|
InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
|
||||||
|
loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
// Apply and Approve Loan
|
||||||
|
Long loanId = applyAndApproveLoan(clientId, loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
|
||||||
|
|
||||||
|
// Disburse Loan
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
|
||||||
|
|
||||||
|
// Verify Repayment Schedule and Due Dates
|
||||||
|
verifyRepaymentSchedule(loanId, //
|
||||||
|
installment(0, null, "01 January 2023"), //
|
||||||
|
installment(313.0, false, "31 January 2023"), //
|
||||||
|
installment(313.0, false, "02 March 2023"), //
|
||||||
|
installment(313.0, false, "01 April 2023"), //
|
||||||
|
installment(311.0, false, "01 May 2023") //
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete all external events
|
||||||
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
|
// run cob
|
||||||
|
updateBusinessDateAndExecuteCOBJob("31 January 2023");
|
||||||
|
|
||||||
|
// verify external events
|
||||||
|
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||||
|
Assertions.assertEquals(0, allExternalEvents.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoSnapshotEventGenerationWhenCustomSnapshotEventIsDisabled() {
|
||||||
|
runAt("31 January 2023", () -> {
|
||||||
|
// Enable Business Step
|
||||||
|
enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
|
||||||
|
"CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
|
||||||
|
"EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
|
||||||
|
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
// Create Loan Product
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
|
||||||
|
InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
|
||||||
|
loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
// Apply and Approve Loan
|
||||||
|
Long loanId = applyAndApproveLoan(clientId, loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
|
||||||
|
|
||||||
|
// Disburse Loan
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
|
||||||
|
|
||||||
|
// Verify Repayment Schedule and Due Dates
|
||||||
|
verifyRepaymentSchedule(loanId, //
|
||||||
|
installment(0, null, "01 January 2023"), //
|
||||||
|
installment(313.0, false, "31 January 2023"), //
|
||||||
|
installment(313.0, false, "02 March 2023"), //
|
||||||
|
installment(313.0, false, "01 April 2023"), //
|
||||||
|
installment(311.0, false, "01 May 2023") //
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete all external events
|
||||||
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
|
// run cob
|
||||||
|
updateBusinessDateAndExecuteCOBJob("01 February 2023");
|
||||||
|
|
||||||
|
// verify external events
|
||||||
|
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||||
|
Assertions.assertEquals(0, allExternalEvents.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteAllExternalEvents() {
|
||||||
|
ExternalEventHelper.deleteAllExternalEvents(requestSpec, createResponseSpecification(Matchers.is(204)));
|
||||||
|
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||||
|
Assertions.assertEquals(0, allExternalEvents.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableCOBBusinessStep(String... steps) {
|
||||||
|
new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", steps);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getExternalEventConfigurationsForUpdateJSON() {
|
||||||
|
Map<String, Map<String, Boolean>> configurationsForUpdate = new HashMap<>();
|
||||||
|
Map<String, Boolean> configurations = new HashMap<>();
|
||||||
|
configurations.put("CentersCreateBusinessEvent", true);
|
||||||
|
configurations.put("ClientActivateBusinessEvent", true);
|
||||||
|
configurationsForUpdate.put("externalEventConfigurations", configurations);
|
||||||
|
return new Gson().toJson(configurationsForUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableLoanAccountCustomSnapshotBusinessEvent() {
|
||||||
|
final Map<String, Boolean> updatedConfigurations = ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
|
||||||
|
responseSpec, "{\"externalEventConfigurations\":{\"LoanAccountCustomSnapshotBusinessEvent\":true}}\n");
|
||||||
|
Assertions.assertEquals(updatedConfigurations.size(), 1);
|
||||||
|
Assertions.assertTrue(updatedConfigurations.containsKey("LoanAccountCustomSnapshotBusinessEvent"));
|
||||||
|
Assertions.assertTrue(updatedConfigurations.get("LoanAccountCustomSnapshotBusinessEvent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBusinessDateAndExecuteCOBJob(String date) {
|
||||||
|
businessDateHelper.updateBusinessDate(
|
||||||
|
new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));
|
||||||
|
schedulerJobHelper.executeAndAwaitJob("Loan COB");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+5
@@ -495,6 +495,11 @@ public class ExternalEventConfigurationHelper {
|
|||||||
loanAccountDelinquencyPauseChangedBusinessEvent.put("enabled", false);
|
loanAccountDelinquencyPauseChangedBusinessEvent.put("enabled", false);
|
||||||
defaults.add(loanAccountDelinquencyPauseChangedBusinessEvent);
|
defaults.add(loanAccountDelinquencyPauseChangedBusinessEvent);
|
||||||
|
|
||||||
|
Map<String, Object> loanAccountCustomSnapshotBusinessEvent = new HashMap<>();
|
||||||
|
loanAccountCustomSnapshotBusinessEvent.put("type", "LoanAccountCustomSnapshotBusinessEvent");
|
||||||
|
loanAccountCustomSnapshotBusinessEvent.put("enabled", false);
|
||||||
|
defaults.add(loanAccountCustomSnapshotBusinessEvent);
|
||||||
|
|
||||||
return defaults;
|
return defaults;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,6 +283,9 @@ public final class Utils {
|
|||||||
final String deleteURL, final String jsonAttributeToGetBack) {
|
final String deleteURL, final String jsonAttributeToGetBack) {
|
||||||
final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().delete(deleteURL).andReturn()
|
final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().delete(deleteURL).andReturn()
|
||||||
.asString();
|
.asString();
|
||||||
|
if (jsonAttributeToGetBack == null) {
|
||||||
|
return (T) json;
|
||||||
|
}
|
||||||
return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
|
return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.integrationtests.common.externalevents;
|
||||||
|
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.client.util.JSON;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class ExternalEventHelper {
|
||||||
|
|
||||||
|
private static final Gson GSON = new JSON().getGson();
|
||||||
|
|
||||||
|
private ExternalEventHelper() {}
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public static class Filter {
|
||||||
|
|
||||||
|
private final String idempotencyKey;
|
||||||
|
private final String type;
|
||||||
|
private final String category;
|
||||||
|
private final Long aggregateRootId;
|
||||||
|
|
||||||
|
public String toQueryParams() {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
if (idempotencyKey != null) {
|
||||||
|
stringBuilder.append("idempotencyKey=").append(idempotencyKey).append("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
stringBuilder.append("type=").append(type).append("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category != null) {
|
||||||
|
stringBuilder.append("category=").append(category).append("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregateRootId != null) {
|
||||||
|
stringBuilder.append("aggregateRootId=").append(aggregateRootId).append("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ExternalEventDTO> getAllExternalEvents(final RequestSpecification requestSpec,
|
||||||
|
final ResponseSpecification responseSpec) {
|
||||||
|
final String url = "/fineract-provider/api/v1/internal/externalevents?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
log.info("---------------------------------GETTING ALL EXTERNAL EVENTS---------------------------------------------");
|
||||||
|
String response = Utils.performServerGet(requestSpec, responseSpec, url);
|
||||||
|
return GSON.fromJson(response, new TypeToken<List<ExternalEventDTO>>() {}.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ExternalEventDTO> getAllExternalEvents(final RequestSpecification requestSpec,
|
||||||
|
final ResponseSpecification responseSpec, Filter filter) {
|
||||||
|
final String url = "/fineract-provider/api/v1/internal/externalevents?" + filter.toQueryParams() + Utils.TENANT_IDENTIFIER;
|
||||||
|
log.info("---------------------------------GETTING ALL EXTERNAL EVENTS---------------------------------------------");
|
||||||
|
String response = Utils.performServerGet(requestSpec, responseSpec, url);
|
||||||
|
return GSON.fromJson(response, new TypeToken<List<ExternalEventDTO>>() {}.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteAllExternalEvents(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
|
||||||
|
final String url = "/fineract-provider/api/v1/internal/externalevents?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
log.info("-----------------------------DELETE ALL EXTERNAL EVENTS PARTITIONS----------------------------------------");
|
||||||
|
Utils.performServerDelete(requestSpec, responseSpec, url, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.integrationtests.common.externalevents;
|
||||||
|
|
||||||
|
import static org.apache.fineract.integrationtests.common.Utils.initializeRESTAssured;
|
||||||
|
|
||||||
|
import com.google.common.collect.MapDifference;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.integrationtests.common.ExternalEventConfigurationHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ExternalEventsExtension implements AfterEachCallback, BeforeEachCallback {
|
||||||
|
|
||||||
|
private Map<String, Boolean> original;
|
||||||
|
private ResponseSpecification responseSpec;
|
||||||
|
private RequestSpecification requestSpec;
|
||||||
|
|
||||||
|
public ExternalEventsExtension() {
|
||||||
|
initializeRESTAssured();
|
||||||
|
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
|
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
|
this.requestSpec.header("Fineract-Platform-TenantId", "default");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterEach(ExtensionContext context) {
|
||||||
|
ArrayList<Map<String, Object>> allExternalEventConfigurations = ExternalEventConfigurationHelper
|
||||||
|
.getAllExternalEventConfigurations(requestSpec, responseSpec);
|
||||||
|
Map<String, Boolean> collected = allExternalEventConfigurations.stream()
|
||||||
|
.map(map -> Map.entry((String) map.get("type"), (Boolean) map.get("enabled")))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
|
||||||
|
Map<String, MapDifference.ValueDifference<Boolean>> diff = Maps.difference(original, collected).entriesDiffering();
|
||||||
|
diff.keySet().forEach(key -> {
|
||||||
|
MapDifference.ValueDifference<Boolean> valueDifference = diff.get(key);
|
||||||
|
log.debug("External event {} changed from {} to {}. Restoring to its original state.", key, valueDifference.leftValue(),
|
||||||
|
valueDifference.rightValue());
|
||||||
|
restore(key, valueDifference.leftValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeEach(ExtensionContext context) {
|
||||||
|
ArrayList<Map<String, Object>> allExternalEventConfigurations = ExternalEventConfigurationHelper
|
||||||
|
.getAllExternalEventConfigurations(requestSpec, responseSpec);
|
||||||
|
original = allExternalEventConfigurations.stream().map(map -> Map.entry((String) map.get("type"), (Boolean) map.get("enabled")))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restore(String key, Boolean value) {
|
||||||
|
final Map<String, Boolean> updatedConfigurations = ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
|
||||||
|
responseSpec, "{\"externalEventConfigurations\":{\"" + key + "\":" + value + "}}\n");
|
||||||
|
Assertions.assertEquals(updatedConfigurations.size(), 1);
|
||||||
|
Assertions.assertTrue(updatedConfigurations.containsKey(key));
|
||||||
|
Assertions.assertEquals(value, updatedConfigurations.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-4
@@ -23,16 +23,17 @@ import io.restassured.specification.ResponseSpecification;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.integrationtests.client.IntegrationTest;
|
|
||||||
import org.apache.fineract.integrationtests.common.Utils;
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CobHelper extends IntegrationTest {
|
public final class CobHelper {
|
||||||
|
|
||||||
|
private CobHelper() {}
|
||||||
|
|
||||||
public static List<Map<String, Object>> getCobPartitions(final RequestSpecification requestSpec,
|
public static List<Map<String, Object>> getCobPartitions(final RequestSpecification requestSpec,
|
||||||
final ResponseSpecification responseSpec, int partitionSize, final String jsonReturn) {
|
final ResponseSpecification responseSpec, int partitionSize, final String jsonReturn) {
|
||||||
final String GET_LOAN_URL = "/fineract-provider/api/v1/internal/cob/partitions/" + partitionSize + "?" + Utils.TENANT_IDENTIFIER;
|
final String url = "/fineract-provider/api/v1/internal/cob/partitions/" + partitionSize + "?" + Utils.TENANT_IDENTIFIER;
|
||||||
log.info("---------------------------------GET COB PARTITIONS---------------------------------------------");
|
log.info("---------------------------------GET COB PARTITIONS---------------------------------------------");
|
||||||
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_URL, jsonReturn);
|
return Utils.performServerGet(requestSpec, responseSpec, url, jsonReturn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user