Merge pull request #1067 from pramodn02/develop

MIFOSX-1537 : added original schedule to history
This commit is contained in:
Vishwas Babu A J
2014-09-14 10:11:38 -07:00
17 changed files with 1620 additions and 1231 deletions
@@ -0,0 +1,20 @@
ALTER TABLE `m_loan_repayment_schedule_history`
DROP COLUMN `principal_completed_derived`,
DROP COLUMN `principal_writtenoff_derived`,
DROP COLUMN `interest_completed_derived`,
DROP COLUMN `interest_writtenoff_derived`,
DROP COLUMN `interest_waived_derived`,
DROP COLUMN `accrual_interest_derived`,
DROP COLUMN `fee_charges_completed_derived`,
DROP COLUMN `fee_charges_writtenoff_derived`,
DROP COLUMN `fee_charges_waived_derived`,
DROP COLUMN `accrual_fee_charges_derived`,
DROP COLUMN `penalty_charges_completed_derived`,
DROP COLUMN `penalty_charges_writtenoff_derived`,
DROP COLUMN `penalty_charges_waived_derived`,
DROP COLUMN `accrual_penalty_charges_derived`,
DROP COLUMN `total_paid_in_advance_derived`,
DROP COLUMN `total_paid_late_derived`,
DROP COLUMN `completed_derived`,
DROP COLUMN `obligations_met_on_date`,
ADD COLUMN `version` INT(5) NOT NULL;
@@ -77,6 +77,7 @@ import org.mifosplatform.portfolio.loanaccount.guarantor.service.GuarantorReadPl
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
@@ -137,6 +138,7 @@ public class LoansApiResource {
private final NoteReadPlatformServiceImpl noteReadPlatformService;
private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
private final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService;
@Autowired
public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
@@ -153,7 +155,8 @@ public class LoansApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService,
final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl,
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService) {
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService) {
this.context = context;
this.loanReadPlatformService = loanReadPlatformService;
this.loanProductReadPlatformService = loanProductReadPlatformService;
@@ -175,6 +178,7 @@ public class LoansApiResource {
this.noteReadPlatformService = noteReadPlatformService;
this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformServiceImpl;
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
this.loanScheduleHistoryReadPlatformService = loanScheduleHistoryReadPlatformService;
}
@GET
@@ -318,8 +322,8 @@ public class LoansApiResource {
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("repaymentSchedule", "futureSchedule", "transactions", "charges", "guarantors",
"collateral", "notes", "linkedAccount", "multiDisburseDetails"));
associationParameters.addAll(Arrays.asList("repaymentSchedule", "futureSchedule", "originalSchedule", "transactions",
"charges", "guarantors", "collateral", "notes", "linkedAccount", "multiDisburseDetails"));
}
if (associationParameters.contains("guarantors")) {
@@ -360,6 +364,14 @@ public class LoansApiResource {
mandatoryResponseParameters.add("futureSchedule");
this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId);
}
if (associationParameters.contains("originalSchedule") && loanBasicDetails.isInterestRecalculationEnabled()
&& loanBasicDetails.isInterestRecalculationEnabled()) {
mandatoryResponseParameters.add("originalSchedule");
LoanScheduleData loanScheduleData = this.loanScheduleHistoryReadPlatformService.retrieveRepaymentArchiveSchedule(
loanId, repaymentScheduleRelatedData, disbursementData);
loanBasicDetails = LoanAccountData.withOriginalSchedule(loanBasicDetails, loanScheduleData);
}
}
if (associationParameters.contains("charges")) {
@@ -115,6 +115,7 @@ public class LoanAccountData {
private final CalendarData meeting;
private final Collection<NoteData> notes;
private final Collection<DisbursementData> disbursementDetails;
private final LoanScheduleData originalSchedule;
// template
private final Collection<LoanProductData> productOptions;
private final Collection<StaffData> loanOfficerOptions;
@@ -260,6 +261,7 @@ public class LoanAccountData {
final EnumOptionData daysInYearType = null;
final boolean isInterestRecalculationEnabled = false;
final LoanInterestRecalculationData interestRecalculationData = null;
final LoanScheduleData originalSchedule = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -275,7 +277,7 @@ public class LoanAccountData {
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, interestRecalculationData);
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
}
/**
@@ -369,6 +371,7 @@ public class LoanAccountData {
final EnumOptionData daysInYearType = null;
final boolean isInterestRecalculationEnabled = false;
final LoanInterestRecalculationData interestRecalculationData = null;
final LoanScheduleData originalSchedule = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -383,7 +386,7 @@ public class LoanAccountData {
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount,
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData);
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -405,7 +408,8 @@ public class LoanAccountData {
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData);
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule);
}
/**
@@ -500,6 +504,7 @@ public class LoanAccountData {
final EnumOptionData daysInYearType = null;
final boolean isInterestRecalculationEnabled = false;
final LoanInterestRecalculationData interestRecalculationData = null;
final LoanScheduleData originalSchedule = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -514,7 +519,7 @@ public class LoanAccountData {
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData);
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
@@ -535,7 +540,8 @@ public class LoanAccountData {
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData);
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule);
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
@@ -643,6 +649,7 @@ public class LoanAccountData {
Map<Long, LoanBorrowerCycleData> memberVariations = null;
final Boolean inArrears = null;
final Boolean isNPA = null;
final LoanScheduleData originalSchedule = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId,
@@ -660,7 +667,8 @@ public class LoanAccountData {
notes, accountLinkingOptions, linkedAccount, disbursementData, product.getMultiDisburseLoan(), fixedEmi,
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears,
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(),
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData());
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
originalSchedule);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -694,7 +702,7 @@ public class LoanAccountData {
for (final ChargeData charge : product.charges()) {
charges.add(charge.toLoanChargeData());
}
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(),
product.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName,
@@ -713,7 +721,8 @@ public class LoanAccountData {
acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), acc.fixedEmiAmount,
product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears,
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(),
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData());
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
acc.originalSchedule);
}
/*
@@ -768,6 +777,7 @@ public class LoanAccountData {
final Map<Long, LoanBorrowerCycleData> memberVariations = null;
final LoanProductData product = null;
final Collection<ChargeData> overdueCharges = null;
final LoanScheduleData originalSchedule = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -782,7 +792,7 @@ public class LoanAccountData {
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData);
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule);
}
/*
@@ -820,7 +830,7 @@ public class LoanAccountData {
accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData);
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
@@ -851,7 +861,8 @@ public class LoanAccountData {
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData);
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -908,7 +919,8 @@ public class LoanAccountData {
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData);
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule);
}
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
@@ -931,7 +943,30 @@ public class LoanAccountData {
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData);
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData,
acc.originalSchedule);
}
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType,
acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions,
acc.repaymentFrequencyTypeOptions, acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions,
acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions,
acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
originalSchedule);
}
private LoanAccountData(
@@ -987,7 +1022,7 @@ public class LoanAccountData {
final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing,
final Collection<ChargeData> overdueCharges, final Boolean isNPA, final EnumOptionData daysInMonthType,
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
final LoanInterestRecalculationData interestRecalculationData) {
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule) {
this.id = id;
this.accountNo = accountNo;
this.status = status;
@@ -1131,6 +1166,7 @@ public class LoanAccountData {
this.daysInYearType = daysInYearType;
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
this.interestRecalculationData = interestRecalculationData;
this.originalSchedule = originalSchedule;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -1207,8 +1243,8 @@ public class LoanAccountData {
public Long loanProductId() {
return this.loanProductId;
}
public BigDecimal getTotalOutstandingAmount(){
public BigDecimal getTotalOutstandingAmount() {
return this.summary.getTotalOutstanding();
}
@@ -61,8 +61,8 @@ import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
@@ -97,7 +97,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType;
@@ -226,7 +225,7 @@ public class Loan extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE)
@Column(name = "rescheduledon_date")
private Date rescheduledOnDate;
@ManyToOne(optional = true)
@JoinColumn(name = "rescheduledon_userid", nullable = true)
private AppUser rescheduledByUser;
@@ -468,7 +467,9 @@ public class Loan extends AbstractPersistable<Long> {
}
public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final List<Long> existingReversedTransactionIds, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
ChangedTransactionDetail changedTransactionDetail = null;
validateLoanIsNotClosed(loanCharge);
@@ -528,7 +529,15 @@ public class Loan extends AbstractPersistable<Long> {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} else {
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
}
if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
@@ -825,7 +834,9 @@ public class Loan extends AbstractPersistable<Long> {
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Integer loanInstallmentNumber) {
final Integer loanInstallmentNumber, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateLoanIsNotClosed(loanCharge);
@@ -854,6 +865,12 @@ public class Loan extends AbstractPersistable<Long> {
transactionDate, feeChargesWaived, penaltyChargesWaived);
this.loanTransactions.add(waiveLoanChargeTransaction);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
}
// Waive of charges whose due date falls after latest 'repayment'
// transaction dont require entire loan schedule to be reprocessed.
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
@@ -1811,17 +1828,19 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTermVariations.add(loanVariationTerms);
isEmiAmountChanged = true;
}
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} else if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule
|| isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
updateLoanSummaryDerivedFields();
handleDisbursementTransaction(actualDisbursementDate);
@@ -2028,6 +2047,19 @@ public class Loan extends AbstractPersistable<Long> {
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
final LoanScheduleModel loanSchedule = regenerateScheduleModel(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
updateLoanSchedule(loanSchedule);
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
}
public LoanScheduleModel regenerateScheduleModel(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
@@ -2064,12 +2096,7 @@ public class Loan extends AbstractPersistable<Long> {
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, charges(),
isHolidayEnabled, holidays, workingDays);
updateLoanSchedule(loanSchedule);
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
return loanSchedule;
}
private void handleDisbursementTransaction(final LocalDate disbursedOn) {
@@ -4003,13 +4030,14 @@ public class Loan extends AbstractPersistable<Long> {
command.stringValueOfParameterNamed(LoanApiConstants.disbursementIdParameterName));
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} else {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
@@ -4310,173 +4338,179 @@ public class Loan extends AbstractPersistable<Long> {
return this.isNpa;
}
/**
/**
* @return List of loan repayments schedule objects
**/
public List<LoanRepaymentScheduleInstallment> getRepaymentScheduleInstallments() {
return this.repaymentScheduleInstallments;
return this.repaymentScheduleInstallments;
}
/**
* @return Loan product minimum repayments schedule related detail
/**
* @return Loan product minimum repayments schedule related detail
**/
public LoanProductRelatedDetail getLoanRepaymentScheduleDetail() {
return this.loanRepaymentScheduleDetail;
return this.loanRepaymentScheduleDetail;
}
/**
* @return Loan Fixed Emi amount
/**
* @return Loan Fixed Emi amount
**/
public BigDecimal getFixedEmiAmount() {
return this.fixedEmiAmount;
return this.fixedEmiAmount;
}
/**
* @return maximum outstanding loan balance
/**
* @return maximum outstanding loan balance
**/
public BigDecimal getMaxOutstandingLoanBalance() {
return this.maxOutstandingLoanBalance;
return this.maxOutstandingLoanBalance;
}
/**
* @param dueDate the due date of the installment
* @return a schedule installment with similar due date to the one provided
* @param dueDate
* the due date of the installment
* @return a schedule installment with similar due date to the one provided
**/
public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment(LocalDate dueDate) {
LoanRepaymentScheduleInstallment installment = null;
if(dueDate != null) {
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : this.repaymentScheduleInstallments) {
if(repaymentScheduleInstallment.getDueDate().isEqual(dueDate)) {
installment = repaymentScheduleInstallment;
break;
}
}
}
return installment;
LoanRepaymentScheduleInstallment installment = null;
if (dueDate != null) {
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : this.repaymentScheduleInstallments) {
if (repaymentScheduleInstallment.getDueDate().isEqual(dueDate)) {
installment = repaymentScheduleInstallment;
break;
}
}
}
return installment;
}
/**
* @param loan the Loan object
* @return loan disbursement data
**/
public List<DisbursementData> getDisbursmentData() {
Iterator<LoanDisbursementDetails> iterator = this.getDisbursementDetails().iterator();
List<DisbursementData> disbursementData = new ArrayList<DisbursementData>();
while(iterator.hasNext()) {
LoanDisbursementDetails loanDisbursementDetails = iterator.next();
LocalDate expectedDisbursementDate = null;
LocalDate actualDisbursementDate = null;
if(loanDisbursementDetails.expectedDisbursementDate() != null) {
expectedDisbursementDate = new LocalDate(loanDisbursementDetails.expectedDisbursementDate());
}
if(loanDisbursementDetails.actualDisbursementDate() != null) {
actualDisbursementDate = new LocalDate(loanDisbursementDetails.actualDisbursementDate());
}
disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate,
actualDisbursementDate, loanDisbursementDetails.principal(), loanDisbursementDetails.approvedPrincipal()));
}
return disbursementData;
}
/**
* @param loan the Loan object
* @return application terms of the Loan object
**/
@SuppressWarnings({ "unused" })
public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency) {
LoanProduct loanProduct = loanProduct();
//LoanProductRelatedDetail loanProductRelatedDetail = getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getCurrency();
final Integer loanTermFrequency = getTermFrequency();
final PeriodFrequencyType loanTermPeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments();
final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery();
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod();
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = this.loanRepaymentScheduleDetail.getInterestCalculationPeriodMethod();
final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal();
final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate();
final LocalDate repaymentsStartingFromDate = getExpectedFirstRepaymentOnDate();
LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
// TODO get calender linked to loan if any exist. As of 17-07-2014, could not find a link in the database.
// skip for now and set the Calender object to null
Calendar calendar = null;
final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.graceOnPrincipalPayment();
final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.graceOnInterestPayment();
final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.graceOnInterestCharged();
final LocalDate interestChargedFromDate = getInterestChargedFromDate();
final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnDueDate();
final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance();
final BigDecimal emiAmount = getFixedEmiAmount();
final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance();
final List<DisbursementData> disbursementData = getDisbursmentData();
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<LoanTermVariationsData>();
/**
* @param loan
* the Loan object
* @return loan disbursement data
**/
public List<DisbursementData> getDisbursmentData() {
Iterator<LoanDisbursementDetails> iterator = this.getDisbursementDetails().iterator();
List<DisbursementData> disbursementData = new ArrayList<DisbursementData>();
while (iterator.hasNext()) {
LoanDisbursementDetails loanDisbursementDetails = iterator.next();
LocalDate expectedDisbursementDate = null;
LocalDate actualDisbursementDate = null;
if (loanDisbursementDetails.expectedDisbursementDate() != null) {
expectedDisbursementDate = new LocalDate(loanDisbursementDetails.expectedDisbursementDate());
}
if (loanDisbursementDetails.actualDisbursementDate() != null) {
actualDisbursementDate = new LocalDate(loanDisbursementDetails.actualDisbursementDate());
}
disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate,
loanDisbursementDetails.principal(), loanDisbursementDetails.approvedPrincipal()));
}
return disbursementData;
}
/**
* @param loan
* the Loan object
* @return application terms of the Loan object
**/
@SuppressWarnings({ "unused" })
public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency) {
LoanProduct loanProduct = loanProduct();
// LoanProductRelatedDetail loanProductRelatedDetail =
// getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getCurrency();
final Integer loanTermFrequency = getTermFrequency();
final PeriodFrequencyType loanTermPeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments();
final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery();
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod();
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = this.loanRepaymentScheduleDetail
.getInterestCalculationPeriodMethod();
final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal();
final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate();
final LocalDate repaymentsStartingFromDate = getExpectedFirstRepaymentOnDate();
LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
// TODO get calender linked to loan if any exist. As of 17-07-2014,
// could not find a link in the database.
// skip for now and set the Calender object to null
Calendar calendar = null;
final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.graceOnPrincipalPayment();
final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.graceOnInterestPayment();
final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.graceOnInterestCharged();
final LocalDate interestChargedFromDate = getInterestChargedFromDate();
final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnDueDate();
final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance();
final BigDecimal emiAmount = getFixedEmiAmount();
final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance();
final List<DisbursementData> disbursementData = getDisbursmentData();
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<LoanTermVariationsData>();
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
loanVariationTermsData.add(data);
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsToleranceMoney,
this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, maxOutstandingBalance,
loanVariationTermsData, interestChargedFromDate);
}
/**
* @return Loan summary embedded object
**/
public LoanSummary getLoanSummary() {
return this.summary;
}
public void updateRescheduledByUser(AppUser rescheduledByUser) {
this.rescheduledByUser = rescheduledByUser;
}
public LoanProductRelatedDetail getLoanProductRelatedDetail() {
return this.loanRepaymentScheduleDetail;
}
public void updateNumberOfRepayments(Integer numberOfRepayments) {
this.loanRepaymentScheduleDetail.updateNumberOfRepayments(numberOfRepayments);
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsToleranceMoney,
this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, maxOutstandingBalance,
loanVariationTermsData, interestChargedFromDate);
}
/**
* @return Loan summary embedded object
**/
public LoanSummary getLoanSummary() {
return this.summary;
}
public void updateRescheduledByUser(AppUser rescheduledByUser) {
this.rescheduledByUser = rescheduledByUser;
}
public LoanProductRelatedDetail getLoanProductRelatedDetail() {
return this.loanRepaymentScheduleDetail;
}
public void updateNumberOfRepayments(Integer numberOfRepayments) {
this.loanRepaymentScheduleDetail.updateNumberOfRepayments(numberOfRepayments);
}
public void updateRescheduledOnDate(LocalDate rescheduledOnDate) {
if (rescheduledOnDate != null) {
this.rescheduledOnDate = rescheduledOnDate.toDate();
}
}
public void updateTermFrequency(Integer termFrequency) {
if (termFrequency != null) {
this.termFrequency = termFrequency;
}
}
public void updateRescheduledOnDate(LocalDate rescheduledOnDate) {
if(rescheduledOnDate != null) {
this.rescheduledOnDate = rescheduledOnDate.toDate();
}
}
public void updateTermFrequency(Integer termFrequency) {
if(termFrequency != null) {
this.termFrequency = termFrequency;
}
}
}
@@ -75,6 +75,27 @@ public class LoanScheduleData {
this.totalOutstanding = totalOutstanding;
}
public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final Integer loanTermInDays,
final BigDecimal totalPrincipalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalInterestCharged,
final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected) {
this.currency = currency;
this.periods = periods;
this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = totalPrincipalDisbursed;
this.totalPrincipalExpected = totalPrincipalExpected;
this.totalPrincipalPaid = null;
this.totalInterestCharged = totalInterestCharged;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalWaived = null;
this.totalWrittenOff = null;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalRepayment = null;
this.totalPaidInAdvance = null;
this.totalPaidLate = null;
this.totalOutstanding = null;
}
public Collection<LoanSchedulePeriodData> getPeriods() {
return this.periods;
}
@@ -9,9 +9,8 @@ import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -30,18 +29,16 @@ import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
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.domain.LoanSummary;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementDisbursementDateException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.mifosplatform.useradministration.domain.AppUser;
/**
*
@@ -522,354 +519,294 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstanding);
}
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) {
final Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
// create an archive of the current loan schedule installments
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = createLoanScheduleArchive(loanRescheduleRequest);
// get the initial list of repayment installments
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
// sort list by installment number in ASC order
Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = new ArrayList<LoanRescheduleModelRepaymentPeriod>();
Money outstandingLoanBalance = loan.getPrincpal();
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
Integer oldPeriodNumber = repaymentScheduleInstallment.getInstallmentNumber();
LocalDate fromDate = repaymentScheduleInstallment.getFromDate();
LocalDate dueDate = repaymentScheduleInstallment.getDueDate();
Money principalDue = repaymentScheduleInstallment.getPrincipal(currency);
Money interestDue = repaymentScheduleInstallment.getInterestCharged(currency);
Money feeChargesDue = repaymentScheduleInstallment.getFeeChargesCharged(currency);
Money penaltyChargesDue = repaymentScheduleInstallment.getPenaltyChargesCharged(currency);
Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
outstandingLoanBalance = outstandingLoanBalance.minus(principalDue);
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(oldPeriodNumber, oldPeriodNumber,
fromDate, dueDate, principalDue, outstandingLoanBalance, interestDue, feeChargesDue, penaltyChargesDue,
totalDue, false);
periods.add(period);
}
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) {
final Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
// create an archive of the current loan schedule installments
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = null;
// get the initial list of repayment installments
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
// sort list by installment number in ASC order
Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = new ArrayList<>();
Money outstandingLoanBalance = loan.getPrincpal();
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
Integer oldPeriodNumber = repaymentScheduleInstallment.getInstallmentNumber();
LocalDate fromDate = repaymentScheduleInstallment.getFromDate();
LocalDate dueDate = repaymentScheduleInstallment.getDueDate();
Money principalDue = repaymentScheduleInstallment.getPrincipal(currency);
Money interestDue = repaymentScheduleInstallment.getInterestCharged(currency);
Money feeChargesDue = repaymentScheduleInstallment.getFeeChargesCharged(currency);
Money penaltyChargesDue = repaymentScheduleInstallment.getPenaltyChargesCharged(currency);
Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
outstandingLoanBalance = outstandingLoanBalance.minus(principalDue);
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod
.instance(oldPeriodNumber, oldPeriodNumber, fromDate, dueDate, principalDue, outstandingLoanBalance, interestDue,
feeChargesDue, penaltyChargesDue, totalDue, false);
periods.add(period);
}
Money outstandingBalance = loan.getPrincpal();
Money totalCumulativePrincipal = Money.zero(currency);
Money totalCumulativeInterest = Money.zero(currency);
Money actualTotalCumulativeInterest = Money.zero(currency);
Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
Money totalPrincipalBeforeReschedulePeriod = Money.zero(currency);
LocalDate installmentDueDate = null;
LocalDate adjustedInstallmentDueDate = null;
LocalDate installmentFromDate = null;
Integer rescheduleFromInstallmentNo = defaultToZeroIfNull(loanRescheduleRequest.getRescheduleFromInstallment());
Integer installmentNumber = rescheduleFromInstallmentNo;
Integer graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
Integer graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
Integer extraTerms = defaultToZeroIfNull(loanRescheduleRequest.getExtraTerms());
final boolean recalculateInterest = loanRescheduleRequest.getRecalculateInterest();
Integer numberOfRepayments = repaymentScheduleInstallments.size();
Integer rescheduleNumberOfRepayments = numberOfRepayments;
final Money principal = loan.getPrincpal();
final Money totalPrincipalOutstanding = Money.of(currency, loanSummary.getTotalPrincipalOutstanding());
LocalDate adjustedDueDate = loanRescheduleRequest.getAdjustedDueDate();
BigDecimal newInterestRate = loanRescheduleRequest.getInterestRate();
int loanTermInDays = Integer.valueOf(0);
if(rescheduleFromInstallmentNo > 0) {
// this will hold the loan repayment installment that is before the reschedule start installment
// (rescheduleFrominstallment)
LoanRepaymentScheduleInstallment previousInstallment = null;
// get the install number of the previous installment
int previousInstallmentNo = rescheduleFromInstallmentNo - 1;
// only fetch the installment if the number is greater than 0
if(previousInstallmentNo > 0) {
previousInstallment = loan.fetchRepaymentScheduleInstallment(previousInstallmentNo);
}
LoanRepaymentScheduleInstallment firstInstallment = loan.fetchRepaymentScheduleInstallment(1);
// the "installment from date" is equal to the due date of the previous installment, if it exists
if(previousInstallment != null) {
installmentFromDate = previousInstallment.getDueDate();
}
else {
installmentFromDate = firstInstallment.getFromDate();
}
installmentDueDate = installmentFromDate;
LocalDate periodStartDateApplicableForInterest = installmentFromDate;
Integer periodNumber = 1;
outstandingLoanBalance = loan.getPrincpal();
for(LoanRescheduleModelRepaymentPeriod period : periods) {
if(period.periodDueDate().isBefore(loanRescheduleRequest.getRescheduleFromDate())) {
totalPrincipalBeforeReschedulePeriod = totalPrincipalBeforeReschedulePeriod.plus(period.principalDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
rescheduleNumberOfRepayments--;
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
while(graceOnPrincipal > 0 || graceOnInterest > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
if(graceOnPrincipal > 0) {
graceOnPrincipal--;
}
if(graceOnInterest > 0) {
graceOnInterest--;
}
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
while(extraTerms > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
extraTerms--;
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
// get the loan application terms from the Loan object
final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency);
// update the number of repayments
loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled,
holidays, workingDays);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
if(newInterestRate != null) {
loanApplicationTerms.updateAnnualNominalInterestRate(newInterestRate);
loanApplicationTerms.updateInterestRatePerPeriod(newInterestRate);
}
graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
loanApplicationTerms.updateInterestPaymentGrace(graceOnInterest);
loanApplicationTerms.updatePrincipalGrace(graceOnPrincipal);
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
Money totalCumulativePrincipal = Money.zero(currency);
Money totalCumulativeInterest = Money.zero(currency);
Money actualTotalCumulativeInterest = Money.zero(currency);
Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
Money totalPrincipalBeforeReschedulePeriod = Money.zero(currency);
LocalDate installmentDueDate = null;
LocalDate adjustedInstallmentDueDate = null;
LocalDate installmentFromDate = null;
Integer rescheduleFromInstallmentNo = defaultToZeroIfNull(loanRescheduleRequest.getRescheduleFromInstallment());
Integer installmentNumber = rescheduleFromInstallmentNo;
Integer graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
Integer graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
Integer extraTerms = defaultToZeroIfNull(loanRescheduleRequest.getExtraTerms());
final boolean recalculateInterest = loanRescheduleRequest.getRecalculateInterest();
Integer numberOfRepayments = repaymentScheduleInstallments.size();
Integer rescheduleNumberOfRepayments = numberOfRepayments;
final Money principal = loan.getPrincpal();
final Money totalPrincipalOutstanding = Money.of(currency, loanSummary.getTotalPrincipalOutstanding());
LocalDate adjustedDueDate = loanRescheduleRequest.getAdjustedDueDate();
BigDecimal newInterestRate = loanRescheduleRequest.getInterestRate();
int loanTermInDays = Integer.valueOf(0);
if (rescheduleFromInstallmentNo > 0) {
// this will hold the loan repayment installment that is before the
// reschedule start installment
// (rescheduleFrominstallment)
LoanRepaymentScheduleInstallment previousInstallment = null;
// get the install number of the previous installment
int previousInstallmentNo = rescheduleFromInstallmentNo - 1;
// only fetch the installment if the number is greater than 0
if (previousInstallmentNo > 0) {
previousInstallment = loan.fetchRepaymentScheduleInstallment(previousInstallmentNo);
}
LoanRepaymentScheduleInstallment firstInstallment = loan.fetchRepaymentScheduleInstallment(1);
// the "installment from date" is equal to the due date of the
// previous installment, if it exists
if (previousInstallment != null) {
installmentFromDate = previousInstallment.getDueDate();
}
else {
installmentFromDate = firstInstallment.getFromDate();
}
installmentDueDate = installmentFromDate;
LocalDate periodStartDateApplicableForInterest = installmentFromDate;
Integer periodNumber = 1;
outstandingLoanBalance = loan.getPrincpal();
for (LoanRescheduleModelRepaymentPeriod period : periods) {
if (period.periodDueDate().isBefore(loanRescheduleRequest.getRescheduleFromDate())) {
totalPrincipalBeforeReschedulePeriod = totalPrincipalBeforeReschedulePeriod.plus(period.principalDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
rescheduleNumberOfRepayments--;
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
while (graceOnPrincipal > 0 || graceOnInterest > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
if (graceOnPrincipal > 0) {
graceOnPrincipal--;
}
if (graceOnInterest > 0) {
graceOnInterest--;
}
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
while (extraTerms > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
extraTerms--;
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
// get the loan application terms from the Loan object
final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency);
// update the number of repayments
loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
if (newInterestRate != null) {
loanApplicationTerms.updateAnnualNominalInterestRate(newInterestRate);
loanApplicationTerms.updateInterestRatePerPeriod(newInterestRate);
}
graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
loanApplicationTerms.updateInterestPaymentGrace(graceOnInterest);
loanApplicationTerms.updatePrincipalGrace(graceOnPrincipal);
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments);
loanApplicationTerms.updateLoanTermFrequency(rescheduleNumberOfRepayments);
loanApplicationTerms.updateInterestChargedFromDate(periodStartDateApplicableForInterest);
Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
this.paymentPeriodsInOneYearCalculator, mathContext);
if(!recalculateInterest && newInterestRate == null) {
totalInterestChargedForFullLoanTerm = Money.of(currency, loanSummary.getTotalInterestCharged());
totalInterestChargedForFullLoanTerm = totalInterestChargedForFullLoanTerm.minus(actualTotalCumulativeInterest);
loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm);
this.paymentPeriodsInOneYearCalculator, mathContext);
if (!recalculateInterest && newInterestRate == null) {
totalInterestChargedForFullLoanTerm = Money.of(currency, loanSummary.getTotalInterestCharged());
totalInterestChargedForFullLoanTerm = totalInterestChargedForFullLoanTerm.minus(actualTotalCumulativeInterest);
loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm);
}
for(LoanRescheduleModelRepaymentPeriod period : periods) {
if(period.periodDueDate().isEqual(loanRescheduleRequest.getRescheduleFromDate()) ||
period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate()) ||
period.isNew()) {
installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate, loanApplicationTerms,
false);
if(adjustedDueDate != null && periodNumber == 1) {
installmentDueDate = adjustedDueDate;
}
adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays();
period.updatePeriodNumber(installmentNumber);
period.updatePeriodFromDate(installmentFromDate);
period.updatePeriodDueDate(adjustedInstallmentDueDate);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, adjustedInstallmentDueDate,
periodStartDateApplicableForInterest, loanApplicationTerms.getLoanTermPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery());
// ========================= Calculate the interest due ========================================
// change the principal to => Principal Disbursed - Total Principal Paid
// interest calculation is always based on the total principal outstanding
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
// determine the interest & principal for the period
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
daysInInstallment, outstandingBalance, loanApplicationTerms, periodNumber, mathContext);
// update the interest due for the period
period.updateInterestDue(principalInterestForThisPeriod.interest());
// =============================================================================================
// ========================== Calculate the principal due ======================================
// change the principal to => Principal Disbursed - Total cumulative Principal Amount before the reschedule installment
loanApplicationTerms.setPrincipal(principal.minus(totalPrincipalBeforeReschedulePeriod));
principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
daysInInstallment, outstandingBalance, loanApplicationTerms, periodNumber, mathContext);
period.updatePrincipalDue(principalInterestForThisPeriod.principal());
// ==============================================================================================
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
period.updateOutstandingLoanBalance(outstandingLoanBalance);
Money principalDue = Money.of(currency, period.principalDue());
Money interestDue = Money.of(currency, period.interestDue());
Money feeChargesDue = Money.of(currency, period.feeChargesDue());
Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue());
Money totalDue = principalDue
.plus(interestDue)
.plus(feeChargesDue)
.plus(penaltyChargesDue);
period.updateTotalDue(totalDue);
// update cumulative fields for principal & interest
totalCumulativePrincipal = totalCumulativePrincipal.plus(period.principalDue());
totalCumulativeInterest = totalCumulativeInterest.plus(period.interestDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
installmentFromDate = adjustedInstallmentDueDate;
installmentNumber++;
periodNumber++;
loanTermInDays += daysInInstallment;
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
}
final Money totalRepaymentExpected = principal // get the loan Principal amount
.plus(actualTotalCumulativeInterest) // add the actual total cumulative interest
.plus(loanSummary.getTotalFeeChargesCharged()) // add the total fees charged
.plus(loanSummary.getTotalPenaltyChargesCharged()); // finally add the total penalty charged
return LoanRescheduleModel.instance(periods, loanRepaymentScheduleHistoryList, applicationCurrency, loanTermInDays,
loan.getPrincpal(), loan.getPrincpal().getAmount(), loanSummary.getTotalPrincipalRepaid(), actualTotalCumulativeInterest.getAmount(),
loanSummary.getTotalFeeChargesCharged(), loanSummary.getTotalPenaltyChargesCharged(), totalRepaymentExpected.getAmount(),
loanSummary.getTotalOutstanding());
}
private Collection<LoanRepaymentScheduleHistory> createLoanScheduleArchive(final LoanRescheduleRequest loanRescheduleRequest) {
final Loan loan = loanRescheduleRequest.getLoan();
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
List<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = new ArrayList<LoanRepaymentScheduleHistory>();
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
final Integer installmentNumber = repaymentScheduleInstallment.getInstallmentNumber();
Date fromDate = null;
Date dueDate = null;
if(repaymentScheduleInstallment.getFromDate() != null) {
fromDate = repaymentScheduleInstallment.getFromDate().toDate();
}
if(repaymentScheduleInstallment.getDueDate() != null) {
dueDate = repaymentScheduleInstallment.getDueDate().toDate();
}
final BigDecimal principal = repaymentScheduleInstallment.getPrincipal(currency).getAmount();
final BigDecimal principalCompleted = repaymentScheduleInstallment.getPrincipalCompleted(currency).getAmount();
final BigDecimal principalWrittenOff = repaymentScheduleInstallment.getPrincipalWrittenOff(currency).getAmount();
final BigDecimal interestCharged = repaymentScheduleInstallment.getInterestCharged(currency).getAmount();
final BigDecimal interestPaid = repaymentScheduleInstallment.getInterestPaid(currency).getAmount();
final BigDecimal interestWaived = repaymentScheduleInstallment.getInterestWaived(currency).getAmount();
final BigDecimal interestWrittenOff = repaymentScheduleInstallment.getInterestWrittenOff(currency).getAmount();
final BigDecimal feeChargesCharged = repaymentScheduleInstallment.getFeeChargesCharged(currency).getAmount();
final BigDecimal feeChargesPaid = repaymentScheduleInstallment.getFeeChargesPaid(currency).getAmount();
final BigDecimal feeChargesWrittenOff = repaymentScheduleInstallment.getFeeChargesWrittenOff(currency).getAmount();
final BigDecimal feeChargesWaived = repaymentScheduleInstallment.getFeeChargesWaived(currency).getAmount();
final BigDecimal penaltyCharges = repaymentScheduleInstallment.getPenaltyChargesCharged(currency).getAmount();
final BigDecimal penaltyChargesPaid = repaymentScheduleInstallment.getPenaltyChargesPaid(currency).getAmount();
final BigDecimal penaltyChargesWrittenOff = repaymentScheduleInstallment.getPenaltyChargesWrittenOff(currency).getAmount();
final BigDecimal penaltyChargesWaived = repaymentScheduleInstallment.getPenaltyChargesWaived(currency).getAmount();
final BigDecimal totalPaidInAdvance = repaymentScheduleInstallment.getTotalPaidInAdvance();
final BigDecimal totalPaidLate = repaymentScheduleInstallment.getTotalPaidLate();
final boolean obligationsMet = repaymentScheduleInstallment.isObligationsMet();
Date obligationsMetOnDate = null;
if(repaymentScheduleInstallment.getObligationsMetOnDate() != null) {
obligationsMetOnDate = repaymentScheduleInstallment.getObligationsMetOnDate().toDate();
}
Date createdOnDate = null;
if(repaymentScheduleInstallment.getCreatedDate() != null) {
createdOnDate = repaymentScheduleInstallment.getCreatedDate().toDate();
}
final AppUser createdByUser = repaymentScheduleInstallment.getCreatedBy();
final AppUser lastModifiedByUser = repaymentScheduleInstallment.getLastModifiedBy();
Date lastModifiedOnDate = null;
if(repaymentScheduleInstallment.getLastModifiedDate() != null) {
lastModifiedOnDate = repaymentScheduleInstallment.getLastModifiedDate().toDate();
}
LoanRepaymentScheduleHistory loanRepaymentScheduleHistory = LoanRepaymentScheduleHistory.instance(loan,
loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal, principalCompleted,
principalWrittenOff, interestCharged, interestPaid, interestWaived, interestWrittenOff,
feeChargesCharged, feeChargesPaid, feeChargesWrittenOff, feeChargesWaived, penaltyCharges,
penaltyChargesPaid, penaltyChargesWrittenOff, penaltyChargesWaived, totalPaidInAdvance, totalPaidLate,
obligationsMet, obligationsMetOnDate, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate);
loanRepaymentScheduleHistoryList.add(loanRepaymentScheduleHistory);
}
return loanRepaymentScheduleHistoryList;
for (LoanRescheduleModelRepaymentPeriod period : periods) {
if (period.periodDueDate().isEqual(loanRescheduleRequest.getRescheduleFromDate())
|| period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate()) || period.isNew()) {
installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate, loanApplicationTerms,
false);
if (adjustedDueDate != null && periodNumber == 1) {
installmentDueDate = adjustedDueDate;
}
adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays();
period.updatePeriodNumber(installmentNumber);
period.updatePeriodFromDate(installmentFromDate);
period.updatePeriodDueDate(adjustedInstallmentDueDate);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest,
adjustedInstallmentDueDate, periodStartDateApplicableForInterest,
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery());
// ========================= Calculate the interest due
// ========================================
// change the principal to => Principal Disbursed - Total
// Principal Paid
// interest calculation is always based on the total
// principal outstanding
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
// determine the interest & principal for the period
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
totalOutstandingInterestPaymentDueToGrace, daysInInstallment, outstandingBalance, loanApplicationTerms,
periodNumber, mathContext);
// update the interest due for the period
period.updateInterestDue(principalInterestForThisPeriod.interest());
// =============================================================================================
// ========================== Calculate the principal due
// ======================================
// change the principal to => Principal Disbursed - Total
// cumulative Principal Amount before the reschedule
// installment
loanApplicationTerms.setPrincipal(principal.minus(totalPrincipalBeforeReschedulePeriod));
principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator,
interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest,
totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, daysInInstallment,
outstandingBalance, loanApplicationTerms, periodNumber, mathContext);
period.updatePrincipalDue(principalInterestForThisPeriod.principal());
// ==============================================================================================
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
period.updateOutstandingLoanBalance(outstandingLoanBalance);
Money principalDue = Money.of(currency, period.principalDue());
Money interestDue = Money.of(currency, period.interestDue());
Money feeChargesDue = Money.of(currency, period.feeChargesDue());
Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue());
Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
period.updateTotalDue(totalDue);
// update cumulative fields for principal & interest
totalCumulativePrincipal = totalCumulativePrincipal.plus(period.principalDue());
totalCumulativeInterest = totalCumulativeInterest.plus(period.interestDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
installmentFromDate = adjustedInstallmentDueDate;
installmentNumber++;
periodNumber++;
loanTermInDays += daysInInstallment;
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
}
final Money totalRepaymentExpected = principal // get the loan Principal
// amount
.plus(actualTotalCumulativeInterest) // add the actual total
// cumulative interest
.plus(loanSummary.getTotalFeeChargesCharged()) // add the total
// fees charged
.plus(loanSummary.getTotalPenaltyChargesCharged()); // finally
// add the
// total
// penalty
// charged
return LoanRescheduleModel.instance(periods, loanRepaymentScheduleHistoryList, applicationCurrency, loanTermInDays,
loan.getPrincpal(), loan.getPrincpal().getAmount(), loanSummary.getTotalPrincipalRepaid(),
actualTotalCumulativeInterest.getAmount(), loanSummary.getTotalFeeChargesCharged(),
loanSummary.getTotalPenaltyChargesCharged(), totalRepaymentExpected.getAmount(), loanSummary.getTotalOutstanding());
}
private BigDecimal calculateInterestForSpecificDays(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
@@ -1669,14 +1606,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
return precloseTransaction;
}
/**
* set the value to zero if the provided value is null
/**
* set the value to zero if the provided value is null
*
* @return integer value equal/greater than 0
**/
private Integer defaultToZeroIfNull(Integer value) {
return (value != null) ? value : 0;
return (value != null) ? value : 0;
}
}
@@ -0,0 +1,138 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_repayment_schedule_history")
public class LoanRepaymentScheduleHistory extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id")
private Loan loan;
@SuppressWarnings("unused")
@OneToOne(optional = true)
@JoinColumn(name = "loan_reschedule_request_id")
private LoanRescheduleRequest loanRescheduleRequest;
@SuppressWarnings("unused")
@Column(name = "installment", nullable = false)
private Integer installmentNumber;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "fromdate", nullable = true)
private Date fromDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "duedate", nullable = false)
private Date dueDate;
@SuppressWarnings("unused")
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal principal;
@SuppressWarnings("unused")
@Column(name = "interest_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal interestCharged;
@SuppressWarnings("unused")
@Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesCharged;
@SuppressWarnings("unused")
@Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyCharges;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "created_date")
private Date createdOnDate;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "createdby_id")
private AppUser createdByUser;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "lastmodifiedby_id")
private AppUser lastModifiedByUser;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "lastmodified_date")
private Date lastModifiedOnDate;
@Column(name = "version")
@SuppressWarnings("unused")
private Integer version;
/**
* LoanRepaymentScheduleHistory constructor
**/
protected LoanRepaymentScheduleHistory() {}
/**
* LoanRepaymentScheduleHistory constructor
**/
private LoanRepaymentScheduleHistory(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal interestCharged, final BigDecimal feeChargesCharged, final BigDecimal penaltyCharges,
final Date createdOnDate, final AppUser createdByUser, final AppUser lastModifiedByUser, final Date lastModifiedOnDate,
final Integer version) {
this.loan = loan;
this.loanRescheduleRequest = loanRescheduleRequest;
this.installmentNumber = installmentNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.principal = principal;
this.interestCharged = interestCharged;
this.feeChargesCharged = feeChargesCharged;
this.penaltyCharges = penaltyCharges;
this.createdOnDate = createdOnDate;
this.createdByUser = createdByUser;
this.lastModifiedByUser = lastModifiedByUser;
this.lastModifiedOnDate = lastModifiedOnDate;
this.version = version;
}
/**
* @return an instance of the LoanRepaymentScheduleHistory class
**/
public static LoanRepaymentScheduleHistory instance(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal interestCharged, final BigDecimal feeChargesCharged, final BigDecimal penaltyCharges,
final Date createdOnDate, final AppUser createdByUser, final AppUser lastModifiedByUser, final Date lastModifiedOnDate,
final Integer version) {
return new LoanRepaymentScheduleHistory(loan, loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal,
interestCharged, feeChargesCharged, penaltyCharges, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate,
version);
}
}
@@ -3,7 +3,7 @@
* 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.rescheduleloan.domain;
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -0,0 +1,15 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.util.Collection;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
public interface LoanScheduleHistoryReadPlatformService {
Integer fetchCurrentVersionNumber(Long loanId);
LoanScheduleData retrieveRepaymentArchiveSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData);
}
@@ -0,0 +1,196 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.stereotype.Service;
@Service
public class LoanScheduleHistoryReadPlatformServiceImpl implements LoanScheduleHistoryReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Autowired
public LoanScheduleHistoryReadPlatformServiceImpl(final RoutingDataSource dataSource, final PlatformSecurityContext context) {
this.context = context;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Integer fetchCurrentVersionNumber(Long loanId) {
final String sql = "select MAX(lrs.version) from m_loan_repayment_schedule_history lrs where lrs.loan_id = ?";
return this.jdbcTemplate.queryForInt(sql, loanId);
}
@Override
public LoanScheduleData retrieveRepaymentArchiveSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData) {
try {
this.context.authenticatedUser();
Integer versionNumber = fetchCurrentVersionNumber(loanId);
if (versionNumber == 0) { return null; }
final LoanScheduleArchiveResultSetExtractor fullResultsetExtractor = new LoanScheduleArchiveResultSetExtractor(
repaymentScheduleRelatedLoanData, disbursementData);
final String sql = "select " + fullResultsetExtractor.schema()
+ " where ls.loan_id = ? and ls.version = ? order by ls.loan_id, ls.installment";
return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId, versionNumber });
} catch (final EmptyResultDataAccessException e) {
throw new LoanNotFoundException(loanId);
}
}
private static final class LoanScheduleArchiveResultSetExtractor implements ResultSetExtractor<LoanScheduleData> {
private final CurrencyData currency;
private final DisbursementData disbursement;
private final BigDecimal totalFeeChargesDueAtDisbursement;
private final Collection<DisbursementData> disbursementData;
private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance;
public LoanScheduleArchiveResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
this.lastDueDate = this.disbursement.disbursementDate();
this.outstandingLoanPrincipalBalance = this.disbursement.amount();
this.disbursementData = disbursementData;
}
public String schema() {
StringBuilder stringBuilder = new StringBuilder(200);
stringBuilder.append(" ls.installment as period, ls.fromdate as fromDate, ls.duedate as dueDate, ");
stringBuilder
.append("ls.principal_amount as principalDue, ls.interest_amount as interestDue, ls.fee_charges_amount as feeChargesDue, ls.penalty_charges_amount as penaltyChargesDue ");
stringBuilder.append(" from m_loan_repayment_schedule_history ls ");
return stringBuilder.toString();
}
@Override
public LoanScheduleData extractData(final ResultSet rs) throws SQLException, DataAccessException {
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(
this.disbursement.disbursementDate(), this.disbursement.amount(), this.totalFeeChargesDueAtDisbursement,
this.disbursement.isDisbursed());
final Collection<LoanSchedulePeriodData> periods = new ArrayList<>();
final MonetaryCurrency monCurrency = new MonetaryCurrency(this.currency.code(), this.currency.decimalPlaces(),
this.currency.currencyInMultiplesOf());
BigDecimal totalPrincipalDisbursed = BigDecimal.ZERO;
if (disbursementData == null || disbursementData.isEmpty()) {
periods.add(disbursementPeriod);
totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount();
} else {
this.outstandingLoanPrincipalBalance = BigDecimal.ZERO;
}
Money totalPrincipalExpected = Money.zero(monCurrency);
Money totalInterestCharged = Money.zero(monCurrency);
Money totalFeeChargesCharged = Money.zero(monCurrency);
Money totalPenaltyChargesCharged = Money.zero(monCurrency);
Money totalRepaymentExpected = Money.zero(monCurrency);
// update totals with details of fees charged during disbursement
totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue());
totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue());
Integer loanTermInDays = Integer.valueOf(0);
while (rs.next()) {
final Integer period = JdbcSupport.getInteger(rs, "period");
LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromDate");
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
if (disbursementData != null) {
BigDecimal principal = BigDecimal.ZERO;
for (DisbursementData data : disbursementData) {
if (fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate)) {
principal = principal.add(data.amount());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed());
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
} else if (data.isDueForDisbursement(fromDate, dueDate)
&& this.outstandingLoanPrincipalBalance.compareTo(BigDecimal.ZERO) == 1) {
principal = principal.add(data.amount());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
}
}
totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal);
}
Integer daysInPeriod = Integer.valueOf(0);
if (fromDate != null) {
daysInPeriod = Days.daysBetween(fromDate, dueDate).getDays();
loanTermInDays = Integer.valueOf(loanTermInDays.intValue() + daysInPeriod.intValue());
}
final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue");
totalPrincipalExpected = totalPrincipalExpected.plus(principalDue);
final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
totalInterestCharged = totalInterestCharged.plus(interestExpectedDue);
final BigDecimal feeChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesDue");
totalFeeChargesCharged = totalFeeChargesCharged.plus(feeChargesExpectedDue);
final BigDecimal penaltyChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesDue");
totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(penaltyChargesExpectedDue);
final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(
penaltyChargesExpectedDue);
final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod);
totalRepaymentExpected = totalRepaymentExpected.plus(totalDueForPeriod);
if (fromDate == null) {
fromDate = this.lastDueDate;
}
final BigDecimal outstandingPrincipalBalanceOfLoan = this.outstandingLoanPrincipalBalance.subtract(principalDue);
// update based on current period values
this.lastDueDate = dueDate;
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.subtract(principalDue);
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.repaymentOnlyPeriod(period, fromDate, dueDate,
principalDue, outstandingPrincipalBalanceOfLoan, interestExpectedDue, feeChargesExpectedDue,
penaltyChargesExpectedDue, totalDueForPeriod);
periods.add(periodData);
}
return new LoanScheduleData(this.currency, periods, loanTermInDays, totalPrincipalDisbursed,
totalPrincipalExpected.getAmount(), totalInterestCharged.getAmount(), totalFeeChargesCharged.getAmount(),
totalPenaltyChargesCharged.getAmount(), totalRepaymentExpected.getAmount());
}
}
}
@@ -0,0 +1,19 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.util.List;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
public interface LoanScheduleHistoryWritePlatformService {
List<LoanRepaymentScheduleHistory> createLoanScheduleArchive(
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final Loan loan,
final LoanRescheduleRequest loanRescheduleRequest);
void createAndSaveLoanScheduleArchive(List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Loan loan,
LoanRescheduleRequest loanRescheduleRequest);
}
@@ -0,0 +1,89 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoanScheduleHistoryWritePlatformServiceImpl implements LoanScheduleHistoryWritePlatformService {
private final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService;
private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
@Autowired
public LoanScheduleHistoryWritePlatformServiceImpl(final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService,
final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository) {
this.loanScheduleHistoryReadPlatformService = loanScheduleHistoryReadPlatformService;
this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
}
@Override
public List<LoanRepaymentScheduleHistory> createLoanScheduleArchive(
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Loan loan, LoanRescheduleRequest loanRescheduleRequest) {
Integer version = this.loanScheduleHistoryReadPlatformService.fetchCurrentVersionNumber(loan.getId()) + 1;
final MonetaryCurrency currency = loan.getCurrency();
final List<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = new ArrayList<>();
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
final Integer installmentNumber = repaymentScheduleInstallment.getInstallmentNumber();
Date fromDate = null;
Date dueDate = null;
if (repaymentScheduleInstallment.getFromDate() != null) {
fromDate = repaymentScheduleInstallment.getFromDate().toDate();
}
if (repaymentScheduleInstallment.getDueDate() != null) {
dueDate = repaymentScheduleInstallment.getDueDate().toDate();
}
final BigDecimal principal = repaymentScheduleInstallment.getPrincipal(currency).getAmount();
final BigDecimal interestCharged = repaymentScheduleInstallment.getInterestCharged(currency).getAmount();
final BigDecimal feeChargesCharged = repaymentScheduleInstallment.getFeeChargesCharged(currency).getAmount();
final BigDecimal penaltyCharges = repaymentScheduleInstallment.getPenaltyChargesCharged(currency).getAmount();
Date createdOnDate = null;
if (repaymentScheduleInstallment.getCreatedDate() != null) {
createdOnDate = repaymentScheduleInstallment.getCreatedDate().toDate();
}
final AppUser createdByUser = repaymentScheduleInstallment.getCreatedBy();
final AppUser lastModifiedByUser = repaymentScheduleInstallment.getLastModifiedBy();
Date lastModifiedOnDate = null;
if (repaymentScheduleInstallment.getLastModifiedDate() != null) {
lastModifiedOnDate = repaymentScheduleInstallment.getLastModifiedDate().toDate();
}
LoanRepaymentScheduleHistory loanRepaymentScheduleHistory = LoanRepaymentScheduleHistory.instance(loan, loanRescheduleRequest,
installmentNumber, fromDate, dueDate, principal, interestCharged, feeChargesCharged, penaltyCharges, createdOnDate,
createdByUser, lastModifiedByUser, lastModifiedOnDate, version);
loanRepaymentScheduleHistoryList.add(loanRepaymentScheduleHistory);
}
return loanRepaymentScheduleHistoryList;
}
@Override
public void createAndSaveLoanScheduleArchive(List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Loan loan,
LoanRescheduleRequest loanRescheduleRequest) {
List<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = createLoanScheduleArchive(repaymentScheduleInstallments,
loan, loanRescheduleRequest);
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistoryList);
}
}
@@ -1,210 +0,0 @@
/**
* 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.rescheduleloan.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_repayment_schedule_history")
public class LoanRepaymentScheduleHistory extends AbstractPersistable<Long> {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id")
private Loan loan;
@OneToOne(optional = true)
@JoinColumn(name = "loan_reschedule_request_id")
private LoanRescheduleRequest loanRescheduleRequest;
@Column(name = "installment", nullable = false)
private Integer installmentNumber;
@Temporal(TemporalType.DATE)
@Column(name = "fromdate", nullable = true)
private Date fromDate;
@Temporal(TemporalType.DATE)
@Column(name = "duedate", nullable = false)
private Date dueDate;
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal principal;
@Column(name = "principal_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalCompleted;
@Column(name = "principal_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalWrittenOff;
@Column(name = "interest_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal interestCharged;
@Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestPaid;
@Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWaived;
@Column(name = "interest_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWrittenOff;
@Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesCharged;
@Column(name = "fee_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesPaid;
@Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWrittenOff;
@Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWaived;
@Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyCharges;
@Column(name = "penalty_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPaid;
@Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWrittenOff;
@Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWaived;
@Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal totalPaidInAdvance;
@Column(name = "total_paid_late_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal totalPaidLate;
@Column(name = "completed_derived", nullable = false)
private boolean obligationsMet;
@Temporal(TemporalType.DATE)
@Column(name = "obligations_met_on_date")
private Date obligationsMetOnDate;
@Temporal(TemporalType.DATE)
@Column(name = "created_date")
private Date createdOnDate;
@ManyToOne
@JoinColumn(name = "createdby_id")
private AppUser createdByUser;
@ManyToOne
@JoinColumn(name = "lastmodifiedby_id")
private AppUser lastModifiedByUser;
@Temporal(TemporalType.DATE)
@Column(name = "lastmodified_date")
private Date lastModifiedOnDate;
/**
* LoanRepaymentScheduleHistory constructor
**/
protected LoanRepaymentScheduleHistory() {}
/**
* LoanRepaymentScheduleHistory constructor
**/
private LoanRepaymentScheduleHistory(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal principalCompleted, final BigDecimal principalWrittenOff, final BigDecimal interestCharged,
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
final BigDecimal feeChargesCharged, final BigDecimal feeChargesPaid, final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesWaived, final BigDecimal penaltyCharges, final BigDecimal penaltyChargesPaid,
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesWaived,
final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
final Date obligationsMetOnDate, final Date createdOnDate, final AppUser createdByUser,
final AppUser lastModifiedByUser, final Date lastModifiedOnDate) {
this.loan = loan;
this.loanRescheduleRequest = loanRescheduleRequest;
this.installmentNumber = installmentNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.principal = principal;
this.principalCompleted = principalCompleted;
this.principalWrittenOff = principalWrittenOff;
this.interestCharged = interestCharged;
this.interestPaid = interestPaid;
this.interestWaived = interestWaived;
this.interestWrittenOff = interestWrittenOff;
this.feeChargesCharged = feeChargesCharged;
this.feeChargesPaid = feeChargesPaid;
this.feeChargesWrittenOff = feeChargesWrittenOff;
this.feeChargesWaived = feeChargesWaived;
this.penaltyCharges = penaltyCharges;
this.penaltyChargesPaid = penaltyChargesPaid;
this.penaltyChargesWrittenOff = penaltyChargesWrittenOff;
this.penaltyChargesWaived = penaltyChargesWaived;
this.totalPaidInAdvance = totalPaidInAdvance;
this.totalPaidLate = totalPaidLate;
this.obligationsMet = obligationsMet;
this.obligationsMetOnDate = obligationsMetOnDate;
this.createdOnDate = createdOnDate;
this.createdByUser = createdByUser;
this.lastModifiedByUser = lastModifiedByUser;
this.lastModifiedOnDate = lastModifiedOnDate;
}
/**
* @return an instance of the LoanRepaymentScheduleHistory class
**/
public static LoanRepaymentScheduleHistory instance(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal principalCompleted, final BigDecimal principalWrittenOff, final BigDecimal interestCharged,
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
final BigDecimal feeChargesCharged, final BigDecimal feeChargesPaid, final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesWaived, final BigDecimal penaltyCharges, final BigDecimal penaltyChargesPaid,
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesWaived,
final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
final Date obligationsMetOnDate, final Date createdOnDate, final AppUser createdByUser,
final AppUser lastModifiedByUser, final Date lastModifiedOnDate) {
return new LoanRepaymentScheduleHistory(loan, loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal,
principalCompleted, principalWrittenOff, interestCharged, interestPaid, interestWaived, interestWrittenOff,
feeChargesCharged, feeChargesPaid, feeChargesWrittenOff, feeChargesWaived, penaltyCharges, penaltyChargesPaid,
penaltyChargesWrittenOff, penaltyChargesWaived, totalPaidInAdvance, totalPaidLate, obligationsMet,
obligationsMetOnDate, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate);
}
@SuppressWarnings("unused")
public static void create(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal principalCompleted, final BigDecimal principalWrittenOff, final BigDecimal interestCharged,
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
final BigDecimal feeChargesCharged, final BigDecimal feeChargesPaid, final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesWaived, final BigDecimal penaltyCharges, final BigDecimal penaltyChargesPaid,
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesWaived,
final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
final Date obligationsMetOnDate, final Date createdOnDate, final AppUser createdByUser,
final AppUser lastModifiedByUser, final Date lastModifiedOnDate) {
new LoanRepaymentScheduleHistory(loan, loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal,
principalCompleted, principalWrittenOff, interestCharged, interestPaid, interestWaived, interestWrittenOff,
feeChargesCharged, feeChargesPaid, feeChargesWrittenOff, feeChargesWaived, penaltyCharges, penaltyChargesPaid,
penaltyChargesWrittenOff, penaltyChargesWaived, totalPaidInAdvance, totalPaidLate, obligationsMet,
obligationsMetOnDate, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate);
}
}
@@ -14,13 +14,14 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
public final class LoanRescheduleModel {
private final Collection<LoanRescheduleModelRepaymentPeriod> periods;
private final Collection<LoanRepaymentScheduleHistory> oldPeriods;
private final ApplicationCurrency applicationCurrency;
private final int loanTermInDays;
private final Collection<LoanRescheduleModelRepaymentPeriod> periods;
private final Collection<LoanRepaymentScheduleHistory> oldPeriods;
private final ApplicationCurrency applicationCurrency;
private final int loanTermInDays;
private final Money totalPrincipalDisbursed;
private final BigDecimal totalPrincipalExpected;
private final BigDecimal totalPrincipalPaid;
@@ -29,14 +30,14 @@ public final class LoanRescheduleModel {
private final BigDecimal totalPenaltyChargesCharged;
private final BigDecimal totalRepaymentExpected;
private final BigDecimal totalOutstanding;
public LoanRescheduleModel(final Collection<LoanRescheduleModelRepaymentPeriod> periods, final Collection<LoanRepaymentScheduleHistory> oldPeriods,
final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed,
final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged,
final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected,
final BigDecimal totalOutstanding) {
this.periods = periods;
this.oldPeriods = oldPeriods;
private LoanRescheduleModel(final Collection<LoanRescheduleModelRepaymentPeriod> periods,
final Collection<LoanRepaymentScheduleHistory> oldPeriods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
this.periods = periods;
this.oldPeriods = oldPeriods;
this.applicationCurrency = applicationCurrency;
this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = principalDisbursed;
@@ -48,25 +49,36 @@ public final class LoanRescheduleModel {
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalOutstanding = totalOutstanding;
}
public static LoanRescheduleModel instance(final Collection<LoanRescheduleModelRepaymentPeriod> periods, final Collection<LoanRepaymentScheduleHistory> oldPeriods,
final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed,
final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged,
final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected,
final BigDecimal totalOutstanding) {
return new LoanRescheduleModel(periods, oldPeriods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged,
public static LoanRescheduleModel instance(final Collection<LoanRescheduleModelRepaymentPeriod> periods,
final Collection<LoanRepaymentScheduleHistory> oldPeriods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
return new LoanRescheduleModel(periods, oldPeriods, applicationCurrency, loanTermInDays, principalDisbursed,
totalPrincipalExpected, totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstanding);
}
public static LoanRescheduleModel createWithSchedulehistory(LoanRescheduleModel loanRescheduleModel,
final Collection<LoanRepaymentScheduleHistory> oldPeriods) {
return new LoanRescheduleModel(loanRescheduleModel.periods, oldPeriods, loanRescheduleModel.applicationCurrency,
loanRescheduleModel.loanTermInDays, loanRescheduleModel.totalPrincipalDisbursed,
loanRescheduleModel.totalPrincipalExpected, loanRescheduleModel.totalPrincipalPaid,
loanRescheduleModel.totalInterestCharged, loanRescheduleModel.totalFeeChargesCharged,
loanRescheduleModel.totalPenaltyChargesCharged, loanRescheduleModel.totalRepaymentExpected,
loanRescheduleModel.totalOutstanding);
}
public LoanScheduleData toData() {
final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal();
final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf();
final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf);
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<LoanSchedulePeriodData>();
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
for (final LoanRescheduleModalPeriod modelPeriod : this.periods) {
periodsData.add(modelPeriod.toData());
}
@@ -82,12 +94,12 @@ public final class LoanRescheduleModel {
this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment,
totalPaidInAdvance, totalPaidLate, this.totalOutstanding);
}
public Collection<LoanRescheduleModelRepaymentPeriod> getPeriods() {
return this.periods;
}
public Collection<LoanRepaymentScheduleHistory> getOldPeriods() {
return this.oldPeriods;
return this.oldPeriods;
}
}
@@ -19,6 +19,8 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
@@ -31,52 +33,54 @@ import org.springframework.stereotype.Service;
@Service
public class LoanReschedulePreviewPlatformServiceImpl implements LoanReschedulePreviewPlatformService {
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
@Autowired
public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final ConfigurationDomainService configurationDomainService,
final HolidayRepository holidayRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository) {
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
}
@Override
public LoanRescheduleModel previewLoanReschedule(Long requestId) {
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId);
if(loanRescheduleRequest == null) {
throw new LoanRescheduleRequestNotFoundException(requestId);
}
Loan loan = loanRescheduleRequest.getLoan();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
@Autowired
public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService) {
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
}
@Override
public LoanRescheduleModel previewLoanReschedule(Long requestId) {
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId);
if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(requestId); }
Loan loan = loanRescheduleRequest.getLoan();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mathContext = new MathContext(8, roundingMode);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
return loanRescheduleModel;
}
List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive(
loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
oldPeriods);
return loanRescheduleModelWithOldPeriods;
}
}
@@ -8,14 +8,11 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
@@ -37,35 +34,25 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRepaymentScheduleHistoryRepository;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,404 +63,396 @@ import org.springframework.transaction.annotation.Transactional;
@Service
public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanRescheduleRequestWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class);
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final PlatformSecurityContext platformSecurityContext;
private final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator;
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
/**
* LoanRescheduleRequestWritePlatformServiceImpl constructor
*
* @return void
**/
@Autowired
public LoanRescheduleRequestWritePlatformServiceImpl(LoanRepositoryWrapper loanRepositoryWrapper,
CodeValueRepositoryWrapper codeValueRepositoryWrapper,
PlatformSecurityContext platformSecurityContext,
LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
LoanRescheduleRequestRepository loanRescheduleRequestRepository,
ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
ConfigurationDomainService configurationDomainService,
HolidayRepository holidayRepository,
WorkingDaysRepositoryWrapper workingDaysRepository,
LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository) {
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
this.platformSecurityContext = platformSecurityContext;
this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator;
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
}
/**
* create a new instance of the LoanRescheduleRequest object from the JsonCommand object
* and persist
*
* @return CommandProcessingResult object
**/
@Override
@Transactional
public CommandProcessingResult create(JsonCommand jsonCommand) {
try {
// get the loan id from the JsonCommand object
final Long loanId = jsonCommand.longValueOfParameterNamed(
RescheduleLoansApiConstants.loanIdParamName);
// use the loan id to get a Loan entity object
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
// validate the request in the JsonCommand object passed as parameter
this.loanRescheduleRequestDataValidator.validateForCreateAction(jsonCommand, loan);
// get the reschedule reason code value id from the JsonCommand object
final Long rescheduleReasonId = jsonCommand.longValueOfParameterNamed(
RescheduleLoansApiConstants.rescheduleReasonIdParamName);
// use the reschedule reason code value id to get a CodeValue entity object
final CodeValue rescheduleReasonCodeValue = this.codeValueRepositoryWrapper
.findOneWithNotFoundDetection(rescheduleReasonId);
// get the grace on principal integer value from the JsonCommand object
final Integer graceOnPrincipal = jsonCommand.integerValueOfParameterNamed(
RescheduleLoansApiConstants.graceOnPrincipalParamName);
// get the grace on interest integer value from the JsonCommand object
final Integer graceOnInterest = jsonCommand.integerValueOfParameterNamed(
RescheduleLoansApiConstants.graceOnInterestParamName);
// get the extra terms to be added at the end of the new schedule from the JsonCommand object
final Integer extraTerms = jsonCommand.integerValueOfParameterNamed(
RescheduleLoansApiConstants.extraTermsParamName);
// get the new interest rate that would be applied to the new loan schedule
final BigDecimal interestRate = jsonCommand.bigDecimalValueOfParameterNamed(
RescheduleLoansApiConstants.newInterestRateParamName);
// get the reschedule reason comment text from the JsonCommand object
final String rescheduleReasonComment = jsonCommand.stringValueOfParameterNamed(
RescheduleLoansApiConstants.rescheduleReasonCommentParamName);
// get the recalculate interest option
final Boolean recalculateInterest = jsonCommand.booleanObjectValueOfParameterNamed(
RescheduleLoansApiConstants.recalculateInterestParamName);
// initialize set the value to null
Date submittedOnDate = null;
// check if the parameter is in the JsonCommand object
if(jsonCommand.hasParameter(RescheduleLoansApiConstants.submittedOnDateParamName)) {
// create a LocalDate object from the "submittedOnDate" Date string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(
RescheduleLoansApiConstants.submittedOnDateParamName);
if(localDate != null) {
// update the value of the "submittedOnDate" variable
submittedOnDate = localDate.toDate();
}
}
// initially set the value to null
Date rescheduleFromDate = null;
// start point of the rescheduling exercise
Integer rescheduleFromInstallment = null;
// initially set the value to null
Date adjustedDueDate = null;
// check if the parameter is in the JsonCommand object
if(jsonCommand.hasParameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)) {
// create a LocalDate object from the "rescheduleFromDate" Date string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(
RescheduleLoansApiConstants.rescheduleFromDateParamName);
if(localDate != null) {
// get installment by due date
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(localDate);
rescheduleFromInstallment = installment.getInstallmentNumber();
// update the value of the "rescheduleFromDate" variable
rescheduleFromDate = localDate.toDate();
}
}
if(jsonCommand.hasParameter(RescheduleLoansApiConstants.adjustedDueDateParamName)) {
// create a LocalDate object from the "adjustedDueDate" Date string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(
RescheduleLoansApiConstants.adjustedDueDateParamName);
if(localDate != null) {
// update the value of the "adjustedDueDate"variable
adjustedDueDate = localDate.toDate();
}
}
final LoanRescheduleRequest loanRescheduleRequest = LoanRescheduleRequest.instance(
loan, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, graceOnPrincipal,
graceOnInterest, rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue,
rescheduleReasonComment, submittedOnDate, this.platformSecurityContext.authenticatedUser(),
null, null, null, null);
// create a new entry in the m_loan_reschedule_request table
this.loanRescheduleRequestRepository.save(loanRescheduleRequest);
return new CommandProcessingResultBuilder()
.withCommandId(jsonCommand.commandId())
.withEntityId(loanRescheduleRequest.getId())
.withLoanId(loan.getId())
.build();
}
catch(final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleCreateActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
@Override
@Transactional
public CommandProcessingResult approve(JsonCommand jsonCommand) {
try {
final Long loanRescheduleRequestId = jsonCommand.entityId();
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(loanRescheduleRequestId);
if(loanRescheduleRequest == null) {
throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId);
}
// validate the request in the JsonCommand object passed as parameter
this.loanRescheduleRequestDataValidator.validateForApproveAction(jsonCommand, loanRescheduleRequest);
final AppUser appUser = this.platformSecurityContext.authenticatedUser();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
LocalDate approvedOnDate = jsonCommand.localDateValueOfParameterNamed("approvedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat
.forPattern(jsonCommand.dateFormat())
.withLocale(jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat());
changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter));
changes.put("approvedByUserId", appUser.getId());
if(!changes.isEmpty()) {
Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mathContext = new MathContext(8, roundingMode);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
for(LoanRescheduleModelRepaymentPeriod period : periods) {
if(period.isNew()) {
LoanRepaymentScheduleInstallment repaymentScheduleInstallment = new LoanRepaymentScheduleInstallment(
loan, period.periodNumber(), period.periodFromDate(), period.periodDueDate(), period.principalDue(),
period.interestDue(), BigDecimal.ZERO, BigDecimal.ZERO, false);
repaymentScheduleInstallments.add(repaymentScheduleInstallment);
}
else {
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
if(repaymentScheduleInstallment.getInstallmentNumber().equals(period.oldPeriodNumber())) {
repaymentScheduleInstallment.updateInstallmentNumber(period.periodNumber());
repaymentScheduleInstallment.updateFromDate(period.periodFromDate());
repaymentScheduleInstallment.updateDueDate(period.periodDueDate());
repaymentScheduleInstallment.updatePrincipal(period.principalDue());
repaymentScheduleInstallment.updateInterestCharged(period.interestDue());
if(Money.of(currency, period.principalDue()).isZero() &&
Money.of(currency, period.interestDue()).isZero() &&
repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isZero() &&
repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isZero() &&
repaymentScheduleInstallment.isNotFullyPaidOff()) {
repaymentScheduleInstallment.updateObligationMet(true);
repaymentScheduleInstallment.updateObligationMetOnDate(new LocalDate());
}
break;
}
}
}
}
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = loanRescheduleModel.getOldPeriods();
for(LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
}
loan.updateRescheduledByUser(appUser);
loan.updateRescheduledOnDate(new LocalDate());
// update the Loan summary
loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(),
true);
// update the total number of schedule repayments
loan.updateNumberOfRepayments(periods.size());
// update the loan term frequency (loan term frequency = number of repayments)
loan.updateTermFrequency(periods.size());
// update the status of the request
loanRescheduleRequest.approve(appUser, approvedOnDate);
}
return new CommandProcessingResultBuilder()
.withCommandId(jsonCommand.commandId())
.withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId())
.with(changes)
.build();
}
catch(final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleApproveActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
private final static Logger logger = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class);
@Override
@Transactional
public CommandProcessingResult reject(JsonCommand jsonCommand) {
try {
final Long loanRescheduleRequestId = jsonCommand.entityId();
final LoanRescheduleRequest loanRescheduleRequest = loanRescheduleRequestRepository
.findOne(loanRescheduleRequestId);
if(loanRescheduleRequest == null) {
throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId);
}
// validate the request in the JsonCommand object passed as parameter
this.loanRescheduleRequestDataValidator.validateForRejectAction(jsonCommand, loanRescheduleRequest);
final AppUser appUser = this.platformSecurityContext.authenticatedUser();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
LocalDate rejectedOnDate = jsonCommand.localDateValueOfParameterNamed("rejectedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat
.forPattern(jsonCommand.dateFormat())
.withLocale(jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat());
changes.put("rejectedOnDate", rejectedOnDate.toString(dateTimeFormatter));
changes.put("rejectedByUserId", appUser.getId());
if(!changes.isEmpty()) {
loanRescheduleRequest.reject(appUser, rejectedOnDate);
}
return new CommandProcessingResultBuilder()
.withCommandId(jsonCommand.commandId())
.withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId())
.with(changes)
.build();
}
catch(final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleRejectActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
/**
* handles the data integrity violation exception caught in the create method
*
* @param jsonCommand JSON command object
* @param dve data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleCreateActionDataIntegrityViolation(final JsonCommand jsonCommand,
final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final PlatformSecurityContext platformSecurityContext;
private final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator;
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
/**
* LoanRescheduleRequestWritePlatformServiceImpl constructor
*
* @return void
**/
@Autowired
public LoanRescheduleRequestWritePlatformServiceImpl(LoanRepositoryWrapper loanRepositoryWrapper,
CodeValueRepositoryWrapper codeValueRepositoryWrapper, PlatformSecurityContext platformSecurityContext,
LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
LoanRescheduleRequestRepository loanRescheduleRequestRepository,
ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, ConfigurationDomainService configurationDomainService,
HolidayRepository holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository,
LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService) {
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
this.platformSecurityContext = platformSecurityContext;
this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator;
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
}
/**
* create a new instance of the LoanRescheduleRequest object from the
* JsonCommand object and persist
*
* @return CommandProcessingResult object
**/
@Override
@Transactional
public CommandProcessingResult create(JsonCommand jsonCommand) {
try {
// get the loan id from the JsonCommand object
final Long loanId = jsonCommand.longValueOfParameterNamed(RescheduleLoansApiConstants.loanIdParamName);
// use the loan id to get a Loan entity object
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
// validate the request in the JsonCommand object passed as
// parameter
this.loanRescheduleRequestDataValidator.validateForCreateAction(jsonCommand, loan);
// get the reschedule reason code value id from the JsonCommand
// object
final Long rescheduleReasonId = jsonCommand.longValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName);
// use the reschedule reason code value id to get a CodeValue entity
// object
final CodeValue rescheduleReasonCodeValue = this.codeValueRepositoryWrapper.findOneWithNotFoundDetection(rescheduleReasonId);
// get the grace on principal integer value from the JsonCommand
// object
final Integer graceOnPrincipal = jsonCommand
.integerValueOfParameterNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName);
// get the grace on interest integer value from the JsonCommand
// object
final Integer graceOnInterest = jsonCommand.integerValueOfParameterNamed(RescheduleLoansApiConstants.graceOnInterestParamName);
// get the extra terms to be added at the end of the new schedule
// from the JsonCommand object
final Integer extraTerms = jsonCommand.integerValueOfParameterNamed(RescheduleLoansApiConstants.extraTermsParamName);
// get the new interest rate that would be applied to the new loan
// schedule
final BigDecimal interestRate = jsonCommand
.bigDecimalValueOfParameterNamed(RescheduleLoansApiConstants.newInterestRateParamName);
// get the reschedule reason comment text from the JsonCommand
// object
final String rescheduleReasonComment = jsonCommand
.stringValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName);
// get the recalculate interest option
final Boolean recalculateInterest = jsonCommand
.booleanObjectValueOfParameterNamed(RescheduleLoansApiConstants.recalculateInterestParamName);
// initialize set the value to null
Date submittedOnDate = null;
// check if the parameter is in the JsonCommand object
if (jsonCommand.hasParameter(RescheduleLoansApiConstants.submittedOnDateParamName)) {
// create a LocalDate object from the "submittedOnDate" Date
// string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.submittedOnDateParamName);
if (localDate != null) {
// update the value of the "submittedOnDate" variable
submittedOnDate = localDate.toDate();
}
}
// initially set the value to null
Date rescheduleFromDate = null;
// start point of the rescheduling exercise
Integer rescheduleFromInstallment = null;
// initially set the value to null
Date adjustedDueDate = null;
// check if the parameter is in the JsonCommand object
if (jsonCommand.hasParameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)) {
// create a LocalDate object from the "rescheduleFromDate" Date
// string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName);
if (localDate != null) {
// get installment by due date
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(localDate);
rescheduleFromInstallment = installment.getInstallmentNumber();
// update the value of the "rescheduleFromDate" variable
rescheduleFromDate = localDate.toDate();
}
}
if (jsonCommand.hasParameter(RescheduleLoansApiConstants.adjustedDueDateParamName)) {
// create a LocalDate object from the "adjustedDueDate" Date
// string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.adjustedDueDateParamName);
if (localDate != null) {
// update the value of the "adjustedDueDate"variable
adjustedDueDate = localDate.toDate();
}
}
final LoanRescheduleRequest loanRescheduleRequest = LoanRescheduleRequest.instance(loan,
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue,
rescheduleReasonComment, submittedOnDate, this.platformSecurityContext.authenticatedUser(), null, null, null, null);
// create a new entry in the m_loan_reschedule_request table
this.loanRescheduleRequestRepository.save(loanRescheduleRequest);
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequest.getId())
.withLoanId(loan.getId()).build();
}
catch (final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleCreateActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
@Override
@Transactional
public CommandProcessingResult approve(JsonCommand jsonCommand) {
try {
final Long loanRescheduleRequestId = jsonCommand.entityId();
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(loanRescheduleRequestId);
if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId); }
// validate the request in the JsonCommand object passed as
// parameter
this.loanRescheduleRequestDataValidator.validateForApproveAction(jsonCommand, loanRescheduleRequest);
final AppUser appUser = this.platformSecurityContext.authenticatedUser();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
LocalDate approvedOnDate = jsonCommand.localDateValueOfParameterNamed("approvedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(
jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat());
changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter));
changes.put("approvedByUserId", appUser.getId());
if (!changes.isEmpty()) {
Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mathContext = new MathContext(8, roundingMode);
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
for (LoanRescheduleModelRepaymentPeriod period : periods) {
if (period.isNew()) {
LoanRepaymentScheduleInstallment repaymentScheduleInstallment = new LoanRepaymentScheduleInstallment(loan,
period.periodNumber(), period.periodFromDate(), period.periodDueDate(), period.principalDue(),
period.interestDue(), BigDecimal.ZERO, BigDecimal.ZERO, false);
repaymentScheduleInstallments.add(repaymentScheduleInstallment);
}
else {
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
if (repaymentScheduleInstallment.getInstallmentNumber().equals(period.oldPeriodNumber())) {
repaymentScheduleInstallment.updateInstallmentNumber(period.periodNumber());
repaymentScheduleInstallment.updateFromDate(period.periodFromDate());
repaymentScheduleInstallment.updateDueDate(period.periodDueDate());
repaymentScheduleInstallment.updatePrincipal(period.principalDue());
repaymentScheduleInstallment.updateInterestCharged(period.interestDue());
if (Money.of(currency, period.principalDue()).isZero() && Money.of(currency, period.interestDue()).isZero()
&& repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isZero()
&& repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isZero()
&& repaymentScheduleInstallment.isNotFullyPaidOff()) {
repaymentScheduleInstallment.updateObligationMet(true);
repaymentScheduleInstallment.updateObligationMetOnDate(new LocalDate());
}
break;
}
}
}
}
for (LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
}
loan.updateRescheduledByUser(appUser);
loan.updateRescheduledOnDate(new LocalDate());
// update the Loan summary
loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true);
// update the total number of schedule repayments
loan.updateNumberOfRepayments(periods.size());
// update the loan term frequency (loan term frequency = number
// of repayments)
loan.updateTermFrequency(periods.size());
// update the status of the request
loanRescheduleRequest.approve(appUser, approvedOnDate);
}
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build();
}
catch (final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleApproveActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
@Override
@Transactional
public CommandProcessingResult reject(JsonCommand jsonCommand) {
try {
final Long loanRescheduleRequestId = jsonCommand.entityId();
final LoanRescheduleRequest loanRescheduleRequest = loanRescheduleRequestRepository.findOne(loanRescheduleRequestId);
if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId); }
// validate the request in the JsonCommand object passed as
// parameter
this.loanRescheduleRequestDataValidator.validateForRejectAction(jsonCommand, loanRescheduleRequest);
final AppUser appUser = this.platformSecurityContext.authenticatedUser();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
LocalDate rejectedOnDate = jsonCommand.localDateValueOfParameterNamed("rejectedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(
jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat());
changes.put("rejectedOnDate", rejectedOnDate.toString(dateTimeFormatter));
changes.put("rejectedByUserId", appUser.getId());
if (!changes.isEmpty()) {
loanRescheduleRequest.reject(appUser, rejectedOnDate);
}
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build();
}
catch (final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleRejectActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
/**
* handles the data integrity violation exception caught in the create
* method
*
* @param jsonCommand
* JSON command object
* @param dve
* data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleCreateActionDataIntegrityViolation(final JsonCommand jsonCommand, final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
/**
* handles the data integrity violation exception caught in the reject method
*
* @param jsonCommand JSON command object
* @param dve data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleRejectActionDataIntegrityViolation(final JsonCommand jsonCommand,
final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
}
/**
* handles the data integrity violation exception caught in the reject
* method
*
* @param jsonCommand
* JSON command object
* @param dve
* data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleRejectActionDataIntegrityViolation(final JsonCommand jsonCommand, final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
/**
* handles the data integrity violation exception caught in the approve method
*
* @param jsonCommand JSON command object
* @param dve data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleApproveActionDataIntegrityViolation(final JsonCommand jsonCommand,
final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
}
/**
* handles the data integrity violation exception caught in the approve
* method
*
* @param jsonCommand
* JSON command object
* @param dve
* data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleApproveActionDataIntegrityViolation(final JsonCommand jsonCommand, final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
}
}
@@ -111,7 +111,10 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFound
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
@@ -168,6 +171,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final AccountTransferRepository accountTransferRepository;
private final CalendarRepository calendarRepository;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -188,7 +192,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService,
final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository,
final CalendarRepository calendarRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler;
@@ -217,6 +222,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransferRepository = accountTransferRepository;
this.calendarRepository = calendarRepository;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -316,7 +322,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
@@ -510,6 +519,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
final Set<LoanCharge> loanCharges = loan.charges();
@@ -1130,9 +1142,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
this.loanChargeRepository.save(loanCharge);
CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
}
final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge, existingTransactionIds,
existingReversedTransactionIds);
existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance);
/**
* we want to apply charge transactions only for those loans charges
@@ -1161,7 +1195,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
// we post Journal entries only for loans in active status
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
@@ -1246,9 +1283,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
}
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber);
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, holidays, workingDays, isHolidayEnabled,
loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
this.loanTransactionRepository.save(waiveTransaction);
saveLoanWithDataIntegrityViolationChecks(loan);
@@ -2103,7 +2162,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan);
return new CommandProcessingResultBuilder() //
@@ -2243,4 +2305,29 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
private void createLoanScheduleArchive(final Loan loan, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) {
LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
List<LoanRepaymentScheduleInstallment> installments = retrieveRepaymentScheduleFromModel(loanScheduleModel);
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, null);
}
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
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());
installments.add(installment);
}
}
return installments;
}
}