From 07f395ef7f25fd8b5b3199c4476058a8afcc959e Mon Sep 17 00:00:00 2001 From: BLasan Date: Tue, 14 Sep 2021 11:29:02 +0530 Subject: [PATCH] Feat: Repayment With Post Dated Checks --- .../fineract/client/util/FineractClient.java | 2 +- .../service/CommandWrapperBuilder.java | 27 +++ .../importhandler/loan/LoanImportHandler.java | 1 + .../loanaccount/api/LoanApiConstants.java | 1 + .../api/LoanTransactionsApiResource.java | 6 + .../LoanRepaymentScheduleInstallmentData.java | 47 ++++ .../loanaccount/data/LoanTransactionData.java | 12 + .../portfolio/loanaccount/domain/Loan.java | 23 +- .../domain/LoanAccountDomainServiceJpa.java | 39 ++- .../LoanRepaymentScheduleInstallment.java | 12 + .../domain/LoanRepositoryWrapper.java | 10 + ...oanRepaymentScheduleNotFoundException.java | 38 +++ .../LoanEventApiJsonValidator.java | 62 ++++- .../service/LoanReadPlatformService.java | 5 + .../service/LoanReadPlatformServiceImpl.java | 28 +++ ...WritePlatformServiceJpaRepositoryImpl.java | 31 ++- .../PostDatedChecksApiResourceSwagger.java | 106 ++++++++ ...paymentWithPostDatedChecksApiResource.java | 147 ++++++++++++ .../data/PostDatedChecksData.java | 59 +++++ .../data/PostDatedChecksStatus.java | 26 ++ .../domain/PostDatedChecks.java | 150 ++++++++++++ .../domain/PostDatedChecksRepository.java | 33 +++ .../PostDatedCheckBouncedCheckInvalid.java | 29 +++ .../PostDatedCheckNotFoundException.java | 33 +++ .../handler/BouncePostDatedCheckHandler.java | 47 ++++ .../handler/DeletePostDatedChecksHandler.java | 47 ++++ .../handler/EditPostDatedChecksHandler.java | 47 ++++ ...RepaymentWithPostDatedChecksAssembler.java | 78 ++++++ ...ithPostDatedChecksReadPlatformService.java | 32 +++ ...ostDatedChecksReadPlatformServiceImpl.java | 98 ++++++++ ...thPostDatedChecksWritePlatformService.java | 31 +++ ...stDatedChecksWritePlatformServiceImpl.java | 227 ++++++++++++++++++ ...V369__repayment_with_post_dated_checks.sql | 40 +++ .../RepaymentWithPostDatedChecksTest.java | 183 ++++++++++++++ .../common/loans/LoanTransactionHelper.java | 51 ++++ 35 files changed, 1801 insertions(+), 7 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanRepaymentScheduleInstallmentData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanRepaymentScheduleNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/PostDatedChecksApiResourceSwagger.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/RepaymentWithPostDatedChecksApiResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksStatus.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecks.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecksRepository.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckBouncedCheckInvalid.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/BouncePostDatedCheckHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/DeletePostDatedChecksHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/EditPostDatedChecksHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V369__repayment_with_post_dated_checks.sql create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/RepaymentWithPostDatedChecksTest.java diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index 485922664..a8cce6540 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -106,9 +106,9 @@ import org.apache.fineract.client.services.ReportsApi; import org.apache.fineract.client.services.RolesApi; import org.apache.fineract.client.services.RunReportsApi; import org.apache.fineract.client.services.SavingsAccountApi; +import org.apache.fineract.client.services.SavingsAccountTransactionsApi; import org.apache.fineract.client.services.SavingsChargesApi; import org.apache.fineract.client.services.SavingsProductApi; -import org.apache.fineract.client.services.SavingsAccountTransactionsApi; import org.apache.fineract.client.services.SchedulerApi; import org.apache.fineract.client.services.ScoreCardApi; import org.apache.fineract.client.services.SearchApiApi; diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 5239b6f20..ee6ddc948 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -896,6 +896,33 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder updatePostDatedCheck(final Long id, final Long loanId) { + this.actionName = "UPDATE"; + this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS"; + this.entityId = id; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id; + return this; + } + + public CommandWrapperBuilder bouncedCheck(final Long id, final Long loanId) { + this.actionName = "BOUNCE"; + this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS"; + this.entityId = id; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id; + return this; + } + + public CommandWrapperBuilder deletePostDatedCheck(final Long id, final Long loanId) { + this.actionName = "DELETE"; + this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS"; + this.entityId = id; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id; + return this; + } + public CommandWrapperBuilder updateLoanApplication(final Long loanId) { this.actionName = "UPDATE"; this.entityName = "LOAN"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java index 17d4b0b7d..654fa17d4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java @@ -433,6 +433,7 @@ public class LoanImportHandler implements ImportHandler { gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); JsonObject loanRepaymentJsonob = gsonBuilder.create().toJsonTree(loanRepayments.get(rowIndex)).getAsJsonObject(); loanRepaymentJsonob.remove("manuallyReversed"); + loanRepaymentJsonob.remove("numberOfRepayments"); String payload = loanRepaymentJsonob.toString(); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .loanRepaymentTransaction(result.getLoanId()) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java index f23c3bc15..d36273d91 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java @@ -32,6 +32,7 @@ public interface LoanApiConstants { String principalDisbursedParameterName = "transactionAmount"; String chargesParameterName = "charges"; String loanIdTobeApproved = "loanId"; + String postDatedChecks = "postDatedChecks"; String approvedLoanAmountParameterName = "approvedLoanAmount"; String approvedOnDateParameterName = "approvedOnDate"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index af5e2d3d5..2e536e209 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -30,6 +30,7 @@ import java.time.LocalDate; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -54,6 +55,7 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSeria import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; @@ -132,6 +134,10 @@ public class LoanTransactionsApiResource { transactionData = this.loanReadPlatformService.retrieveNewClosureDetails(); } else if (is(commandParam, "disburse")) { transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true); + transactionData.setNumberOfRepayments(this.loanReadPlatformService.retrieveNumberOfRepayments(loanId)); + final List loanRepaymentScheduleInstallmentData = this.loanReadPlatformService + .getRepaymentDataResponse(loanId); + transactionData.setLoanRepaymentScheduleInstallments(loanRepaymentScheduleInstallmentData); } else if (is(commandParam, "disburseToSavings")) { transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false); } else if (is(commandParam, "recoverypayment")) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanRepaymentScheduleInstallmentData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanRepaymentScheduleInstallmentData.java new file mode 100644 index 000000000..c9111cde5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanRepaymentScheduleInstallmentData.java @@ -0,0 +1,47 @@ +/** + * 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.portfolio.loanaccount.data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public final class LoanRepaymentScheduleInstallmentData { + + private Long id; + + private Integer installmentId; + + private LocalDate date; + + private BigDecimal amount; + + private LoanRepaymentScheduleInstallmentData(final Long id, final Integer installmentId, final LocalDate date, + final BigDecimal amount) { + this.amount = amount; + this.date = date; + this.installmentId = installmentId; + this.id = id; + } + + public static LoanRepaymentScheduleInstallmentData instanceOf(final Long id, final Integer installmentId, final LocalDate date, + final BigDecimal amount) { + return new LoanRepaymentScheduleInstallmentData(id, installmentId, date, amount); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java index 1b76ef618..381e5c8c0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.data; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Collection; +import java.util.List; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.account.data.AccountTransferData; @@ -66,6 +67,8 @@ public class LoanTransactionData { private Collection writeOffReasonOptions = null; + private Integer numberOfRepayments = 0; + // import fields private transient Integer rowIndex; private String dateFormat; @@ -80,6 +83,7 @@ public class LoanTransactionData { private Integer bankNumber; private transient Long accountId; private transient String transactionType; + private List loanRepaymentScheduleInstallments; public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId, Integer rowIndex, String locale, String dateFormat) { @@ -167,6 +171,14 @@ public class LoanTransactionData { this.writeOffReasonOptions = null; } + public void setNumberOfRepayments(Integer numberOfRepayments) { + this.numberOfRepayments = numberOfRepayments; + } + + public void setLoanRepaymentScheduleInstallments(final List loanRepaymentScheduleInstallments) { + this.loanRepaymentScheduleInstallments = loanRepaymentScheduleInstallments; + } + public Long getAccountId() { return accountId; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 2ea7e17de..debfd2866 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -143,6 +143,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyTy import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.rate.domain.Rate; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -322,7 +323,7 @@ public class Loan extends AbstractPersistableCustom { private Set loanOfficerHistory; @OrderBy(value = "installmentNumber") - @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.EAGER) private List repaymentScheduleInstallments = new ArrayList<>(); @OrderBy(value = "dateOf, id") @@ -358,6 +359,9 @@ public class Loan extends AbstractPersistableCustom { @OrderBy(value = "expectedDisbursementDate, id") private List disbursementDetails = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.EAGER) + private List postDatedChecks = new ArrayList<>(); + @OrderBy(value = "termApplicableFrom, id") @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY) private List loanTermVariations = new ArrayList<>(); @@ -528,6 +532,10 @@ public class Loan extends AbstractPersistableCustom { } + public Integer getNumberOfRepayments() { + return this.loanRepaymentScheduleDetail.getNumberOfRepayments(); + } + private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) { if (this.summary == null) { this.summary = LoanSummary.create(feeChargesDueAtDisbursement); @@ -2821,6 +2829,11 @@ public class Loan extends AbstractPersistableCustom { return chargesPayment; } + public void removePostDatedChecks() { + List postDatedChecks = new ArrayList<>(); + this.postDatedChecks = postDatedChecks; + } + public Map undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List existingTransactionIds, final List existingReversedTransactionIds, AppUser currentUser) { @@ -6267,6 +6280,14 @@ public class Loan extends AbstractPersistableCustom { return amount; } + public void updatePostDatedChecks(final List postDatedChecks) { + this.postDatedChecks = postDatedChecks; + } + + public List getPostDatedChecks() { + return this.postDatedChecks; + } + public void updateWriteOffReason(CodeValue writeOffReason) { this.writeOffReason = writeOffReason; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index dec83bcc5..a9590d2ee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -57,6 +57,7 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstr import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; +import org.apache.fineract.portfolio.accountdetails.domain.AccountType; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEntity; @@ -74,6 +75,9 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; @@ -102,6 +106,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final BusinessEventNotifierService businessEventNotifierService; private final LoanUtilService loanUtilService; private final StandingInstructionRepository standingInstructionRepository; + private final PostDatedChecksRepository postDatedChecksRepository; private final LoanCollateralManagementRepository loanCollateralManagementRepository; @Autowired @@ -116,10 +121,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context, final BusinessEventNotifierService businessEventNotifierService, final LoanUtilService loanUtilService, - final StandingInstructionRepository standingInstructionRepository, + final StandingInstructionRepository standingInstructionRepository, final PostDatedChecksRepository postDatedChecksRepository, final LoanCollateralManagementRepository loanCollateralManagementRepository) { this.loanAccountAssembler = loanAccountAssembler; - this.loanRepositoryWrapper = loanRepositoryWrapper; this.loanTransactionRepository = loanTransactionRepository; this.noteRepository = noteRepository; this.configurationDomainService = configurationDomainService; @@ -135,7 +139,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { this.businessEventNotifierService = businessEventNotifierService; this.loanUtilService = loanUtilService; this.standingInstructionRepository = standingInstructionRepository; + this.postDatedChecksRepository = postDatedChecksRepository; this.loanCollateralManagementRepository = loanCollateralManagementRepository; + this.loanRepositoryWrapper = loanRepositoryWrapper; } @Transactional @@ -246,6 +252,35 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { builderResult.withEntityId(newRepaymentTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()) .withGroupId(loan.getGroupId()); + if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { + // Mark Post Dated Check as paid. + final Set loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction + .getLoanTransactionToRepaymentScheduleMappings(); + + if (loanTransactionToRepaymentScheduleMappings != null) { + for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : loanTransactionToRepaymentScheduleMappings) { + LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping + .getLoanRepaymentScheduleInstallment(); + if (loanRepaymentScheduleInstallment != null) { + final boolean isPaid = loanRepaymentScheduleInstallment.isNotFullyPaidOff(); + PostDatedChecks postDatedChecks = this.postDatedChecksRepository + .getPendingPostDatedCheck(loanRepaymentScheduleInstallment); + + if (postDatedChecks != null) { + if (!isPaid) { + postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PAID); + } else { + postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PENDING); + } + this.postDatedChecksRepository.saveAndFlush(postDatedChecks); + } else { + break; + } + } + } + } + } + return newRepaymentTransaction; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index f276cdd43..c47ea396a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; @Entity @Table(name = "m_loan_repayment_schedule") @@ -137,6 +138,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @JoinColumn(name = "loan_repayment_schedule_id", referencedColumnName = "id", nullable = false) private Set loanCompoundingDetails = new HashSet<>(); + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment") + private Set postDatedChecks; + LoanRepaymentScheduleInstallment() { this.installmentNumber = null; this.fromDate = null; @@ -212,6 +216,14 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus return fromLocalDate; } + public void setPostDatedChecksToNull() { + this.postDatedChecks = null; + } + + public Set getPostDatedCheck() { + return this.postDatedChecks; + } + public LocalDate getDueDate() { return LocalDate.ofInstant(this.dueDate.toInstant(), DateUtils.getDateTimeZoneOfTenant()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java index 00fab0d12..752c2071d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java @@ -111,6 +111,16 @@ public class LoanRepositoryWrapper { return this.repository.getMaxClientOrJLGLoanCounter(clientId); } + public List getLoanRepaymentScheduleInstallments(final Long loanId) { + final Loan loan = this.repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); + final List loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); + return loanRepaymentScheduleInstallments; + } + + public Integer getNumberOfRepayments(final Long loanId) { + return this.repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)).getNumberOfRepayments(); + } + public Integer getMaxClientOrJLGLoanProductCounter(@Param("productId") Long productId, @Param("clientId") Long clientId) { return this.repository.getMaxClientOrJLGLoanProductCounter(productId, clientId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanRepaymentScheduleNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanRepaymentScheduleNotFoundException.java new file mode 100644 index 000000000..5ca96c147 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanRepaymentScheduleNotFoundException.java @@ -0,0 +1,38 @@ +/** + * 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.portfolio.loanaccount.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class LoanRepaymentScheduleNotFoundException extends AbstractPlatformResourceNotFoundException { + + public LoanRepaymentScheduleNotFoundException(final Integer id) { + super("error.msg.loan.repayment.installment.id.invalid", "Loan repayment with installment identifier " + id + " does not exist", + id); + } + + public LoanRepaymentScheduleNotFoundException(final Long id) { + super("error.msg.loan.repayment.id.invalid", "Loan repayment with identifier " + id + " does not exist", id); + } + + public LoanRepaymentScheduleNotFoundException(final Long id, final Integer loan) { + super("error.msg.loan.id" + id + ".not.contains.repayment.schedule", "Loan does not have repayment scheduled", loan); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java index f2a0944b2..2cac0dd93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java @@ -18,7 +18,9 @@ */ package org.apache.fineract.portfolio.loanaccount.serialization; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -30,18 +32,25 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -50,12 +59,14 @@ public final class LoanEventApiJsonValidator { private final FromJsonHelper fromApiJsonHelper; private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer; + private final LoanRepository loanRepository; @Autowired public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper, - final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer) { + final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, final LoanRepository loanRepository) { this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.loanRepository = loanRepository; } private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { @@ -79,7 +90,8 @@ public final class LoanEventApiJsonValidator { } else { disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate", - LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName)); + LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName, + LoanApiConstants.postDatedChecks)); } final Type typeOfMap = new TypeToken>() {}.getType(); @@ -110,6 +122,52 @@ public final class LoanEventApiJsonValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); } + public void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) { + final JsonElement jsonElement = this.fromApiJsonHelper.parse(json); + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.disbursement"); + final Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); + final List loanRepaymentScheduleInstallment = loan.getRepaymentScheduleInstallments(); + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(jsonObject); + if (jsonObject.has("postDatedChecks") && jsonObject.get("postDatedChecks").isJsonArray()) { + JsonArray postDatedChecks = jsonObject.get("postDatedChecks").getAsJsonArray(); + for (int i = 0; i < postDatedChecks.size(); i++) { + final JsonObject postDatedCheck = postDatedChecks.get(i).getAsJsonObject(); + + final String name = this.fromApiJsonHelper.extractStringNamed("name", postDatedCheck); + baseDataValidator.reset().parameter("name").value(name).notNull(); + + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", postDatedCheck, locale); + baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount(); + + final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", postDatedCheck); + baseDataValidator.reset().parameter("accountNo").value(accountNo).notNull().positiveAmount(); + + final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", postDatedCheck); + baseDataValidator.reset().parameter("checkNo").value(checkNo).notNull().positiveAmount(); + + final Integer installmentId = this.fromApiJsonHelper.extractIntegerNamed("installmentId", postDatedCheck, locale); + final List installmentList = loanRepaymentScheduleInstallment.stream().filter( + repayment -> repayment.getInstallmentNumber().equals(installmentId) && repayment.getLoan().getId().equals(loanId)) + .collect(Collectors.toList()); + if (installmentList.size() > 1) { + throw new PlatformDataIntegrityException("error.repayment.redundancy", "Multiple installment data found", + "postDatedChecks"); + } else if (installmentList.size() == 0) { + throw new LoanRepaymentScheduleNotFoundException(installmentId); + } + + } + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors); + } + } + } + public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) { if (null != calendarInstance) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java index 8ad6f37f9..3ad197ea3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -31,6 +31,7 @@ import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData; +import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; @@ -140,6 +141,10 @@ public interface LoanReadPlatformService { Integer retrieveNumberOfActiveLoans(); + Integer retrieveNumberOfRepayments(Long loanId); + List retrieveGLIMChildLoansByGLIMParentAccount(String parentloanAccountNumber); + List getRepaymentDataResponse(Long loanId); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index d99113c61..a40178886 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -86,6 +86,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData; import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData; import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData; +import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData; import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData; @@ -105,6 +106,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; @@ -535,12 +537,38 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { if (paymentDetailsRequired) { paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); } + return LoanTransactionData.loanTransactionDataForDisbursalTemplate(transactionType, loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), paymentOptions, loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling()); } + @Override + public Integer retrieveNumberOfRepayments(final Long loanId) { + this.context.authenticatedUser(); + return this.loanRepositoryWrapper.getNumberOfRepayments(loanId); + } + + @Override + public List getRepaymentDataResponse(final Long loanId) { + this.context.authenticatedUser(); + final List loanRepaymentScheduleInstallments = this.loanRepositoryWrapper + .getLoanRepaymentScheduleInstallments(loanId); + List loanRepaymentScheduleInstallmentData = new ArrayList<>(); + + if (loanRepaymentScheduleInstallments == null || loanRepaymentScheduleInstallments.size() == 0) { + throw new LoanRepaymentScheduleNotFoundException(loanId, Integer.valueOf(0)); + } + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loanRepaymentScheduleInstallments) { + loanRepaymentScheduleInstallmentData.add(LoanRepaymentScheduleInstallmentData.instanceOf( + loanRepaymentScheduleInstallment.getId(), loanRepaymentScheduleInstallment.getInstallmentNumber(), + loanRepaymentScheduleInstallment.getDueDate(), loanRepaymentScheduleInstallment + .getTotalOutstanding(loanRepaymentScheduleInstallment.getLoan().getCurrency()).getAmount())); + } + return loanRepaymentScheduleInstallmentData; + } + @Override public LoanTransactionData retrieveLoanTransaction(final Long loanId, final Long transactionId) { this.context.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 448cce74d..c37bc17f6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -193,6 +193,9 @@ import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.transfer.api.TransferApiConstants; import org.apache.fineract.useradministration.domain.AppUser; @@ -249,6 +252,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final CashierTransactionDataValidator cashierTransactionDataValidator; private final GLIMAccountInfoRepository glimRepository; private final LoanRepository loanRepository; + private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler; + private final PostDatedChecksRepository postDatedChecksRepository; private final LoanChargePaidByRepository loanChargePaidByRepository; @Autowired @@ -279,7 +284,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, final CodeValueRepositoryWrapper codeValueRepository, final LoanRepositoryWrapper loanRepositoryWrapper, final CashierTransactionDataValidator cashierTransactionDataValidator, final GLIMAccountInfoRepository glimRepository, - final LoanRepository loanRepository, final LoanChargePaidByRepository loanChargePaidByRepository) { + final LoanRepository loanRepository, final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler, + final PostDatedChecksRepository postDatedChecksRepository, final LoanChargePaidByRepository loanChargePaidByRepository) { this.context = context; this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanAssembler = loanAssembler; @@ -320,6 +326,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.cashierTransactionDataValidator = cashierTransactionDataValidator; this.loanRepository = loanRepository; this.glimRepository = glimRepository; + this.repaymentWithPostDatedChecksAssembler = repaymentWithPostDatedChecksAssembler; + this.postDatedChecksRepository = postDatedChecksRepository; this.loanChargePaidByRepository = loanChargePaidByRepository; } @@ -359,6 +367,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer); + if (command.parameterExists("postDatedChecks")) { + // validate with post dated checks for the disbursement + this.loanEventApiJsonValidator.validateDisbursementWithPostDatedChecks(command.json(), loanId); + } + final Loan loan = this.loanAssembler.assembleFrom(loanId); // Get disbursedAmount @@ -541,6 +554,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf updateRecurringCalendarDatesForInterestRecalculation(loan); this.loanAccountDomainService.recalculateAccruals(loan); + + // Post Dated Checks + if (command.parameterExists("postDatedChecks")) { + // get repayment with post dates checks to update + Set postDatedChecks = this.repaymentWithPostDatedChecksAssembler.fromParsedJson(command.json(), loan); + updatePostDatedChecks(postDatedChecks); + } + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_DISBURSAL, constructEntityMap(BusinessEntity.LOAN, loan)); return new CommandProcessingResultBuilder() // @@ -554,6 +575,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } + private void updatePostDatedChecks(Set postDatedChecks) { + this.postDatedChecksRepository.saveAll(postDatedChecks); + } + private void createAndSaveLoanScheduleArchive(final Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) { LoanRescheduleRequest loanRescheduleRequest = null; LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO); @@ -623,6 +648,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf try { List installments = loan.getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment installment : installments) { + // installment.setPostDatedChecksToNull(); if (installment.getId() == null) { this.repaymentScheduleInstallmentRepository.save(installment); } @@ -840,6 +866,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.setActualDisbursementDate(null); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); + // Remove post dated checks if added. + loan.removePostDatedChecks(); + final Map changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/PostDatedChecksApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/PostDatedChecksApiResourceSwagger.java new file mode 100644 index 000000000..85b56acda --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/PostDatedChecksApiResourceSwagger.java @@ -0,0 +1,106 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; +import java.time.LocalDate; + +final class PostDatedChecksApiResourceSwagger { + + private PostDatedChecksApiResourceSwagger() {} + + @Schema(description = "GetPostDatedChecks") + public static final class GetPostDatedChecks { + + private GetPostDatedChecks() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "1") + public Integer installmentId; + @Schema(example = "AMANA") + public String name; + @Schema(example = "900800200300") + public Long accountNo; + @Schema(example = "100000") + public BigDecimal amount; + @Schema(example = "2021-07-18") + public LocalDate date; + + } + + @Schema(description = "UpdatePostDatedCheckRequest") + public static final class UpdatePostDatedCheckRequest { + + private UpdatePostDatedCheckRequest() {} + + @Schema(example = "en") + public String locale; + @Schema(example = "10000") + public BigDecimal amount; + @Schema(example = "900800300400") + public Long accountNo; + @Schema(example = "2021-07-19") + public LocalDate repaymentDate; + @Schema(example = "AMANA") + public String name; + @Schema(example = "dd MM YYYY") + public String dateFormat; + @Schema(example = "2021-08-10") + public LocalDate date; + } + + @Schema(description = "UpdatePostDatedCheckResponse") + public static final class UpdatePostDatedCheckResponse { + + private UpdatePostDatedCheckResponse() {} + + @Schema(example = "12") + public Integer resourceId; + public UpdateChangesResponse changes; + + static final class UpdateChangesResponse { + + private UpdateChangesResponse() {} + + @Schema(example = "10000") + public BigDecimal amount; + @Schema(example = "AMANA") + public String bankName; + @Schema(example = "900800500600") + public Long accountNo; + @Schema(example = "2021-08-10") + public LocalDate date; + } + } + + @Schema(description = "DeletePostDatedCheck") + public static final class DeletePostDatedCheck { + + private DeletePostDatedCheck() {} + + @Schema(example = "1") + public Long loanId; + @Schema(example = "2") + public Long resourceId; + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/RepaymentWithPostDatedChecksApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/RepaymentWithPostDatedChecksApiResource.java new file mode 100644 index 000000000..e8d2293a6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/api/RepaymentWithPostDatedChecksApiResource.java @@ -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.portfolio.repaymentwithpostdatedchecks.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksData; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/loans/{loanId}/postdatedchecks") +@Component +@Scope("singleton") +@Tag(name = "repayment with post dated checks", description = "Repay with post dated checks") +public class RepaymentWithPostDatedChecksApiResource { + + private final PlatformSecurityContext context; + private final FromJsonHelper fromJsonHelper; + private final DefaultToApiJsonSerializer apiJsonSerializer; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final RepaymentWithPostDatedChecksReadPlatformService repaymentWithPostDatedChecksReadPlatformService; + + @Autowired + public RepaymentWithPostDatedChecksApiResource(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, + final DefaultToApiJsonSerializer apiJsonSerializer, + final PortfolioCommandSourceWritePlatformService portfolioCommandSourceWritePlatformService, + final RepaymentWithPostDatedChecksReadPlatformService repaymentWithPostDatedChecksReadPlatformService) { + this.context = context; + this.fromJsonHelper = fromJsonHelper; + this.apiJsonSerializer = apiJsonSerializer; + this.commandsSourceWritePlatformService = portfolioCommandSourceWritePlatformService; + this.repaymentWithPostDatedChecksReadPlatformService = repaymentWithPostDatedChecksReadPlatformService; + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Get All Post Dated Checks", description = "Get All Post dated Checks") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.GetPostDatedChecks.class)))) }) + public String getPostDatedChecks(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId) { + this.context.authenticatedUser(); + final List postDatedChecksDataList = this.repaymentWithPostDatedChecksReadPlatformService + .getPostDatedChecks(loanId); + return this.apiJsonSerializer.serialize(postDatedChecksDataList); + } + + @GET + @Path("{installmentId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Get Post Dated Check", description = "Get Post Dated Check") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.GetPostDatedChecks.class)))) }) + public String getPostDatedCheck(@PathParam("installmentId") @Parameter(description = "installmentId") final Integer installmentId, + @PathParam("loanId") @Parameter(description = "loanId") final Long loanId) { + this.context.authenticatedUser(); + final PostDatedChecksData postDatedChecksData = this.repaymentWithPostDatedChecksReadPlatformService + .getPostDatedCheckByInstallmentId(installmentId, loanId); + return this.apiJsonSerializer.serialize(postDatedChecksData); + } + + @PUT + @Path("{postDatedCheckId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Update Post Dated Check, Bounced Check", description = "Update Post Dated Check, Bounced Check") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.UpdatePostDatedCheckRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.UpdatePostDatedCheckResponse.class)))) }) + public String updatePostDatedChecks(@PathParam("postDatedCheckId") @Parameter(description = "postDatedCheckId") final Long id, + @PathParam("loanId") @Parameter(description = "loanId") final Long loanId, + @Parameter(hidden = true) final String apiRequestBodyAsJson, + @QueryParam("editType") @Parameter(description = "editType") final String type) { + + CommandWrapper commandRequest = null; + + if ("update".equals(type)) { + commandRequest = new CommandWrapperBuilder().updatePostDatedCheck(id, loanId).withJson(apiRequestBodyAsJson).build(); + } else if ("bounced".equals(type)) { + commandRequest = new CommandWrapperBuilder().bouncedCheck(id, loanId).withJson(apiRequestBodyAsJson).build(); + } + + final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.apiJsonSerializer.serialize(commandProcessingResult); + } + + @DELETE + @Path("{postDatedCheckId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete Post Dated Check", description = "Delete Post Dated Check") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PostDatedChecksApiResourceSwagger.DeletePostDatedCheck.class)))) }) + public String deletePostDatedCheck(@PathParam("postDatedCheckId") @Parameter(description = "postDatedCheckId") final Long id, + @PathParam("loanId") @Parameter(description = "loanId") final Long loanId) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().deletePostDatedCheck(id, loanId).build(); + + final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.apiJsonSerializer.serialize(commandProcessingResult); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksData.java new file mode 100644 index 000000000..19caf3bbb --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksData.java @@ -0,0 +1,59 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public final class PostDatedChecksData { + + private final Long id; + + private final Integer installmentId; + + private final String name; + + private final Long accountNo; + + private final BigDecimal amount; + + private final LocalDate installmentDate; + + private final Long checkNo; + + private final Integer status; + + private PostDatedChecksData(final LocalDate installmentDate, final Long id, final Integer installmentId, final Long accountNo, + final BigDecimal amount, final String name, final Long checkNo, final Integer status) { + this.accountNo = accountNo; + this.amount = amount; + this.name = name; + this.id = id; + this.installmentDate = installmentDate; + this.installmentId = installmentId; + this.checkNo = checkNo; + this.status = status; + } + + public static PostDatedChecksData from(final LocalDate installmentDate, final Long id, final Integer installmentId, + final Long accountNo, final BigDecimal amount, final String name, final Long checkNo, final Integer status) { + return new PostDatedChecksData(installmentDate, id, installmentId, accountNo, amount, name, checkNo, status); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksStatus.java new file mode 100644 index 000000000..ed66d0f06 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/data/PostDatedChecksStatus.java @@ -0,0 +1,26 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.data; + +public interface PostDatedChecksStatus { + + Integer POST_DATED_CHECKS_BOUNCED = 1; + Integer POST_DATED_CHECKS_PAID = 2; + Integer POST_DATED_CHECKS_PENDING = 0; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecks.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecks.java new file mode 100644 index 000000000..b4cc449ba --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecks.java @@ -0,0 +1,150 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.domain; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; + +@Entity +@Table(name = "m_repayment_with_post_dated_checks") +public class PostDatedChecks extends AbstractPersistableCustom { + + @ManyToOne(optional = false) + @JoinColumn(name = "loan_id", referencedColumnName = "id", nullable = false) + private Loan loan; + @OneToOne(optional = false) + @JoinColumn(name = "repayment_id", referencedColumnName = "id", nullable = false) + private LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment; + @Column(name = "account_no", nullable = false, length = 10) + private Long accountNo; + @Column(name = "bank_name", nullable = false, length = 100) + private String bankName; + @Column(name = "amount", scale = 6, precision = 19) + private BigDecimal amount; + @Column(name = "repayment_date", nullable = false) + private Date repaymentDate; + @Column(name = "status", columnDefinition = "0") + private Integer status; + @Column(name = "check_no", nullable = false, unique = true) + private Long checkNo; + + public PostDatedChecks() {} + + private PostDatedChecks(final Long accountNo, final String bankName, final BigDecimal amount, + final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final Date date, final Loan loan, final Long checkNo) { + this.bankName = bankName; + this.accountNo = accountNo; + this.amount = amount; + this.loanRepaymentScheduleInstallment = loanRepaymentScheduleInstallment; + this.repaymentDate = date; + this.loan = loan; + this.checkNo = checkNo; + } + + public static PostDatedChecks instanceOf(final Long accountNo, final String bankName, final BigDecimal amount, + final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final Loan loan, final Long checkNo) { + return new PostDatedChecks(accountNo, bankName, amount, loanRepaymentScheduleInstallment, + Date.from(loanRepaymentScheduleInstallment.getDueDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()), + loan, checkNo); + } + + public void setLoan(Loan loan) { + this.loan = loan; + } + + public void setLoanRepaymentScheduleInstallment(final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) { + this.loanRepaymentScheduleInstallment = loanRepaymentScheduleInstallment; + } + + public Map updatePostDatedChecks(JsonCommand command) { + final Map changes = new HashMap<>(); + + if (command.isChangeInBigDecimalParameterNamed("amount", this.amount)) { + final BigDecimal newAmount = command.bigDecimalValueOfParameterNamed("amount"); + this.amount = newAmount; + changes.put("amount", newAmount); + } + + if (command.isChangeInStringParameterNamed("name", this.bankName)) { + final String newName = command.stringValueOfParameterNamed("name"); + this.bankName = newName; + changes.put("bankName", newName); + } + + if (command.isChangeInLongParameterNamed("accountNo", this.accountNo)) { + final Long newAccountNo = command.longValueOfParameterNamed("accountNo"); + this.accountNo = newAccountNo; + changes.put("accountNo", newAccountNo); + } + + if (command.isChangeInLongParameterNamed("checkNo", this.accountNo)) { + final Long newCheckNo = command.longValueOfParameterNamed("checkNo"); + this.checkNo = newCheckNo; + changes.put("checkNo", newCheckNo); + } + + return changes; + } + + public Loan getLoan() { + return this.loan; + } + + public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() { + return this.loanRepaymentScheduleInstallment; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getBankName() { + return this.bankName; + } + + public Long getCheckNo() { + return this.checkNo; + } + + public Long getAccountNo() { + return this.accountNo; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public Integer getStatus() { + return this.status; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecksRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecksRepository.java new file mode 100644 index 000000000..4cf74ebe8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/domain/PostDatedChecksRepository.java @@ -0,0 +1,33 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.domain; + +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PostDatedChecksRepository extends JpaRepository, JpaSpecificationExecutor { + + @Query("select pdc from PostDatedChecks pdc where pdc.status = 0 and pdc.loanRepaymentScheduleInstallment = :loanRepaymentScheduleInstallment") + PostDatedChecks getPendingPostDatedCheck( + @Param("loanRepaymentScheduleInstallment") LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckBouncedCheckInvalid.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckBouncedCheckInvalid.java new file mode 100644 index 000000000..0460631db --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckBouncedCheckInvalid.java @@ -0,0 +1,29 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class PostDatedCheckBouncedCheckInvalid extends AbstractPlatformDomainRuleException { + + public PostDatedCheckBouncedCheckInvalid(final String defaultUserMessage, final Object... defaultUserMessageArgs) { + super("error.msg.post.dated.checkNo..invalid", defaultUserMessage, defaultUserMessageArgs); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckNotFoundException.java new file mode 100644 index 000000000..1860fb62f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/exception/PostDatedCheckNotFoundException.java @@ -0,0 +1,33 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class PostDatedCheckNotFoundException extends AbstractPlatformResourceNotFoundException { + + public PostDatedCheckNotFoundException(final Long id) { + super("error.msg.post.dated.check.id.invalid", "Post Dated Check with identifier " + id + " does not exist", id); + } + + public PostDatedCheckNotFoundException(final Long id, final Integer installmentNo) { + super("error.msg.post.dated.check.for.installment.not.found", + "Post Dated Check for loan id " + id + " and installment number " + installmentNo + " does not exist"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/BouncePostDatedCheckHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/BouncePostDatedCheckHandler.java new file mode 100644 index 000000000..c2a7ede41 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/BouncePostDatedCheckHandler.java @@ -0,0 +1,47 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "REPAYMENT_WITH_POSTDATEDCHECKS", action = "BOUNCE") +public class BouncePostDatedCheckHandler implements NewCommandSourceHandler { + + private final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService; + + @Autowired + public BouncePostDatedCheckHandler( + final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService) { + this.repaymentWithPostDatedChecksWritePlatformService = repaymentWithPostDatedChecksWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.repaymentWithPostDatedChecksWritePlatformService.bouncePostDatedChecks(command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/DeletePostDatedChecksHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/DeletePostDatedChecksHandler.java new file mode 100644 index 000000000..25bc5d451 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/DeletePostDatedChecksHandler.java @@ -0,0 +1,47 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "REPAYMENT_WITH_POSTDATEDCHECKS", action = "DELETE") +public class DeletePostDatedChecksHandler implements NewCommandSourceHandler { + + private final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService; + + @Autowired + public DeletePostDatedChecksHandler( + final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService) { + this.repaymentWithPostDatedChecksWritePlatformService = repaymentWithPostDatedChecksWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.repaymentWithPostDatedChecksWritePlatformService.deletePostDatedChecks(command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/EditPostDatedChecksHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/EditPostDatedChecksHandler.java new file mode 100644 index 000000000..ca05b0c15 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/handler/EditPostDatedChecksHandler.java @@ -0,0 +1,47 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "REPAYMENT_WITH_POSTDATEDCHECKS", action = "UPDATE") +public class EditPostDatedChecksHandler implements NewCommandSourceHandler { + + private final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService; + + @Autowired + public EditPostDatedChecksHandler( + final RepaymentWithPostDatedChecksWritePlatformService repaymentWithPostDatedChecksWritePlatformService) { + this.repaymentWithPostDatedChecksWritePlatformService = repaymentWithPostDatedChecksWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.repaymentWithPostDatedChecksWritePlatformService.updatePostDatedChecks(command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java new file mode 100644 index 000000000..8282ce987 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksAssembler.java @@ -0,0 +1,78 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.service; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class RepaymentWithPostDatedChecksAssembler { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public RepaymentWithPostDatedChecksAssembler(final FromJsonHelper fromJsonHelper) { + this.fromApiJsonHelper = fromJsonHelper; + } + + public Set fromParsedJson(final String json, final Loan loan) { + final Set postDatedChecks = new HashSet<>(); + final JsonElement jsonElement = this.fromApiJsonHelper.parse(json); + final List loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(jsonObject); + if (jsonObject.has("postDatedChecks") && jsonObject.get("postDatedChecks").isJsonArray()) { + JsonArray postDatedCheckArray = jsonObject.get("postDatedChecks").getAsJsonArray(); + for (int i = 0; i < postDatedCheckArray.size(); i++) { + final JsonObject postDatedCheck = postDatedCheckArray.get(i).getAsJsonObject(); + if (postDatedCheck == null) { + continue; + } + + final String name = this.fromApiJsonHelper.extractStringNamed("name", postDatedCheck); + + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", postDatedCheck, locale); + + final Integer installmentId = this.fromApiJsonHelper.extractIntegerNamed("installmentId", postDatedCheck, locale); + final List installmentList = loanRepaymentScheduleInstallments.stream() + .filter(repayment -> repayment.getInstallmentNumber().equals(installmentId)).collect(Collectors.toList()); + final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", postDatedCheck); + final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", postDatedCheck); + + postDatedChecks.add(PostDatedChecks.instanceOf(accountNo, name, amount, installmentList.get(0), loan, checkNo)); + } + } + + return postDatedChecks; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformService.java new file mode 100644 index 000000000..366561815 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformService.java @@ -0,0 +1,32 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.service; + +import java.util.List; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksData; + +public interface RepaymentWithPostDatedChecksReadPlatformService { + + List getPostDatedChecks(Long id); + + PostDatedChecksData getPostDatedCheck(Long id); + + PostDatedChecksData getPostDatedCheckByInstallmentId(Integer id, Long loanId); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java new file mode 100644 index 000000000..59ec8543e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksReadPlatformServiceImpl.java @@ -0,0 +1,98 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksData; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.exception.PostDatedCheckNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class RepaymentWithPostDatedChecksReadPlatformServiceImpl implements RepaymentWithPostDatedChecksReadPlatformService { + + private final PostDatedChecksRepository postDatedChecksRepository; + private final LoanRepository loanRepository; + private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository; + + @Autowired + public RepaymentWithPostDatedChecksReadPlatformServiceImpl(final PostDatedChecksRepository postDatedChecksRepository, + final LoanRepository loanRepository, + final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository) { + this.postDatedChecksRepository = postDatedChecksRepository; + this.loanRepository = loanRepository; + this.loanRepaymentScheduleInstallmentRepository = loanRepaymentScheduleInstallmentRepository; + } + + @Override + public List getPostDatedChecks(final Long id) { + final Loan loan = this.loanRepository.findById(id).orElseThrow(() -> new LoanNotFoundException(id)); + final List postDatedChecks = loan.getPostDatedChecks(); + List postDatedChecksDataList = new ArrayList<>(); + for (PostDatedChecks postDatedCheckObject : postDatedChecks) { + // Avoid bounced checks + if (!PostDatedChecksStatus.POST_DATED_CHECKS_BOUNCED.equals(postDatedCheckObject.getStatus())) { + postDatedChecksDataList.add(PostDatedChecksData.from( + postDatedCheckObject.getLoanRepaymentScheduleInstallment().getDueDate(), postDatedCheckObject.getId(), + postDatedCheckObject.getLoanRepaymentScheduleInstallment().getInstallmentNumber(), + postDatedCheckObject.getAccountNo(), postDatedCheckObject.getAmount(), postDatedCheckObject.getBankName(), + postDatedCheckObject.getCheckNo(), postDatedCheckObject.getStatus())); + } + } + return postDatedChecksDataList; + } + + @Override + public PostDatedChecksData getPostDatedCheck(final Long id) { + final PostDatedChecks postDatedChecks = this.postDatedChecksRepository.findById(id) + .orElseThrow(() -> new PostDatedCheckNotFoundException(id)); + + return PostDatedChecksData.from(postDatedChecks.getLoanRepaymentScheduleInstallment().getDueDate(), postDatedChecks.getId(), + postDatedChecks.getLoanRepaymentScheduleInstallment().getInstallmentNumber(), postDatedChecks.getAccountNo(), + postDatedChecks.getAmount(), postDatedChecks.getBankName(), postDatedChecks.getCheckNo(), postDatedChecks.getStatus()); + } + + @Override + public PostDatedChecksData getPostDatedCheckByInstallmentId(final Integer id, final Long loanId) { + final Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); + final List postDatedChecks = loan.getPostDatedChecks(); + + if (postDatedChecks == null || postDatedChecks.size() == 0) { + throw new PostDatedCheckNotFoundException(loanId, id); + } + + final PostDatedChecks postDatedChecksData = postDatedChecks.stream() + .filter((postDatedCheck) -> postDatedCheck.getLoanRepaymentScheduleInstallment().getInstallmentNumber().equals(id) + && !PostDatedChecksStatus.POST_DATED_CHECKS_BOUNCED.equals(postDatedCheck.getStatus())) + .collect(Collectors.toList()).get(0); + return PostDatedChecksData.from(postDatedChecksData.getLoanRepaymentScheduleInstallment().getDueDate(), postDatedChecksData.getId(), + postDatedChecksData.getLoanRepaymentScheduleInstallment().getInstallmentNumber(), postDatedChecksData.getAccountNo(), + postDatedChecksData.getAmount(), postDatedChecksData.getBankName(), postDatedChecksData.getCheckNo(), + postDatedChecksData.getStatus()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformService.java new file mode 100644 index 000000000..f0b50e2dc --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformService.java @@ -0,0 +1,31 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface RepaymentWithPostDatedChecksWritePlatformService { + + CommandProcessingResult deletePostDatedChecks(JsonCommand command); + + CommandProcessingResult updatePostDatedChecks(JsonCommand command); + + CommandProcessingResult bouncePostDatedChecks(JsonCommand command); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java new file mode 100644 index 000000000..039933e96 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/repaymentwithpostdatedchecks/service/RepaymentWithPostDatedChecksWritePlatformServiceImpl.java @@ -0,0 +1,227 @@ +/** + * 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.portfolio.repaymentwithpostdatedchecks.service; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; +import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.exception.PostDatedCheckNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.jpa.JpaSystemException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class RepaymentWithPostDatedChecksWritePlatformServiceImpl implements RepaymentWithPostDatedChecksWritePlatformService { + + private final PostDatedChecksRepository postDatedChecksRepository; + private final FromJsonHelper fromApiJsonHelper; + private final LoanRepository loanRepository; + /** + * The parameters supported for this command. + */ + private final Set supportedParametersForUpdate = new HashSet<>(Arrays.asList("name", "amount", "accountNo", "locale")); + private final Set supportedParametersForBounce = new HashSet<>( + Arrays.asList("name", "amount", "accountNo", "checkNo", "locale", "installmentId")); + + @Autowired + public RepaymentWithPostDatedChecksWritePlatformServiceImpl(final PostDatedChecksRepository postDatedChecksRepository, + final FromJsonHelper fromApiJsonHelper, final LoanRepository loanRepository) { + this.postDatedChecksRepository = postDatedChecksRepository; + this.fromApiJsonHelper = fromApiJsonHelper; + this.loanRepository = loanRepository; + } + + @Transactional + @Override + public CommandProcessingResult updatePostDatedChecks(JsonCommand command) { + validateForUpdate(command); + final PostDatedChecks postDatedChecks = this.postDatedChecksRepository.findById(command.entityId()) + .orElseThrow(() -> new PostDatedCheckNotFoundException(command.entityId())); + Map changes = postDatedChecks.updatePostDatedChecks(command); + this.postDatedChecksRepository.saveAndFlush(postDatedChecks); + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(command.entityId()).with(changes) + .build(); + } + + @Transactional + @Override + public CommandProcessingResult bouncePostDatedChecks(JsonCommand command) { + validateForBounce(command); + + final PostDatedChecks postDatedCheck = this.postDatedChecksRepository.findById(command.entityId()) + .orElseThrow(() -> new PostDatedCheckNotFoundException(command.entityId())); + postDatedCheck.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_BOUNCED); + + final Loan loan = this.loanRepository.findById(command.getLoanId()) + .orElseThrow(() -> new LoanNotFoundException(command.getLoanId())); + + final JsonElement jsonElement = this.fromApiJsonHelper.parse(command.json()); + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(jsonObject); + + final String name = this.fromApiJsonHelper.extractStringNamed("name", jsonObject); + + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", jsonObject, locale); + + final Integer installmentId = this.fromApiJsonHelper.extractIntegerNamed("installmentId", jsonObject, locale); + + final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", jsonObject); + final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", jsonObject); + + final List loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); + + final List installmentList = loanRepaymentScheduleInstallments.stream() + .filter(repayment -> repayment.getInstallmentNumber().equals(installmentId)).collect(Collectors.toList()); + + final PostDatedChecks postDatedChecks = PostDatedChecks.instanceOf(accountNo, name, amount, installmentList.get(0), loan, checkNo); + try { + this.postDatedChecksRepository.saveAndFlush(postDatedChecks); + } catch (final JpaSystemException | DataIntegrityViolationException e) { + final Throwable realCause = e.getCause(); + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("postdatedChecks"); + if (realCause.getMessage().toLowerCase().contains("transaction has been rolled back")) { + baseDataValidator.reset().parameter("checkNo").failWithCode("value.must.be.unique"); + } + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors, e); + } + } + + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(Integer.toUnsignedLong(installmentId)) + .withLoanId(postDatedChecks.getLoan().getId()).build(); + } + + private void validateForBounce(JsonCommand command) { + final JsonElement jsonElement = this.fromApiJsonHelper.parse(command.json()); + final List dataValidationErrors = new ArrayList<>(); + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, command.json(), this.supportedParametersForBounce); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource("repayment-with-post-dated-checks"); + + if (!command.parameterExists("locale")) { + baseDataValidator.reset().parameter("locale").notNull().failWithCode("locale.not.exists"); + } else { + final String locale = this.fromApiJsonHelper.extractStringNamed("locale", jsonElement); + baseDataValidator.reset().parameter("locale").value(locale).notNull(); + } + + if (command.parameterExists("amount")) { + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", jsonElement); + baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount(); + } + + if (command.parameterExists("accountNo")) { + final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", jsonElement); + baseDataValidator.reset().parameter("accountNo").value(accountNo).notNull().positiveAmount(); + } + + if (command.parameterExists("name")) { + final String name = this.fromApiJsonHelper.extractStringNamed("name", jsonElement); + baseDataValidator.reset().parameter("name").value(name).notNull(); + } + + if (command.parameterExists("checkNo")) { + final Long checkNo = this.fromApiJsonHelper.extractLongNamed("checkNo", jsonElement); + baseDataValidator.reset().parameter("checkNo").value(checkNo).notNull().longGreaterThanZero(); + } + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + } + + private void validateForUpdate(JsonCommand command) { + final JsonElement jsonElement = this.fromApiJsonHelper.parse(command.json()); + final List dataValidationErrors = new ArrayList<>(); + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, command.json(), this.supportedParametersForUpdate); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource("repayment-with-post-dated-checks"); + + if (!command.parameterExists("locale")) { + baseDataValidator.reset().parameter("locale").notNull().failWithCode("locale.not.exists"); + } else { + final String locale = this.fromApiJsonHelper.extractStringNamed("locale", jsonElement); + baseDataValidator.reset().parameter("locale").value(locale).notNull(); + } + + if (command.parameterExists("amount")) { + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", jsonElement); + baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount(); + } + + if (command.parameterExists("accountNo")) { + final Long accountNo = this.fromApiJsonHelper.extractLongNamed("accountNo", jsonElement); + baseDataValidator.reset().parameter("accountNo").value(accountNo).notNull().positiveAmount(); + } + + if (command.parameterExists("name")) { + final String name = this.fromApiJsonHelper.extractStringNamed("name", jsonElement); + baseDataValidator.reset().parameter("name").value(name).notNull(); + } + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + } + + @Transactional + @Override + public CommandProcessingResult deletePostDatedChecks(final JsonCommand command) { + this.postDatedChecksRepository.deleteById(command.entityId()); + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withLoanId(command.getLoanId()) + .withEntityId(command.entityId()).build(); + } +} diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V369__repayment_with_post_dated_checks.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V369__repayment_with_post_dated_checks.sql new file mode 100644 index 000000000..61f7ca60f --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V369__repayment_with_post_dated_checks.sql @@ -0,0 +1,40 @@ +-- +-- 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. +-- + +-- create m_repayment_with_post_dated_checks management +CREATE TABLE `m_repayment_with_post_dated_checks` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `check_no` bigint(20) NOT NULL UNIQUE, + `amount` decimal(20,5) NOT NULL, + `loan_id` bigint(20) DEFAULT NULL, + `repayment_id` bigint(20) DEFAULT NULL, + `account_no` bigint(20) NOT NULL, + `bank_name` VARCHAR(200) NOT NULL, + `repayment_date` DATE NOT NULL, + `status` SMALLINT(1) DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE, + CONSTRAINT `fkloanrepayment` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fkrepaymentwithpostdated` FOREIGN KEY (`repayment_id`) REFERENCES `m_loan_repayment_schedule` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +); + +INSERT INTO `m_permission` +(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES +('portfolio', 'UPDATE_REPAYMENT_WITH_POSTDATEDCHECKS', 'REPAYMENT_WITH_POSTDATEDCHECKS', 'UPDATE', '0'), +('portfolio', 'DELETE_REPAYMENT_WITH_POSTDATEDCHECKS', 'REPAYMENT_WITH_POSTDATEDCHECKS', 'DELETE', '0'), +('portfolio', 'BOUNCE_REPAYMENT_WITH_POSTDATEDCHECKS', 'REPAYMENT_WITH_POSTDATEDCHECKS', 'BOUNCE', '0'); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/RepaymentWithPostDatedChecksTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/RepaymentWithPostDatedChecksTest.java new file mode 100644 index 000000000..472e7b32e --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/RepaymentWithPostDatedChecksTest.java @@ -0,0 +1,183 @@ +/** + * 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 com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +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.math.BigDecimal; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.CollateralManagementHelper; +import org.apache.fineract.integrationtests.common.PaymentTypeDomain; +import org.apache.fineract.integrationtests.common.PaymentTypeHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class RepaymentWithPostDatedChecksTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private final SimpleDateFormat dateFormatterStandard = new SimpleDateFormat("dd MMMM yyyy"); + private LoanTransactionHelper loanTransactionHelper; + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + } + + @Test + public void testRepaymentWithPostDatedChecks() { + this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + + Calendar meetingCalendar = Calendar.getInstance(); + meetingCalendar.set(2012, 3, 4); + + final String disbursalDate = this.dateFormatterStandard.format(meetingCalendar.getTime()); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientID); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + + final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().build(null)); + Assertions.assertNotNull(loanProductID, "Could not create Loan Product"); + + final Integer loanID = applyForLoanApplication(clientID, loanProductID, "8000"); + Assertions.assertNotNull(loanID, "Could not create Loan Account"); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + // Test for loan account is created, can be approved + this.loanTransactionHelper.approveLoan(disbursalDate, loanID); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + // Get repayments + final ArrayList installmentData = this.loanTransactionHelper.getRepayments(loanID); + Assertions.assertNotNull(installmentData, "Empty Installment Data"); + List postDatedChecks = new ArrayList<>(); + Gson gson = new Gson(); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + + // Get the first installment date + ArrayList installmentDate = (ArrayList) installmentData.get(0).get("date"); + Assertions.assertNotNull(installmentDate); + Assertions.assertEquals(3, installmentDate.size()); + Calendar calendar = Calendar.getInstance(); + calendar.set((Integer) installmentDate.get(0), (Integer) installmentDate.get(1) - 1, (Integer) installmentDate.get(2)); + final String LOAN_REPAYMENT_DATE = dateFormat.format(calendar.getTime()); + Float firstInstallmentAmount = (Float) installmentData.get(0).get("amount"); + + for (int i = 0; i < installmentData.size(); i++) { + String result = gson.toJson(installmentData.get(i)); + JsonObject reportObject = JsonParser.parseString(result).getAsJsonObject(); + final Integer installmentId = reportObject.get("installmentId").getAsInt(); + final BigDecimal amount = reportObject.get("amount").getAsBigDecimal(); + postDatedChecks.add(postDatedCheck(installmentId, amount)); + } + + Assertions.assertNotNull(postDatedChecks); + + // Test for loan account approved can be disbursed + this.loanTransactionHelper.disburseLoanWithPostDatedChecks(disbursalDate, loanID, BigDecimal.valueOf(8000), postDatedChecks); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + // Create payment type PDC - Post Dated Checks + String name = "PDC"; + String description = PaymentTypeHelper.randomNameGenerator("PDC", 15); + Boolean isCashPayment = false; + Integer position = 1; + + Integer paymentTypeId = PaymentTypeHelper.createPaymentType(requestSpec, responseSpec, name, description, isCashPayment, position); + Assertions.assertNotNull(paymentTypeId); + PaymentTypeHelper.verifyPaymentTypeCreatedOnServer(requestSpec, responseSpec, paymentTypeId); + PaymentTypeDomain paymentTypeResponse = PaymentTypeHelper.retrieveById(requestSpec, responseSpec, paymentTypeId); + Assertions.assertEquals(name, paymentTypeResponse.getName()); + + // Repay for the installment 1 using post dated check + HashMap postDatedCheck = this.loanTransactionHelper.getPostDatedCheck(loanID, Integer.valueOf(1)); + Assertions.assertNotNull(postDatedCheck); + Assertions.assertNotNull(Float.parseFloat(String.valueOf(postDatedCheck.get("amount")))); + + this.loanTransactionHelper.makeRepaymentWithPDC(LOAN_REPAYMENT_DATE, firstInstallmentAmount, loanID, paymentTypeId); + } + + private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String proposedAmount) { + List collaterals = new ArrayList<>(); + final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(collateralId); + final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec, + clientID.toString(), collateralId); + Assertions.assertNotNull(clientCollateralId); + addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1)); + final String loanApplication = new LoanApplicationTestBuilder().withPrincipal(proposedAmount).withLoanTermFrequency("5") + .withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("1") + .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withExpectedDisbursementDate("04 April 2012") + .withCollaterals(collaterals).withSubmittedOnDate("02 April 2012") + .build(clientID.toString(), loanProductID.toString(), null); + return this.loanTransactionHelper.getLoanId(loanApplication); + } + + private void addCollaterals(List collaterals, Integer collateralId, BigDecimal quantity) { + collaterals.add(collaterals(collateralId, quantity)); + } + + private HashMap collaterals(Integer collateralId, BigDecimal quantity) { + HashMap collateral = new HashMap(2); + collateral.put("clientCollateralId", collateralId.toString()); + collateral.put("quantity", quantity.toString()); + return collateral; + } + + private HashMap postDatedCheck(final Integer installmentId, final BigDecimal amount) { + HashMap map = new HashMap(); + map.put("installmentId", installmentId.toString()); + map.put("name", "AMANA BANK"); + map.put("amount", amount.toString()); + map.put("accountNo", "900400500621"); + map.put("checkNo", Utils.randomNumberGenerator(12).toString()); + + return map; + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index e653abb57..7f88182d4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -28,6 +28,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; @@ -165,6 +166,12 @@ public class LoanTransactionHelper { return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, ""); } + public ArrayList getRepayments(final Integer loanId) { + final String GET_REPAYMENTS_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/template?command=disburse&" + + Utils.TENANT_IDENTIFIER; + return Utils.performServerGet(requestSpec, responseSpec, GET_REPAYMENTS_URL, "loanRepaymentScheduleInstallments"); + } + public HashMap approveLoan(final String approvalDate, final Integer loanID) { String loanApprovalCommand = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID); String loanApprovalRequest = getApproveLoanAsJSON(approvalDate); @@ -203,6 +210,28 @@ public class LoanTransactionHelper { return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, null)); } + public HashMap disburseLoanWithPostDatedChecks(final String date, final Integer loanId, final BigDecimal transactionAmount, + final List postDatedChecks) { + return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanId), + getDisburseLoanWithPostDatedChecksAsJSON(date, transactionAmount.toString(), postDatedChecks)); + } + + private String getDisburseLoanWithPostDatedChecksAsJSON(final String actualDisbursementDate, final String transactionAmount, + final List postDatedChecks) { + final HashMap map = new HashMap(); + map.put("locale", "en"); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("actualDisbursementDate", actualDisbursementDate); + map.put("note", "DISBURSE NOTE"); + if (transactionAmount != null) { + map.put("transactionAmount", transactionAmount); + } + + map.put("postDatedChecks", postDatedChecks); + LOG.info("Loan Application disburse request : {} ", map); + return new Gson().toJson(map); + } + public HashMap disburseLoanWithRepaymentReschedule(final String date, final Integer loanID, String adjustRepaymentDate) { return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanWithRepaymentRescheduleAsJSON(date, null, adjustRepaymentDate)); @@ -291,6 +320,11 @@ public class LoanTransactionHelper { getRepaymentBodyAsJSON(date, amountToBePaid), ""); } + public HashMap makeRepaymentWithPDC(final String date, final Float amountToBePaid, final Integer loanID, final Integer paymentType) { + return (HashMap) performLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID), + getRepaymentWithPDCBodyAsJSON(date, amountToBePaid, paymentType), ""); + } + public HashMap forecloseLoan(final String transactionDate, final Integer loanID) { return (HashMap) performLoanTransaction(createLoanTransactionURL(FORECLOSURE_COMMAND, loanID), getForeclosureBodyAsJSON(transactionDate, loanID), ""); @@ -367,6 +401,12 @@ public class LoanTransactionHelper { return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, ""); } + public HashMap getPostDatedCheck(final Integer loanId, final Integer installmentId) { + final String GET_POST_DATED_TRANS_URL = "/fineract-provider/api/v1/loans/" + loanId + "/postdatedchecks/" + installmentId + "?" + + Utils.TENANT_IDENTIFIER; + return Utils.performServerGet(requestSpec, responseSpec, GET_POST_DATED_TRANS_URL, ""); + } + private String getDisburseLoanAsJSON(final String actualDisbursementDate, final String transactionAmount) { final HashMap map = new HashMap<>(); map.put("locale", "en"); @@ -444,6 +484,17 @@ public class LoanTransactionHelper { return new Gson().toJson(map); } + private String getRepaymentWithPDCBodyAsJSON(final String transactionDate, final Float transactionAmount, final Integer paymentTypeId) { + final HashMap map = new HashMap<>(); + map.put("locale", "en"); + map.put("paymentTypeId", paymentTypeId.toString()); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("transactionDate", transactionDate); + map.put("transactionAmount", transactionAmount.toString()); + map.put("note", "Repayment Made!!!"); + return new Gson().toJson(map); + } + private String getForeclosureBodyAsJSON(final String transactionDate, final Integer loanId) { final HashMap map = new HashMap<>(); map.put("locale", "en");