FINERACT-202 : enabling the option for multi reshedule

This commit is contained in:
venkatconflux
2016-08-01 17:07:34 +05:30
parent ada9ced9a5
commit fe6171237e
31 changed files with 1496 additions and 1310 deletions
+59 -31
View File
@@ -11877,11 +11877,11 @@ Content-Type: application/json Request Body:
{
"loanId": 1,
"graceOnPrincipal": 2,
"rescheduleFromDate": "04 December 2014",
"adjustedDueDate": "20 December 2014",
"rescheduleFromDate": "25 December 2013",
"adjustedDueDate": "31 December 2013",
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"submittedOnDate": "04 September 2014",
"submittedOnDate": "04 September 2013",
"rescheduleReasonComment" : "Client has gone AWOL",
"rescheduleReasonId": 1
}
@@ -11907,52 +11907,80 @@ GET https://DomainName/api/v1/rescheduleloans/{requestId}
</code>
<code class="method-response">
{
"id": 2,
"loanId": 17,
"clientId": 4,
"clientName": "Fernando Michael Torres",
"loanAccountNumber": "000000017",
"id": 1,
"loanId": 1,
"clientId": 1,
"clientName": "test test",
"loanAccountNumber": "000000001",
"statusEnum": {
"id": 500,
"code": "loanStatusType.rejected",
"value": "Rejected",
"pendingApproval": false,
"id": 100,
"code": "loanStatusType.submitted.and.pending.approval",
"value": "Submitted and pending approval",
"pendingApproval": true,
"approved": false,
"rejected": true
"rejected": false
},
"rescheduleFromInstallment": 5,
"graceOnPrincipal": 3,
"rescheduleFromDate": [
2013,
12,
6
25
],
"recalculateInterest": false,
"rescheduleReasonCodeValue": {
"id": 239,
"name": "client cannot pay on time"
"id": 1,
"name": "Passport",
"isActive": false
},
"timeline": {
"submittedOnDate": [
2014,
8,
2013,
9,
4
],
"submittedByUsername": "mifos",
"submittedByFirstname": "App",
"submittedByLastname": "Administrator",
"rejectedOnDate": [
2014,
8,
5
],
"rejectedByUsername": "mifos",
"rejectedByFirstname": "App",
"rejectedByLastname": "Administrator"
"submittedByLastname": "Administrator"
},
"rescheduleReasonComment": ""
}
</code>
"rescheduleReasonComment": "Client has gone AWOL",
"loanTermVariationsData": [
{
"id": 13,
"termType": {
"id": 4,
"code": "loanTermType.dueDate",
"value": "dueDate"
},
"termVariationApplicableFrom": [
2013,
12,
25
],
"dateValue": [
2013,
12,
31
],
"isSpecificToInstallment": false
},
{
"id": 14,
"termType": {
"id": 8,
"code": "loanTermType.graceOnPrincipal",
"value": "graceOnPrincipal"
},
"termVariationApplicableFrom": [
2013,
12,
25
],
"decimalValue": 2,
"isSpecificToInstallment": false
}
]
}
</code>
</div>
</div>
<a id="loan_reschedule_preview_retrieve" name="loan_reschedule_preview_retrieve" class="old-syle-anchor">&nbsp;</a>
@@ -211,7 +211,7 @@ public class LoanRescheduleRequestTest {
final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
assertEquals("NUMBER OF REPAYMENTS SHOULD BE 16, NOT 12", "16", numberOfRepayments.toString());
assertEquals("NUMBER OF REPAYMENTS SHOULD BE 16, NOT 12", "12", numberOfRepayments.toString());
assertEquals("TOTAL EXPECTED REPAYMENT MUST BE EQUAL TO 118000.0", "118000.0", totalExpectedRepayment.toString());
System.out.println("Successfully approved loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
@@ -29,7 +29,7 @@ public class LoanTermVariationsData implements Comparable<LoanTermVariationsData
@SuppressWarnings("unused")
private final Long id;
private final EnumOptionData termType;
private final LocalDate termVariationApplicableFrom;
private LocalDate termVariationApplicableFrom;
private final BigDecimal decimalValue;
private final LocalDate dateValue;
private final boolean isSpecificToInstallment;
@@ -113,5 +113,9 @@ public class LoanTermVariationsData implements Comparable<LoanTermVariationsData
}
return comparsion;
}
public void setApplicableFromDate(final LocalDate applicableFromDate) {
this.termVariationApplicableFrom = applicableFromDate;
}
}
@@ -22,37 +22,27 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import org.joda.time.LocalDate;
public class LoanTermVariationsDataWrapper {
private final List<LoanTermVariationsData> exceptionData;
private final ListIterator<LoanTermVariationsData> iterator;
private ListIterator<LoanTermVariationsData> iterator;
private final List<LoanTermVariationsData> interestRateChanges;
private final List<LoanTermVariationsData> interestRateFromInstallment;
private final List<LoanTermVariationsData> dueDateVariation;
private final ListIterator<LoanTermVariationsData> dueDateIterator;
private ListIterator<LoanTermVariationsData> dueDateIterator;
public LoanTermVariationsDataWrapper(final List<LoanTermVariationsData> exceptionData) {
if (exceptionData == null) {
this.exceptionData = new ArrayList<>(1);
} else {
this.exceptionData = exceptionData;
Collections.sort(this.exceptionData);
}
this.interestRateChanges = new ArrayList<>();
this.dueDateVariation = new ArrayList<>();
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) {
this.interestRateChanges.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isDueDateVariation()) {
this.dueDateVariation.add(loanTermVariationsData);
}
}
this.exceptionData.removeAll(this.interestRateChanges);
this.exceptionData.removeAll(this.dueDateVariation);
iterator = this.exceptionData.listIterator();
dueDateIterator = this.dueDateVariation.listIterator();
this.interestRateFromInstallment = new ArrayList<>();
deriveLoanTermVariations();
}
public boolean hasVariation(final LocalDate date) {
@@ -84,6 +74,10 @@ public class LoanTermVariationsDataWrapper {
public LoanTermVariationsData nextDueDateVariation() {
return this.dueDateIterator.next();
}
public LoanTermVariationsData previousDueDateVariation() {
return this.dueDateIterator.previous();
}
public List<LoanTermVariationsData> getInterestRateChanges() {
return this.interestRateChanges;
@@ -96,6 +90,23 @@ public class LoanTermVariationsDataWrapper {
public List<LoanTermVariationsData> getExceptionData() {
return this.exceptionData;
}
public void setExceptionData(final List<LoanTermVariationsData> exceptionData) {
clearTerms();
this.exceptionData.addAll(exceptionData);
deriveLoanTermVariations();
}
public void clearTerms() {
this.exceptionData.clear();
this.interestRateChanges.clear();
this.dueDateVariation.clear();
this.interestRateFromInstallment.clear();
}
public List<LoanTermVariationsData> getInterestRateFromInstallment() {
return this.interestRateFromInstallment;
}
public int adjustNumberOfRepayments() {
int repaymetsForAdjust = 0;
@@ -108,7 +119,7 @@ public class LoanTermVariationsDataWrapper {
}
return repaymetsForAdjust;
}
public LoanTermVariationsData fetchLoanTermDueDateVariationsData(final LocalDate onDate) {
LoanTermVariationsData data = null;
for (LoanTermVariationsData termVariationsData : this.dueDateVariation) {
@@ -125,4 +136,39 @@ public class LoanTermVariationsDataWrapper {
return hasNext(date, iterator);
}
public void updateLoanTermVariationsData(final List<LoanTermVariationsData> exceptionData){
if(this.exceptionData != null && exceptionData != null && exceptionData.size() > 0){
this.exceptionData.addAll(exceptionData);
deriveLoanTermVariations();
}
}
private void deriveLoanTermVariations() {
Collections.sort(this.exceptionData);
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) {
this.interestRateChanges.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isDueDateVariation()) {
this.dueDateVariation.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isInterestRateFromInstallment()) {
this.interestRateFromInstallment.add(loanTermVariationsData);
}
}
Collections.sort(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateChanges);
this.exceptionData.removeAll(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateFromInstallment);
this.iterator = this.exceptionData.listIterator();
this.dueDateIterator = this.dueDateVariation.listIterator();
}
public void resetVariations(){
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
loanTermVariationsData.setProcessed(false);
}
this.iterator = this.exceptionData.listIterator();
this.dueDateIterator = this.dueDateVariation.listIterator();
}
}
@@ -2734,6 +2734,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus);
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, currentStatus);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate());
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
if (!statusEnum.hasStateOf(currentStatus)) {
this.loanStatus = statusEnum.getValue();
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
@@ -2750,6 +2752,7 @@ public class Loan extends AbstractPersistable<Long> {
}
}
boolean isEmiAmountChanged = this.loanTermVariations.size() > 0;
updateLoanToPreDisbursalState();
if (isScheduleRegenerateRequired || isDisbursedAmountChanged || isEmiAmountChanged
|| this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -2760,14 +2763,14 @@ public class Loan extends AbstractPersistable<Long> {
if (isDisbursedAmountChanged) {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
}else if(isPeriodicAccrualAccountingEnabledOnLoanProduct()){
for (final LoanRepaymentScheduleInstallment period : getRepaymentScheduleInstallments()) {
period.resetAccrualComponents();
}
}
actualChanges.put("actualDisbursementDate", "");
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
this.accruedTill = null;
reverseExistingTransactions();
updateLoanSummaryDerivedFields();
}
@@ -2776,14 +2779,22 @@ public class Loan extends AbstractPersistable<Long> {
}
private final void reverseExistingTransactions() {
Collection<LoanTransaction> retainTransactions = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
transaction.reverse();
if(transaction.getId() != null){
retainTransactions.add(transaction);
}
}
this.loanTransactions.retainAll(retainTransactions);
isTransactionsListDirty = true ;
}
private void updateLoanToPreDisbursalState() {
this.actualDisbursementDate = null;
this.accruedTill = null;
reverseExistingTransactions();
for (final LoanCharge charge : charges()) {
if (charge.isOverdueInstallmentCharge()) {
@@ -2796,13 +2807,11 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
currentInstallment.resetDerivedComponents();
}
final List<LoanTermVariations> removeTerms = new ArrayList<>(this.loanTermVariations.size());
for (LoanTermVariations variations : this.loanTermVariations) {
if (variations.getOnLoanStatus().equals(LoanStatus.ACTIVE.getValue())) {
removeTerms.add(variations);
variations.markAsInactive();
}
}
this.loanTermVariations.removeAll(removeTerms);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges());
@@ -5024,7 +5033,7 @@ public class Loan extends AbstractPersistable<Long> {
}
private ChangedTransactionDetail processTransactions() {
public ChangedTransactionDetail processTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
@@ -5292,9 +5301,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(),
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
getRepaymentScheduleInstallments(), generatorDTO.getRecalculateFrom());
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(),
loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
}
public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
@@ -5310,9 +5318,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(),
scheduleGeneratorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
loanRepaymentScheduleTransactionProcessor, getRepaymentScheduleInstallments());
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
} else {
installment = this.getTotalOutstandingOnLoan();
}
@@ -5377,10 +5384,12 @@ public class Loan extends AbstractPersistable<Long> {
return loanApplicationTerms;
}
private BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate,
public BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate,
List<LoanTermVariationsData> loanTermVariations) {
for (LoanTermVariations variationTerms : this.loanTermVariations) {
loanTermVariations.add(variationTerms.toData());
if(variationTerms.isActive()) {
loanTermVariations.add(variationTerms.toData());
}
}
annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations);
return annualNominalInterestRate;
@@ -6332,4 +6341,16 @@ public class Loan extends AbstractPersistable<Long> {
return isForeClosure;
}
public Set<LoanTermVariations> getActiveLoanTermVariations() {
Set<LoanTermVariations> retData = new HashSet<>();
if(this.loanTermVariations != null && this.loanTermVariations.size() > 0){
for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
if(loanTermVariations.isActive()){
retData.add(loanTermVariations);
}
}
}
return retData.size()>0?retData:null;
}
}
@@ -65,4 +65,6 @@ public interface LoanAccountDomainService {
* @param loan {@link Loan} object
*/
void disableStandingInstructionsLinkedToClosedLoan(Loan loan);
void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened);
}
@@ -447,15 +447,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
*/
@Override
public void recalculateAccruals(Loan loan) {
boolean isInterestCalcualtionHappened = loan.repaymentScheduleDetail().isInterestRecalculationEnabled();
recalculateAccruals(loan, isInterestCalcualtionHappened);
}
@Override
public void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened) {
LocalDate accruedTill = loan.getAccruedTill();
if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !isInterestCalcualtionHappened
|| accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; }
boolean isOrganisationDateEnabled = this.configurationDomainService.isOrganisationstartDateEnabled();
Date organisationStartDate = new Date();
if(isOrganisationDateEnabled){
organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
}
if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
|| accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; }
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>();
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Long loanId = loan.getId();
@@ -697,17 +703,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
private LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate fromDate, final Loan loan) {
LoanRepaymentScheduleInstallment installment = null;
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) {
if (fromDate.equals(loanRepaymentScheduleInstallment.getFromDate())) {
installment = loanRepaymentScheduleInstallment;
break;
}
}
return installment;
}
private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
@@ -344,6 +344,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.obligationsMet = false;
this.obligationsMetOnDate = null;
}
public void resetAccrualComponents() {
this.interestAccrued = null;
this.feeAccrued = null;
this.penaltyAccrued = null;
}
public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name="m_loan_reschedule_request_term_variations_mapping")
public class LoanRescheduleRequestToTermVariationMapping extends AbstractPersistable<Long> {
@ManyToOne(optional = false, cascade = CascadeType.PERSIST)
@JoinColumn(name = "loan_term_variations_id", nullable = false)
private LoanTermVariations loanTermVariations;
protected LoanRescheduleRequestToTermVariationMapping(){
}
private LoanRescheduleRequestToTermVariationMapping(final LoanTermVariations loanTermVariations) {
this.loanTermVariations = loanTermVariations;
}
public static LoanRescheduleRequestToTermVariationMapping createNew(final LoanTermVariations loanTermVariation) {
return new LoanRescheduleRequestToTermVariationMapping(loanTermVariation);
}
public LoanTermVariations getLoanTermVariations() {
return this.loanTermVariations;
}
}
@@ -26,7 +26,11 @@ public enum LoanTermVariationType {
PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), //
DUE_DATE(4, "loanTermType.dueDate"), //
INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), //
DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment");
DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment"),
GRACE_ON_INTEREST(7, "loanTermType.graceOnInterest"),
GRACE_ON_PRINCIPAL(8, "loanTermType.graceOnPrincipal"),
EXTEND_REPAYMENT_PERIOD(9, "loanTermType.extendRepaymentPeriod"),
INTEREST_RATE_FROM_INSTALLMENT(10, "loanTermType.interestRateFromInstallment");
private final Integer value;
private final String code;
@@ -66,6 +70,18 @@ public enum LoanTermVariationType {
case 6:
enumeration = LoanTermVariationType.DELETE_INSTALLMENT;
break;
case 7:
enumeration = LoanTermVariationType.GRACE_ON_INTEREST;
break;
case 8:
enumeration = LoanTermVariationType.GRACE_ON_PRINCIPAL;
break;
case 9:
enumeration = LoanTermVariationType.EXTEND_REPAYMENT_PERIOD;
break;
case 10:
enumeration = LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT;
break;
}
return enumeration;
}
@@ -93,4 +109,20 @@ public enum LoanTermVariationType {
public boolean isDeleteInstallment() {
return this.value.equals(LoanTermVariationType.DELETE_INSTALLMENT.getValue());
}
public boolean isGraceOnInterest() {
return this.value.equals(LoanTermVariationType.GRACE_ON_INTEREST.getValue());
}
public boolean isGraceOnPrincipal() {
return this.value.equals(LoanTermVariationType.GRACE_ON_PRINCIPAL.getValue());
}
public boolean isExtendRepaymentPeriod() {
return this.value.equals(LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue());
}
public boolean isInterestRateFromInstallment() {
return this.value.equals(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getValue());
}
}
@@ -23,12 +23,13 @@ import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
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.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
@@ -62,6 +63,13 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
@Column(name = "applied_on_loan_status", nullable = false)
private Integer onLoanStatus;
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private LoanTermVariations parent;
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
final boolean isSpecificToInstallment, final Loan loan) {
@@ -72,6 +80,8 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loan.status().getValue();
this.isActive = true;
this.parent = null;
}
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
@@ -83,6 +93,21 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loanStatus;
this.isActive = true;
this.parent = null;
}
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
final boolean isSpecificToInstallment, final Loan loan, final Integer loanStatus, final Boolean isActive, final LoanTermVariations parent) {
this.loan = loan;
this.termApplicableFrom = termApplicableFrom;
this.termType = termType;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loanStatus;
this.isActive = isActive;
this.parent = parent;
}
protected LoanTermVariations() {
@@ -135,5 +160,21 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
public Integer getOnLoanStatus() {
return this.onLoanStatus;
}
public Boolean isActive() {
return this.isActive;
}
public LoanTermVariations parent() {
return this.parent;
}
public void updateIsActive(final Boolean isActive){
this.isActive = isActive;
}
public void markAsInactive() {
this.isActive = false;
}
}
@@ -777,4 +777,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public boolean isAccrualTransaction() {
return isAccrual();
}
public boolean isPaymentTransaction() {
return this.isNotReversed()
&& !(this.isDisbursement() || this.isAccrual() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction() || this
.isIncomePosting());
}
}
@@ -73,6 +73,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
Money balanceForInterestCalculation = outstandingBalance;
Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace;
Map<LocalDate, BigDecimal> interestRates = new HashMap<>(termVariations.size());
for (LoanTermVariationsData loanTermVariation : termVariations) {
if (loanTermVariation.getTermVariationType().isInterestRateVariation()
&& loanTermVariation.isApplicable(periodStartDate, periodEndDate)) {
@@ -86,6 +87,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
}
}
}
if (principalVariation != null) {
for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) {
@@ -99,6 +101,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
interestStartDate = principal.getKey();
}
Money compoundFee = totalCumulativePrincipal.zero();
if (compoundingMap.containsKey(principal.getKey())) {
@@ -118,13 +121,14 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey()));
}
}
}
}
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace,
balanceForInterestCalculation, interestStartDate, periodEndDate);
interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
@@ -32,11 +32,12 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener
@Override
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
final Money totalCumulativeInterest, final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
Money totalCumulativeInterest, Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
@SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
@SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
@SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) {
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
periodNumber, mc, null);
@@ -54,6 +55,11 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener
principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
totalCumulativePrincipalToDate, periodNumber);
// totalCumulativeInterest from partial schedule generation for multi rescheduling
/*if (loanApplicationTerms.getPartialTotalCumulativeInterest() != null && loanApplicationTerms.getTotalInterestDue() != null) {
totalInterestDueForLoan = loanApplicationTerms.getTotalInterestDue();
totalInterestDueForLoan = totalInterestDueForLoan.plus(loanApplicationTerms.getPartialTotalCumulativeInterest());
}*/
interestForThisInstallment = loanApplicationTerms.adjustInterestIfLastRepaymentPeriod(interestForThisInstallment,
totalCumulativeInterestToDate, totalInterestDueForLoan, periodNumber);
@@ -21,7 +21,9 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -192,6 +194,25 @@ public final class LoanApplicationTerms {
private final boolean isSkipRepaymentOnFirstDayOfMonth;
private final HolidayDetailDTO holidayDetailDTO;
private final Set<Integer> periodNumbersApplicableForPrincipalGrace = new HashSet<>();
private final Set<Integer> periodNumbersApplicableForInterestGrace = new HashSet<>();
// used for FLAT loans when interest rate changed
private Integer excludePeriodsForCalculation = 0;
private Money totalPrincipalAccountedForInterestCalcualtion;
//used for FLAT loans generation on modifying terms
private Money totalPrincipalAccounted;
private Money totalInterestAccounted;
private int periodsCompleted = 0;
private int extraPeriods = 0;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
@@ -482,6 +503,12 @@ public final class LoanApplicationTerms {
this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled;
this.holidayDetailDTO = holidayDetailDTO;
this.allowCompoundingOnEod = allowCompoundingOnEod;
Integer periodNumber = 1;
updatePeriodNumberApplicableForPrincipalOrInterestGrace(periodNumber);
updateRecurringMoratoriumOnPrincipalPeriods(periodNumber);
this.totalPrincipalAccountedForInterestCalcualtion = principal.zero();
this.totalInterestAccounted = principal.zero();
this.totalPrincipalAccounted = principal.zero();
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -706,7 +733,8 @@ public final class LoanApplicationTerms {
switch (this.interestMethod) {
case FLAT:
final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc);
totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode());
totalInterestDue = this.principal.minus(totalPrincipalAccountedForInterestCalcualtion).multiplyRetainScale(interestRateForLoanTerm,
mc.getRoundingMode());
break;
case DECLINING_BALANCE:
@@ -715,10 +743,6 @@ public final class LoanApplicationTerms {
break;
}
if (this.totalInterestDue != null) {
totalInterestDue = this.totalInterestDue;
}
return totalInterestDue;
}
@@ -844,13 +868,19 @@ public final class LoanApplicationTerms {
private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments), mc.getRoundingMode());
Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments) - defaultToZeroIfNull(this.excludePeriodsForCalculation), mc.getRoundingMode());
if (this.excludePeriodsForCalculation < this.periodsCompleted) {
Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted);
interestPerInstallment = interestLeft.dividedBy(Long.valueOf(this.actualNumberOfRepayments)
- defaultToZeroIfNull(this.periodsCompleted), mc.getRoundingMode());
}
return interestPerInstallment;
}
private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) {
final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment();
Money principalPerPeriod = this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(
Money principalPerPeriod = this.principal.minus(totalPrincipalAccounted).dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(
this.adjustPrincipalForFlatLoans);
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
principalPerPeriod = principalPerPeriod.zero();
@@ -906,11 +936,30 @@ public final class LoanApplicationTerms {
final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace());
final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree);
final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments);
Integer interestPaymentDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments,
this.excludePeriodsForCalculation);
interestForInstallment = realTotalInterestForLoan
.dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode());
if (this.excludePeriodsForCalculation < this.periodsCompleted) {
Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted);
Integer interestDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments,
this.periodsCompleted);
interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc.getRoundingMode());
}
if(!this.periodNumbersApplicableForInterestGrace.isEmpty()){
int periodsElapsed = calculateLastInterestGracePeriod(periodNumber);
if (periodsElapsed > this.excludePeriodsForCalculation && periodsElapsed > this.periodsCompleted) {
Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted);
Integer interestDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments,
periodsElapsed);
interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc.getRoundingMode());
}
}
}
return interestForInstallment;
@@ -1032,24 +1081,46 @@ public final class LoanApplicationTerms {
private int calculateNumberOfRepaymentsWithPrincipalPayment() {
int numPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments,
this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), 0);
this.periodsCompleted);
// numPeriods = numPeriods - this.periodsCompleted;
return numPeriods;
}
private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) {
return totalNumberOfRepaymentPeriods - Math.max(getInterestChargingGrace(), getInterestPaymentGrace());
private Integer calculateNumberOfRemainingInterestPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, int periodsElapsed) {
int principalFeePeriods = 0;
for (Integer intNumber : this.periodNumbersApplicableForInterestGrace) {
if (intNumber > periodsElapsed) {
principalFeePeriods++;
}
}
Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed - principalFeePeriods;
return periodsRemaining;
}
private Integer calculateLastInterestGracePeriod(int periodNumber){
int lastGracePeriod = 0;
for (Integer grace : this.periodNumbersApplicableForInterestGrace) {
if(grace < periodNumber && lastGracePeriod < grace){
lastGracePeriod = grace;
}
}
return lastGracePeriod;
}
public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) {
if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) {
return ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this
.getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1)));
boolean isPrincipalGraceApplicableForThisPeriod = false;
if (this.periodNumbersApplicableForPrincipalGrace.contains(periodNumber)) {
isPrincipalGraceApplicableForThisPeriod = true;
}
return periodNumber > 0 && periodNumber <= getPrincipalGrace();
return isPrincipalGraceApplicableForThisPeriod;
}
private boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) {
return periodNumber > 0 && periodNumber <= getInterestPaymentGrace();
public boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) {
boolean isInterestPaymentGraceApplicableForThisPeriod = false;
if (this.periodNumbersApplicableForInterestGrace.contains(periodNumber)) {
isInterestPaymentGraceApplicableForThisPeriod = true;
}
return isInterestPaymentGraceApplicableForThisPeriod;
}
private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) {
@@ -1099,7 +1170,7 @@ public final class LoanApplicationTerms {
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
final Integer periodsRemaining = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments,
this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodsElapsed);
periodsElapsed);
double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(),
principalDouble, futureValue, false);
@@ -1165,7 +1236,7 @@ public final class LoanApplicationTerms {
Money principal = this.principal;
if (this.fixedPrincipalAmount == null) {
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(
this.actualNumberOfRepayments, this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber);
this.actualNumberOfRepayments, periodNumber);
principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode());
this.fixedPrincipalAmount = principal.getAmount();
}
@@ -1179,21 +1250,19 @@ public final class LoanApplicationTerms {
public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) {
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments,
this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber - 1);
periodNumber - 1);
Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode());
this.fixedPrincipalAmount = principal.getAmount();
}
private static Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods,
final int recurringMoratoriumOnPrincipalPeriods, final int PrincipalGrace, int periodsElapsed) {
if (PrincipalGrace > periodsElapsed) {
periodsElapsed = PrincipalGrace;
}
Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed;
if (recurringMoratoriumOnPrincipalPeriods > 0) {
double periodsRemainingDouble = ((double) periodsRemaining / ((double) (recurringMoratoriumOnPrincipalPeriods + 1)));
periodsRemaining = (int) Math.ceil(periodsRemainingDouble);
private Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, int periodsElapsed) {
int principalFeePeriods = 0;
for (Integer intNumber : this.periodNumbersApplicableForPrincipalGrace) {
if (intNumber > periodsElapsed) {
principalFeePeriods++;
}
}
Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed - principalFeePeriods;
return periodsRemaining;
}
@@ -1469,10 +1538,7 @@ public final class LoanApplicationTerms {
}
public void updateTotalInterestDue(Money totalInterestDue) {
if (totalInterestDue != null) {
this.totalInterestDue = totalInterestDue;
}
this.totalInterestDue = totalInterestDue;
}
public ApplicationCurrency getApplicationCurrency() {
@@ -1576,4 +1642,81 @@ public final class LoanApplicationTerms {
}
return disbursedAmount;
}
public void updatePeriodNumberApplicableForPrincipalOrInterestGrace(final Integer periodsApplicationForGrace) {
int applicablePeriodNumber = periodsApplicationForGrace;
int graceOnPrincipal = defaultToZeroIfNull(this.principalGrace);
int graceOnInterest = defaultToZeroIfNull(this.interestPaymentGrace);
while (graceOnPrincipal > 0 || graceOnInterest > 0) {
if (graceOnPrincipal > 0) {
this.periodNumbersApplicableForPrincipalGrace.add(applicablePeriodNumber);
}
if (graceOnInterest > 0) {
this.periodNumbersApplicableForInterestGrace.add(applicablePeriodNumber);
}
applicablePeriodNumber++;
graceOnPrincipal--;
graceOnInterest--;
}
}
/**
* 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;
}
public void updateExcludePeriodsForCalculation(Integer excludePeriodsForCalculation){
this.excludePeriodsForCalculation = excludePeriodsForCalculation;
this.extraPeriods = 0;
}
public Integer getActualNoOfRepaymnets() {
return this.actualNumberOfRepayments;
}
public Money getTotalInterestDue() {
return this.totalInterestDue;
}
private void updateRecurringMoratoriumOnPrincipalPeriods(Integer periodNumber) {
Boolean isPrincipalGraceApplicableForThisPeriod = false;
Integer numberOfRepayments = this.actualNumberOfRepayments;
if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) {
while (numberOfRepayments > 0) {
isPrincipalGraceApplicableForThisPeriod = ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this
.getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1)));
if (isPrincipalGraceApplicableForThisPeriod) {
this.periodNumbersApplicableForPrincipalGrace.add(periodNumber);
}
numberOfRepayments--;
periodNumber++;
}
}
}
public void setTotalPrincipalAccounted(Money totalPrincipalAccounted) {
this.totalPrincipalAccountedForInterestCalcualtion = totalPrincipalAccounted;
}
//Used for FLAT loans to calculate principal and interest per installment
public void updateAccountedTillPeriod(int periodNumber, Money totalPrincipalAccounted,Money totalInterestAccounted, int extendPeriods){
this.periodsCompleted = periodNumber;
this.totalPrincipalAccounted = totalPrincipalAccounted;
this.totalInterestAccounted = totalInterestAccounted;
this.extraPeriods = this.extraPeriods + extendPeriods;
}
public void updateTotalInterestAccounted(Money totalInterestAccounted){
this.totalInterestAccounted = totalInterestAccounted;
}
}
@@ -19,22 +19,15 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext;
import java.util.List;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.joda.time.LocalDate;
public interface LoanScheduleGenerator {
@@ -42,18 +35,12 @@ public interface LoanScheduleGenerator {
LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
final HolidayDetailDTO holidayDetailDTO);
LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
final HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> transactions,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, LocalDate rescheduleFrom);
LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
final HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
LocalDate rescheduleFrom);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges, HolidayDetailDTO holidayDetailDTO,
List<LoanTransaction> loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance,
CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, FloatingRateDTO floatingRateDTO,
final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays);
}
@@ -86,7 +86,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.apache.fineract.portfolio.loanaccount.exception.MinDaysBetweenDisbursalAndFirstRepaymentViolationException;
@@ -621,9 +620,8 @@ public class LoanScheduleAssembler {
}
public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId,
List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom) {
Loan loan, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final LocalDate rescheduleFrom) {
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(8, roundingMode);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
@@ -634,15 +632,13 @@ public class LoanScheduleAssembler {
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions,
loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom).getLoanScheduleModel();
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, detailDTO,
loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel();
}
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final Long officeId,
List<LoanTransaction> loanTransactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments) {
LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(8, roundingMode);
@@ -653,8 +649,8 @@ public class LoanScheduleAssembler {
final WorkingDays workingDays = this.workingDaysRepository.findOne();
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loanCharges, holidayDetailDTO,
loanTransactions, loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments);
return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loan, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor);
}
@@ -171,9 +171,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
}
LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan);
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency,
today, loanApplicationTerms, loan.charges(), loan.getOfficeId(),
loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
loan.getRepaymentScheduleInstallments());
today, loanApplicationTerms, loan, loan.getOfficeId(),
loanRepaymentScheduleTransactionProcessor);
Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus(
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
Money interestDue = Money.zero(currency);
@@ -194,8 +193,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
}
LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(),
modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, loan.getRepaymentScheduleInstallments(),
loan.fetchInterestRecalculateFromDate());
loan, loanRepaymentScheduleTransactionProcessor, loan.fetchInterestRecalculateFromDate());
LoanScheduleData scheduleDate = model.toData();
Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods();
for (LoanSchedulePeriodData periodData : periodDatas) {
@@ -42,9 +42,9 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSeria
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanReschedulePreviewPlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -108,14 +108,14 @@ public class RescheduleLoansApiResource {
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (compareIgnoreCase(command, "previewLoanReschedule")) {
final LoanRescheduleModel loanRescheduleModel = this.loanReschedulePreviewPlatformService.previewLoanReschedule(scheduleId);
final LoanScheduleModel loanRescheduleModel = this.loanReschedulePreviewPlatformService.previewLoanReschedule(scheduleId);
return this.loanRescheduleToApiJsonSerializer.serialize(settings, loanRescheduleModel.toData(), new HashSet<String>());
}
final LoanRescheduleRequestData loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequest(scheduleId);
return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleRequestData);
}
@@ -170,4 +170,20 @@ public class RescheduleLoansApiResource {
private boolean compareIgnoreCase(String firstString, String secondString) {
return StringUtils.isNotBlank(firstString) && firstString.trim().equalsIgnoreCase(secondString);
}
/*@GET
@Path("{scheduleId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTemplate(@Context final UriInfo uriInfo) {
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
LoanRescheduleRequestData loanRescheduleReasons = null;
loanRescheduleReasons = this.loanRescheduleRequestReadPlatformService
.retrieveAllRescheduleReasons(RescheduleLoansApiConstants.LOAN_RESCHEDULE_REASON);
return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleReasons);
}*/
}
@@ -18,10 +18,10 @@
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
import java.math.BigDecimal;
import java.util.Collection;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.joda.time.LocalDate;
/**
@@ -36,59 +36,55 @@ public class LoanRescheduleRequestData {
private final String loanAccountNumber;
private final LoanRescheduleRequestStatusEnumData statusEnum;
private final Integer rescheduleFromInstallment;
private final Integer graceOnPrincipal;
private final Integer graceOnInterest;
private final LocalDate rescheduleFromDate;
private final Integer extraTerms;
private final BigDecimal interestRate;
private final Boolean recalculateInterest;
private final CodeValueData rescheduleReasonCodeValue;
private final LoanRescheduleRequestTimelineData timeline;
private final String rescheduleReasonComment;
private final LocalDate adjustedDueDate;
@SuppressWarnings("unused")
private final Collection<CodeValueData> rescheduleReasons;
@SuppressWarnings("unused")
private final Collection<LoanTermVariationsData> loanTermVariationsData;
/**
* LoanRescheduleRequestData constructor
* @param loanTermVariationsData TODO
**/
private LoanRescheduleRequestData(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate,
LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue,
Integer rescheduleFromInstallment, LocalDate rescheduleFromDate, CodeValueData rescheduleReasonCodeValue,
String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName,
final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection<CodeValueData> rescheduleReasons) {
final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest,
Collection<CodeValueData> rescheduleReasons, final Collection<LoanTermVariationsData> loanTermVariationsData) {
this.id = id;
this.loanId = loanId;
this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment;
this.graceOnPrincipal = graceOnPrincipal;
this.graceOnInterest = graceOnInterest;
this.rescheduleFromDate = rescheduleFromDate;
this.extraTerms = extraTerms;
this.interestRate = interestRate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment;
this.adjustedDueDate = adjustedDueDate;
this.timeline = timeline;
this.clientName = clientName;
this.loanAccountNumber = loanAccountNumber;
this.clientId = clientId;
this.recalculateInterest = recalculateInterest;
this.rescheduleReasons = rescheduleReasons ;
this.loanTermVariationsData = loanTermVariationsData;
}
/**
* @param loanTermVariationsData TODO
* @return an instance of the LoanRescheduleRequestData class
**/
public static LoanRescheduleRequestData instance(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate,
LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue,
Integer rescheduleFromInstallment, LocalDate rescheduleFromDate, CodeValueData rescheduleReasonCodeValue,
String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName,
final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection<CodeValueData> rescheduleReasons) {
final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest,
Collection<CodeValueData> rescheduleReasons, final Collection<LoanTermVariationsData> loanTermVariationsData) {
return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons);
return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate,
rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId, recalculateInterest,
rescheduleReasons, loanTermVariationsData);
}
/**
@@ -119,20 +115,6 @@ public class LoanRescheduleRequestData {
return rescheduleFromInstallment;
}
/**
* @return the graceOnPrincipal
*/
public Integer getGraceOnPrincipal() {
return graceOnPrincipal;
}
/**
* @return the graceOnInterest
*/
public Integer getGraceOnInterest() {
return graceOnInterest;
}
/**
* @return the reschedule from date
*/
@@ -140,20 +122,6 @@ public class LoanRescheduleRequestData {
return rescheduleFromDate;
}
/**
* @return the extraTerms
*/
public Integer getExtraTerms() {
return extraTerms;
}
/**
* @return the interestRate
*/
public BigDecimal getInterestRate() {
return interestRate;
}
/**
* @return the rescheduleReasonCodeValueId
*/
@@ -175,13 +143,6 @@ public class LoanRescheduleRequestData {
return this.timeline;
}
/**
* @return the adjustedDueDate
*/
public LocalDate getAdjustedDueDate() {
return adjustedDueDate;
}
/**
* @return the clientName
*/
@@ -32,10 +32,8 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -47,13 +45,10 @@ import com.google.gson.reflect.TypeToken;
public class LoanRescheduleRequestDataValidator {
private final FromJsonHelper fromJsonHelper;
private final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService;
@Autowired
public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper,
LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService) {
public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper) {
this.fromJsonHelper = fromJsonHelper;
this.loanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService;
}
/**
@@ -159,21 +154,8 @@ public class LoanRescheduleRequestDataValidator {
.failWithCode("repayment.schedule.installment.obligation.met", "Repayment schedule installment obligation met");
}
if (installment != null && installment.isPartlyPaid()) {
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
.failWithCode("repayment.schedule.installment.partly.paid", "Repayment schedule installment is partly paid");
}
}
if (loanId != null) {
List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
if (loanRescheduleRequestData.size() > 0) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
"The loan can only be rescheduled once.");
}
}
if(loan.isMultiDisburmentLoan()) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCode,
"Loan rescheduling is not supported for multidisbursement loans");
@@ -232,7 +214,6 @@ public class LoanRescheduleRequestDataValidator {
final Loan loan = loanRescheduleRequest.getLoan();
if (loan != null) {
Long loanId = loan.getId();
if (!loan.status().isActive()) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active", "Loan is not active");
@@ -251,16 +232,6 @@ public class LoanRescheduleRequestDataValidator {
"loan.repayment.schedule.installment." + "obligation.met", "Repayment schedule installment obligation met");
}
}
if (loanId != null) {
List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
if (loanRescheduleRequestData.size() > 0) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
"The loan can only be rescheduled once.");
}
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -1,62 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance,
final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, final FloatingRateDTO floatingRateDTO,
final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) {
LoanRescheduleModel loanRescheduleModel = null;
switch (interestMethod) {
case DECLINING_BALANCE:
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
break;
case FLAT:
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
break;
case INVALID:
break;
}
return loanRescheduleModel;
}
}
@@ -18,20 +18,27 @@
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.useradministration.domain.AppUser;
import org.joda.time.LocalDate;
import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -40,116 +47,97 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_loan_reschedule_request")
public class LoanRescheduleRequest extends AbstractPersistable<Long> {
@ManyToOne
@ManyToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Column(name = "status_enum", nullable = false)
private Integer statusEnum;
@Column(name = "reschedule_from_installment")
private Integer rescheduleFromInstallment;
@Column(name = "grace_on_principal")
private Integer graceOnPrincipal;
@Column(name = "grace_on_interest")
private Integer graceOnInterest;
@Temporal(TemporalType.DATE)
@Column(name = "status_enum", nullable = false)
private Integer statusEnum;
@Column(name = "reschedule_from_installment")
private Integer rescheduleFromInstallment;
@Temporal(TemporalType.DATE)
@Column(name = "reschedule_from_date")
private Date rescheduleFromDate;
@Temporal(TemporalType.DATE)
@Column(name = "adjusted_due_date")
private Date adjustedDueDate;
@Column(name = "extra_terms")
private Integer extraTerms;
@Column(name = "recalculate_interest")
private Boolean recalculateInterest;
@Column(name = "interest_rate", scale = 6, precision = 19)
private BigDecimal interestRate;
@ManyToOne
private Date rescheduleFromDate;
@Column(name = "recalculate_interest")
private Boolean recalculateInterest;
@ManyToOne
@JoinColumn(name = "reschedule_reason_cv_id")
private CodeValue rescheduleReasonCodeValue;
@Column(name = "reschedule_reason_comment")
private String rescheduleReasonComment;
@Temporal(TemporalType.DATE)
private CodeValue rescheduleReasonCodeValue;
@Column(name = "reschedule_reason_comment")
private String rescheduleReasonComment;
@Temporal(TemporalType.DATE)
@Column(name = "submitted_on_date")
private Date submittedOnDate;
@ManyToOne
private Date submittedOnDate;
@ManyToOne
@JoinColumn(name = "submitted_by_user_id")
private AppUser submittedByUser;
@Temporal(TemporalType.DATE)
private AppUser submittedByUser;
@Temporal(TemporalType.DATE)
@Column(name = "approved_on_date")
private Date approvedOnDate;
private Date approvedOnDate;
@ManyToOne
@ManyToOne
@JoinColumn(name = "approved_by_user_id")
private AppUser approvedByUser;
private AppUser approvedByUser;
@Temporal(TemporalType.DATE)
@Temporal(TemporalType.DATE)
@Column(name = "rejected_on_date")
private Date rejectedOnDate;
private Date rejectedOnDate;
@ManyToOne
@ManyToOne
@JoinColumn(name = "rejected_by_user_id")
private AppUser rejectedByUser;
private AppUser rejectedByUser;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch=FetchType.EAGER)
@JoinColumn(name = "loan_reschedule_request_id", nullable = false)
private Set<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = new HashSet<>();
/**
* LoanRescheduleRequest constructor
**/
protected LoanRescheduleRequest() {}
/**
* LoanRescheduleRequest constructor
**/
protected LoanRescheduleRequest() {}
/**
* LoanRescheduleRequest constructor
**/
private LoanRescheduleRequest(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Date rescheduleFromDate, final Boolean recalculateInterest, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser, final Date approvedOnDate,
final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
this.loan = loan;
this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment;
this.rescheduleFromDate = rescheduleFromDate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment;
this.submittedOnDate = submittedOnDate;
this.submittedByUser = submittedByUser;
this.approvedOnDate = approvedOnDate;
this.approvedByUser = approvedByUser;
this.rejectedOnDate = rejectedOnDate;
this.rejectedByUser = rejectedByUser;
this.recalculateInterest = recalculateInterest;
}
/**
* LoanRescheduleRequest constructor
**/
private LoanRescheduleRequest(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate,
final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser,
final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
this.loan = loan;
this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment;
this.graceOnPrincipal = graceOnPrincipal;
this.graceOnInterest = graceOnInterest;
this.rescheduleFromDate = rescheduleFromDate;
this.extraTerms = extraTerms;
this.interestRate = interestRate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment;
this.submittedOnDate = submittedOnDate;
this.submittedByUser = submittedByUser;
this.approvedOnDate = approvedOnDate;
this.approvedByUser = approvedByUser;
this.rejectedOnDate = rejectedOnDate;
this.rejectedByUser = rejectedByUser;
this.adjustedDueDate = adjustedDueDate;
this.recalculateInterest = recalculateInterest;
}
/**
* @return a new instance of the LoanRescheduleRequest class
**/
public static LoanRescheduleRequest instance(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate,
final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser,
final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
return new LoanRescheduleRequest(loan, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate,
submittedByUser, approvedOnDate, approvedByUser, rejectedOnDate, rejectedByUser);
}
/**
* @return a new instance of the LoanRescheduleRequest class
**/
public static LoanRescheduleRequest instance(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Date rescheduleFromDate, final Boolean recalculateInterest, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser, final Date approvedOnDate,
final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
return new LoanRescheduleRequest(loan, statusEnum, rescheduleFromInstallment, rescheduleFromDate, recalculateInterest,
rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate, submittedByUser, approvedOnDate, approvedByUser,
rejectedOnDate, rejectedByUser);
}
/**
* @return the reschedule request loan object
@@ -172,20 +160,6 @@ public class LoanRescheduleRequest extends AbstractPersistable<Long> {
return this.rescheduleFromInstallment;
}
/**
* @return the grace on principal
**/
public Integer getGraceOnPrincipal() {
return this.graceOnPrincipal;
}
/**
* @return the grace on interest
**/
public Integer getGraceOnInterest() {
return this.graceOnInterest;
}
/**
* @return due date of the rescheduling start point
**/
@@ -200,34 +174,6 @@ public class LoanRescheduleRequest extends AbstractPersistable<Long> {
return localDate;
}
/**
* @return due date of the first rescheduled installment
**/
public LocalDate getAdjustedDueDate() {
LocalDate localDate = null;
if(this.adjustedDueDate != null) {
localDate = new LocalDate(this.adjustedDueDate);
}
return localDate;
}
/**
* @return extra terms to be added after the last loan installment
**/
public Integer getExtraTerms() {
return this.extraTerms;
}
/**
* @return the new interest rate to be applied to unpaid installments
**/
public BigDecimal getInterestRate() {
return this.interestRate;
}
/**
* @return the reschedule reason code value object
**/
@@ -348,4 +294,24 @@ public class LoanRescheduleRequest extends AbstractPersistable<Long> {
this.statusEnum = LoanStatus.REJECTED.getValue();
}
}
public void updateLoanRescheduleRequestToTermVariationMappings(final List<LoanRescheduleRequestToTermVariationMapping> mapping) {
this.loanRescheduleRequestToTermVariationMappings.addAll(mapping);
}
public Set<LoanRescheduleRequestToTermVariationMapping> getLoanRescheduleRequestToTermVariationMappings() {
return this.loanRescheduleRequestToTermVariationMappings;
}
public LoanTermVariations getDueDateTermVariationIfExists() {
if(this.loanRescheduleRequestToTermVariationMappings != null
&& this.loanRescheduleRequestToTermVariationMappings.size() > 0){
for (LoanRescheduleRequestToTermVariationMapping mapping : this.loanRescheduleRequestToTermVariationMappings) {
if(mapping.getLoanTermVariations().getTermType().isDueDateVariation()){
return mapping.getLoanTermVariations();
}
}
}
return null;
}
}
@@ -1,37 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
public interface LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance,
final Calendar loanCalendar, FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst,
final Integer numberofdays);
}
@@ -18,9 +18,9 @@
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
public interface LoanReschedulePreviewPlatformService {
public LoanRescheduleModel previewLoanReschedule(Long requestId);
public LoanScheduleModel previewLoanReschedule(Long requestId);
}
@@ -18,41 +18,33 @@
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -60,103 +52,94 @@ import org.springframework.stereotype.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;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final LoanUtilService loanUtilService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final LoanSummaryWrapper loanSummaryWrapper;
private final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
@Autowired
public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
final CalendarInstanceRepository calendarInstanceRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService) {
final LoanUtilService loanUtilService,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanSummaryWrapper loanSummaryWrapper) {
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
this.calendarInstanceRepository = calendarInstanceRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.loanUtilService = loanUtilService;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.loanScheduleFactory = loanScheduleFactory;
this.loanSummaryWrapper = loanSummaryWrapper;
}
@Override
public LoanRescheduleModel previewLoanReschedule(Long requestId) {
public LoanScheduleModel previewLoanReschedule(Long requestId) {
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId);
if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(requestId); }
Loan loan = loanRescheduleRequest.getLoan();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan,
loanRescheduleRequest.getRescheduleFromDate());
LocalDate rescheduleFromDate = null;
List<LoanTermVariationsData> removeLoanTermVariationsData = new ArrayList<>();
final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists();
if(dueDateVariationInCurrentRequest != null){
for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (loanTermVariation.getDateValue().equals(dueDateVariationInCurrentRequest.fetchTermApplicaDate())) {
rescheduleFromDate = loanTermVariation.getTermApplicableFrom();
removeLoanTermVariationsData.add(loanTermVariation);
}
}
}
loanApplicationTerms.getLoanTermVariations().getDueDateVariation().removeAll(removeLoanTermVariationsData);
if (rescheduleFromDate == null) {
rescheduleFromDate = loanRescheduleRequest.getRescheduleFromDate();
}
List<LoanTermVariationsData> loanTermVariationsData = new ArrayList<>();
LocalDate adjustedApplicableDate = null;
Set<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = loanRescheduleRequest.getLoanRescheduleRequestToTermVariationMappings();
if (!loanRescheduleRequestToTermVariationMappings.isEmpty()) {
for (LoanRescheduleRequestToTermVariationMapping loanRescheduleRequestToTermVariationMapping : loanRescheduleRequestToTermVariationMappings) {
if (loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().getTermType().isDueDateVariation()
&& rescheduleFromDate != null) {
adjustedApplicableDate = loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().fetchDateValue();
loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().setTermApplicableFrom(
rescheduleFromDate.toDate());
}
loanTermVariationsData.add(loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().toData());
}
}
for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms,
false, loanApplicationTerms.getHolidayDetailDTO());
if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
loanTermVariation.setApplicableFromDate(adjustedDate);
loanTermVariationsData.add(loanTermVariation);
}
}
}
loanApplicationTerms.getLoanTermVariations().updateLoanTermVariationsData(loanTermVariationsData);
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mathContext = new MathContext(8, roundingMode);
List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive(
loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
CalendarInstance restCalendarInstance = null;
CalendarInstance compoundingCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
}
final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
Calendar loanCalendar = null;
if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar();
}
final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
loanCalendar, floatingRateDTO, isSkipRepaymentOnFirstMonth, numberOfDays);
LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
oldPeriods);
return loanRescheduleModelWithOldPeriods;
}
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
FloatingRateDTO floatingRateDTO = null;
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try{
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
}catch(final FloatingRateNotFoundException ex){
// Do not do anything
}
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
final LoanLifecycleStateMachine loanLifecycleStateMachine = null;
loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms,
loan, loanApplicationTerms.getHolidayDetailDTO(),
loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
final LoanScheduleModel loanScheduleModel = loanSchedule.getLoanScheduleModel();
LoanScheduleModel loanScheduleModels = LoanScheduleModel.withLoanScheduleModelPeriods(loanScheduleModel.getPeriods(),
loanScheduleModel);
return loanScheduleModels;
}
}
@@ -29,6 +29,7 @@ import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
@@ -36,6 +37,7 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanResched
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestEnumerations;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestTimelineData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -71,13 +73,8 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
sqlBuilder.append("mc.id as clientId, ");
sqlBuilder.append("ml.account_no as loanAccountNumber, ");
sqlBuilder.append("lr.reschedule_from_installment as rescheduleFromInstallment, ");
sqlBuilder.append("lr.grace_on_principal as graceOnPrincipal, ");
sqlBuilder.append("lr.grace_on_interest as graceOnInterest, ");
sqlBuilder.append("lr.reschedule_from_date as rescheduleFromDate, ");
sqlBuilder.append("lr.adjusted_due_date as adjustedDueDate, ");
sqlBuilder.append("lr.extra_terms as extraTerms, ");
sqlBuilder.append("lr.recalculate_interest as recalculateInterest, ");
sqlBuilder.append("lr.interest_rate as interestRate, ");
sqlBuilder.append("lr.reschedule_reason_cv_id as rescheduleReasonCvId, ");
sqlBuilder.append("cv.code_value as rescheduleReasonCvValue, ");
sqlBuilder.append("lr.reschedule_reason_comment as rescheduleReasonComment, ");
@@ -95,7 +92,14 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
sqlBuilder.append("lr.rejected_on_date as rejectedOnDate, ");
sqlBuilder.append("rbu.username as rejectedByUsername, ");
sqlBuilder.append("rbu.firstname as rejectedByFirstname, ");
sqlBuilder.append("rbu.lastname as rejectedByLastname ");
sqlBuilder.append("rbu.lastname as rejectedByLastname, ");
sqlBuilder.append("tv.id as termId,");
sqlBuilder.append("tv.term_type as termType,");
sqlBuilder.append("tv.applicable_date as variationApplicableFrom, ");
sqlBuilder.append("tv.decimal_value as decimalValue, ");
sqlBuilder.append("tv.date_value as dateValue, ");
sqlBuilder.append("tv.is_specific_to_installment as isSpecificToInstallment ");
sqlBuilder.append("from " + loanRescheduleRequestTableName() + " lr ");
sqlBuilder.append("left join m_code_value cv on cv.id = lr.reschedule_reason_cv_id ");
@@ -104,6 +108,8 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
sqlBuilder.append("left join m_appuser rbu on rbu.id = lr.rejected_by_user_id ");
sqlBuilder.append("left join m_loan ml on ml.id = lr.loan_id ");
sqlBuilder.append("left join m_client mc on mc.id = ml.client_id ");
sqlBuilder.append("join m_loan_reschedule_request_term_variations_mapping rrtvm on lr.id = rrtvm.loan_reschedule_request_id ");
sqlBuilder.append("join m_loan_term_variations tv on tv.id = rrtvm.loan_term_variations_id and tv.parent_id is null") ;
this.schema = sqlBuilder.toString();
}
@@ -127,12 +133,7 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
final String loanAccountNumber = rs.getString("loanAccountNumber");
final Long clientId = rs.getLong("clientId");
final Integer rescheduleFromInstallment = JdbcSupport.getInteger(rs, "rescheduleFromInstallment");
final Integer graceOnPrincipal = JdbcSupport.getInteger(rs, "graceOnPrincipal");
final Integer graceOnInterest = JdbcSupport.getInteger(rs, "graceOnInterest");
final LocalDate rescheduleFromDate = JdbcSupport.getLocalDate(rs, "rescheduleFromDate");
final LocalDate adjustedDueDate = JdbcSupport.getLocalDate(rs, "adjustedDueDate");
final Integer extraTerms = JdbcSupport.getInteger(rs, "extraTerms");
final BigDecimal interestRate = rs.getBigDecimal("interestRate");
final Long rescheduleReasonCvId = JdbcSupport.getLong(rs, "rescheduleReasonCvId");
final String rescheduleReasonCvValue = rs.getString("rescheduleReasonCvValue");
final CodeValueData rescheduleReasonCodeValue = CodeValueData.instance(rescheduleReasonCvId, rescheduleReasonCvValue);
@@ -157,14 +158,40 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
final LoanRescheduleRequestTimelineData timeline = new LoanRescheduleRequestTimelineData(submittedOnDate, submittedByUsername,
submittedByFirstname, submittedByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname,
rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname);
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
do {
Long tempId = rs.getLong("id");
if (id.equals(tempId)) {
loanTermVariations.add(fetchLoanTermVariation(rs));
} else {
rs.previous();
break;
}
} while (rs.next());
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons);
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate,
rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId,
recalculateInterest, rescheduleReasons, loanTermVariations);
}
private LoanTermVariationsData fetchLoanTermVariation(final ResultSet rs) throws SQLException {
final Long id = rs.getLong("termId");
final LocalDate variationApplicableFrom = JdbcSupport.getLocalDate(rs, "variationApplicableFrom");
final BigDecimal decimalValue = rs.getBigDecimal("decimalValue");
final LocalDate dateValue = JdbcSupport.getLocalDate(rs, "dateValue");
final boolean isSpecificToInstallment = rs.getBoolean("isSpecificToInstallment");
final int termType = rs.getInt("termType");
final LoanTermVariationsData loanTermVariationsData = new LoanTermVariationsData(id,
LoanEnumerations.loanvariationType(termType), variationApplicableFrom, decimalValue, dateValue,
isSpecificToInstallment);
return loanTermVariationsData;
}
}
@Override
public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId) {
final Loan loan = this.loanRepository.findOne(loanId);
@@ -209,12 +236,7 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
final Long loanId = null;
final LoanRescheduleRequestStatusEnumData statusEnum = null;
final Integer rescheduleFromInstallment = null;
final Integer graceOnPrincipal = null;
final Integer graceOnInterest = null;
final LocalDate rescheduleFromDate = null;
final LocalDate adjustedDueDate = null;
final Integer extraTerms = null;
final BigDecimal interestRate = null;
final CodeValueData rescheduleReasonCodeValue = null;
final String rescheduleReasonComment = null;
final LoanRescheduleRequestTimelineData timeline = null;
@@ -222,9 +244,10 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
final String loanAccountNumber = null;
final Long clientId = null;
final Boolean recalculateInterest = null;
final Collection<LoanTermVariationsData> loanTermVariationsData = null;
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons);
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate,
rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId, recalculateInterest,
rescheduleReasons, loanTermVariationsData);
}
}
@@ -22,9 +22,9 @@ import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -33,64 +33,52 @@ import java.util.Set;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.apache.fineract.useradministration.domain.AppUser;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
@@ -107,25 +95,25 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
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 HolidayRepositoryWrapper holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final LoanTransactionRepository loanTransactionRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanRepository loanRepository;
private final LoanAssembler loanAssembler;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final LoanUtilService loanUtilService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final LoanSummaryWrapper loanSummaryWrapper;
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
private final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final LoanAccountDomainService loanAccountDomainService;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
/**
* LoanRescheduleRequestWritePlatformServiceImpl constructor
@@ -133,38 +121,39 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
* @return void
**/
@Autowired
public LoanRescheduleRequestWritePlatformServiceImpl(LoanRepositoryWrapper loanRepositoryWrapper,
CodeValueRepositoryWrapper codeValueRepositoryWrapper, PlatformSecurityContext platformSecurityContext,
LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
LoanRescheduleRequestRepository loanRescheduleRequestRepository,
ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, ConfigurationDomainService configurationDomainService,
HolidayRepositoryWrapper holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository,
LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository,
public LoanRescheduleRequestWritePlatformServiceImpl(final CodeValueRepositoryWrapper codeValueRepositoryWrapper,
final PlatformSecurityContext platformSecurityContext,
final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
final CalendarInstanceRepository calendarInstanceRepository, final LoanChargeReadPlatformService loanChargeReadPlatformService,
final LoanTransactionRepository loanTransactionRepository,
final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository,
final LoanAssembler loanAssembler, final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
final LoanUtilService loanUtilService) {
this.loanRepositoryWrapper = loanRepositoryWrapper;
final LoanAssembler loanAssembler, final LoanUtilService loanUtilService,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanSummaryWrapper loanSummaryWrapper,
final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
final LoanAccountDomainService loanAccountDomainService,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
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;
this.calendarInstanceRepository = calendarInstanceRepository;
this.loanChargeReadPlatformService = loanChargeReadPlatformService;
this.loanTransactionRepository = loanTransactionRepository;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.loanRepository = loanRepository;
this.loanAssembler = loanAssembler;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.loanUtilService = loanUtilService;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.loanScheduleFactory = loanScheduleFactory;
this.loanSummaryWrapper = loanSummaryWrapper;
this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
this.loanAccountDomainService = loanAccountDomainService;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
}
/**
@@ -182,7 +171,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final Long loanId = jsonCommand.longValueOfParameterNamed(RescheduleLoansApiConstants.loanIdParamName);
// use the loan id to get a Loan entity object
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
final Loan loan = this.loanAssembler.assembleFrom(loanId);
// validate the request in the JsonCommand object passed as
// parameter
@@ -275,12 +264,24 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
}
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);
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, rescheduleFromDate,
recalculateInterest, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate,
this.platformSecurityContext.authenticatedUser(), null, null, null, null);
// update reschedule request to term variations mapping
List<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = new ArrayList<>();
final Boolean isActive = false;
final boolean isSpecificToInstallment = false;
BigDecimal decimalValue = null;
Date dueDate = null;
// create term variations for flat and declining balance loans
createLoanTermVariationsForRegularLoans(loan, graceOnPrincipal, graceOnInterest, extraTerms, interestRate, rescheduleFromDate,
adjustedDueDate, loanRescheduleRequest, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, decimalValue, dueDate);
// create a new entry in the m_loan_reschedule_request table
this.loanRescheduleRequestRepository.save(loanRescheduleRequest);
this.loanRepository.save(loan);
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequest.getId())
.withLoanId(loan.getId()).build();
@@ -295,6 +296,63 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
}
}
private void createLoanTermVariationsForRegularLoans(final Loan loan, final Integer graceOnPrincipal, final Integer graceOnInterest,
final Integer extraTerms, final BigDecimal interestRate, Date rescheduleFromDate, Date adjustedDueDate,
final LoanRescheduleRequest loanRescheduleRequest,
List<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings, final Boolean isActive,
final boolean isSpecificToInstallment, BigDecimal decimalValue, Date dueDate) {
if (rescheduleFromDate != null && adjustedDueDate != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.DUE_DATE.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, adjustedDueDate, loanRescheduleRequestToTermVariationMappings,
isActive, isSpecificToInstallment, decimalValue, parent);
}
if (rescheduleFromDate != null && interestRate != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, interestRate, parent);
}
if (rescheduleFromDate != null && graceOnPrincipal != null) {
final Integer termType = LoanTermVariationType.GRACE_ON_PRINCIPAL.getValue();
LoanTermVariations parent = null;
parent = createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings,
isActive, isSpecificToInstallment, BigDecimal.valueOf(graceOnPrincipal), parent);
BigDecimal extraTermsBasedOnGracePeriods = BigDecimal.valueOf(graceOnPrincipal);
createLoanTermVariations(LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue(), loan, rescheduleFromDate, dueDate,
loanRescheduleRequestToTermVariationMappings, isActive, isSpecificToInstallment, extraTermsBasedOnGracePeriods, parent);
}
if (rescheduleFromDate != null && graceOnInterest != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.GRACE_ON_INTEREST.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, BigDecimal.valueOf(graceOnInterest), parent);
}
if (rescheduleFromDate != null && extraTerms != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, BigDecimal.valueOf(extraTerms), parent);
}
loanRescheduleRequest.updateLoanRescheduleRequestToTermVariationMappings(loanRescheduleRequestToTermVariationMappings);
}
private LoanTermVariations createLoanTermVariations(final Integer termType, final Loan loan, Date rescheduleFromDate,
Date adjustedDueDate, List<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings,
final Boolean isActive, final boolean isSpecificToInstallment, final BigDecimal decimalValue, LoanTermVariations parent) {
LoanTermVariations loanTermVariation = new LoanTermVariations(termType, rescheduleFromDate, decimalValue, adjustedDueDate,
isSpecificToInstallment, loan, loan.status().getValue(), isActive, parent);
loanRescheduleRequestToTermVariationMappings.add(LoanRescheduleRequestToTermVariationMapping.createNew(loanTermVariation));
return loanTermVariation;
}
@Override
@Transactional
public CommandProcessingResult approve(JsonCommand jsonCommand) {
@@ -322,132 +380,112 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter));
changes.put("approvedByUserId", appUser.getId());
if (!changes.isEmpty()) {
Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
Loan loan = loanRescheduleRequest.getLoan();
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getDisbursementDate().toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan,
loanRescheduleRequest.getRescheduleFromDate());
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mathContext = new MathContext(8, roundingMode);
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
CalendarInstance restCalendarInstance = null;
CalendarInstance compoundingCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
}
final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
Calendar loanCalendar = null;
if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar();
}
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
loanCalendar, floatingRateDTO, isSkipRepaymentOnFirstMonth, numberOfDays);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
Collection<LoanCharge> waiveLoanCharges = new ArrayList<>();
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
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, compoundingDetails);
loan.addLoanRepaymentScheduleInstallment(repaymentScheduleInstallment);
repaymentScheduleInstallments.add(repaymentScheduleInstallment) ;
}
else {
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
if (repaymentScheduleInstallment.getInstallmentNumber().equals(period.oldPeriodNumber())) {
LocalDate periodDueDate = repaymentScheduleInstallment.getDueDate();
Money zeroAmount = Money.of(currency, new BigDecimal(0));
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.isNotFullyPaidOff()) {
if (repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isGreaterThan(zeroAmount)
|| repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isGreaterThan(zeroAmount)) {
waiveLoanCharges.addAll(loan.getLoanCharges(periodDueDate));
}
}
break;
}
LocalDate rescheduleFromDate = null;
Set<LoanTermVariations> activeLoanTermVariations = loan.getActiveLoanTermVariations();
LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists();
if (dueDateVariationInCurrentRequest != null && activeLoanTermVariations != null) {
LocalDate fromScheduleDate = dueDateVariationInCurrentRequest.fetchTermApplicaDate();
LocalDate currentScheduleDate = fromScheduleDate;
LocalDate modifiedScheduleDate = dueDateVariationInCurrentRequest.fetchDateValue();
Map<LocalDate, LocalDate> changeMap = new HashMap<>();
changeMap.put(currentScheduleDate, modifiedScheduleDate);
for (LoanTermVariations activeLoanTermVariation : activeLoanTermVariations) {
if (activeLoanTermVariation.getTermType().isDueDateVariation()
&& activeLoanTermVariation.fetchDateValue().equals(dueDateVariationInCurrentRequest.fetchTermApplicaDate())) {
activeLoanTermVariation.markAsInactive();
rescheduleFromDate = activeLoanTermVariation.fetchTermApplicaDate();
dueDateVariationInCurrentRequest.setTermApplicableFrom(rescheduleFromDate.toDate());
} else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) {
while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) {
currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
changeMap.put(currentScheduleDate, modifiedScheduleDate);
}
if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) {
activeLoanTermVariation.setTermApplicableFrom(changeMap.get(activeLoanTermVariation.fetchTermApplicaDate())
.toDate());
}
}
}
for (LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
}
loan.updateRescheduledByUser(appUser);
loan.updateRescheduledOnDate(new LocalDate());
// waive all loan charges of zero instalments
waiveLoanCharges(loan, waiveLoanCharges);
// update the Loan summary
loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true, null);
// 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);
// update the derived fields of each loan repayments schedule
// instalments
for (final LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
repaymentScheduleInstallment.updateDerivedFields(currency, new LocalDate());
}
// updates maturity date
loan.updateLoanScheduleDependentDerivedFields();
// update the loan object
this.loanRepository.save(loan);
}
if (rescheduleFromDate == null) {
rescheduleFromDate = loanRescheduleRequest.getRescheduleFromDate();
}
for (LoanRescheduleRequestToTermVariationMapping mapping : loanRescheduleRequest
.getLoanRescheduleRequestToTermVariationMappings()) {
mapping.getLoanTermVariations().updateIsActive(true);
}
BigDecimal annualNominalInterestRate = null;
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
loan.constructLoanTermVariations(scheduleGeneratorDTO.getFloatingRateDTO(), annualNominalInterestRate, loanTermVariations);
loanApplicationTerms.getLoanTermVariations().setExceptionData(loanTermVariations);
/*for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
loanTermVariation.setApplicableFromDate(adjustedDate);
}
}
}*/
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mathContext = new MathContext(8, roundingMode);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
final LoanLifecycleStateMachine loanLifecycleStateMachine = null;
loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms,
loan, loanApplicationTerms.getHolidayDetailDTO(),
loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
loan.updateLoanSchedule(loanSchedule.getInstallments(), appUser);
loan.recalculateAllCharges();
ChangedTransactionDetail changedTransactionDetail = loan.processTransactions();
for (LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
}
loan.updateRescheduledByUser(appUser);
loan.updateRescheduledOnDate(new LocalDate());
// update the status of the request
loanRescheduleRequest.approve(appUser, approvedOnDate);
// update the loan object
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
// update loan with references to the newly created
// transactions
loan.addLoanTransaction(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan, true);
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build();
@@ -462,60 +500,24 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
}
}
/**
* waive all charges in the collection
*
* @param loan
* Loan object
* @param loanCharges
* collection of LoanCharge objects
* @return void
**/
private void waiveLoanCharges(Loan loan, Collection<LoanCharge> loanCharges) {
AppUser currentUser = this.platformSecurityContext.authenticatedUser();
this.loanAssembler.setHelpers(loan);
for (LoanCharge loanCharge : loanCharges) {
if (loanCharge.isChargePending()) {
Integer loanInstallmentNumber = null;
if (loanCharge.isInstalmentFee()) {
LoanInstallmentCharge chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
if (chargePerInstallment != null) {
loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
}
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
try {
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
}
final Map<String, Object> changes = new LinkedHashMap<>(3);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
LocalDate recalculateFrom = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
recalculateFrom = DateUtils.getLocalDateOfTenant();
}
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
Money accruedCharge = Money.zero(loan.getCurrency());
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy(
loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber);
for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) {
accruedCharge = accruedCharge.plus(chargePaidByData.getAmount());
}
}
final LoanTransaction loanTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge,
currentUser);
this.loanTransactionRepository.save(loanTransaction);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -528,11 +530,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
}
@Override
@Transactional
public CommandProcessingResult reject(JsonCommand jsonCommand) {
@@ -562,6 +559,11 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
if (!changes.isEmpty()) {
loanRescheduleRequest.reject(appUser, rejectedOnDate);
Set<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = loanRescheduleRequest
.getLoanRescheduleRequestToTermVariationMappings();
for (LoanRescheduleRequestToTermVariationMapping loanRescheduleRequestToTermVariationMapping : loanRescheduleRequestToTermVariationMappings) {
loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().markAsInactive();
}
}
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
@@ -593,20 +595,4 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
"Unknown data integrity issue with resource.");
}
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
FloatingRateDTO floatingRateDTO = null;
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
} catch (final FloatingRateNotFoundException ex) {
// Do not do anything
}
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
}
@@ -551,6 +551,22 @@ public class LoanEnumerations {
optionData = new EnumOptionData(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue().longValue(),
LoanTermVariationType.PRINCIPAL_AMOUNT.getCode(), "principalAmount");
break;
case GRACE_ON_INTEREST:
optionData = new EnumOptionData(LoanTermVariationType.GRACE_ON_INTEREST.getValue().longValue(),
LoanTermVariationType.GRACE_ON_INTEREST.getCode(), "graceOnInterest");
break;
case GRACE_ON_PRINCIPAL:
optionData = new EnumOptionData(LoanTermVariationType.GRACE_ON_PRINCIPAL.getValue().longValue(),
LoanTermVariationType.GRACE_ON_PRINCIPAL.getCode(), "graceOnPrincipal");
break;
case EXTEND_REPAYMENT_PERIOD:
optionData = new EnumOptionData(LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue().longValue(),
LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getCode(), "extendRepaymentPeriod");
break;
case INTEREST_RATE_FROM_INSTALLMENT:
optionData = new EnumOptionData(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getValue().longValue(),
LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getCode(), "interestRateForInstallment");
break;
default:
optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(),
LoanTermVariationType.INVALID.getCode(), "Invalid");
@@ -0,0 +1,141 @@
ALTER TABLE `m_loan_term_variations`
ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `applied_on_loan_status`;
ALTER TABLE `m_loan_term_variations`
ADD COLUMN `parent_id` BIGINT(20) NULL DEFAULT NULL AFTER `is_active`;
ALTER TABLE `m_loan_term_variations`
ADD COLUMN `reshedule_request_id` BIGINT(20) NULL DEFAULT NULL AFTER `parent_id`;
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
7 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.grace_on_interest decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.grace_on_interest is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
8 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.grace_on_principal decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.grace_on_principal is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
4 term_type,
mlrr.reschedule_from_date applicable_date,
null decimal_value,
mlrr.adjusted_due_date date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.adjusted_due_date is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
9 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.extra_terms decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.extra_terms is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
2 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.interest_rate decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.interest_rate is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
9 term_type,
mlrr.reschedule_from_date applicable_date,
if(ifnull(mlrr.grace_on_principal,0) > ifnull(mlrr.grace_on_interest,0),mlrr.grace_on_principal,mlrr.grace_on_interest) decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
(select distinct mlt.id
from m_loan_term_variations mlt
where mlt.reshedule_request_id is not null
and (mlt.term_type =8 or mlt.term_type = 7)
and mlt.reshedule_request_id = mlrr.id
order by mlt.term_type desc limit 1) parent_id
from m_loan_reschedule_request mlrr
where (mlrr.grace_on_interest is not null or mlrr.grace_on_principal is not null));
ALTER TABLE `m_loan_reschedule_request`
DROP COLUMN `grace_on_principal`,
DROP COLUMN `grace_on_interest`,
DROP COLUMN `extra_terms`,
DROP COLUMN `interest_rate`,
DROP COLUMN `adjusted_due_date`;
CREATE TABLE `m_loan_reschedule_request_term_variations_mapping` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_reschedule_request_id` BIGINT(20) NOT NULL,
`loan_term_variations_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`id`),
INDEX `FK__m_loan_reschedule_request` (`loan_reschedule_request_id`),
INDEX `FK__m_loan_term_variations` (`loan_term_variations_id`),
CONSTRAINT `FK__m_loan_reschedule_request` FOREIGN KEY (`loan_reschedule_request_id`) REFERENCES `m_loan_reschedule_request` (`id`),
CONSTRAINT `FK__m_loan_term_variations` FOREIGN KEY (`loan_term_variations_id`) REFERENCES `m_loan_term_variations` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;
insert ignore into m_loan_reschedule_request_term_variations_mapping (`loan_reschedule_request_id`, `loan_term_variations_id`)
(
select distinct mltv.reshedule_request_id,mltv.id
from m_loan_term_variations mltv
where reshedule_request_id is not null
);
ALTER TABLE `m_loan_term_variations`
DROP COLUMN `reshedule_request_id`;