MIFOSX-2228 : added recalcualte from date flexibility for interest recalculation
This commit is contained in:
+63
-32
@@ -96,6 +96,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDa
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
@@ -364,6 +365,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
@Column(name = "guarantee_amount_derived", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal guaranteeAmountDerived;
|
||||
|
||||
@Temporal(TemporalType.DATE)
|
||||
@Column(name = "interest_recalcualated_on")
|
||||
private Date interestRecalculatedOn;
|
||||
|
||||
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
@@ -496,8 +501,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
private Set<LoanCharge> associateChargesWithThisLoan(final Set<LoanCharge> loanCharges) {
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
loanCharge.update(this);
|
||||
if(loanCharge.getTrancheDisbursementCharge() != null) {
|
||||
addTrancheLoanCharge(loanCharge.getCharge()) ;
|
||||
if (loanCharge.getTrancheDisbursementCharge() != null) {
|
||||
addTrancheLoanCharge(loanCharge.getCharge());
|
||||
}
|
||||
}
|
||||
return loanCharges;
|
||||
@@ -569,7 +574,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
this.charges.add(loanCharge);
|
||||
|
||||
|
||||
this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||
|
||||
// store Id's of existing loan transactions and existing reversed loan
|
||||
@@ -906,9 +911,12 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
Money chargeComponent = amountWaived;
|
||||
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||
Money receivableCharge = Money.zero(getCurrency());
|
||||
if(loanInstallmentNumber !=null){
|
||||
receivableCharge = accruedCharge.minus(loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getAmountPaid(getCurrency()));
|
||||
}else{ receivableCharge = accruedCharge.minus(loanCharge.getAmountPaid(getCurrency()));}
|
||||
if (loanInstallmentNumber != null) {
|
||||
receivableCharge = accruedCharge.minus(loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getAmountPaid(
|
||||
getCurrency()));
|
||||
} else {
|
||||
receivableCharge = accruedCharge.minus(loanCharge.getAmountPaid(getCurrency()));
|
||||
}
|
||||
if (receivableCharge.isLessThanZero()) {
|
||||
receivableCharge = amountWaived.zero();
|
||||
}
|
||||
@@ -924,10 +932,11 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
feeChargesWaived = Money.zero(loanCurrency());
|
||||
}
|
||||
|
||||
LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
|
||||
LocalDate transactionDate = getDisbursementDate();
|
||||
if (loanCharge.isSpecifiedDueDate()) {
|
||||
transactionDate = loanCharge.getDueLocalDate();
|
||||
}
|
||||
scheduleGeneratorDTO.setRecalculateFrom(transactionDate);
|
||||
|
||||
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||
|
||||
@@ -1107,6 +1116,17 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
}
|
||||
|
||||
public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments, AppUser currentUser) {
|
||||
this.repaymentScheduleInstallments.clear();
|
||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||
addRepaymentScheduleInstallment(installment);
|
||||
}
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
updateLoanSummaryDerivedFields();
|
||||
applyAccurals(currentUser);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* method updates accrual derived fields on installments and reverse the
|
||||
* unprocessed transactions
|
||||
@@ -1197,8 +1217,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
updateLoanOutstandingBalaces();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateLoanSummarAndStatus(){
|
||||
|
||||
public void updateLoanSummarAndStatus() {
|
||||
updateLoanSummaryDerivedFields();
|
||||
doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine);
|
||||
}
|
||||
@@ -1485,9 +1505,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
public boolean isMultiDisburmentLoan() {
|
||||
return this.loanProduct.isMultiDisburseLoan() ;
|
||||
return this.loanProduct.isMultiDisburseLoan();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update interest recalculation settings if product configuration changes
|
||||
*/
|
||||
@@ -1660,12 +1680,13 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
List<Long> loanChargeIds = fetchLoanChargeIds();
|
||||
int chargeIdLength = loanChargeIds.size();
|
||||
String chargeIds = null;
|
||||
//From modify application page, if user removes all charges, we should get empty array.
|
||||
//So we need to remove all charges applied for this loan
|
||||
// From modify application page, if user removes all charges, we should
|
||||
// get empty array.
|
||||
// So we need to remove all charges applied for this loan
|
||||
boolean removeAllChages = false;
|
||||
if (jsonCommand.parameterExists(LoanApiConstants.chargesParameterName)) {
|
||||
JsonArray chargesArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.chargesParameterName);
|
||||
if (chargesArray.size() == 0) removeAllChages = true;
|
||||
if (chargesArray.size() == 0) removeAllChages = true;
|
||||
}
|
||||
|
||||
if (jsonCommand.parameterExists(LoanApiConstants.disbursementDataParameterName)) {
|
||||
@@ -1754,13 +1775,13 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.disbursementDetails.add(disbursementDetails);
|
||||
for (LoanTrancheCharge trancheCharge : trancheCharges) {
|
||||
Charge chargeDefinition = trancheCharge.getCharge();
|
||||
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null,
|
||||
new LocalDate(expectedDisbursementDate), null, null);
|
||||
loanCharge.update(this);
|
||||
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
|
||||
disbursementDetails);
|
||||
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
|
||||
addLoanCharge(loanCharge);
|
||||
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null, new LocalDate(
|
||||
expectedDisbursementDate), null, null);
|
||||
loanCharge.update(this);
|
||||
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
|
||||
disbursementDetails);
|
||||
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
|
||||
addLoanCharge(loanCharge);
|
||||
}
|
||||
actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal);
|
||||
actualChanges.put("recalculateLoanSchedule", true);
|
||||
@@ -4857,9 +4878,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) {
|
||||
|
||||
LocalDate lastTransactionDate = getLastUserTransactionDate();
|
||||
final LoanScheduleModel loanSchedule = getRecalculatedSchedule(generatorDTO);
|
||||
final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO);
|
||||
if (loanSchedule == null) { return; }
|
||||
updateLoanSchedule(loanSchedule, currentUser);
|
||||
updateLoanSchedule(loanSchedule.getInstallments(), currentUser);
|
||||
this.interestRecalculatedOn = DateUtils.getDateOfTenant();
|
||||
LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate();
|
||||
Set<LoanCharge> charges = this.charges();
|
||||
for (LoanCharge loanCharge : charges) {
|
||||
@@ -4896,7 +4918,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
private LoanScheduleModel getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
|
||||
private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
|
||||
|
||||
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || !isOpen()) { return null; }
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
@@ -4912,10 +4934,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
|
||||
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(),
|
||||
generatorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
|
||||
loanRepaymentScheduleTransactionProcessor);
|
||||
return loanSchedule;
|
||||
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(),
|
||||
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
|
||||
repaymentScheduleInstallments, generatorDTO.getRecalculateFrom());
|
||||
}
|
||||
|
||||
public LoanApplicationTerms constructLoanApplicationTerms(final ApplicationCurrency applicationCurrency,
|
||||
@@ -5017,8 +5038,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
getApprovedPrincipal());
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), onDate,
|
||||
loanApplicationTerms, mc, charges(), holidayDetailDTO, retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
|
||||
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(),
|
||||
holidayDetailDTO, retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
|
||||
loanRepaymentScheduleTransactionProcessor);
|
||||
} else {
|
||||
installment = this.getTotalOutstandingOnLoan();
|
||||
@@ -5053,6 +5074,16 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return accruedTill;
|
||||
}
|
||||
|
||||
public LocalDate fetchInterestRecalculateFromDate() {
|
||||
LocalDate interestRecalculatedOn = null;
|
||||
if (this.interestRecalculatedOn != null) {
|
||||
interestRecalculatedOn = getDisbursementDate();
|
||||
} else {
|
||||
interestRecalculatedOn = new LocalDate(this.interestRecalculatedOn);
|
||||
}
|
||||
return interestRecalculatedOn;
|
||||
}
|
||||
|
||||
private void updateLoanOutstandingBalaces() {
|
||||
Money outstanding = Money.zero(getCurrency());
|
||||
List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals();
|
||||
@@ -5473,8 +5504,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
public void addTrancheLoanCharge(Charge charge) {
|
||||
if(!trancheCharges.contains(charge)) {
|
||||
trancheCharges.add(new LoanTrancheCharge(charge, this));
|
||||
if (!trancheCharges.contains(charge)) {
|
||||
trancheCharges.add(new LoanTrancheCharge(charge, this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -186,6 +186,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
|
||||
recalculateFrom = transactionDate;
|
||||
}
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
|
||||
allowTransactionsOnNonWorkingDay);
|
||||
|
||||
+1
@@ -319,6 +319,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
|
||||
public void reverse() {
|
||||
this.reversed = true;
|
||||
this.loanTransactionToRepaymentScheduleMappings.clear();
|
||||
}
|
||||
|
||||
public void resetDerivedComponents() {
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
|
||||
/**
|
||||
* Transfer object to return the schedule after generation of schedule
|
||||
*/
|
||||
public class LoanScheduleDTO {
|
||||
|
||||
private final List<LoanRepaymentScheduleInstallment> installments;
|
||||
private final LoanScheduleModel loanScheduleModel;
|
||||
|
||||
private LoanScheduleDTO(final List<LoanRepaymentScheduleInstallment> installments, final LoanScheduleModel loanScheduleModel) {
|
||||
this.installments = installments;
|
||||
this.loanScheduleModel = loanScheduleModel;
|
||||
}
|
||||
|
||||
public static LoanScheduleDTO from(final List<LoanRepaymentScheduleInstallment> installments, final LoanScheduleModel loanScheduleModel){
|
||||
return new LoanScheduleDTO(installments, loanScheduleModel);
|
||||
}
|
||||
|
||||
public List<LoanRepaymentScheduleInstallment> getInstallments() {
|
||||
return this.installments;
|
||||
}
|
||||
|
||||
public LoanScheduleModel getLoanScheduleModel() {
|
||||
return this.loanScheduleModel;
|
||||
}
|
||||
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail;
|
||||
|
||||
public class LoanScheduleRecalculationDTO {
|
||||
|
||||
private final int periodNumber;
|
||||
private final int instalmentNumber;
|
||||
private final LocalDate periodStartDate;
|
||||
private final LocalDate actualRepaymentDate;
|
||||
private final Money totalCumulativePrincipal;
|
||||
private final Money totalCumulativeInterest;
|
||||
private final Money totalFeeChargesCharged;
|
||||
private final Money totalPenaltyChargesCharged;
|
||||
private final Money totalRepaymentExpected;
|
||||
private final Money reducePrincipal;
|
||||
private final Map<LocalDate, Money> principalPortionMap;
|
||||
private final Map<LocalDate, Money> latePaymentMap;
|
||||
private final TreeMap<LocalDate, Money> compoundingMap;
|
||||
private final Map<LocalDate, Money> disburseDetailMap;
|
||||
private final Money outstandingBalance;
|
||||
private final Money outstandingBalanceAsPerRest;
|
||||
private final List<LoanRepaymentScheduleInstallment> installments;
|
||||
private final Collection<RecalculationDetail> recalculationDetails;
|
||||
private final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor;
|
||||
private final LocalDate scheduleTillDate;
|
||||
private final boolean partialUpdate;
|
||||
|
||||
private LoanScheduleRecalculationDTO(final int periodNumber, final int instalmentNumber, LocalDate periodStartDate,
|
||||
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
|
||||
final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
|
||||
final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
|
||||
final TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, final Money outstandingBalance,
|
||||
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
|
||||
final boolean partialUpdate) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.instalmentNumber = instalmentNumber;
|
||||
this.periodStartDate = periodStartDate;
|
||||
this.actualRepaymentDate = actualRepaymentDate;
|
||||
this.totalCumulativePrincipal = totalCumulativePrincipal;
|
||||
this.totalCumulativeInterest = totalCumulativeInterest;
|
||||
this.totalFeeChargesCharged = totalFeeChargesCharged;
|
||||
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
|
||||
this.totalRepaymentExpected = totalRepaymentExpected;
|
||||
this.reducePrincipal = reducePrincipal;
|
||||
this.principalPortionMap = principalPortionMap;
|
||||
this.latePaymentMap = latePaymentMap;
|
||||
this.compoundingMap = compoundingMap;
|
||||
this.disburseDetailMap = disburseDetailMap;
|
||||
this.outstandingBalance = outstandingBalance;
|
||||
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
|
||||
this.installments = installments;
|
||||
this.recalculationDetails = recalculationDetails;
|
||||
this.loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessor;
|
||||
this.scheduleTillDate = scheduleTillDate;
|
||||
this.partialUpdate = partialUpdate;
|
||||
}
|
||||
|
||||
public static LoanScheduleRecalculationDTO createLoanScheduleDTOForPartialUpdate(final int periodNumber, final int instalmentNumber,
|
||||
LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal,
|
||||
final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
|
||||
final Money totalRepaymentExpected, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
|
||||
final Map<LocalDate, Money> latePaymentMap, final TreeMap<LocalDate, Money> compoundingMap,
|
||||
final Map<LocalDate, Money> disburseDetailMap, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) {
|
||||
final boolean partialUpdate = true;
|
||||
return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap,
|
||||
outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate);
|
||||
}
|
||||
|
||||
public static LoanScheduleRecalculationDTO createLoanScheduleDTOForCompleteUpdate(
|
||||
final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) {
|
||||
final int periodNumber = 1;
|
||||
final int instalmentNumber = 1;
|
||||
final LocalDate periodStartDate = null;
|
||||
final LocalDate actualRepaymentDate = null;
|
||||
final Money totalCumulativePrincipal = null;
|
||||
final Money totalCumulativeInterest = null;
|
||||
final Money totalFeeChargesCharged = null;
|
||||
final Money totalPenaltyChargesCharged = null;
|
||||
final Money totalRepaymentExpected = null;
|
||||
final Money reducePrincipal = null;
|
||||
final Map<LocalDate, Money> principalPortionMap = null;
|
||||
final Map<LocalDate, Money> latePaymentMap = null;
|
||||
final TreeMap<LocalDate, Money> compoundingMap = null;
|
||||
final Map<LocalDate, Money> disburseDetailMap = null;
|
||||
final Money outstandingBalance = null;
|
||||
final Money outstandingBalanceAsPerRest = null;
|
||||
final List<LoanRepaymentScheduleInstallment> installments = null;
|
||||
final boolean partialUpdate = false;
|
||||
return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap,
|
||||
outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate);
|
||||
}
|
||||
|
||||
public int getPeriodNumber() {
|
||||
return this.periodNumber;
|
||||
}
|
||||
|
||||
public int getInstalmentNumber() {
|
||||
return this.instalmentNumber;
|
||||
}
|
||||
|
||||
public LocalDate getActualRepaymentDate() {
|
||||
return this.actualRepaymentDate;
|
||||
}
|
||||
|
||||
public Money getTotalCumulativePrincipal() {
|
||||
return this.totalCumulativePrincipal;
|
||||
}
|
||||
|
||||
public Money getTotalCumulativeInterest() {
|
||||
return this.totalCumulativeInterest;
|
||||
}
|
||||
|
||||
public Money getTotalFeeChargesCharged() {
|
||||
return this.totalFeeChargesCharged;
|
||||
}
|
||||
|
||||
public Money getTotalPenaltyChargesCharged() {
|
||||
return this.totalPenaltyChargesCharged;
|
||||
}
|
||||
|
||||
public Money getTotalRepaymentExpected() {
|
||||
return this.totalRepaymentExpected;
|
||||
}
|
||||
|
||||
public Money getReducePrincipal() {
|
||||
return this.reducePrincipal;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Money> getPrincipalPortionMap() {
|
||||
return this.principalPortionMap;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Money> getLatePaymentMap() {
|
||||
return this.latePaymentMap;
|
||||
}
|
||||
|
||||
public TreeMap<LocalDate, Money> getCompoundingMap() {
|
||||
return this.compoundingMap;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Money> getDisburseDetailMap() {
|
||||
return this.disburseDetailMap;
|
||||
}
|
||||
|
||||
public Money getOutstandingBalance() {
|
||||
return this.outstandingBalance;
|
||||
}
|
||||
|
||||
public Money getOutstandingBalanceAsPerRest() {
|
||||
return this.outstandingBalanceAsPerRest;
|
||||
}
|
||||
|
||||
public List<LoanRepaymentScheduleInstallment> getInstallments() {
|
||||
return this.installments;
|
||||
}
|
||||
|
||||
public Collection<RecalculationDetail> getRecalculationDetails() {
|
||||
return this.recalculationDetails;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleTransactionProcessor getLoanRepaymentScheduleTransactionProcessor() {
|
||||
return this.loanRepaymentScheduleTransactionProcessor;
|
||||
}
|
||||
|
||||
public LocalDate getScheduleTillDate() {
|
||||
return this.scheduleTillDate;
|
||||
}
|
||||
|
||||
public boolean isPartialUpdate() {
|
||||
return this.partialUpdate;
|
||||
}
|
||||
|
||||
public LocalDate getPeriodStartDate() {
|
||||
return this.periodStartDate;
|
||||
}
|
||||
|
||||
}
|
||||
+442
-43
@@ -7,7 +7,15 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
@@ -22,8 +30,14 @@ import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.*;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleRecalculationDTO;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
||||
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
|
||||
@@ -42,16 +56,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
@Override
|
||||
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
||||
final Collection<RecalculationDetail> diffAmt = null;
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
|
||||
final LocalDate scheduleTillDate = null;
|
||||
return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate);
|
||||
final LoanScheduleRecalculationDTO loanScheduleRecalculationDTO = null;
|
||||
return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleRecalculationDTO);
|
||||
}
|
||||
|
||||
private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final Collection<RecalculationDetail> transactions,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) {
|
||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO,
|
||||
final LoanScheduleRecalculationDTO loanScheduleRecalculationDTO) {
|
||||
|
||||
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
|
||||
// generate list of proposed schedule due dates
|
||||
@@ -63,16 +74,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
// setup variables for tracking important facts required for loan
|
||||
// schedule generation.
|
||||
Money principalToBeScheduled;
|
||||
|
||||
if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) {
|
||||
principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal();
|
||||
} else {
|
||||
principalToBeScheduled = loanApplicationTerms.getPrincipal();
|
||||
}
|
||||
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
|
||||
final MonetaryCurrency currency = principalToBeScheduled.getCurrency();
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
|
||||
final LocalDate scheduleTillDate = loanScheduleRecalculationDTO == null ? null : loanScheduleRecalculationDTO.getScheduleTillDate();
|
||||
final Collection<RecalculationDetail> transactions = loanScheduleRecalculationDTO == null ? null : loanScheduleRecalculationDTO
|
||||
.getRecalculationDetails();
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleRecalculationDTO == null ? null
|
||||
: loanScheduleRecalculationDTO.getLoanRepaymentScheduleTransactionProcessor();
|
||||
|
||||
// variables for cumulative totals
|
||||
int loanTermInDays = Integer.valueOf(0);
|
||||
final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
@@ -107,10 +119,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
// Actual period Number plus interest only repayments
|
||||
int instalmentNumber = 1;
|
||||
|
||||
// actual outstanding balance for interest calculation
|
||||
Money outstandingBalance = principalToBeScheduled;
|
||||
|
||||
// Set fixed EMI Amount
|
||||
updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO);
|
||||
if (loanApplicationTerms.getFixedEmiAmount() == null) {
|
||||
updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO);
|
||||
}
|
||||
|
||||
// disbursement map for tranche details(will added to outstanding
|
||||
// balance as per the start date)
|
||||
@@ -165,6 +180,34 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
holidayDetailDTO.getWorkingDays().setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue());
|
||||
extendTermForDailyRepayments = true;
|
||||
}
|
||||
|
||||
// this block is to start the schedule generation from specified date
|
||||
if (loanScheduleRecalculationDTO != null && loanScheduleRecalculationDTO.isPartialUpdate()) {
|
||||
periodNumber = loanScheduleRecalculationDTO.getPeriodNumber();
|
||||
instalmentNumber = loanScheduleRecalculationDTO.getInstalmentNumber();
|
||||
periodStartDate = loanScheduleRecalculationDTO.getPeriodStartDate();
|
||||
periodStartDateApplicableForInterest = periodStartDate;
|
||||
actualRepaymentDate = loanScheduleRecalculationDTO.getActualRepaymentDate();
|
||||
totalCumulativePrincipal = loanScheduleRecalculationDTO.getTotalCumulativePrincipal();
|
||||
totalCumulativeInterest = loanScheduleRecalculationDTO.getTotalCumulativeInterest();
|
||||
totalFeeChargesCharged = loanScheduleRecalculationDTO.getTotalFeeChargesCharged().getAmount();
|
||||
totalPenaltyChargesCharged = loanScheduleRecalculationDTO.getTotalPenaltyChargesCharged().getAmount();
|
||||
totalRepaymentExpected = loanScheduleRecalculationDTO.getTotalRepaymentExpected().getAmount();
|
||||
reducePrincipal = loanScheduleRecalculationDTO.getReducePrincipal();
|
||||
principalPortionMap.clear();
|
||||
principalPortionMap.putAll(loanScheduleRecalculationDTO.getPrincipalPortionMap());
|
||||
latePaymentMap.clear();
|
||||
latePaymentMap.putAll(loanScheduleRecalculationDTO.getLatePaymentMap());
|
||||
compoundingMap.clear();
|
||||
compoundingMap.putAll(loanScheduleRecalculationDTO.getCompoundingMap());
|
||||
disburseDetailMap.clear();
|
||||
disburseDetailMap.putAll(loanScheduleRecalculationDTO.getDisburseDetailMap());
|
||||
outstandingBalance = loanScheduleRecalculationDTO.getOutstandingBalance();
|
||||
outstandingBalanceAsPerRest = loanScheduleRecalculationDTO.getOutstandingBalanceAsPerRest();
|
||||
installments.clear();
|
||||
installments.addAll(loanScheduleRecalculationDTO.getInstallments());
|
||||
}
|
||||
|
||||
while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) {
|
||||
|
||||
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms,
|
||||
@@ -451,9 +494,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
}
|
||||
|
||||
}
|
||||
LocalDate tillDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms,
|
||||
holidayDetailDTO);
|
||||
if (arrears.isGreaterThanZero() && applicableDate.isBefore(tillDate)) {
|
||||
if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) {
|
||||
updateLatePaidAmountsToPrincipalMap(principalPortionMap, detail.getTransaction(), latePaymentMap,
|
||||
compoundingMap, loanApplicationTerms, currency, holidayDetailDTO, lastRestDate);
|
||||
compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>(compoundingMap));
|
||||
@@ -519,8 +560,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
if (earlyPaidAmount.isGreaterThanZero()) {
|
||||
reducePrincipal = reducePrincipal.plus(earlyPaidAmount);
|
||||
BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount();
|
||||
reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, totalCumulativePrincipal,
|
||||
periodNumber + 1, mc, holidayDetailDTO);
|
||||
reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal,
|
||||
totalCumulativePrincipal.plus(principalForThisPeriod).plus(earlyPaidAmount), periodNumber + 1, mc, holidayDetailDTO);
|
||||
if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment()
|
||||
&& fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) {
|
||||
isEmiAmountChanged = true;
|
||||
@@ -740,6 +781,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding);
|
||||
}
|
||||
|
||||
/**
|
||||
* this method calculates the principal amount for generating the repayment
|
||||
* schedule.
|
||||
*/
|
||||
private Money getPrincipalToBeScheduled(final LoanApplicationTerms loanApplicationTerms) {
|
||||
Money principalToBeScheduled;
|
||||
if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) {
|
||||
principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal();
|
||||
} else {
|
||||
principalToBeScheduled = loanApplicationTerms.getPrincipal();
|
||||
}
|
||||
return principalToBeScheduled;
|
||||
}
|
||||
|
||||
private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
LocalDate actualRepaymentDate, int periodNumber, Money outstandingBalance, final HolidayDetailDTO holidayDetailDTO) {
|
||||
boolean isAmountChanged = false;
|
||||
@@ -943,14 +998,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
case REDUCE_EMI_AMOUNT:
|
||||
// in this case emi amount will be reduced but number of
|
||||
// installments won't change
|
||||
Money principal = getPrincipalToBeScheduled(loanApplicationTerms);
|
||||
if (loanApplicationTerms.getActualFixedEmiAmount() == null) {
|
||||
loanApplicationTerms.setFixedEmiAmount(null);
|
||||
updateFixedInstallmentAmount(mc, loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
periodNumber, loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal), holidayDetailDTO);
|
||||
periodNumber, principal.minus(totalCumulativePrincipal), holidayDetailDTO);
|
||||
}
|
||||
if (loanApplicationTerms.getAmortizationMethod().isEqualPrincipal()) {
|
||||
loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber,
|
||||
loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal));
|
||||
loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber, principal.minus(totalCumulativePrincipal));
|
||||
}
|
||||
reducePrincipal = reducePrincipal.zero();
|
||||
break;
|
||||
@@ -1743,23 +1798,353 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
}
|
||||
|
||||
/**
|
||||
* Method calls schedule regeneration by passing transactions one after
|
||||
* another(this is done mainly to handle the scenario where interest or fee
|
||||
* of over due installment should be collected before collecting principal )
|
||||
* Method preprocess the installments and transactions and sets the required
|
||||
* fields to generate the schedule
|
||||
*/
|
||||
@Override
|
||||
public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom) {
|
||||
|
||||
// Loan transactions to process and find the variation on payments
|
||||
Collection<RecalculationDetail> recalculationDetails = new ArrayList<>();
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction
|
||||
.copyTransactionProperties(loanTransaction)));
|
||||
}
|
||||
// Fixed schedule End Date for generating schedule
|
||||
final LocalDate scheduleTillDate = null;
|
||||
return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate);
|
||||
LoanScheduleRecalculationDTO loanScheduleRecalculationDTO = null;
|
||||
Collection<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||
final List<LoanRepaymentScheduleInstallment> retainedInstallments = new ArrayList<>();
|
||||
|
||||
// this block is to retain the schedule installments prior to the
|
||||
// provided date and creates late and early payment details for further
|
||||
// calculations
|
||||
if (rescheduleFrom != null) {
|
||||
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
|
||||
// actual outstanding balance for interest calculation
|
||||
Money outstandingBalance = principalToBeScheduled;
|
||||
// total outstanding balance as per rest for interest calculation.
|
||||
Money outstandingBalanceAsPerRest = outstandingBalance;
|
||||
|
||||
// this is required to update total fee amounts in the
|
||||
// LoanScheduleModel
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.getNumberOfRepayments(), loanApplicationTerms,
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments = new ArrayList<>();
|
||||
MonetaryCurrency currency = outstandingBalance.getCurrency();
|
||||
final Map<LocalDate, Money> disburseDetailMap = new HashMap<>();
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
// fetches the first tranche amount and also updates other
|
||||
// tranche
|
||||
// details to map
|
||||
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true);
|
||||
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
|
||||
outstandingBalanceAsPerRest = outstandingBalance;
|
||||
}
|
||||
|
||||
// early payments will be added here and as per the selected
|
||||
// strategy
|
||||
// action will be performed on this value
|
||||
Money reducePrincipal = outstandingBalanceAsPerRest.zero();
|
||||
|
||||
// principal changes will be added along with date(after applying
|
||||
// rest)
|
||||
// from when these amounts will effect the outstanding balance for
|
||||
// interest calculation
|
||||
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
|
||||
// compounding(principal) amounts will be added along with
|
||||
// date(after applying compounding frequency)
|
||||
// from when these amounts will effect the outstanding balance for
|
||||
// interest calculation
|
||||
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
|
||||
|
||||
// compounding(interest/Fee) amounts will be added along with
|
||||
// date(after applying compounding frequency)
|
||||
// from when these amounts will effect the outstanding balance for
|
||||
// interest calculation
|
||||
final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
|
||||
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
|
||||
LocalDate lastRestDate = currentDate;
|
||||
if (loanApplicationTerms.getRestCalendarInstance() != null) {
|
||||
lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
|
||||
}
|
||||
LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
boolean isFirstRepayment = true;
|
||||
|
||||
// cumulative fields
|
||||
Money totalCumulativePrincipal = principalToBeScheduled.zero();
|
||||
Money totalCumulativeInterest = principalToBeScheduled.zero();
|
||||
Money totalFeeChargesCharged = principalToBeScheduled.zero().plus(chargesDueAtTimeOfDisbursement);
|
||||
Money totalPenaltyChargesCharged = principalToBeScheduled.zero();
|
||||
Money totalRepaymentExpected = principalToBeScheduled.zero();
|
||||
|
||||
// Actual period Number as per the schedule
|
||||
int periodNumber = 1;
|
||||
// Actual period Number plus interest only repayments
|
||||
int instalmentNumber = 1;
|
||||
LocalDate lastInstallmentDate = actualRepaymentDate;
|
||||
LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
// Set fixed Amortization Amounts(either EMI or Principal )
|
||||
updateAmortization(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO);
|
||||
|
||||
// Block process the installment and creates the period if it falls
|
||||
// before reschedule from date
|
||||
// This will create the recalculation details by applying the
|
||||
// transactions
|
||||
for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
|
||||
for (Map.Entry<LocalDate, Money> disburseDetail : disburseDetailMap.entrySet()) {
|
||||
if (disburseDetail.getKey().isAfter(installment.getFromDate())
|
||||
&& !disburseDetail.getKey().isAfter(installment.getDueDate())) {
|
||||
// creates and add disbursement detail to the repayments
|
||||
// period
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||
disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
// updates actual outstanding balance with new
|
||||
// disbursement detail
|
||||
outstandingBalance = outstandingBalance.plus(disburseDetail.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// this will generate the next schedule due date and allows to
|
||||
// process the installment only if recalculate from date is
|
||||
// greater than due date
|
||||
if (installment.getDueDate().isAfter(lastInstallmentDate)) {
|
||||
LocalDate previousRepaymentDate = actualRepaymentDate;
|
||||
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms,
|
||||
isFirstRepayment, holidayDetailDTO);
|
||||
isFirstRepayment = false;
|
||||
lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
|
||||
holidayDetailDTO);
|
||||
if (!lastInstallmentDate.isBefore(rescheduleFrom)) {
|
||||
actualRepaymentDate = previousRepaymentDate;
|
||||
break;
|
||||
}
|
||||
periodNumber++;
|
||||
}
|
||||
|
||||
// calculation of basic fields to start the schedule generation
|
||||
// from the middle
|
||||
periodStartDate = installment.getDueDate();
|
||||
installment.resetDerivedComponents();
|
||||
newRepaymentScheduleInstallments.add(installment);
|
||||
outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency));
|
||||
final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance);
|
||||
periods.add(loanScheduleModelPeriod);
|
||||
totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency));
|
||||
totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency));
|
||||
totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency));
|
||||
totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency));
|
||||
instalmentNumber++;
|
||||
|
||||
// populates the collection with transactions till the due date
|
||||
// of
|
||||
// the period for interest recalculation enabled loans
|
||||
Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms,
|
||||
installment.getDueDate(), recalculationDetails);
|
||||
|
||||
// calculates the expected principal value for this repayment
|
||||
// schedule
|
||||
Money principalPortionCalculated = principalToBeScheduled.zero();
|
||||
if (!installment.isRecalculatedInterestComponent()) {
|
||||
principalPortionCalculated = calculateExpectedPrincipalPortion(installment.getInterestCharged(currency),
|
||||
loanApplicationTerms);
|
||||
}
|
||||
|
||||
// expected principal considering the previously paid excess
|
||||
// amount
|
||||
Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal);
|
||||
if (actualPrincipalPortion.isLessThanZero()) {
|
||||
actualPrincipalPortion = principalPortionCalculated.zero();
|
||||
}
|
||||
|
||||
Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO,
|
||||
loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap,
|
||||
installment, applicableTransactions, actualPrincipalPortion);
|
||||
|
||||
// this block is to adjust the period number based on the actual
|
||||
// schedule due date and installment due date
|
||||
// recalculatedInterestComponent installment shouldn't be
|
||||
// considered while calculating fixed EMI amounts
|
||||
int period = periodNumber;
|
||||
if (!lastInstallmentDate.isEqual(installment.getDueDate())) {
|
||||
period--;
|
||||
}
|
||||
reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal,
|
||||
loanApplicationTerms, totalCumulativePrincipal, period, mc, holidayDetailDTO);
|
||||
// Updates principal paid map with efective date for reducing
|
||||
// the amount from outstanding balance(interest calculation)
|
||||
LocalDate amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms,
|
||||
holidayDetailDTO);
|
||||
// updates map with the installment principal amount excluding
|
||||
// unprocessed amount since this amount is already accounted.
|
||||
updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate);
|
||||
// update outstanding balance for interest calculation
|
||||
outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(),
|
||||
outstandingBalanceAsPerRest, false);
|
||||
outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(),
|
||||
outstandingBalanceAsPerRest, true);
|
||||
|
||||
}
|
||||
totalRepaymentExpected = totalCumulativePrincipal.plus(totalCumulativeInterest).plus(totalFeeChargesCharged)
|
||||
.plus(totalPenaltyChargesCharged);
|
||||
|
||||
// updates the map with over due amounts
|
||||
updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate,
|
||||
newRepaymentScheduleInstallments, true, lastRestDate, compoundingMap);
|
||||
|
||||
// for partial schedule generation
|
||||
if (!newRepaymentScheduleInstallments.isEmpty()) {
|
||||
loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO
|
||||
.createLoanScheduleDTOForPartialUpdate(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
|
||||
disburseDetailMap, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments,
|
||||
recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate);
|
||||
retainedInstallments.addAll(newRepaymentScheduleInstallments);
|
||||
}
|
||||
|
||||
}
|
||||
// for complete schedule generation
|
||||
if (loanScheduleRecalculationDTO == null) {
|
||||
loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO.createLoanScheduleDTOForCompleteUpdate(recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate);
|
||||
}
|
||||
|
||||
LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
|
||||
loanScheduleRecalculationDTO);
|
||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) {
|
||||
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
|
||||
// adding newly created repayment periods to installments
|
||||
addLoanRepaymentScheduleInstallment(retainedInstallments, loanScheduleModelPeriod);
|
||||
}
|
||||
}
|
||||
periods.addAll(loanScheduleModel.getPeriods());
|
||||
LoanScheduleModel loanScheduleModelwithPeriodChanges = LoanScheduleModel.withLoanScheduleModelPeriods(periods, loanScheduleModel);
|
||||
return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method identifies the early paid amounts for a installment and update the
|
||||
* principal map for further calculations
|
||||
*/
|
||||
private Money updateEarlyPaidAmountsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments, MonetaryCurrency currency,
|
||||
final Map<LocalDate, Money> principalPortionMap, LoanRepaymentScheduleInstallment installment,
|
||||
Collection<RecalculationDetail> applicableTransactions, Money actualPrincipalPortion) {
|
||||
Money unprocessed = Money.zero(currency);
|
||||
for (RecalculationDetail detail : applicableTransactions) {
|
||||
if (!detail.isProcessed()) {
|
||||
Money principalProcessed = installment.getPrincipalCompleted(currency);
|
||||
List<LoanTransaction> currentTransactions = new ArrayList<>(2);
|
||||
currentTransactions.add(detail.getTransaction());
|
||||
// applies the transaction as per transaction strategy
|
||||
// on scheduled installments to identify the
|
||||
// unprocessed(early payment ) amounts
|
||||
loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency,
|
||||
newRepaymentScheduleInstallments);
|
||||
|
||||
// Identifies totalEarlyPayment and early paid amount with this
|
||||
// transaction
|
||||
Money principalPaidWithTransaction = installment.getPrincipalCompleted(currency).minus(principalProcessed);
|
||||
Money totalEarlyPayment = installment.getPrincipalCompleted(currency).minus(actualPrincipalPortion);
|
||||
|
||||
if (totalEarlyPayment.isGreaterThanZero()) {
|
||||
unprocessed = principalPaidWithTransaction;
|
||||
// will execute this block if partial amount paid as
|
||||
// early
|
||||
if (principalPaidWithTransaction.isGreaterThan(totalEarlyPayment)) {
|
||||
unprocessed = totalEarlyPayment;
|
||||
}
|
||||
}
|
||||
// updates principal portion map with the early
|
||||
// payment amounts and applicable date as per rest
|
||||
LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms,
|
||||
holidayDetailDTO);
|
||||
updateMapWithAmount(principalPortionMap, unprocessed, applicableDate);
|
||||
|
||||
}
|
||||
}
|
||||
return unprocessed;
|
||||
}
|
||||
|
||||
private void updateAmortization(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, LocalDate actualRepaymentDate,
|
||||
int periodNumber, Money outstandingBalance, final HolidayDetailDTO holidayDetailDTO) {
|
||||
if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
|
||||
updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO);
|
||||
} else {
|
||||
loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber, outstandingBalance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method identifies early paid amount and applies the early payment
|
||||
* strategy
|
||||
*/
|
||||
private Money fetchEarlyPaidAmount(final Money principalPortion, final Money principalPortionCalculated, final Money reducePrincipal,
|
||||
final LoanApplicationTerms applicationTerms, final Money totalCumulativePrincipal, int periodNumber, final MathContext mc,
|
||||
final HolidayDetailDTO holidayDetailDTO) {
|
||||
Money existingEarlyPayment = reducePrincipal.minus(principalPortionCalculated);
|
||||
Money earlyPaidAmount = principalPortion.plus(existingEarlyPayment);
|
||||
if (existingEarlyPayment.isLessThanZero()) {
|
||||
existingEarlyPayment = existingEarlyPayment.zero();
|
||||
}
|
||||
boolean isEarlyPaid = earlyPaidAmount.isGreaterThan(existingEarlyPayment);
|
||||
|
||||
if (earlyPaidAmount.isLessThanZero()) {
|
||||
earlyPaidAmount = earlyPaidAmount.zero();
|
||||
}
|
||||
|
||||
if (isEarlyPaid) {
|
||||
switch (applicationTerms.getRescheduleStrategyMethod()) {
|
||||
case REDUCE_EMI_AMOUNT:
|
||||
// in this case emi amount will be reduced but number of
|
||||
// installments won't change
|
||||
Money principal = getPrincipalToBeScheduled(applicationTerms);
|
||||
if (applicationTerms.getActualFixedEmiAmount() == null) {
|
||||
applicationTerms.setFixedEmiAmount(null);
|
||||
updateFixedInstallmentAmount(mc, applicationTerms, applicationTerms.getExpectedDisbursementDate(), periodNumber,
|
||||
principal.minus(totalCumulativePrincipal), holidayDetailDTO);
|
||||
}
|
||||
if (applicationTerms.getAmortizationMethod().isEqualPrincipal()) {
|
||||
applicationTerms.updateFixedPrincipalAmount(mc, periodNumber, principal.minus(totalCumulativePrincipal));
|
||||
}
|
||||
earlyPaidAmount = earlyPaidAmount.zero();
|
||||
break;
|
||||
case REDUCE_NUMBER_OF_INSTALLMENTS:
|
||||
// number of installments will reduce but emi amount won't
|
||||
// get effected
|
||||
earlyPaidAmount = earlyPaidAmount.zero();
|
||||
break;
|
||||
case RESCHEDULE_NEXT_REPAYMENTS:
|
||||
// will reduce principal from the reduce Principal for each
|
||||
// installment(means installments will have less emi amount)
|
||||
// until this
|
||||
// amount becomes zero
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return earlyPaidAmount;
|
||||
}
|
||||
|
||||
private Money calculateExpectedPrincipalPortion(final Money interestPortion, final LoanApplicationTerms applicationTerms) {
|
||||
Money principalPortionCalculated = interestPortion.zero();
|
||||
if (applicationTerms.getAmortizationMethod().isEqualInstallment()) {
|
||||
principalPortionCalculated = principalPortionCalculated.plus(applicationTerms.getFixedEmiAmount()).minus(interestPortion);
|
||||
} else {
|
||||
principalPortionCalculated = principalPortionCalculated.plus(applicationTerms.getFixedPrincipalAmount());
|
||||
}
|
||||
return principalPortionCalculated;
|
||||
}
|
||||
|
||||
private List<LoanRepaymentScheduleInstallment> fetchInstallmentsFromScheduleModel(final LoanScheduleModel loanScheduleModel) {
|
||||
@@ -1770,16 +2155,29 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
return installments;
|
||||
}
|
||||
|
||||
private void addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
||||
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
|
||||
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
||||
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
|
||||
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
|
||||
scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent());
|
||||
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
||||
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
||||
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
||||
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
||||
scheduledLoanInstallment.isRecalculatedInterestComponent());
|
||||
installments.add(installment);
|
||||
}
|
||||
return installment;
|
||||
}
|
||||
|
||||
private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment,
|
||||
final Money outstandingPrincipal) {
|
||||
final MonetaryCurrency currency = outstandingPrincipal.getCurrency();
|
||||
LoanScheduleModelPeriod scheduledLoanInstallment = LoanScheduleModelRepaymentPeriod
|
||||
.repayment(installment.getInstallmentNumber(), installment.getFromDate(), installment.getDueDate(),
|
||||
installment.getPrincipal(currency), outstandingPrincipal, installment.getInterestCharged(currency),
|
||||
installment.getFeeChargesCharged(currency), installment.getPenaltyChargesCharged(currency),
|
||||
installment.getDue(currency), installment.isRecalculatedInterestComponent());
|
||||
return scheduledLoanInstallment;
|
||||
}
|
||||
|
||||
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
|
||||
@@ -1815,9 +2213,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
* Method returns the amount payable to close the loan account as of today.
|
||||
*/
|
||||
@Override
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
|
||||
MonetaryCurrency currency, final LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc,
|
||||
Set<LoanCharge> charges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> loanTransactions,
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final MonetaryCurrency currency, final LocalDate onDate,
|
||||
final LoanApplicationTerms loanApplicationTerms, final MathContext mc, final Set<LoanCharge> charges,
|
||||
final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> loanTransactions,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
|
||||
LocalDate calculateTill = onDate;
|
||||
@@ -1829,9 +2227,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction
|
||||
.copyTransactionProperties(loanTransaction)));
|
||||
}
|
||||
LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, charges, holidayDetailDTO, recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, calculateTill);
|
||||
installments = fetchInstallmentsFromScheduleModel(loanScheduleModel);
|
||||
LoanScheduleRecalculationDTO loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO.createLoanScheduleDTOForCompleteUpdate(
|
||||
recalculationDetails, loanRepaymentScheduleTransactionProcessor, calculateTill);
|
||||
LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, charges, holidayDetailDTO, loanScheduleRecalculationDTO);
|
||||
List<LoanRepaymentScheduleInstallment> installments = fetchInstallmentsFromScheduleModel(loanScheduleModel);
|
||||
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions,
|
||||
currency, installments, charges);
|
||||
Money feeCharges = Money.zero(currency);
|
||||
|
||||
+5
@@ -1270,4 +1270,9 @@ public final class LoanApplicationTerms {
|
||||
return loanCalendar;
|
||||
}
|
||||
|
||||
|
||||
public BigDecimal getFixedPrincipalAmount() {
|
||||
return this.fixedPrincipalAmount;
|
||||
}
|
||||
|
||||
}
|
||||
+6
-6
@@ -19,6 +19,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
|
||||
|
||||
@@ -27,14 +28,13 @@ public interface LoanScheduleGenerator {
|
||||
LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
|
||||
final HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
LoanScheduleModel rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
|
||||
LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
|
||||
final HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> transactions,
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, LocalDate rescheduleFrom);
|
||||
|
||||
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
|
||||
MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc,
|
||||
Set<LoanCharge> charges, HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> loanTransactions,
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
||||
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency,
|
||||
LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges,
|
||||
HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
||||
|
||||
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
|
||||
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance,
|
||||
|
||||
+9
-1
@@ -51,6 +51,15 @@ public final class LoanScheduleModel {
|
||||
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
|
||||
}
|
||||
|
||||
public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection<LoanScheduleModelPeriod> periods,
|
||||
final LoanScheduleModel loanScheduleModel) {
|
||||
|
||||
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
|
||||
loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
|
||||
loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged,
|
||||
loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
|
||||
}
|
||||
|
||||
private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
|
||||
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
|
||||
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
|
||||
@@ -95,7 +104,6 @@ public final class LoanScheduleModel {
|
||||
return this.periods;
|
||||
}
|
||||
|
||||
|
||||
public BigDecimal getTotalPenaltyChargesCharged() {
|
||||
return this.totalPenaltyChargesCharged;
|
||||
}
|
||||
|
||||
+41
-39
@@ -231,8 +231,8 @@ public class LoanScheduleAssembler {
|
||||
final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
|
||||
final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element);
|
||||
|
||||
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper
|
||||
.extractIntegerWithLocaleNamed(LoanProductConstants.graceOnArrearsAgeingParameterName, element);
|
||||
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
|
||||
LoanProductConstants.graceOnArrearsAgeingParameterName, element);
|
||||
|
||||
// other
|
||||
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
|
||||
@@ -240,14 +240,14 @@ public class LoanScheduleAssembler {
|
||||
|
||||
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
|
||||
element);
|
||||
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
|
||||
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
|
||||
LoanApiConstants.maxOutstandingBalanceParameterName, element);
|
||||
|
||||
final List<DisbursementData> disbursementDatas = fetchDisbursementData(element.getAsJsonObject());
|
||||
|
||||
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<>();
|
||||
LoanTermVariationsData data = new LoanTermVariationsData(null, LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT),
|
||||
expectedDisbursementDate, emiAmount);
|
||||
LoanTermVariationsData data = new LoanTermVariationsData(null,
|
||||
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
|
||||
loanVariationTermsData.add(data);
|
||||
|
||||
/**
|
||||
@@ -267,8 +267,8 @@ public class LoanScheduleAssembler {
|
||||
.getProductInterestRecalculationDetails();
|
||||
recalculationFrequencyType = loanProductInterestRecalculationDetails.getRestFrequencyType();
|
||||
if (!recalculationFrequencyType.isSameAsRepayment()) {
|
||||
LocalDate calendarStartDate = this.fromApiJsonHelper
|
||||
.extractLocalDateNamed(LoanProductConstants.recalculationRestFrequencyDateParamName, element);
|
||||
LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed(
|
||||
LoanProductConstants.recalculationRestFrequencyDateParamName, element);
|
||||
if (calendarStartDate == null) {
|
||||
calendarStartDate = expectedDisbursementDate;
|
||||
}
|
||||
@@ -280,8 +280,8 @@ public class LoanScheduleAssembler {
|
||||
if (compoundingMethod.isCompoundingEnabled()) {
|
||||
compoundingFrequencyType = loanProductInterestRecalculationDetails.getCompoundingFrequencyType();
|
||||
if (!compoundingFrequencyType.isSameAsRepayment()) {
|
||||
LocalDate calendarStartDate = this.fromApiJsonHelper
|
||||
.extractLocalDateNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element);
|
||||
LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed(
|
||||
LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element);
|
||||
if (calendarStartDate == null) {
|
||||
calendarStartDate = expectedDisbursementDate;
|
||||
}
|
||||
@@ -346,12 +346,12 @@ public class LoanScheduleAssembler {
|
||||
BigDecimal principal = null;
|
||||
|
||||
if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) {
|
||||
expectedDisbursementDate = this.fromApiJsonHelper
|
||||
.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale);
|
||||
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
|
||||
LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale);
|
||||
}
|
||||
if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
|
||||
&& jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() && StringUtils
|
||||
.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
|
||||
&& jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
|
||||
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
|
||||
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
|
||||
}
|
||||
disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null));
|
||||
@@ -363,8 +363,9 @@ public class LoanScheduleAssembler {
|
||||
}
|
||||
|
||||
private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) {
|
||||
if (repaymentsStartingFromDate != null && !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(),
|
||||
calendar.getStartDateLocalDate(), repaymentsStartingFromDate)) {
|
||||
if (repaymentsStartingFromDate != null
|
||||
&& !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(),
|
||||
repaymentsStartingFromDate)) {
|
||||
final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date";
|
||||
throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage,
|
||||
repaymentsStartingFromDate);
|
||||
@@ -439,9 +440,10 @@ public class LoanScheduleAssembler {
|
||||
}
|
||||
|
||||
public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays, final JsonElement element, Set<LoanDisbursementDetails> disbursementDetails) {
|
||||
final List<Holiday> holidays, final WorkingDays workingDays, final JsonElement element,
|
||||
Set<LoanDisbursementDetails> disbursementDetails) {
|
||||
|
||||
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails);
|
||||
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails);
|
||||
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
|
||||
@@ -455,37 +457,38 @@ public class LoanScheduleAssembler {
|
||||
|
||||
public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId,
|
||||
List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom) {
|
||||
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId,
|
||||
loanApplicationTerms.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
|
||||
.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions,
|
||||
loanRepaymentScheduleTransactionProcessor);
|
||||
loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom).getLoanScheduleModel();
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
|
||||
MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges,
|
||||
final Long officeId, List<LoanTransaction> loanTransactions,
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||
LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final Long officeId,
|
||||
List<LoanTransaction> loanTransactions,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId,
|
||||
loanApplicationTerms.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
|
||||
.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, onDate, loanApplicationTerms, mc, loanCharges,
|
||||
holidayDetailDTO, loanTransactions, loanRepaymentScheduleTransactionProcessor);
|
||||
return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loanCharges, holidayDetailDTO,
|
||||
loanTransactions, loanRepaymentScheduleTransactionProcessor);
|
||||
|
||||
}
|
||||
|
||||
@@ -522,7 +525,7 @@ public class LoanScheduleAssembler {
|
||||
refernceDateForCalculatingFirstRepaymentDate, repaymentPeriodFrequencyType,
|
||||
minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
|
||||
|
||||
} /*** Individual or group account, or JLG not linked to a meeting ***/
|
||||
}/*** Individual or group account, or JLG not linked to a meeting ***/
|
||||
else {
|
||||
LocalDate dateBasedOnRepaymentFrequency;
|
||||
// Derive the first repayment date as greater date among
|
||||
@@ -535,12 +538,12 @@ public class LoanScheduleAssembler {
|
||||
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusWeeks(repaymentEvery);
|
||||
} else if (repaymentPeriodFrequencyType.isMonthly()) {
|
||||
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusMonths(repaymentEvery);
|
||||
} /** yearly loan **/
|
||||
}/** yearly loan **/
|
||||
else {
|
||||
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusYears(repaymentEvery);
|
||||
}
|
||||
derivedFirstRepayment = dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment)
|
||||
? dateBasedOnRepaymentFrequency : dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
derivedFirstRepayment = dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment) ? dateBasedOnRepaymentFrequency
|
||||
: dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
}
|
||||
|
||||
return derivedFirstRepayment;
|
||||
@@ -554,17 +557,16 @@ public class LoanScheduleAssembler {
|
||||
final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar,
|
||||
refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency);
|
||||
final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment
|
||||
: deriveFirstRepaymentDateForJLGLoans(repaymentEvery, expectedDisbursementDate, derivedFirstRepayment,
|
||||
repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
|
||||
return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment : deriveFirstRepaymentDateForJLGLoans(
|
||||
repaymentEvery, expectedDisbursementDate, derivedFirstRepayment, repaymentPeriodFrequencyType,
|
||||
minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
|
||||
}
|
||||
|
||||
private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final LocalDate disbursalDate, final LocalDate firstRepaymentDate,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment) {
|
||||
|
||||
final LocalDate minimumFirstRepaymentDate = disbursalDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
if (firstRepaymentDate
|
||||
.isBefore(minimumFirstRepaymentDate)) { throw new MinDaysBetweenDisbursalAndFirstRepaymentViolationException(disbursalDate,
|
||||
firstRepaymentDate, minimumDaysBetweenDisbursalAndFirstRepayment); }
|
||||
if (firstRepaymentDate.isBefore(minimumFirstRepaymentDate)) { throw new MinDaysBetweenDisbursalAndFirstRepaymentViolationException(
|
||||
disbursalDate, firstRepaymentDate, minimumDaysBetweenDisbursalAndFirstRepayment); }
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -167,8 +167,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
|
||||
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency,
|
||||
calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance);
|
||||
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(
|
||||
loan.fetchRepaymentScheduleInstallments(), currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId(),
|
||||
loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor);
|
||||
currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
|
||||
loanRepaymentScheduleTransactionProcessor);
|
||||
Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus(
|
||||
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
|
||||
Money interestDue = Money.zero(currency);
|
||||
@@ -189,7 +189,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
|
||||
}
|
||||
|
||||
LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(),
|
||||
modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor);
|
||||
modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, loan.fetchRepaymentScheduleInstallments(),
|
||||
loan.fetchInterestRecalculateFromDate());
|
||||
LoanScheduleData scheduleDate = model.toData();
|
||||
Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods();
|
||||
for (LoanSchedulePeriodData periodData : periodDatas) {
|
||||
|
||||
+2
@@ -21,6 +21,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.holiday.domain.HolidayRepositoryWrapper;
|
||||
@@ -463,6 +464,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
||||
loan.getDisbursementDate().toDate());
|
||||
workingDays = this.workingDaysRepository.findOne();
|
||||
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
|
||||
recalculateFrom = DateUtils.getLocalDateOfTenant();
|
||||
}
|
||||
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
+1
-3
@@ -1071,9 +1071,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.getId() == null) {
|
||||
this.repaymentScheduleInstallmentRepository.save(installment);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loanRepository.saveAndFlush(loan);
|
||||
} catch (final DataIntegrityViolationException e) {
|
||||
|
||||
+3
-1
@@ -1695,14 +1695,16 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
|
||||
sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 ");
|
||||
sqlBuilder.append(" and ml.is_npa = 0 ");
|
||||
sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)");
|
||||
sqlBuilder.append(" and ((");
|
||||
sqlBuilder.append(" mr.completed_derived is false ");
|
||||
sqlBuilder.append(" and mr.duedate < ? )");
|
||||
sqlBuilder.append(" or dd.expected_disburse_date < ? ) ");
|
||||
sqlBuilder.append(" group by ml.id");
|
||||
try {
|
||||
String currentdate = formatter.print(DateUtils.getLocalDateOfTenant());
|
||||
return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class,
|
||||
new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()), formatter.print(LocalDate.now()) });
|
||||
new Object[] { LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate });
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+36
-13
@@ -492,8 +492,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.getId() == null) {
|
||||
this.repaymentScheduleInstallmentRepository.save(installment);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.loanRepository.saveAndFlush(loan);
|
||||
@@ -515,9 +513,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.getId() == null) {
|
||||
this.repaymentScheduleInstallmentRepository.save(installment);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loanRepository.save(loan);
|
||||
} catch (final DataIntegrityViolationException e) {
|
||||
@@ -893,6 +889,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
|
||||
recalculateFrom = transactionToAdjust.getTransactionDate().isAfter(transactionDate) ? transactionDate : transactionToAdjust
|
||||
.getTransactionDate();
|
||||
}
|
||||
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
|
||||
@@ -1040,6 +1038,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
|
||||
workingDays = this.workingDaysRepository.findOne();
|
||||
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
|
||||
recalculateFrom = transactionDate;
|
||||
}
|
||||
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
@@ -1135,6 +1134,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
|
||||
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
|
||||
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
|
||||
recalculateFrom = command.localDateValueOfParameterNamed("transactionDate");
|
||||
}
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
|
||||
@@ -1217,6 +1217,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
|
||||
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
|
||||
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
|
||||
recalculateFrom = command.localDateValueOfParameterNamed("transactionDate");
|
||||
}
|
||||
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
@@ -1356,6 +1357,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
boolean isAppliedOnBackDate = false;
|
||||
LoanCharge loanCharge = null;
|
||||
LocalDate recalculateFrom =loan.fetchInterestRecalculateFromDate();
|
||||
if (chargeDefinition.isPercentageOfDisbursementAmount()) {
|
||||
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
|
||||
for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) {
|
||||
@@ -1367,7 +1369,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
|
||||
validateAddLoanCharge(loan, chargeDefinition, loanCharge);
|
||||
isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
|
||||
addCharge(loan, chargeDefinition, loanCharge);
|
||||
isAppliedOnBackDate = true;
|
||||
if (recalculateFrom.isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) {
|
||||
recalculateFrom = disbursementDetail.expectedDisbursementDateAsLocalDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
loan.addTrancheLoanCharge(chargeDefinition);
|
||||
@@ -1378,12 +1384,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
validateAddLoanCharge(loan, chargeDefinition, loanCharge);
|
||||
isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
|
||||
if (loanCharge.getDueLocalDate() == null || recalculateFrom.isAfter(loanCharge.getDueLocalDate())) {
|
||||
isAppliedOnBackDate = true;
|
||||
recalculateFrom = loanCharge.getDueLocalDate();
|
||||
}
|
||||
}
|
||||
|
||||
boolean reprocessRequired = true;
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
runScheduleRecalculation(loan);
|
||||
|
||||
runScheduleRecalculation(loan, recalculateFrom);
|
||||
reprocessRequired = false;
|
||||
}
|
||||
updateOriginalSchedule(loan);
|
||||
@@ -1445,7 +1456,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
}
|
||||
|
||||
public void runScheduleRecalculation(Loan loan) {
|
||||
public void runScheduleRecalculation(final Loan loan, final LocalDate recalculateFrom) {
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
|
||||
@@ -1466,7 +1477,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
final Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
|
||||
|
||||
LocalDate recalculateFrom = null;
|
||||
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
|
||||
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
|
||||
@@ -2413,6 +2423,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||
boolean runInterestRecalculation = false;
|
||||
LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant();
|
||||
for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduleDatas) {
|
||||
|
||||
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString());
|
||||
@@ -2422,12 +2433,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
overdueInstallment.getPeriodNumber(), command, loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loan = overdueDTO.getLoan();
|
||||
runInterestRecalculation = runInterestRecalculation || overdueDTO.isRunInterestRecalculation();
|
||||
if (recalculateFrom.isAfter(overdueDTO.getRecalculateFrom())) {
|
||||
recalculateFrom = overdueDTO.getRecalculateFrom();
|
||||
}
|
||||
}
|
||||
if (loan != null) {
|
||||
boolean reprocessRequired = true;
|
||||
LocalDate recalculatedTill = loan.fetchInterestRecalculateFromDate();
|
||||
if(recalculateFrom.isAfter(recalculatedTill)){
|
||||
recalculateFrom = recalculatedTill;
|
||||
}
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
runScheduleRecalculation(loan);
|
||||
runScheduleRecalculation(loan, recalculateFrom);
|
||||
reprocessRequired = false;
|
||||
}
|
||||
updateOriginalSchedule(loan);
|
||||
@@ -2482,7 +2501,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (feeFrequency == null) {
|
||||
scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
|
||||
} else {
|
||||
while (new LocalDate().isAfter(startDate)) {
|
||||
while (DateUtils.getLocalDateOfTenant().isAfter(startDate)) {
|
||||
scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
|
||||
LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency),
|
||||
chargeDefinition.feeInterval(), startDate, null, null);
|
||||
@@ -2505,6 +2524,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
installment = loan.fetchRepaymentScheduleInstallment(periodNumber);
|
||||
}
|
||||
LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant();
|
||||
if (loan != null) {
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
@@ -2518,10 +2538,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
|
||||
runInterestRecalculation = runInterestRecalculation || isAppliedOnBackDate;
|
||||
if (entry.getValue().isBefore(recalculateFrom)) {
|
||||
recalculateFrom = entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LoanOverdueDTO(loan, runInterestRecalculation);
|
||||
return new LoanOverdueDTO(loan, runInterestRecalculation, recalculateFrom);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2845,7 +2868,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
List<Holiday> holidays = null;
|
||||
boolean isHolidayEnabled = false;
|
||||
WorkingDays workingDays = null;
|
||||
LocalDate recalculateFrom = null;
|
||||
LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
|
||||
Long overdurPenaltyWaitPeriod = null;
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||
|
||||
+9
-1
@@ -5,16 +5,19 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanproduct.data;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||
|
||||
public class LoanOverdueDTO {
|
||||
|
||||
private final Loan loan;
|
||||
private final boolean runInterestRecalculation;
|
||||
private final LocalDate recalculateFrom;
|
||||
|
||||
public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation) {
|
||||
public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation, final LocalDate recalculateFrom) {
|
||||
this.loan = loan;
|
||||
this.runInterestRecalculation = runInterestRecalculation;
|
||||
this.recalculateFrom = recalculateFrom;
|
||||
}
|
||||
|
||||
public boolean isRunInterestRecalculation() {
|
||||
@@ -24,4 +27,9 @@ public class LoanOverdueDTO {
|
||||
public Loan getLoan() {
|
||||
return this.loan;
|
||||
}
|
||||
|
||||
|
||||
public LocalDate getRecalculateFrom() {
|
||||
return this.recalculateFrom;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `m_loan`
|
||||
ADD COLUMN `interest_recalcualated_on` DATE NULL DEFAULT NULL AFTER `accrued_till`;
|
||||
Reference in New Issue
Block a user