Feat: Repayment With Post Dated Checks

This commit is contained in:
BLasan
2021-09-21 21:34:58 +08:00
committed by Petri Tuomola
parent f0d4f5e332
commit 07f395ef7f
35 changed files with 1801 additions and 7 deletions
@@ -106,9 +106,9 @@ import org.apache.fineract.client.services.ReportsApi;
import org.apache.fineract.client.services.RolesApi; import org.apache.fineract.client.services.RolesApi;
import org.apache.fineract.client.services.RunReportsApi; import org.apache.fineract.client.services.RunReportsApi;
import org.apache.fineract.client.services.SavingsAccountApi; 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.SavingsChargesApi;
import org.apache.fineract.client.services.SavingsProductApi; 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.SchedulerApi;
import org.apache.fineract.client.services.ScoreCardApi; import org.apache.fineract.client.services.ScoreCardApi;
import org.apache.fineract.client.services.SearchApiApi; import org.apache.fineract.client.services.SearchApiApi;
@@ -896,6 +896,33 @@ public class CommandWrapperBuilder {
return this; 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) { public CommandWrapperBuilder updateLoanApplication(final Long loanId) {
this.actionName = "UPDATE"; this.actionName = "UPDATE";
this.entityName = "LOAN"; this.entityName = "LOAN";
@@ -433,6 +433,7 @@ public class LoanImportHandler implements ImportHandler {
gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat));
JsonObject loanRepaymentJsonob = gsonBuilder.create().toJsonTree(loanRepayments.get(rowIndex)).getAsJsonObject(); JsonObject loanRepaymentJsonob = gsonBuilder.create().toJsonTree(loanRepayments.get(rowIndex)).getAsJsonObject();
loanRepaymentJsonob.remove("manuallyReversed"); loanRepaymentJsonob.remove("manuallyReversed");
loanRepaymentJsonob.remove("numberOfRepayments");
String payload = loanRepaymentJsonob.toString(); String payload = loanRepaymentJsonob.toString();
final CommandWrapper commandRequest = new CommandWrapperBuilder() // final CommandWrapper commandRequest = new CommandWrapperBuilder() //
.loanRepaymentTransaction(result.getLoanId()) // .loanRepaymentTransaction(result.getLoanId()) //
@@ -32,6 +32,7 @@ public interface LoanApiConstants {
String principalDisbursedParameterName = "transactionAmount"; String principalDisbursedParameterName = "transactionAmount";
String chargesParameterName = "charges"; String chargesParameterName = "charges";
String loanIdTobeApproved = "loanId"; String loanIdTobeApproved = "loanId";
String postDatedChecks = "postDatedChecks";
String approvedLoanAmountParameterName = "approvedLoanAmount"; String approvedLoanAmountParameterName = "approvedLoanAmount";
String approvedOnDateParameterName = "approvedOnDate"; String approvedOnDateParameterName = "approvedOnDate";
@@ -30,6 +30,7 @@ import java.time.LocalDate;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; 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.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; 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.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -132,6 +134,10 @@ public class LoanTransactionsApiResource {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails(); transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (is(commandParam, "disburse")) { } else if (is(commandParam, "disburse")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true); transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
transactionData.setNumberOfRepayments(this.loanReadPlatformService.retrieveNumberOfRepayments(loanId));
final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallmentData = this.loanReadPlatformService
.getRepaymentDataResponse(loanId);
transactionData.setLoanRepaymentScheduleInstallments(loanRepaymentScheduleInstallmentData);
} else if (is(commandParam, "disburseToSavings")) { } else if (is(commandParam, "disburseToSavings")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false); transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
} else if (is(commandParam, "recoverypayment")) { } else if (is(commandParam, "recoverypayment")) {
@@ -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);
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.account.data.AccountTransferData; import org.apache.fineract.portfolio.account.data.AccountTransferData;
@@ -66,6 +67,8 @@ public class LoanTransactionData {
private Collection<CodeValueData> writeOffReasonOptions = null; private Collection<CodeValueData> writeOffReasonOptions = null;
private Integer numberOfRepayments = 0;
// import fields // import fields
private transient Integer rowIndex; private transient Integer rowIndex;
private String dateFormat; private String dateFormat;
@@ -80,6 +83,7 @@ public class LoanTransactionData {
private Integer bankNumber; private Integer bankNumber;
private transient Long accountId; private transient Long accountId;
private transient String transactionType; private transient String transactionType;
private List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments;
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId, public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
Integer rowIndex, String locale, String dateFormat) { Integer rowIndex, String locale, String dateFormat) {
@@ -167,6 +171,14 @@ public class LoanTransactionData {
this.writeOffReasonOptions = null; this.writeOffReasonOptions = null;
} }
public void setNumberOfRepayments(Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
}
public void setLoanRepaymentScheduleInstallments(final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments) {
this.loanRepaymentScheduleInstallments = loanRepaymentScheduleInstallments;
}
public Long getAccountId() { public Long getAccountId() {
return accountId; return accountId;
} }
@@ -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.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.rate.domain.Rate; 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.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -322,7 +323,7 @@ public class Loan extends AbstractPersistableCustom {
private Set<LoanOfficerAssignmentHistory> loanOfficerHistory; private Set<LoanOfficerAssignmentHistory> loanOfficerHistory;
@OrderBy(value = "installmentNumber") @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<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = new ArrayList<>(); private List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = new ArrayList<>();
@OrderBy(value = "dateOf, id") @OrderBy(value = "dateOf, id")
@@ -358,6 +359,9 @@ public class Loan extends AbstractPersistableCustom {
@OrderBy(value = "expectedDisbursementDate, id") @OrderBy(value = "expectedDisbursementDate, id")
private List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>(); private List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.EAGER)
private List<PostDatedChecks> postDatedChecks = new ArrayList<>();
@OrderBy(value = "termApplicableFrom, id") @OrderBy(value = "termApplicableFrom, id")
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
private List<LoanTermVariations> loanTermVariations = new ArrayList<>(); private List<LoanTermVariations> 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) { private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
if (this.summary == null) { if (this.summary == null) {
this.summary = LoanSummary.create(feeChargesDueAtDisbursement); this.summary = LoanSummary.create(feeChargesDueAtDisbursement);
@@ -2821,6 +2829,11 @@ public class Loan extends AbstractPersistableCustom {
return chargesPayment; return chargesPayment;
} }
public void removePostDatedChecks() {
List<PostDatedChecks> postDatedChecks = new ArrayList<>();
this.postDatedChecks = postDatedChecks;
}
public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds, public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, AppUser currentUser) { final List<Long> existingReversedTransactionIds, AppUser currentUser) {
@@ -6267,6 +6280,14 @@ public class Loan extends AbstractPersistableCustom {
return amount; return amount;
} }
public void updatePostDatedChecks(final List<PostDatedChecks> postDatedChecks) {
this.postDatedChecks = postDatedChecks;
}
public List<PostDatedChecks> getPostDatedChecks() {
return this.postDatedChecks;
}
public void updateWriteOffReason(CodeValue writeOffReason) { public void updateWriteOffReason(CodeValue writeOffReason) {
this.writeOffReason = writeOffReason; this.writeOffReason = writeOffReason;
} }
@@ -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.AccountTransferTransaction;
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; 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.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEntity; 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.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; 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.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
@@ -102,6 +106,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final StandingInstructionRepository standingInstructionRepository; private final StandingInstructionRepository standingInstructionRepository;
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanCollateralManagementRepository loanCollateralManagementRepository; private final LoanCollateralManagementRepository loanCollateralManagementRepository;
@Autowired @Autowired
@@ -116,10 +121,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context, final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context,
final BusinessEventNotifierService businessEventNotifierService, final LoanUtilService loanUtilService, final BusinessEventNotifierService businessEventNotifierService, final LoanUtilService loanUtilService,
final StandingInstructionRepository standingInstructionRepository, final StandingInstructionRepository standingInstructionRepository, final PostDatedChecksRepository postDatedChecksRepository,
final LoanCollateralManagementRepository loanCollateralManagementRepository) { final LoanCollateralManagementRepository loanCollateralManagementRepository) {
this.loanAccountAssembler = loanAccountAssembler; this.loanAccountAssembler = loanAccountAssembler;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
this.noteRepository = noteRepository; this.noteRepository = noteRepository;
this.configurationDomainService = configurationDomainService; this.configurationDomainService = configurationDomainService;
@@ -135,7 +139,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.businessEventNotifierService = businessEventNotifierService; this.businessEventNotifierService = businessEventNotifierService;
this.loanUtilService = loanUtilService; this.loanUtilService = loanUtilService;
this.standingInstructionRepository = standingInstructionRepository; this.standingInstructionRepository = standingInstructionRepository;
this.postDatedChecksRepository = postDatedChecksRepository;
this.loanCollateralManagementRepository = loanCollateralManagementRepository; this.loanCollateralManagementRepository = loanCollateralManagementRepository;
this.loanRepositoryWrapper = loanRepositoryWrapper;
} }
@Transactional @Transactional
@@ -246,6 +252,35 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
builderResult.withEntityId(newRepaymentTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()) builderResult.withEntityId(newRepaymentTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId())
.withGroupId(loan.getGroupId()); .withGroupId(loan.getGroupId());
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
// Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> 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; return newRepaymentTransaction;
} }
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
@Entity @Entity
@Table(name = "m_loan_repayment_schedule") @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) @JoinColumn(name = "loan_repayment_schedule_id", referencedColumnName = "id", nullable = false)
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>(); private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
private Set<PostDatedChecks> postDatedChecks;
LoanRepaymentScheduleInstallment() { LoanRepaymentScheduleInstallment() {
this.installmentNumber = null; this.installmentNumber = null;
this.fromDate = null; this.fromDate = null;
@@ -212,6 +216,14 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return fromLocalDate; return fromLocalDate;
} }
public void setPostDatedChecksToNull() {
this.postDatedChecks = null;
}
public Set<PostDatedChecks> getPostDatedCheck() {
return this.postDatedChecks;
}
public LocalDate getDueDate() { public LocalDate getDueDate() {
return LocalDate.ofInstant(this.dueDate.toInstant(), DateUtils.getDateTimeZoneOfTenant()); return LocalDate.ofInstant(this.dueDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
} }
@@ -111,6 +111,16 @@ public class LoanRepositoryWrapper {
return this.repository.getMaxClientOrJLGLoanCounter(clientId); return this.repository.getMaxClientOrJLGLoanCounter(clientId);
} }
public List<LoanRepaymentScheduleInstallment> getLoanRepaymentScheduleInstallments(final Long loanId) {
final Loan loan = this.repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
final List<LoanRepaymentScheduleInstallment> 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) { public Integer getMaxClientOrJLGLoanProductCounter(@Param("productId") Long productId, @Param("clientId") Long clientId) {
return this.repository.getMaxClientOrJLGLoanProductCounter(productId, clientId); return this.repository.getMaxClientOrJLGLoanProductCounter(productId, clientId);
} }
@@ -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);
}
}
@@ -18,7 +18,9 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.serialization; package org.apache.fineract.portfolio.loanaccount.serialization;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -30,18 +32,25 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; 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.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException; import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; 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.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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -50,12 +59,14 @@ public final class LoanEventApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer; private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
private final LoanRepository loanRepository;
@Autowired @Autowired
public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper, public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper,
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer) { final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, final LoanRepository loanRepository) {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanRepository = loanRepository;
} }
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -79,7 +90,8 @@ public final class LoanEventApiJsonValidator {
} else { } else {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat", disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
"paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName)); LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.postDatedChecks));
} }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
@@ -110,6 +122,52 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
public void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) {
final JsonElement jsonElement = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> 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> 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<LoanRepaymentScheduleInstallment> 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, public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance,
Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) { Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
if (null != calendarInstance) { if (null != calendarInstance) {
@@ -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.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData; 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.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
@@ -140,6 +141,10 @@ public interface LoanReadPlatformService {
Integer retrieveNumberOfActiveLoans(); Integer retrieveNumberOfActiveLoans();
Integer retrieveNumberOfRepayments(Long loanId);
List<LoanAccountData> retrieveGLIMChildLoansByGLIMParentAccount(String parentloanAccountNumber); List<LoanAccountData> retrieveGLIMChildLoansByGLIMParentAccount(String parentloanAccountNumber);
List<LoanRepaymentScheduleInstallmentData> getRepaymentDataResponse(Long loanId);
} }
@@ -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.LoanApplicationTimelineData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData; import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData; 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.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData; import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData; 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.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; 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.exception.LoanTransactionNotFoundException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
@@ -535,12 +537,38 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
if (paymentDetailsRequired) { if (paymentDetailsRequired) {
paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
} }
return LoanTransactionData.loanTransactionDataForDisbursalTemplate(transactionType, return LoanTransactionData.loanTransactionDataForDisbursalTemplate(transactionType,
loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), paymentOptions, loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), paymentOptions,
loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling()); loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling());
} }
@Override
public Integer retrieveNumberOfRepayments(final Long loanId) {
this.context.authenticatedUser();
return this.loanRepositoryWrapper.getNumberOfRepayments(loanId);
}
@Override
public List<LoanRepaymentScheduleInstallmentData> getRepaymentDataResponse(final Long loanId) {
this.context.authenticatedUser();
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = this.loanRepositoryWrapper
.getLoanRepaymentScheduleInstallments(loanId);
List<LoanRepaymentScheduleInstallmentData> 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 @Override
public LoanTransactionData retrieveLoanTransaction(final Long loanId, final Long transactionId) { public LoanTransactionData retrieveLoanTransaction(final Long loanId, final Long transactionId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
@@ -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.note.domain.NoteRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; 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.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.transfer.api.TransferApiConstants; import org.apache.fineract.portfolio.transfer.api.TransferApiConstants;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
@@ -249,6 +252,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final CashierTransactionDataValidator cashierTransactionDataValidator; private final CashierTransactionDataValidator cashierTransactionDataValidator;
private final GLIMAccountInfoRepository glimRepository; private final GLIMAccountInfoRepository glimRepository;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler;
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanChargePaidByRepository loanChargePaidByRepository; private final LoanChargePaidByRepository loanChargePaidByRepository;
@Autowired @Autowired
@@ -279,7 +284,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy,
final CodeValueRepositoryWrapper codeValueRepository, final LoanRepositoryWrapper loanRepositoryWrapper, final CodeValueRepositoryWrapper codeValueRepository, final LoanRepositoryWrapper loanRepositoryWrapper,
final CashierTransactionDataValidator cashierTransactionDataValidator, final GLIMAccountInfoRepository glimRepository, 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.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
@@ -320,6 +326,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.cashierTransactionDataValidator = cashierTransactionDataValidator; this.cashierTransactionDataValidator = cashierTransactionDataValidator;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.glimRepository = glimRepository; this.glimRepository = glimRepository;
this.repaymentWithPostDatedChecksAssembler = repaymentWithPostDatedChecksAssembler;
this.postDatedChecksRepository = postDatedChecksRepository;
this.loanChargePaidByRepository = loanChargePaidByRepository; this.loanChargePaidByRepository = loanChargePaidByRepository;
} }
@@ -359,6 +367,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer); 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); final Loan loan = this.loanAssembler.assembleFrom(loanId);
// Get disbursedAmount // Get disbursedAmount
@@ -541,6 +554,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
updateRecurringCalendarDatesForInterestRecalculation(loan); updateRecurringCalendarDatesForInterestRecalculation(loan);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
// Post Dated Checks
if (command.parameterExists("postDatedChecks")) {
// get repayment with post dates checks to update
Set<PostDatedChecks> postDatedChecks = this.repaymentWithPostDatedChecksAssembler.fromParsedJson(command.json(), loan);
updatePostDatedChecks(postDatedChecks);
}
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_DISBURSAL, this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_DISBURSAL,
constructEntityMap(BusinessEntity.LOAN, loan)); constructEntityMap(BusinessEntity.LOAN, loan));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -554,6 +575,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build(); .build();
} }
private void updatePostDatedChecks(Set<PostDatedChecks> postDatedChecks) {
this.postDatedChecksRepository.saveAll(postDatedChecks);
}
private void createAndSaveLoanScheduleArchive(final Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) { private void createAndSaveLoanScheduleArchive(final Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) {
LoanRescheduleRequest loanRescheduleRequest = null; LoanRescheduleRequest loanRescheduleRequest = null;
LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO); LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO);
@@ -623,6 +648,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
try { try {
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) { for (LoanRepaymentScheduleInstallment installment : installments) {
// installment.setPostDatedChecksToNull();
if (installment.getId() == null) { if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment); this.repaymentScheduleInstallmentRepository.save(installment);
} }
@@ -840,6 +866,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.setActualDisbursementDate(null); loan.setActualDisbursementDate(null);
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
// Remove post dated checks if added.
loan.removePostDatedChecks();
final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds,
currentUser); currentUser);
@@ -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;
}
}
@@ -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<PostDatedChecksData> apiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final RepaymentWithPostDatedChecksReadPlatformService repaymentWithPostDatedChecksReadPlatformService;
@Autowired
public RepaymentWithPostDatedChecksApiResource(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
final DefaultToApiJsonSerializer<PostDatedChecksData> 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<PostDatedChecksData> 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);
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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<String, Object> updatePostDatedChecks(JsonCommand command) {
final Map<String, Object> 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;
}
}
@@ -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<PostDatedChecks, Long>, JpaSpecificationExecutor<PostDatedChecks> {
@Query("select pdc from PostDatedChecks pdc where pdc.status = 0 and pdc.loanRepaymentScheduleInstallment = :loanRepaymentScheduleInstallment")
PostDatedChecks getPendingPostDatedCheck(
@Param("loanRepaymentScheduleInstallment") LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment);
}
@@ -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);
}
}
@@ -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");
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<PostDatedChecks> fromParsedJson(final String json, final Loan loan) {
final Set<PostDatedChecks> postDatedChecks = new HashSet<>();
final JsonElement jsonElement = this.fromApiJsonHelper.parse(json);
final List<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> 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;
}
}
@@ -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<PostDatedChecksData> getPostDatedChecks(Long id);
PostDatedChecksData getPostDatedCheck(Long id);
PostDatedChecksData getPostDatedCheckByInstallmentId(Integer id, Long loanId);
}
@@ -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<PostDatedChecksData> getPostDatedChecks(final Long id) {
final Loan loan = this.loanRepository.findById(id).orElseThrow(() -> new LoanNotFoundException(id));
final List<PostDatedChecks> postDatedChecks = loan.getPostDatedChecks();
List<PostDatedChecksData> 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> 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());
}
}
@@ -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);
}
@@ -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<String> supportedParametersForUpdate = new HashSet<>(Arrays.asList("name", "amount", "accountNo", "locale"));
private final Set<String> 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<String, Object> 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<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
final List<LoanRepaymentScheduleInstallment> 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<ApiParameterError> 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<ApiParameterError> dataValidationErrors = new ArrayList<>();
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.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<ApiParameterError> dataValidationErrors = new ArrayList<>();
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.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();
}
}
@@ -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');
@@ -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<HashMap> installmentData = this.loanTransactionHelper.getRepayments(loanID);
Assertions.assertNotNull(installmentData, "Empty Installment Data");
List<HashMap> 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<HashMap> 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<HashMap> collaterals, Integer collateralId, BigDecimal quantity) {
collaterals.add(collaterals(collateralId, quantity));
}
private HashMap<String, String> collaterals(Integer collateralId, BigDecimal quantity) {
HashMap<String, String> collateral = new HashMap<String, String>(2);
collateral.put("clientCollateralId", collateralId.toString());
collateral.put("quantity", quantity.toString());
return collateral;
}
private HashMap<String, String> postDatedCheck(final Integer installmentId, final BigDecimal amount) {
HashMap<String, String> map = new HashMap<String, String>();
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;
}
}
@@ -28,6 +28,7 @@ import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -165,6 +166,12 @@ public class LoanTransactionHelper {
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, ""); return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, "");
} }
public ArrayList<HashMap> 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) { public HashMap approveLoan(final String approvalDate, final Integer loanID) {
String loanApprovalCommand = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID); String loanApprovalCommand = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID);
String loanApprovalRequest = getApproveLoanAsJSON(approvalDate); String loanApprovalRequest = getApproveLoanAsJSON(approvalDate);
@@ -203,6 +210,28 @@ public class LoanTransactionHelper {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, null)); return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, null));
} }
public HashMap disburseLoanWithPostDatedChecks(final String date, final Integer loanId, final BigDecimal transactionAmount,
final List<HashMap> postDatedChecks) {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanId),
getDisburseLoanWithPostDatedChecksAsJSON(date, transactionAmount.toString(), postDatedChecks));
}
private String getDisburseLoanWithPostDatedChecksAsJSON(final String actualDisbursementDate, final String transactionAmount,
final List<HashMap> postDatedChecks) {
final HashMap<String, Object> map = new HashMap<String, Object>();
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) { public HashMap disburseLoanWithRepaymentReschedule(final String date, final Integer loanID, String adjustRepaymentDate) {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID),
getDisburseLoanWithRepaymentRescheduleAsJSON(date, null, adjustRepaymentDate)); getDisburseLoanWithRepaymentRescheduleAsJSON(date, null, adjustRepaymentDate));
@@ -291,6 +320,11 @@ public class LoanTransactionHelper {
getRepaymentBodyAsJSON(date, amountToBePaid), ""); 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) { public HashMap forecloseLoan(final String transactionDate, final Integer loanID) {
return (HashMap) performLoanTransaction(createLoanTransactionURL(FORECLOSURE_COMMAND, loanID), return (HashMap) performLoanTransaction(createLoanTransactionURL(FORECLOSURE_COMMAND, loanID),
getForeclosureBodyAsJSON(transactionDate, loanID), ""); getForeclosureBodyAsJSON(transactionDate, loanID), "");
@@ -367,6 +401,12 @@ public class LoanTransactionHelper {
return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_CHARGES_URL, ""); 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) { private String getDisburseLoanAsJSON(final String actualDisbursementDate, final String transactionAmount) {
final HashMap<String, String> map = new HashMap<>(); final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en"); map.put("locale", "en");
@@ -444,6 +484,17 @@ public class LoanTransactionHelper {
return new Gson().toJson(map); return new Gson().toJson(map);
} }
private String getRepaymentWithPDCBodyAsJSON(final String transactionDate, final Float transactionAmount, final Integer paymentTypeId) {
final HashMap<String, String> 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) { private String getForeclosureBodyAsJSON(final String transactionDate, final Integer loanId) {
final HashMap<String, Object> map = new HashMap<>(); final HashMap<String, Object> map = new HashMap<>();
map.put("locale", "en"); map.put("locale", "en");