FINERACT-2181: Fix fully paid check on EmiModel

This commit is contained in:
Adam Saghy
2025-05-21 17:16:02 +02:00
parent 4861ad19fd
commit 15c968bc1b
16 changed files with 326 additions and 199 deletions
@@ -108,6 +108,8 @@ final class LoanTransactionsApiResourceSwagger {
public List<GetLoanProductsChargeOffReasonOptions> chargeOffReasonOptions; public List<GetLoanProductsChargeOffReasonOptions> chargeOffReasonOptions;
public List<GetPaymentTypeOptions> paymentTypeOptions; public List<GetPaymentTypeOptions> paymentTypeOptions;
@Schema(example = "200.000000")
public Double netDisbursalAmount;
} }
public static final class GetLoanCurrency { public static final class GetLoanCurrency {
@@ -30,6 +30,7 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -1025,7 +1026,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
} }
public void copyFrom(final LoanRepaymentScheduleInstallment installment) { public void copyFrom(final LoanRepaymentScheduleInstallment installment) {
if (getId().equals(installment.getId())) { if (nonNullAndEqual(getId(), installment.getId())) {
return; return;
} }
// Reset balances // Reset balances
@@ -1149,4 +1150,8 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
} }
setLoanTransactionToRepaymentScheduleMappings(retainedTransactionRepaymentScheduleMapping); setLoanTransactionToRepaymentScheduleMappings(retainedTransactionRepaymentScheduleMapping);
} }
private static boolean nonNullAndEqual(Object a, Object b) {
return a != null && b != null && Objects.equals(a, b);
}
} }
@@ -253,7 +253,7 @@ public final class RepaymentPeriod {
} }
public boolean isFullyPaid() { public boolean isFullyPaid() {
return getEmi().isEqualTo(getTotalPaidAmount()); return getEmiPlusChargeback().isEqualTo(getTotalPaidAmount());
} }
/** /**
@@ -18,12 +18,6 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.api; package org.apache.fineract.portfolio.loanaccount.api;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE;
import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
@@ -33,17 +27,13 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import java.util.HashMap; import java.time.OffsetDateTime;
import java.util.List; import java.util.List;
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.boot.FineractProfiles;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
@@ -62,12 +52,8 @@ public class InternalLoanInformationApiResource implements InitializingBean {
private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanTransactionRepository loanTransactionRepository; private final LoanTransactionRepository loanTransactionRepository;
private final ToApiJsonSerializer<Map> toApiJsonSerializerForMap;
private final ToApiJsonSerializer<List> toApiJsonSerializerForList;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final AdvancedPaymentDataMapper advancedPaymentDataMapper; private final AdvancedPaymentDataMapper advancedPaymentDataMapper;
private final LoanAccountDomainService loanAccountDomainService;
private final Gson gson = new Gson();
@Override @Override
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
@@ -87,7 +73,7 @@ public class InternalLoanInformationApiResource implements InitializingBean {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
public String getLoanAuditFields(@Context final UriInfo uriInfo, @PathParam("loanId") Long loanId) { public AuditData getLoanAuditFields(@Context final UriInfo uriInfo, @PathParam("loanId") Long loanId) {
log.warn("------------------------------------------------------------"); log.warn("------------------------------------------------------------");
log.warn(" "); log.warn(" ");
log.warn("Fetching loan with {}", loanId); log.warn("Fetching loan with {}", loanId);
@@ -95,11 +81,8 @@ public class InternalLoanInformationApiResource implements InitializingBean {
log.warn("------------------------------------------------------------"); log.warn("------------------------------------------------------------");
final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
Map<String, Object> auditFields = new HashMap<>( return new AuditData(loan.getCreatedBy().orElse(null), loan.getCreatedDate().orElse(null), loan.getLastModifiedBy().orElse(null),
Map.of(CREATED_BY, loan.getCreatedBy().orElse(null), CREATED_DATE, loan.getCreatedDate().orElse(null), LAST_MODIFIED_BY, loan.getLastModifiedDate().orElse(null));
loan.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, loan.getLastModifiedDate().orElse(null)));
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializerForMap.serialize(settings, auditFields);
} }
@GET @GET
@@ -107,7 +90,7 @@ public class InternalLoanInformationApiResource implements InitializingBean {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
public String getLoanTransactionAuditFields(@Context final UriInfo uriInfo, @PathParam("loanId") Long loanId, public AuditData getLoanTransactionAuditFields(@Context final UriInfo uriInfo, @PathParam("loanId") Long loanId,
@PathParam("transactionId") Long transactionId) { @PathParam("transactionId") Long transactionId) {
log.warn("------------------------------------------------------------"); log.warn("------------------------------------------------------------");
log.warn(" "); log.warn(" ");
@@ -116,11 +99,8 @@ public class InternalLoanInformationApiResource implements InitializingBean {
log.warn("------------------------------------------------------------"); log.warn("------------------------------------------------------------");
final LoanTransaction transaction = loanTransactionRepository.findById(transactionId).orElseThrow(); final LoanTransaction transaction = loanTransactionRepository.findById(transactionId).orElseThrow();
Map<String, Object> auditFields = new HashMap<>(Map.of(CREATED_BY, transaction.getCreatedBy().orElse(null), CREATED_DATE, return new AuditData(transaction.getCreatedBy().orElse(null), transaction.getCreatedDate().orElse(null),
transaction.getCreatedDate().orElse(null), LAST_MODIFIED_BY, transaction.getLastModifiedBy().orElse(null), transaction.getLastModifiedBy().orElse(null), transaction.getLastModifiedDate().orElse(null));
LAST_MODIFIED_DATE, transaction.getLastModifiedDate().orElse(null)));
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializerForMap.serialize(settings, auditFields);
} }
@GET @GET
@@ -128,16 +108,14 @@ public class InternalLoanInformationApiResource implements InitializingBean {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
public String getLoansByStatus(@Context final UriInfo uriInfo, @PathParam("statusId") Integer statusId) { public List<Long> getLoansByStatus(@Context final UriInfo uriInfo, @PathParam("statusId") Integer statusId) {
log.warn("------------------------------------------------------------"); log.warn("------------------------------------------------------------");
log.warn(" "); log.warn(" ");
log.warn("Fetching loans by status {}", statusId); log.warn("Fetching loans by status {}", statusId);
log.warn(" "); log.warn(" ");
log.warn("------------------------------------------------------------"); log.warn("------------------------------------------------------------");
final List<Long> loanIds = loanRepositoryWrapper.findLoanIdsByStatusId(statusId); return loanRepositoryWrapper.findLoanIdsByStatusId(statusId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializerForList.serialize(settings, loanIds);
} }
@GET @GET
@@ -156,4 +134,7 @@ public class InternalLoanInformationApiResource implements InitializingBean {
final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
return advancedPaymentDataMapper.mapLoanPaymentAllocationRule(loan.getPaymentAllocationRules()); return advancedPaymentDataMapper.mapLoanPaymentAllocationRule(loan.getPaymentAllocationRules());
} }
private record AuditData(Long createdBy, OffsetDateTime createdDate, Long lastModifiedBy, OffsetDateTime lastModifiedDate) {
}
} }
@@ -279,7 +279,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
writeOffTransaction.reverse(); writeOffTransaction.reverse();
} }
if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) { if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isWaiver()) {
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, transactionForAdjustment, loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, transactionForAdjustment,
scheduleGeneratorDTO); scheduleGeneratorDTO);
} }
@@ -345,8 +345,8 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
.isInterestRecalculationEnabled(true)// .isInterestRecalculationEnabled(true)//
.interestRecalculationCompoundingMethod(0)// .interestRecalculationCompoundingMethod(0)//
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)// .rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
.recalculationRestFrequencyType(RecalculationRestFrequencyType.SAME_AS_REPAYMENT_PERIOD)// .recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
.recalculationRestFrequencyInterval(0)// .recalculationRestFrequencyInterval(1)//
.isArrearsBasedOnOriginalSchedule(false)// .isArrearsBasedOnOriginalSchedule(false)//
.isCompoundingToBePostedAsTransaction(false)// .isCompoundingToBePostedAsTransaction(false)//
.preClosureInterestCalculationStrategy(1)// .preClosureInterestCalculationStrategy(1)//
@@ -301,7 +301,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
@Test @Test
public void testUpdateInterestPauseByLoanId_overlapping_shouldFail3() { public void testUpdateInterestPauseByLoanId_overlapping_shouldFail3() {
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByLoanId("2023-01-02", "2023-01-06", PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByLoanId("2023-01-03", "2023-01-06",
"yyyy-MM-dd", "en", loanId); "yyyy-MM-dd", "en", loanId);
PostLoansLoanIdTransactionsResponse createResponse2 = LOAN_TRANSACTION_HELPER.createInterestPauseByLoanId("2023-01-07", PostLoansLoanIdTransactionsResponse createResponse2 = LOAN_TRANSACTION_HELPER.createInterestPauseByLoanId("2023-01-07",
"2023-01-12", "yyyy-MM-dd", "en", loanId); "2023-01-12", "yyyy-MM-dd", "en", loanId);
@@ -495,7 +495,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
@Test @Test
public void testUpdateInterestPauseByExternalId_overlapping_shouldFail3() { public void testUpdateInterestPauseByExternalId_overlapping_shouldFail3() {
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-02", PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-03",
"2023-01-06", "yyyy-MM-dd", "en", externalId); "2023-01-06", "yyyy-MM-dd", "en", externalId);
PostLoansLoanIdTransactionsResponse createResponse2 = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-07", PostLoansLoanIdTransactionsResponse createResponse2 = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-07",
"2023-01-12", "yyyy-MM-dd", "en", externalId); "2023-01-12", "yyyy-MM-dd", "en", externalId);
@@ -517,7 +517,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
@Test @Test
public void testUpdateInterestPauseByExternalId_endDateBeforeStartDate_shouldFail() { public void testUpdateInterestPauseByExternalId_endDateBeforeStartDate_shouldFail() {
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-01", PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-03",
"2023-01-12", "yyyy-MM-dd", "en", externalId); "2023-01-12", "yyyy-MM-dd", "en", externalId);
Assertions.assertNotNull(createResponse); Assertions.assertNotNull(createResponse);
@@ -538,7 +538,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
@Test @Test
public void testUpdateInterestPauseByExternalId_startDateBeforeLoanStart_shouldFail() { public void testUpdateInterestPauseByExternalId_startDateBeforeLoanStart_shouldFail() {
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-01", PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-03",
"2023-01-12", "yyyy-MM-dd", "en", externalId); "2023-01-12", "yyyy-MM-dd", "en", externalId);
Assertions.assertNotNull(createResponse); Assertions.assertNotNull(createResponse);
@@ -559,7 +559,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
@Test @Test
public void testDeleteInterestPauseByExternalId_validRequest_shouldSucceed() { public void testDeleteInterestPauseByExternalId_validRequest_shouldSucceed() {
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-01", PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-03",
"2023-01-12", "yyyy-MM-dd", "en", externalId); "2023-01-12", "yyyy-MM-dd", "en", externalId);
Assertions.assertNotNull(createResponse, "Create response should not be null"); Assertions.assertNotNull(createResponse, "Create response should not be null");
@@ -591,7 +591,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
@Test @Test
public void testDeleteInterestPauseByExternalId_invalidExternalId_shouldFail() { public void testDeleteInterestPauseByExternalId_invalidExternalId_shouldFail() {
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-01", PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTION_HELPER.createInterestPauseByExternalId("2023-01-03",
"2023-01-12", "yyyy-MM-dd", "en", externalId); "2023-01-12", "yyyy-MM-dd", "en", externalId);
Assertions.assertNotNull(createResponse); Assertions.assertNotNull(createResponse);
@@ -698,7 +698,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(futureInstallmentAllocationRule); AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(futureInstallmentAllocationRule);
String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount).withNumberOfRepayments(numberOfRepayments) String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount).withNumberOfRepayments(numberOfRepayments)
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod(interestRatePerPeriod) .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod(interestRatePerPeriod)
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() .withInterestRateFrequencyTypeAsYear().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
.withAccountingRulePeriodicAccrual(new Account[] { assetAccount, incomeAccount, expenseAccount, overpaymentAccount }) .withAccountingRulePeriodicAccrual(new Account[] { assetAccount, incomeAccount, expenseAccount, overpaymentAccount })
.withInterestCalculationPeriodTypeAsRepaymentPeriod(true).addAdvancedPaymentAllocation(defaultAllocation) .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).addAdvancedPaymentAllocation(defaultAllocation)
.withLoanScheduleType(LoanScheduleType.PROGRESSIVE).withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL) .withLoanScheduleType(LoanScheduleType.PROGRESSIVE).withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL)
@@ -719,10 +719,10 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount) String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
.withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsDays().withNumberOfRepayments(numberOfRepayments) .withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsDays().withNumberOfRepayments(numberOfRepayments)
.withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod) .withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod)
.withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments() .withInterestTypeAsDecliningBalance().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays()
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate(dateString) .withExpectedDisbursementDate(dateString).withSubmittedOnDate(dateString).withLoanType("individual")
.withSubmittedOnDate(dateString).withLoanType("individual").withExternalId(externalId) .withExternalId(externalId).withRepaymentStrategy("advanced-payment-allocation-strategy")
.withRepaymentStrategy("advanced-payment-allocation-strategy").build(clientId.toString(), loanProductId.toString(), null); .build(clientId.toString(), loanProductId.toString(), null);
loanId = LOAN_TRANSACTION_HELPER.getLoanId(loanApplicationJSON); loanId = LOAN_TRANSACTION_HELPER.getLoanId(loanApplicationJSON);
@@ -455,7 +455,8 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest {
public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOn4IProgressiveLoanCOBStep() { public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOn4IProgressiveLoanCOBStep() {
AtomicReference<Long> loanIdRef = new AtomicReference<>(); AtomicReference<Long> loanIdRef = new AtomicReference<>();
runAt("1 January 2023", () -> { runAt("1 January 2023", () -> {
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive()); PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(
create4IProgressive().recalculationRestFrequencyType(RecalculationRestFrequencyType.SAME_AS_REPAYMENT_PERIOD));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0, Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0,
4, null); 4, null);
@@ -532,7 +533,8 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest {
public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOn4IProgressiveLoanCOBStepLatePaidPaidOnTimeLatePaidPayoffOnTime() { public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOn4IProgressiveLoanCOBStepLatePaidPaidOnTimeLatePaidPayoffOnTime() {
AtomicReference<Long> loanIdRef = new AtomicReference<>(); AtomicReference<Long> loanIdRef = new AtomicReference<>();
runAt("1 January 2023", () -> { runAt("1 January 2023", () -> {
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive()); PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(
create4IProgressive().recalculationRestFrequencyType(RecalculationRestFrequencyType.SAME_AS_REPAYMENT_PERIOD));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0, Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0,
4, null); 4, null);
@@ -875,7 +877,8 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest {
public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOn4IProgressiveLoanCOBStepOnePaid() { public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOn4IProgressiveLoanCOBStepOnePaid() {
AtomicReference<Long> loanIdRef = new AtomicReference<>(); AtomicReference<Long> loanIdRef = new AtomicReference<>();
runAt("1 January 2023", () -> { runAt("1 January 2023", () -> {
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive()); PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(
create4IProgressive().recalculationRestFrequencyType(RecalculationRestFrequencyType.SAME_AS_REPAYMENT_PERIOD));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0, Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0,
4, null); 4, null);
@@ -997,7 +1000,8 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest {
public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOnProgressiveLoanJob() { public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOnProgressiveLoanJob() {
AtomicReference<Long> loanIdRef = new AtomicReference<>(); AtomicReference<Long> loanIdRef = new AtomicReference<>();
runAt("1 January 2023", () -> { runAt("1 January 2023", () -> {
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive()); PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(
create4IProgressive().recalculationRestFrequencyType(RecalculationRestFrequencyType.SAME_AS_REPAYMENT_PERIOD));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0, Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 8000.0, 10.0,
4, null); 4, null);
@@ -265,7 +265,7 @@ public class LoanRescheduleRequestTest extends BaseLoanIntegrationTest {
runAt("15 February 2023", () -> { runAt("15 February 2023", () -> {
loanResponse.set(applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, loanResponse.set(applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3,
BigDecimal.ZERO, "01 January 2023", "01 January 2023")); BigDecimal.TEN, "01 January 2023", "01 January 2023"));
loanTransactionHelper.approveLoan(loanResponse.get().getLoanId(), loanTransactionHelper.approveLoan(loanResponse.get().getLoanId(),
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN)
@@ -469,7 +469,7 @@ public final class BatchHelper {
final String dateString = LocalDate.now(Utils.getZoneIdOfTenant()).format(DateTimeFormatter.ofPattern(dateFormat)); final String dateString = LocalDate.now(Utils.getZoneIdOfTenant()).format(DateTimeFormatter.ofPattern(dateFormat));
final String body = String.format( final String body = String.format(
"{\"chargeId\": \"%d\", \"locale\": \"en\", \"amount\": \"11.15\", " + "\"dateFormat\": \"%s\", \"dueDate\": \"%s\"}", "{\"chargeId\": \"%d\", \"locale\": \"en\", \"amount\": \"100.0\", " + "\"dateFormat\": \"%s\", \"dueDate\": \"%s\"}",
chargeId, dateFormat, dateString); chargeId, dateFormat, dateString);
br.setBody(body); br.setBody(body);
@@ -18,6 +18,8 @@
*/ */
package org.apache.fineract.integrationtests.common; package org.apache.fineract.integrationtests.common;
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
import com.google.gson.Gson; import com.google.gson.Gson;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
@@ -28,13 +30,16 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.BusinessDateData; import org.apache.fineract.client.models.BusinessDateData;
import org.apache.fineract.client.models.BusinessDateRequest; import org.apache.fineract.client.models.BusinessDateRequest;
import org.apache.fineract.client.models.BusinessDateResponse; import org.apache.fineract.client.models.BusinessDateResponse;
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
import org.apache.fineract.client.util.Calls; import org.apache.fineract.client.util.Calls;
import org.apache.fineract.client.util.JSON; import org.apache.fineract.client.util.JSON;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
@Slf4j @Slf4j
public final class BusinessDateHelper { public final class BusinessDateHelper {
private static final String DATETIME_PATTERN = "dd MMMM yyyy";
private static final Gson GSON = new JSON().getGson(); private static final Gson GSON = new JSON().getGson();
public BusinessDateHelper() {} public BusinessDateHelper() {}
@@ -51,7 +56,7 @@ public final class BusinessDateHelper {
return Utils.performServerPost(requestSpec, responseSpec, BUSINESS_DATE_API, buildBusinessDateRequest(type, date), "changes"); return Utils.performServerPost(requestSpec, responseSpec, BUSINESS_DATE_API, buildBusinessDateRequest(type, date), "changes");
} }
public BusinessDateResponse updateBusinessDate(final BusinessDateRequest request) { public static BusinessDateResponse updateBusinessDate(final BusinessDateRequest request) {
log.info("------------------UPDATE BUSINESS DATE----------------------"); log.info("------------------UPDATE BUSINESS DATE----------------------");
log.info("------------------Type: {}, date: {}----------------------", request.getType(), request.getDate()); log.info("------------------Type: {}, date: {}----------------------", request.getType(), request.getDate());
return Calls.ok(FineractClientHelper.getFineractClient().businessDateManagement.updateBusinessDate(request)); return Calls.ok(FineractClientHelper.getFineractClient().businessDateManagement.updateBusinessDate(request));
@@ -91,4 +96,17 @@ public final class BusinessDateHelper {
return new Gson().toJson(map); return new Gson().toJson(map);
} }
public static void runAt(String date, Runnable runnable) {
try {
new GlobalConfigurationHelper().updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
new PutGlobalConfigurationsRequest().enabled(true));
updateBusinessDate(
new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));
runnable.run();
} finally {
new GlobalConfigurationHelper().updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
new PutGlobalConfigurationsRequest().enabled(false));
}
}
} }
@@ -127,8 +127,8 @@ public class LoanProductTestBuilder {
private String interestRecalculationCompoundingMethod = "0"; private String interestRecalculationCompoundingMethod = "0";
private String preCloseInterestCalculationStrategy = INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE; private String preCloseInterestCalculationStrategy = INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE;
private String rescheduleStrategyMethod = "1"; private String rescheduleStrategyMethod = "1";
private String recalculationRestFrequencyType = "1"; private String recalculationRestFrequencyType = "2";
private String recalculationRestFrequencyInterval = "0"; private String recalculationRestFrequencyInterval = "1";
private String recalculationCompoundingFrequencyType = null; private String recalculationCompoundingFrequencyType = null;
private String recalculationCompoundingFrequencyInterval = null; private String recalculationCompoundingFrequencyInterval = null;
private String minimumDaysBetweenDisbursalAndFirstRepayment = null; private String minimumDaysBetweenDisbursalAndFirstRepayment = null;
@@ -18,62 +18,73 @@
*/ */
package org.apache.fineract.integrationtests.common.loans; package org.apache.fineract.integrationtests.common.loans;
import io.restassured.builder.RequestSpecBuilder; import java.math.BigDecimal;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
import java.util.HashMap;
import java.util.List; import java.util.List;
import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.util.Calls;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.FineractClientHelper;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext;
public class LoanTestLifecycleExtension implements AfterEachCallback { public class LoanTestLifecycleExtension implements AfterEachCallback {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private LoanTransactionHelper loanTransactionHelper; private LoanTransactionHelper loanTransactionHelper;
private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); public static final String DATE_FORMAT = "dd MMMM yyyy";
private final DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern(DATE_FORMAT).toFormatter();
@Override @Override
public void afterEach(ExtensionContext context) { public void afterEach(ExtensionContext context) {
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); BusinessDateHelper.runAt(DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.getLocalDateOfTenant()), () -> {
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.loanTransactionHelper = new LoanTransactionHelper(null, null);
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.requestSpec.header("Fineract-Platform-TenantId", "default");
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
// Fully repay ACTIVE loans, so it will not be picked up by any jobs // Fully repay ACTIVE loans, so it will not be picked up by any jobs
List<Integer> loanIds = LoanTransactionHelper.getLoanIdsByStatusId(requestSpec, responseSpec, 300); List<Long> loanIds = LoanTransactionHelper.getLoanIdsByStatusId(300);
loanIds.forEach(loanId -> { loanIds.forEach(loanId -> {
HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanId); GetLoansLoanIdResponse loanResponse = Calls
LocalDate transactionDate = LocalDate.of((Integer) ((List) prepayDetail.get("date")).get(0), .ok(FineractClientHelper.getFineractClient().loans.retrieveLoan((long) loanId, null, "all", null, null));
(Integer) ((List) prepayDetail.get("date")).get(1), (Integer) ((List) prepayDetail.get("date")).get(2)); loanResponse.getDisbursementDetails().forEach(disbursementDetail -> {
Double amount = Double.parseDouble(String.valueOf(prepayDetail.get("amount"))); if (disbursementDetail.getActualDisbursementDate() == null) {
Double netDisbursalAmount = Double.parseDouble(String.valueOf(prepayDetail.get("netDisbursalAmount"))); loanTransactionHelper.disburseLoan((long) loanId,
Double repayAmount = Double.compare(amount, 0.0) > 0 ? amount : netDisbursalAmount; new PostLoansLoanIdRequest()
loanTransactionHelper.makeLoanRepayment((long) loanId, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy") .actualDisbursementDate(dateFormatter.format(disbursementDetail.getExpectedDisbursementDate()))
.transactionDate(dateFormatter.format(transactionDate)).locale("en").transactionAmount(repayAmount)); .dateFormat(DATE_FORMAT).locale("en")
}); .transactionAmount(BigDecimal.valueOf(disbursementDetail.getPrincipal())));
// Undo APPROVED loans, so the next step can REJECT them, so it will not be picked up by any jobs }
loanIds = LoanTransactionHelper.getLoanIdsByStatusId(requestSpec, responseSpec, 200); });
loanIds.forEach(loanId -> { loanResponse = Calls
loanTransactionHelper.undoApproval(loanId); .ok(FineractClientHelper.getFineractClient().loans.retrieveLoan((long) loanId, null, "all", null, null));
}); GetLoansLoanIdTransactionsTemplateResponse prepayDetail = this.loanTransactionHelper.getPrepaymentAmount(loanId,
// Mark SUBMITTED loans, as REJECTED, so it will not be picked up by any jobs dateFormatter.format(Utils.getLocalDateOfTenant()), DATE_FORMAT);
loanIds = LoanTransactionHelper.getLoanIdsByStatusId(requestSpec, responseSpec, 100); LocalDate transactionDate = prepayDetail.getDate();
loanIds.forEach(loanId -> { Double amount = prepayDetail.getAmount();
GetLoansLoanIdResponse details = loanTransactionHelper.getLoanDetails((long) loanId); Double netDisbursalAmount = prepayDetail.getNetDisbursalAmount();
loanTransactionHelper.rejectLoan((long) loanId, Double repayAmount = Double.compare(amount, 0.0) > 0 ? amount : netDisbursalAmount;
new PostLoansLoanIdRequest().rejectedOnDate(dateFormatter.format(details.getTimeline().getSubmittedOnDate())) loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat(DATE_FORMAT)
.locale("en").dateFormat("dd MMMM yyyy")); .transactionDate(dateFormatter.format(transactionDate)).locale("en").transactionAmount(repayAmount));
});
// Undo APPROVED loans, so the next step can REJECT them, so it will not be picked up by any jobs
loanIds = LoanTransactionHelper.getLoanIdsByStatusId(200);
loanIds.forEach(loanId -> {
loanTransactionHelper.undoApprovalForLoan(loanId, new PostLoansLoanIdRequest());
});
// Mark SUBMITTED loans, as REJECTED, so it will not be picked up by any jobs
loanIds = LoanTransactionHelper.getLoanIdsByStatusId(100);
loanIds.forEach(loanId -> {
GetLoansLoanIdResponse details = loanTransactionHelper.getLoanDetails((long) loanId);
loanTransactionHelper.rejectLoan(loanId,
new PostLoansLoanIdRequest().rejectedOnDate(dateFormatter.format(details.getTimeline().getSubmittedOnDate()))
.locale("en").dateFormat(DATE_FORMAT));
});
// loanIds = LoanTransactionHelper.getLoanIdsByStatusId(300);
// assertEquals(0, loanIds.size());
}); });
} }
} }
@@ -2983,6 +2983,10 @@ public class LoanTransactionHelper {
return new Gson().fromJson(get, new TypeToken<ArrayList<Integer>>() {}.getType()); return new Gson().fromJson(get, new TypeToken<ArrayList<Integer>>() {}.getType());
} }
public static List<Long> getLoanIdsByStatusId(Integer statusId) {
return Calls.ok(FineractClientHelper.getFineractClient().legacy.getLoansByStatus(statusId));
}
public PutLoanProductsProductIdResponse updateLoanProduct(Long id, PutLoanProductsProductIdRequest requestModifyLoan) { public PutLoanProductsProductIdResponse updateLoanProduct(Long id, PutLoanProductsProductIdRequest requestModifyLoan) {
return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.updateLoanProduct(id, requestModifyLoan)); return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.updateLoanProduct(id, requestModifyLoan));
} }
@@ -30,6 +30,7 @@ import io.restassured.specification.ResponseSpecification;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.CommonConstants; import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -42,7 +43,6 @@ import org.mockserver.model.MediaType;
@ExtendWith(MockServerExtension.class) @ExtendWith(MockServerExtension.class)
@MockServerSettings(ports = { 9191 }) @MockServerSettings(ports = { 9191 })
public class CampaignsTest { public class CampaignsTest {
private RequestSpecification requestSpec; private RequestSpecification requestSpec;
@@ -93,145 +93,155 @@ public class CampaignsTest {
@Test @Test
public void testSupportedActionsForCampaignWithTriggerTypeAsDirect() { public void testSupportedActionsForCampaignWithTriggerTypeAsDirect() {
// creating new campaign BusinessDateHelper.runAt(DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.getLocalDateOfTenant()), () -> {
Integer campaignId = this.campaignsHelper.createCampaign(NON_TRIGGERED_REPORT_NAME, DIRECT_TRIGGER_TYPE); // creating new campaign
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId); Integer campaignId = this.campaignsHelper.createCampaign(NON_TRIGGERED_REPORT_NAME, DIRECT_TRIGGER_TYPE);
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId);
// updating campaign // updating campaign
Integer updatedCampaignId = this.campaignsHelper.updateCampaign(this.requestSpec, this.responseSpec, campaignId, Integer updatedCampaignId = this.campaignsHelper.updateCampaign(this.requestSpec, this.responseSpec, campaignId,
NON_TRIGGERED_REPORT_NAME, DIRECT_TRIGGER_TYPE); NON_TRIGGERED_REPORT_NAME, DIRECT_TRIGGER_TYPE);
assertEquals(campaignId, updatedCampaignId); assertEquals(campaignId, updatedCampaignId);
// activating campaign // activating campaign
Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
ACTIVATE_COMMAND); ACTIVATE_COMMAND);
assertEquals(activatedCampaignId, campaignId); assertEquals(activatedCampaignId, campaignId);
// closing campaign // closing campaign
Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
CLOSE_COMMAND); CLOSE_COMMAND);
assertEquals(closedCampaignId, campaignId); assertEquals(closedCampaignId, campaignId);
// reactivating campaign // reactivating campaign
Integer reactivateCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer reactivateCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
REACTIVATE_COMMAND); REACTIVATE_COMMAND);
assertEquals(reactivateCampaignId, campaignId); assertEquals(reactivateCampaignId, campaignId);
// closing campaign again for deletion // closing campaign again for deletion
closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, CLOSE_COMMAND); closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
assertEquals(closedCampaignId, campaignId); CLOSE_COMMAND);
assertEquals(closedCampaignId, campaignId);
// deleting campaign // deleting campaign
Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId); Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId);
assertEquals(deletedCampaignId, campaignId); assertEquals(deletedCampaignId, campaignId);
});
} }
@Test @Test
public void testSupportedActionsForCampaignWithTriggerTypeAsScheduled() { public void testSupportedActionsForCampaignWithTriggerTypeAsScheduled() {
// creating new campaign BusinessDateHelper.runAt(DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.getLocalDateOfTenant()), () -> {
Integer campaignId = this.campaignsHelper.createCampaign(NON_TRIGGERED_REPORT_NAME, SCHEDULED_TRIGGER_TYPE); // creating new campaign
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId); Integer campaignId = this.campaignsHelper.createCampaign(NON_TRIGGERED_REPORT_NAME, SCHEDULED_TRIGGER_TYPE);
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId);
// updating campaign // updating campaign
Integer updatedCampaignId = this.campaignsHelper.updateCampaign(this.requestSpec, this.responseSpec, campaignId, Integer updatedCampaignId = this.campaignsHelper.updateCampaign(this.requestSpec, this.responseSpec, campaignId,
NON_TRIGGERED_REPORT_NAME, SCHEDULED_TRIGGER_TYPE); NON_TRIGGERED_REPORT_NAME, SCHEDULED_TRIGGER_TYPE);
assertEquals(campaignId, updatedCampaignId); assertEquals(campaignId, updatedCampaignId);
// activating campaign // activating campaign
Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
ACTIVATE_COMMAND); ACTIVATE_COMMAND);
assertEquals(activatedCampaignId, campaignId); assertEquals(activatedCampaignId, campaignId);
// closing campaign // closing campaign
Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
CLOSE_COMMAND); CLOSE_COMMAND);
assertEquals(closedCampaignId, campaignId); assertEquals(closedCampaignId, campaignId);
// reactivating campaign // reactivating campaign
Integer reactivateCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer reactivateCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
REACTIVATE_COMMAND); REACTIVATE_COMMAND);
assertEquals(reactivateCampaignId, campaignId); assertEquals(reactivateCampaignId, campaignId);
// closing campaign again for deletion // closing campaign again for deletion
closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, CLOSE_COMMAND); closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
assertEquals(closedCampaignId, campaignId); CLOSE_COMMAND);
assertEquals(closedCampaignId, campaignId);
// deleting campaign // deleting campaign
Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId); Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId);
assertEquals(deletedCampaignId, campaignId); assertEquals(deletedCampaignId, campaignId);
});
} }
@Test @Test
public void testSupportedActionsForCampaignWithTriggerTypeAsTriggered() { public void testSupportedActionsForCampaignWithTriggerTypeAsTriggered() {
// creating new campaign BusinessDateHelper.runAt(DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.getLocalDateOfTenant()), () -> {
Integer campaignId = this.campaignsHelper.createCampaign(TRIGGERED_REPORT_NAME, TRIGGERED_TRIGGER_TYPE); // creating new campaign
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId); Integer campaignId = this.campaignsHelper.createCampaign(TRIGGERED_REPORT_NAME, TRIGGERED_TRIGGER_TYPE);
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId);
// updating campaign // updating campaign
Integer updatedCampaignId = this.campaignsHelper.updateCampaign(this.requestSpec, this.responseSpec, campaignId, Integer updatedCampaignId = this.campaignsHelper.updateCampaign(this.requestSpec, this.responseSpec, campaignId,
TRIGGERED_REPORT_NAME, TRIGGERED_TRIGGER_TYPE); TRIGGERED_REPORT_NAME, TRIGGERED_TRIGGER_TYPE);
assertEquals(campaignId, updatedCampaignId); assertEquals(campaignId, updatedCampaignId);
// activating campaign // activating campaign
Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
ACTIVATE_COMMAND); ACTIVATE_COMMAND);
assertEquals(activatedCampaignId, campaignId); assertEquals(activatedCampaignId, campaignId);
// closing campaign // closing campaign
Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
CLOSE_COMMAND); CLOSE_COMMAND);
assertEquals(closedCampaignId, campaignId); assertEquals(closedCampaignId, campaignId);
// reactivating campaign // reactivating campaign
Integer reactivateCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer reactivateCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
REACTIVATE_COMMAND); REACTIVATE_COMMAND);
assertEquals(reactivateCampaignId, campaignId); assertEquals(reactivateCampaignId, campaignId);
// closing campaign again for deletion // closing campaign again for deletion
closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, CLOSE_COMMAND); closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
assertEquals(closedCampaignId, campaignId); CLOSE_COMMAND);
assertEquals(closedCampaignId, campaignId);
// deleting campaign // deleting campaign
Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId); Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId);
assertEquals(deletedCampaignId, campaignId); assertEquals(deletedCampaignId, campaignId);
});
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void testSupportedActionsForCampaignWithError() { public void testSupportedActionsForCampaignWithError() {
final ResponseSpecification responseSpecWithError = new ResponseSpecBuilder().expectStatusCode(400).build(); BusinessDateHelper.runAt(DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.getLocalDateOfTenant()), () -> {
CampaignsHelper campaignsHelperWithError = new CampaignsHelper(this.requestSpec, responseSpecWithError); final ResponseSpecification responseSpecWithError = new ResponseSpecBuilder().expectStatusCode(400).build();
// creating new campaign CampaignsHelper campaignsHelperWithError = new CampaignsHelper(this.requestSpec, responseSpecWithError);
Integer campaignId = this.campaignsHelper.createCampaign(NON_TRIGGERED_REPORT_NAME, DIRECT_TRIGGER_TYPE); // creating new campaign
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId); Integer campaignId = this.campaignsHelper.createCampaign(NON_TRIGGERED_REPORT_NAME, DIRECT_TRIGGER_TYPE);
this.campaignsHelper.verifyCampaignCreatedOnServer(this.requestSpec, this.responseSpec, campaignId);
// activating campaign with failure // activating campaign with failure
ArrayList<HashMap<String, Object>> campaignDateValidationData = (ArrayList<HashMap<String, Object>>) campaignsHelperWithError ArrayList<HashMap<String, Object>> campaignDateValidationData = (ArrayList<HashMap<String, Object>>) campaignsHelperWithError
.performActionsOnCampaignWithFailure(campaignId, ACTIVATE_COMMAND, .performActionsOnCampaignWithFailure(campaignId, ACTIVATE_COMMAND,
Utils.getLocalDateOfTenant().plusDays(1).format(DateTimeFormatter.ofPattern(DATE_FORMAT)), Utils.getLocalDateOfTenant().plusDays(1).format(DateTimeFormatter.ofPattern(DATE_FORMAT)),
CommonConstants.RESPONSE_ERROR); CommonConstants.RESPONSE_ERROR);
assertEquals("error.msg.campaign.activationDate.in.the.future", assertEquals("error.msg.campaign.activationDate.in.the.future",
campaignDateValidationData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); campaignDateValidationData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
// activating campaign // activating campaign
Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer activatedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
ACTIVATE_COMMAND); ACTIVATE_COMMAND);
assertEquals(activatedCampaignId, campaignId); assertEquals(activatedCampaignId, campaignId);
// activating campaign with failure // activating campaign with failure
ArrayList<HashMap<String, Object>> campaignErrorData = (ArrayList<HashMap<String, Object>>) campaignsHelperWithError ArrayList<HashMap<String, Object>> campaignErrorData = (ArrayList<HashMap<String, Object>>) campaignsHelperWithError
.performActionsOnCampaignWithFailure(activatedCampaignId, ACTIVATE_COMMAND, .performActionsOnCampaignWithFailure(activatedCampaignId, ACTIVATE_COMMAND,
Utils.getLocalDateOfTenant().format(DateTimeFormatter.ofPattern(DATE_FORMAT)), CommonConstants.RESPONSE_ERROR); Utils.getLocalDateOfTenant().format(DateTimeFormatter.ofPattern(DATE_FORMAT)), CommonConstants.RESPONSE_ERROR);
assertEquals("error.msg.campaign.already.active", campaignErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); assertEquals("error.msg.campaign.already.active", campaignErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
// closing campaign again for deletion // closing campaign again for deletion
Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId, Integer closedCampaignId = this.campaignsHelper.performActionsOnCampaign(this.requestSpec, this.responseSpec, campaignId,
CLOSE_COMMAND); CLOSE_COMMAND);
assertEquals(closedCampaignId, campaignId); assertEquals(closedCampaignId, campaignId);
// deleting campaign
Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId);
assertEquals(deletedCampaignId, campaignId);
// deleting campaign
Integer deletedCampaignId = this.campaignsHelper.deleteCampaign(this.requestSpec, this.responseSpec, campaignId);
assertEquals(deletedCampaignId, campaignId);
});
} }
} }
@@ -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.
#
additionalProperties=Additional properties are not allowed: {0}
allOf=One or more conditions in allOf failed: {0}
anyOf=Value does not match any of the required schemas: {0}
const=Value must be exactly: {0}
contains=Array does not contain required item: {0}
crossEdits=Cross edits are not allowed: {0}
dependencies=Missing dependencies: {0}
dependentRequired=Missing dependent properties: {0}
edits=Invalid edits: {0}
enum=Value {0} is not allowed. Allowed values are: {1}
exclusiveMaximum=Value {0} must be less than (exclusive): {1}
exclusiveMinimum=Value {0} must be greater than (exclusive): {1}
format=Value {0} does not match required format: {1}
items=Invalid item in array: {0}
maxItems=Array has too many items: {0}, maximum: {1}
maxLength=String is too long: {0}, maximum length: {1}
maxProperties=Too many properties: {0}, maximum allowed: {1}
maximum=Value {0} exceeds maximum: {1}
minItems=Array has too few items: {0}, minimum: {1}
minLength=String is too short: {0}, minimum length: {1}
minProperties=Too few properties: {0}, minimum required: {1}
minimum=Value {0} is below minimum: {1}
multipleOf=Value {0} is not a multiple of {1}
not=Value matches schema when it should not: {0}
notAllowed=The value is not allowed: {0}
oneOf=Value matches more than one schema in oneOf: {0}
pattern=String does not match pattern: {0}
prefixItems=Array does not match prefixItems validation rules: {0}
properties=Invalid property: {0}
propertyNames=Invalid property name: {0}
readOnly=Property is read-only: {0}
required=Required field is missing: {0}
then='then' schema failed validation
type=Expected type: {0}, found: {1}
unionType=Value does not match any of the expected types: {0}
uniqueItems=Array has duplicate items: {0}
writeOnly=Property is write-only: {0}
$ref=Reference {0} could not be resolved or is invalid
if=Condition 'if' failed
else='else' schema failed validation
allErrors=Multiple validation errors occurred: {0}
default=Validation failed for value: {0}
patternProperties=Pattern property not matched: {0}
# Format-specific
date=Value does not match date format: {0}
dateTime=Value does not match date-time format: {0}
time=Value does not match time format: {0}
email=Value is not a valid email address: {0}
hostname=Value is not a valid hostname: {0}
ipv4=Value is not a valid IPv4 address: {0}
ipv6=Value is not a valid IPv6 address: {0}
uri=Value is not a valid URI: {0}
uuid=Value is not a valid UUID: {0}
uriReference=Value is not a valid URI reference: {0}
iri=Value is not a valid IRI: {0}
iriReference=Value is not a valid IRI reference: {0}
jsonPointer=Value is not a valid JSON Pointer: {0}
relativeJsonPointer=Value is not a valid Relative JSON Pointer: {0}
regex=Value is not a valid regular expression: {0}
byte=Value is not a valid base64 string: {0}
int32=Value is not a valid 32-bit integer: {0}
int64=Value is not a valid 64-bit integer: {0}
float=Value is not a valid float: {0}
double=Value is not a valid double: {0}
binary=Value is not a valid binary: {0}
password=Password format validation failed
contentEncoding=Value does not match expected encoding: {0}
contentMediaType=Value does not match expected media type: {0}
id=Invalid ID value: {0}
false=Schema explicitly disallows all values
dependentSchemas=Object does not satisfy dependent schemas: {0}
unevaluatedProperties=Object has unevaluated properties that are not allowed: {0}