diff --git a/mifosng-db/migrations/core_db/V199__removed_extra_columns_from_schedule_history.sql b/mifosng-db/migrations/core_db/V199__removed_extra_columns_from_schedule_history.sql new file mode 100755 index 000000000..b7470df68 --- /dev/null +++ b/mifosng-db/migrations/core_db/V199__removed_extra_columns_from_schedule_history.sql @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 1ddb083c7..fefed4eb7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -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")) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index fb76aa31c..316cc8976 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -115,6 +115,7 @@ public class LoanAccountData { private final CalendarData meeting; private final Collection notes; private final Collection disbursementDetails; + private final LoanScheduleData originalSchedule; // template private final Collection productOptions; private final Collection 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 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 memberVariations = null; final LoanProductData product = null; final Collection 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 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 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 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(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 40d1b81fe..b3a967a4b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -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 { @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 { } public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge, final List existingTransactionIds, - final List existingReversedTransactionIds) { + final List existingReversedTransactionIds, final List 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 { 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), @@ -825,7 +834,9 @@ public class Loan extends AbstractPersistable { public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, - final Integer loanInstallmentNumber) { + final Integer loanInstallmentNumber, final List 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 { 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 { 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 { final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + final LoanScheduleModel loanSchedule = regenerateScheduleModel(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + + updateLoanSchedule(loanSchedule); + Set 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 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 { final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays); - - updateLoanSchedule(loanSchedule); - Set 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 { 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 { return this.isNpa; } - /** + /** * @return List of loan repayments schedule objects **/ public List 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 getDisbursmentData() { - Iterator iterator = this.getDisbursementDetails().iterator(); - List disbursementData = new ArrayList(); - - 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 = getDisbursmentData(); - - final List loanVariationTermsData = new ArrayList(); + + /** + * @param loan + * the Loan object + * @return loan disbursement data + **/ + public List getDisbursmentData() { + Iterator iterator = this.getDisbursementDetails().iterator(); + List disbursementData = new ArrayList(); + + 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 = getDisbursmentData(); + + final List loanVariationTermsData = new ArrayList(); 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; - } - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java index d29685562..92803aae7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java @@ -75,6 +75,27 @@ public class LoanScheduleData { this.totalOutstanding = totalOutstanding; } + public LoanScheduleData(final CurrencyData currency, final Collection 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 getPeriods() { return this.periods; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 6576f53c8..4bdca5d96 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -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 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 loanRepaymentScheduleHistoryList = createLoanScheduleArchive(loanRescheduleRequest); - - // get the initial list of repayment installments - List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); - - // sort list by installment number in ASC order - Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator); - - final Collection 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); - } - + public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, + final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final List 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 loanRepaymentScheduleHistoryList = null; + + // get the initial list of repayment installments + List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); + + // sort list by installment number in ASC order + Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator); + + final Collection 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 createLoanScheduleArchive(final LoanRescheduleRequest loanRescheduleRequest) { - final Loan loan = loanRescheduleRequest.getLoan(); - final List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); - final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); - final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); - List 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 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; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistory.java new file mode 100644 index 000000000..5feb68751 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistory.java @@ -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 { + + @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); + + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRepaymentScheduleHistoryRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistoryRepository.java similarity index 88% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRepaymentScheduleHistoryRepository.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistoryRepository.java index a239a16ae..af68d6867 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRepaymentScheduleHistoryRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleHistoryRepository.java @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java new file mode 100755 index 000000000..b4d833e3d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java @@ -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); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java new file mode 100755 index 000000000..9d433d106 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java @@ -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) { + + 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 { + + private final CurrencyData currency; + private final DisbursementData disbursement; + private final BigDecimal totalFeeChargesDueAtDisbursement; + private final Collection disbursementData; + private LocalDate lastDueDate; + private BigDecimal outstandingLoanPrincipalBalance; + + public LoanScheduleArchiveResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, + Collection 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 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()); + } + + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformService.java new file mode 100755 index 000000000..41f71fca0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformService.java @@ -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 createLoanScheduleArchive( + final List repaymentScheduleInstallments, final Loan loan, + final LoanRescheduleRequest loanRescheduleRequest); + + void createAndSaveLoanScheduleArchive(List repaymentScheduleInstallments, Loan loan, + LoanRescheduleRequest loanRescheduleRequest); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java new file mode 100755 index 000000000..e51b5ed57 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryWritePlatformServiceImpl.java @@ -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 createLoanScheduleArchive( + List repaymentScheduleInstallments, Loan loan, LoanRescheduleRequest loanRescheduleRequest) { + Integer version = this.loanScheduleHistoryReadPlatformService.fetchCurrentVersionNumber(loan.getId()) + 1; + final MonetaryCurrency currency = loan.getCurrency(); + final List 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 repaymentScheduleInstallments, Loan loan, + LoanRescheduleRequest loanRescheduleRequest) { + List loanRepaymentScheduleHistoryList = createLoanScheduleArchive(repaymentScheduleInstallments, + loan, loanRescheduleRequest); + this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistoryList); + + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRepaymentScheduleHistory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRepaymentScheduleHistory.java deleted file mode 100644 index 28d33cc9a..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRepaymentScheduleHistory.java +++ /dev/null @@ -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 { - - @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); - } -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java index 4834ef4c4..dd4f608a1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java @@ -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 periods; - private final Collection oldPeriods; - private final ApplicationCurrency applicationCurrency; - private final int loanTermInDays; + private final Collection periods; + private final Collection 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 periods, final Collection 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 periods, + final Collection 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 periods, final Collection 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 periods, + final Collection 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 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 periodsData = new ArrayList(); + final Collection 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 getPeriods() { return this.periods; } - + public Collection getOldPeriods() { - return this.oldPeriods; + return this.oldPeriods; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index 8b1ce89a0..f5fb48881 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -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 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 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 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; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index a68571cb5..94c10bf27 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -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 changes = new LinkedHashMap(); - - 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 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 periods = loanRescheduleModel.getPeriods(); - List 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 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 changes = new LinkedHashMap(); - - 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 changes = new LinkedHashMap(); + + 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 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 loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService + .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); + + LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, + loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); + + final Collection periods = loanRescheduleModel.getPeriods(); + List 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 changes = new LinkedHashMap(); + + 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."); - } + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 3153e3d98..e28e38414 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -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 loanCharges = loan.charges(); @@ -1130,9 +1142,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); this.loanChargeRepository.save(loanCharge); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + List 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 existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + List 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 holidays, + final WorkingDays workingDays) { + LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + List installments = retrieveRepaymentScheduleFromModel(loanScheduleModel); + this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, null); + + } + + private List retrieveRepaymentScheduleFromModel(LoanScheduleModel model) { + final List 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; + } + } \ No newline at end of file