MIFOSX-1613 : changes for overdue status and npa

This commit is contained in:
Pramod Nuthakki
2014-12-31 18:28:43 +05:30
parent d310eb308d
commit 4b6d794a47
30 changed files with 1198 additions and 374 deletions
+24 -2
View File
@@ -11295,6 +11295,13 @@ An enumeration that indicates the type of transaction processing strategy to be
be overdue before being classified as a NPA (non performing asset) be overdue before being classified as a NPA (non performing asset)
</td> </td>
</tr> </tr>
<tr class=alt>
<td>accountMovesOutOfNPAOnlyOnArrearsCompletion</td>
</tr>
<tr>
<td class=fielddesc>Optional: Boolean - if provided as true, Loan Account moves out of NPA state only when all arrears are cleared
</td>
</tr>
<tr class=alt> <tr class=alt>
<td>accountingRule</td> <td>accountingRule</td>
</tr> </tr>
@@ -11356,6 +11363,12 @@ An enumeration that indicates the type of transaction processing strategy to be
<tr> <tr>
<td class=fielddesc>Specifies rest frequency start date for interest recalculation.<br> <td class=fielddesc>Specifies rest frequency start date for interest recalculation.<br>
</tr> </tr>
<tr class=alt>
<td>isArrearsBasedOnOriginalSchedule</td>
</tr>
<tr>
<td class=fielddesc>If Specified as true, arrears will be identified based on original schedule.<br>
</tr>
</table> </table>
</div> </div>
</div> </div>
@@ -11470,7 +11483,8 @@ GET https://DomainName/api/v1/loanproducts
"id":1, "id":1,
"code":"interestRecalculationFrequencyType.same.as.repayment.period", "code":"interestRecalculationFrequencyType.same.as.repayment.period",
"value":"Same as repayment period" "value":"Same as repayment period"
} },
"isArrearsBasedOnOriginalSchedule" : true
}, },
"accountingRule": { "accountingRule": {
"id": 2, "id": 2,
@@ -12598,7 +12612,7 @@ GET https://DomainName/api/v1/loanproducts/{productId}
numberOfRepaymentVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
multiDisburseLoan,maxTrancheCount, multiDisburseLoan,maxTrancheCount,
outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds outstandingLoanBalance,overdueDaysForNPA,accountMovesOutOfNPAOnlyOnArrearsCompletion,holdGuaranteeFunds
</td> </td>
</tr> </tr>
</table> </table>
@@ -12631,6 +12645,14 @@ GET https://DomainName/api/v1/loanproducts/{productId}
<td>interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType</td> <td>interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType</td>
</tr> </tr>
</table> </table>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Additional Optional Fields if interest recalculation is enabled(true) </div></td>
</tr>
<tr class=alt>
<td>isArrearsBasedOnOriginalSchedule</td>
</tr>
</table>
<table class=matrixHeading> <table class=matrixHeading>
<tr class="matrixHeadingBG"> <tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Additional Optional Fields if interest recalculation is enabled(true) and recalculationRestFrequencyType is not same as repayment period</div></td> <td><div class="mifosXHeading2">Additional Optional Fields if interest recalculation is enabled(true) and recalculationRestFrequencyType is not same as repayment period</div></td>
@@ -3584,7 +3584,7 @@ public class ClientLoanIntegrationTest {
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS, LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", REST_START_DATE, null, overdueFeeChargeId.toString()); LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", REST_START_DATE, null, overdueFeeChargeId.toString(), false);
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, null); REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, null);
@@ -3766,6 +3766,156 @@ public class ClientLoanIntegrationTest {
} }
@Test
public void testLoanScheduleWithInterestRecalculation_WITH_CURRENT_REPAYMENT_BASED_ARREARS_AGEING() {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -14);
final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST,
LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, null);
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0));
Assert.assertNotNull(loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
List<Map<String, Object>> expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2505.73", "23.18", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2517.29", "11.62", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2505.67", "23.24", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2529.02", "11.67", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -7);
HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
List dates = (List) loanSummary.get("overdueSinceDate");
assertEquals(todaysDate.get(Calendar.YEAR), dates.get(0));
assertEquals(todaysDate.get(Calendar.MONTH) + 1, dates.get(1));
assertEquals(todaysDate.get(Calendar.DAY_OF_MONTH), dates.get(2));
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -8);
final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -7);
loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
dates = (List) loanSummary.get("overdueSinceDate");
assertEquals(todaysDate.get(Calendar.YEAR), dates.get(0));
assertEquals(todaysDate.get(Calendar.MONTH) + 1, dates.get(1));
assertEquals(todaysDate.get(Calendar.DAY_OF_MONTH), dates.get(2));
}
@Test
public void testLoanScheduleWithInterestRecalculation_WITH_ORIGINAL_REPAYMENT_BASED_ARREARS_AGEING() {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -14);
final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST,
LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, null, null, true);
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0));
Assert.assertNotNull(loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
List<Map<String, Object>> expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2505.73", "23.18", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2517.29", "11.62", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2505.67", "23.24", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2529.02", "11.67", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -7);
HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
List dates = (List) loanSummary.get("overdueSinceDate");
assertEquals(todaysDate.get(Calendar.YEAR), dates.get(0));
assertEquals(todaysDate.get(Calendar.MONTH) + 1, dates.get(1));
assertEquals(todaysDate.get(Calendar.DAY_OF_MONTH), dates.get(2));
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -8);
final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime());
Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -7);
loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
dates = (List) loanSummary.get("overdueSinceDate");
Assert.assertNull(dates);
}
private void addRepaymentValues(List<Map<String, Object>> expectedvalues, Calendar todaysDate, int addMonths, String principalDue, private void addRepaymentValues(List<Map<String, Object>> expectedvalues, Calendar todaysDate, int addMonths, String principalDue,
String interestDue, String feeChargesDue, String penaltyChargesDue) { String interestDue, String feeChargesDue, String penaltyChargesDue) {
Map<String, Object> values = new HashMap<>(3); Map<String, Object> values = new HashMap<>(3);
@@ -3795,13 +3945,14 @@ public class ClientLoanIntegrationTest {
final String recalculationRestFrequencyDate, final Account[] accounts) { final String recalculationRestFrequencyDate, final Account[] accounts) {
return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval,
recalculationRestFrequencyDate, accounts, null); recalculationRestFrequencyDate, accounts, null, false);
} }
private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
final String recalculationRestFrequencyDate, final Account[] accounts, final String chargeId) { final String recalculationRestFrequencyDate, final Account[] accounts, final String chargeId,
boolean isArrearsBasedOnOriginalSchedule) {
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
LoanProductTestBuilder builder = new LoanProductTestBuilder() LoanProductTestBuilder builder = new LoanProductTestBuilder()
.withPrincipal("10000000.00") .withPrincipal("10000000.00")
@@ -3820,6 +3971,8 @@ public class ClientLoanIntegrationTest {
builder = builder.withAccountingRulePeriodicAccrual(accounts); builder = builder.withAccountingRulePeriodicAccrual(accounts);
} }
if (isArrearsBasedOnOriginalSchedule) builder = builder.withArrearsConfiguration();
final String loanProductJSON = builder.build(chargeId); final String loanProductJSON = builder.build(chargeId);
return this.loanTransactionHelper.getLoanProductId(loanProductJSON); return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
} }
@@ -92,6 +92,7 @@ public class LoanProductTestBuilder {
private String mandatoryGuarantee = null; private String mandatoryGuarantee = null;
private String minimumGuaranteeFromOwnFunds = null; private String minimumGuaranteeFromOwnFunds = null;
private String minimumGuaranteeFromGuarantor = null; private String minimumGuaranteeFromGuarantor = null;
private String isArrearsBasedOnOriginalSchedule = null;
public String build(final String chargeId) { public String build(final String chargeId) {
final HashMap<String, Object> map = new HashMap<>(); final HashMap<String, Object> map = new HashMap<>();
@@ -148,6 +149,9 @@ public class LoanProductTestBuilder {
map.put("recalculationRestFrequencyType", recalculationRestFrequencyType); map.put("recalculationRestFrequencyType", recalculationRestFrequencyType);
map.put("recalculationRestFrequencyInterval", recalculationRestFrequencyInterval); map.put("recalculationRestFrequencyInterval", recalculationRestFrequencyInterval);
map.put("recalculationRestFrequencyDate", recalculationRestFrequencyDate); map.put("recalculationRestFrequencyDate", recalculationRestFrequencyDate);
if (isArrearsBasedOnOriginalSchedule != null) {
map.put("isArrearsBasedOnOriginalSchedule", isArrearsBasedOnOriginalSchedule);
}
} }
if (holdGuaranteeFunds != null) { if (holdGuaranteeFunds != null) {
map.put("holdGuaranteeFunds", this.holdGuaranteeFunds); map.put("holdGuaranteeFunds", this.holdGuaranteeFunds);
@@ -398,6 +402,11 @@ public class LoanProductTestBuilder {
return this; return this;
} }
public LoanProductTestBuilder withArrearsConfiguration() {
this.isArrearsBasedOnOriginalSchedule = "true";
return this;
}
public LoanProductTestBuilder withOnHoldFundDetails(final String mandatoryGuarantee, final String minimumGuaranteeFromGuarantor, public LoanProductTestBuilder withOnHoldFundDetails(final String mandatoryGuarantee, final String minimumGuaranteeFromGuarantor,
final String minimumGuaranteeFromOwnFunds) { final String minimumGuaranteeFromOwnFunds) {
this.holdGuaranteeFunds = true; this.holdGuaranteeFunds = true;
@@ -13,7 +13,6 @@ import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstan
import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName; import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -37,7 +36,6 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
@@ -12,8 +12,14 @@ public class BusinessEventNotificationConstants {
public static enum BUSINESS_EVENTS { public static enum BUSINESS_EVENTS {
LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_TRANSACTION( LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_TRANSACTION(
"loan_undo_transaction"), LOAN_MAKE_REPAYMENT("loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF( "loan_undo_transaction"), LOAN_ADJUST_TRANSACTION("loan_adjust_transaction"), LOAN_MAKE_REPAYMENT(
"loan_undo_writtenoff"); "loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF("loan_undo_writtenoff"), LOAN_DISBURSAL(
"loan_disbursal"), LOAN_WAIVE_INTEREST("loan_waive_interest"), LOAN_CLOSE("loan_close"), LOAN_CLOSE_AS_RESCHEDULE(
"loan_close_as_reschedule"), LOAN_ADD_CHARGE("loan_add_charge"), LOAN_UPDATE_CHARGE("loan_update_charge"), LOAN_WAIVE_CHARGE(
"loan_waive_charge"), LOAN_DELETE_CHARGE("loan_delete_charge"), LOAN_CHARGE_PAYMENT("loan_charge_payment"), LOAN_INITIATE_TRANSFER(
"loan_initiate_transfer"), LOAN_ACCEPT_TRANSFER("loan_accept_transfer"), LOAN_WITHDRAW_TRANSFER("loan_withdraw_transfer"), LOAN_REJECT_TRANSFER(
"loan_reject_transfer"), LOAN_REASSIGN_OFFICER("loan_reassign_officer"), LOAN_REMOVE_OFFICER("loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE(
"loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"), LOAN_REFUND("loan_refund");
private final String value; private final String value;
@@ -36,4 +42,19 @@ public class BusinessEventNotificationConstants {
return this.value; return this.value;
} }
} }
public static enum BUSINESS_ENTITY {
LOAN("loan"), LOAN_TRANSACTION("loan_transaction"), LOAN_CHARGE("loan_charge"), LOAN_ADJUSTED_TRANSACTION(
"loan_adjusted_transaction");
private final String value;
private BUSINESS_ENTITY(final String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
} }
@@ -5,7 +5,9 @@
*/ */
package org.mifosplatform.portfolio.common.service; package org.mifosplatform.portfolio.common.service;
import org.springframework.data.jpa.domain.AbstractPersistable; import java.util.Map;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
/** /**
* The interface to be implemented by classes that want to be informed when a * The interface to be implemented by classes that want to be informed when a
@@ -18,11 +20,11 @@ public interface BusinessEventListner {
/** /**
* Implement this method for notifications before executing Business Event * Implement this method for notifications before executing Business Event
*/ */
public void businessEventToBeExecuted(AbstractPersistable<Long> businessEventEntity); public void businessEventToBeExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity);
/** /**
* Implement this method for notifications after executing Business Event * Implement this method for notifications after executing Business Event
*/ */
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity); public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity);
} }
@@ -6,7 +6,8 @@
package org.mifosplatform.portfolio.common.service; package org.mifosplatform.portfolio.common.service;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import java.util.Map;
/** /**
* Implemented class is responsible for notifying the business event to * Implemented class is responsible for notifying the business event to
@@ -19,13 +20,13 @@ public interface BusinessEventNotifierService {
* Method should be called to notify listeners before Business event * Method should be called to notify listeners before Business event
* execution for any pre-processing of event * execution for any pre-processing of event
*/ */
public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity); public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity);
/** /**
* Method should be called to notify listeners after Business event * Method should be called to notify listeners after Business event
* execution for any post-processing of event * execution for any post-processing of event
*/ */
public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity); public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity);
/** /**
* Method is to register a class as listener for pre-processing of any * Method is to register a class as listener for pre-processing of any
@@ -10,8 +10,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@@ -31,7 +31,7 @@ public class BusinessEventNotifierServiceImpl implements BusinessEventNotifierSe
* org.springframework.data.jpa.domain.AbstractPersistable) * org.springframework.data.jpa.domain.AbstractPersistable)
*/ */
@Override @Override
public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity) { public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity) {
List<BusinessEventListner> businessEventListners = this.preListners.get(businessEvent); List<BusinessEventListner> businessEventListners = this.preListners.get(businessEvent);
if (businessEventListners != null) { if (businessEventListners != null) {
for (BusinessEventListner eventListner : businessEventListners) { for (BusinessEventListner eventListner : businessEventListners) {
@@ -51,7 +51,7 @@ public class BusinessEventNotifierServiceImpl implements BusinessEventNotifierSe
* org.springframework.data.jpa.domain.AbstractPersistable) * org.springframework.data.jpa.domain.AbstractPersistable)
*/ */
@Override @Override
public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity) { public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, Map<BUSINESS_ENTITY, Object> businessEventEntity) {
List<BusinessEventListner> businessEventListners = this.postListners.get(businessEvent); List<BusinessEventListner> businessEventListners = this.postListners.get(businessEvent);
if (businessEventListners != null) { if (businessEventListners != null) {
for (BusinessEventListner eventListner : businessEventListners) { for (BusinessEventListner eventListner : businessEventListners) {
@@ -299,9 +299,6 @@ public class Loan extends AbstractPersistable<Long> {
@Embedded @Embedded
private LoanSummary summary; private LoanSummary summary;
@OneToOne(mappedBy = "loan", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
private LoanSummaryArrearsAging summaryArrearsAging;
@Transient @Transient
private boolean accountNumberRequiresAutoGeneration = false; private boolean accountNumberRequiresAutoGeneration = false;
@Transient @Transient
@@ -1146,7 +1143,6 @@ public class Loan extends AbstractPersistable<Long> {
if (isNotDisbursed()) { if (isNotDisbursed()) {
this.summary.zeroFields(); this.summary.zeroFields();
this.summaryArrearsAging = null;
this.totalOverpaid = null; this.totalOverpaid = null;
} else { } else {
final Money overpaidBy = calculateTotalOverpayment(); final Money overpaidBy = calculateTotalOverpayment();
@@ -1158,13 +1154,6 @@ public class Loan extends AbstractPersistable<Long> {
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal(); final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper, this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper,
isDisbursed()); isDisbursed());
if (this.summaryArrearsAging == null) {
this.summaryArrearsAging = new LoanSummaryArrearsAging(this);
}
this.summaryArrearsAging.updateSummary(loanCurrency(), this.repaymentScheduleInstallments, this.loanSummaryWrapper);
if (this.summaryArrearsAging.isNotInArrears(loanCurrency())) {
this.summaryArrearsAging = null;
}
updateLoanOutstandingBalaces(); updateLoanOutstandingBalaces();
} }
} }
@@ -1928,7 +1917,7 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount); actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount); actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
} else if(approvedLoanAmount.compareTo(this.proposedPrincipal) == 1){ } else if (approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) {
final String errorMessage = "Loan approved amount can't be greater than loan amount demanded."; final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded", throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded",
errorMessage, this.proposedPrincipal, approvedLoanAmount); errorMessage, this.proposedPrincipal, approvedLoanAmount);
@@ -3377,7 +3366,7 @@ public class Loan extends AbstractPersistable<Long> {
return status().isRejected(); return status().isRejected();
} }
private boolean isOpen() { public boolean isOpen() {
return status().isActive(); return status().isActive();
} }
@@ -5113,4 +5102,5 @@ public class Loan extends AbstractPersistable<Long> {
return lastTransactionDate == null ? now : lastTransactionDate; return lastTransactionDate == null ? now : lastTransactionDate;
} }
} }
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -44,6 +45,7 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
@@ -126,6 +128,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final String txnExternalId, final boolean isRecoveryRepayment, boolean isAccountTransfer) { final String txnExternalId, final boolean isRecoveryRepayment, boolean isAccountTransfer) {
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
// TODO: Is it required to validate transaction date with meeting dates // TODO: Is it required to validate transaction date with meeting dates
// if repayments is synced with meeting? // if repayments is synced with meeting?
@@ -221,7 +225,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
recalculateAccruals(loan); recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, newRepaymentTransaction); this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRepaymentTransaction));
builderResult.withEntityId(newRepaymentTransaction.getId()) // builderResult.withEntityId(newRepaymentTransaction.getId()) //
.withOfficeId(loan.getOfficeId()) // .withOfficeId(loan.getOfficeId()) //
@@ -276,7 +281,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
@@ -311,6 +317,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
recalculateAccruals(loan); recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newPaymentTransaction));
return newPaymentTransaction; return newPaymentTransaction;
} }
@@ -349,7 +357,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
@@ -374,7 +383,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction));
builderResult.withEntityId(newRefundTransaction.getId()) // builderResult.withEntityId(newRefundTransaction.getId()) //
.withOfficeId(loan.getOfficeId()) // .withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) // .withClientId(loan.getClientId()) //
@@ -556,7 +566,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) { BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
@@ -584,6 +595,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
recalculateAccruals(loan); recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction));
builderResult.withEntityId(newRefundTransaction.getId()) // builderResult.withEntityId(newRefundTransaction.getId()) //
.withOfficeId(loan.getOfficeId()) // .withOfficeId(loan.getOfficeId()) //
@@ -592,4 +605,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return newRefundTransaction; return newRefundTransaction;
} }
private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
} }
@@ -927,4 +927,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
return paidChargePerInstallment; return paidChargePerInstallment;
} }
public Loan getLoan() {
return this.loan;
}
} }
@@ -306,4 +306,29 @@ public final class LoanSummary {
public BigDecimal getTotalPrincipalWrittenOff() { public BigDecimal getTotalPrincipalWrittenOff() {
return this.totalPrincipalWrittenOff; return this.totalPrincipalWrittenOff;
} }
public BigDecimal getTotalInterestWaived() {
return this.totalInterestWaived;
}
public BigDecimal getTotalFeeChargesRepaid() {
return this.totalFeeChargesRepaid;
}
public BigDecimal getTotalFeeChargesWaived() {
return this.totalFeeChargesWaived;
}
public BigDecimal getTotalPenaltyChargesRepaid() {
return this.totalPenaltyChargesRepaid;
}
public BigDecimal getTotalPenaltyChargesWaived() {
return this.totalPenaltyChargesWaived;
}
} }
@@ -1,130 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.springframework.data.domain.Persistable;
/**
* <p>
* A specialist view of arrears & aging summary details of a {@link Loan}.
* <p>
*
* <p>
* This is intentionally split from the loan summary concept and maps to its own
* table that contains only arrears and aging information. The reason is to make
* any overnight batch update of these details simpler and improve performance.
* </p>
*/
@Entity
@Table(name = "m_loan_arrears_aging")
@org.hibernate.annotations.GenericGenerator(name = "loan-primarykey", strategy = "foreign", parameters = { //
@org.hibernate.annotations.Parameter(name = "property", value = "loan") //
})
public final class LoanSummaryArrearsAging implements Persistable<Long> {
@Id
@GeneratedValue(generator = "loan-primarykey")
@Column(name = "loan_id", unique = true, nullable = false)
private Long loanId;
@Column(name = "principal_overdue_derived", scale = 6, precision = 19)
private BigDecimal totalPrincipalOverdue;
@Column(name = "interest_overdue_derived", scale = 6, precision = 19)
private BigDecimal totalInterestOverdue;
@Column(name = "fee_charges_overdue_derived", scale = 6, precision = 19)
private BigDecimal totalFeeChargesOverdue;
@Column(name = "penalty_charges_overdue_derived", scale = 6, precision = 19)
private BigDecimal totalPenaltyChargesOverdue;
@Column(name = "total_overdue_derived", scale = 6, precision = 19)
private BigDecimal totalOverdue;
@Temporal(TemporalType.DATE)
@Column(name = "overdue_since_date_derived")
private Date overdueSinceDate;
@OneToOne(optional = false)
@PrimaryKeyJoinColumn
private Loan loan;
protected LoanSummaryArrearsAging() {
//
}
public LoanSummaryArrearsAging(final Loan loan) {
this.loan = loan;
zeroFields();
}
@Override
public Long getId() {
return this.loanId;
}
@Override
public boolean isNew() {
return null == getId();
}
public void zeroFields() {
this.totalPrincipalOverdue = BigDecimal.ZERO;
this.totalInterestOverdue = BigDecimal.ZERO;
this.totalFeeChargesOverdue = BigDecimal.ZERO;
this.totalPenaltyChargesOverdue = BigDecimal.ZERO;
this.totalOverdue = BigDecimal.ZERO;
this.overdueSinceDate = null;
}
public void updateSummary(final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final LoanSummaryWrapper summaryWrapper) {
this.totalPrincipalOverdue = summaryWrapper.calculateTotalPrincipalOverdueOn(repaymentScheduleInstallments, currency,
DateUtils.getLocalDateOfTenant()).getAmount();
this.totalInterestOverdue = summaryWrapper.calculateTotalInterestOverdueOn(repaymentScheduleInstallments, currency,
DateUtils.getLocalDateOfTenant()).getAmount();
this.totalFeeChargesOverdue = summaryWrapper.calculateTotalFeeChargesOverdueOn(repaymentScheduleInstallments, currency,
DateUtils.getLocalDateOfTenant()).getAmount();
this.totalPenaltyChargesOverdue = summaryWrapper.calculateTotalPenaltyChargesOverdueOn(repaymentScheduleInstallments, currency,
DateUtils.getLocalDateOfTenant()).getAmount();
final Money totalOverdue = Money.of(currency, this.totalPrincipalOverdue).plus(this.totalInterestOverdue)
.plus(this.totalFeeChargesOverdue).plus(this.totalPenaltyChargesOverdue);
this.totalOverdue = totalOverdue.getAmount();
final LocalDate overdueSinceLocalDate = summaryWrapper.determineOverdueSinceDateFrom(repaymentScheduleInstallments, currency,
DateUtils.getLocalDateOfTenant());
if (overdueSinceLocalDate != null) {
this.overdueSinceDate = overdueSinceLocalDate.toDate();
} else {
this.overdueSinceDate = null;
}
}
public boolean isNotInArrears(final MonetaryCurrency currency) {
return Money.of(currency, this.totalOverdue).isZero();
}
}
@@ -26,6 +26,7 @@ import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
import org.mifosplatform.portfolio.account.domain.AccountTransferDetails; import org.mifosplatform.portfolio.account.domain.AccountTransferDetails;
import org.mifosplatform.portfolio.account.domain.AccountTransferType; import org.mifosplatform.portfolio.account.domain.AccountTransferType;
import org.mifosplatform.portfolio.account.service.AccountTransfersWritePlatformService; import org.mifosplatform.portfolio.account.service.AccountTransfersWritePlatformService;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.service.BusinessEventListner; import org.mifosplatform.portfolio.common.service.BusinessEventListner;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
@@ -46,7 +47,6 @@ import org.mifosplatform.portfolio.savings.domain.DepositAccountOnHoldTransactio
import org.mifosplatform.portfolio.savings.domain.SavingsAccount; import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@@ -83,8 +83,8 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL, new UndoAllFundTransactions()); this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL, new UndoAllFundTransactions());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL, this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
new ReverseAllFundsOnBusinessEvent()); new ReverseAllFundsOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_TRANSACTION, this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION,
new ReverseFundsOnBusinessEvent()); new AdjustFundsOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
new ReleaseFundsOnBusinessEvent()); new ReleaseFundsOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WRITTEN_OFF, new ReleaseAllFunds()); this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WRITTEN_OFF, new ReleaseAllFunds());
@@ -236,8 +236,9 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
releaseLoanIds.put(loanId, guarantorFundingDetails.getId()); releaseLoanIds.put(loanId, guarantorFundingDetails.getId());
try { try {
BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining(); BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining();
if(loan.getGuaranteeAmount().compareTo(loan.getPrincpal().getAmount()) == 1){ if (loan.getGuaranteeAmount().compareTo(loan.getPrincpal().getAmount()) == 1) {
remainingAmount = remainingAmount.multiply(loan.getPrincpal().getAmount()).divide(loan.getGuaranteeAmount(), roundingMode); remainingAmount = remainingAmount.multiply(loan.getPrincpal().getAmount()).divide(loan.getGuaranteeAmount(),
roundingMode);
} }
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType, AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType, toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType,
@@ -499,12 +500,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private class ValidateOnBusinessEvent implements BusinessEventListner { private class ValidateOnBusinessEvent implements BusinessEventListner {
@Override @Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {} public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override @Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) { public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
if (businessEventEntity instanceof Loan) { Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
Loan loan = (Loan) businessEventEntity; if (entity instanceof Loan) {
Loan loan = (Loan) entity;
validateGuarantorBusinessRules(loan); validateGuarantorBusinessRules(loan);
} }
} }
@@ -513,12 +515,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private class HoldFundsOnBusinessEvent implements BusinessEventListner { private class HoldFundsOnBusinessEvent implements BusinessEventListner {
@Override @Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {} public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override @Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) { public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
if (businessEventEntity instanceof Loan) { Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
Loan loan = (Loan) businessEventEntity; if (entity instanceof Loan) {
Loan loan = (Loan) entity;
holdGuarantorFunds(loan); holdGuarantorFunds(loan);
} }
} }
@@ -527,12 +530,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private class ReleaseFundsOnBusinessEvent implements BusinessEventListner { private class ReleaseFundsOnBusinessEvent implements BusinessEventListner {
@Override @Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {} public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override @Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) { public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
if (businessEventEntity instanceof LoanTransaction) { Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity; if (entity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) entity;
if (releaseLoanIds.containsKey(loanTransaction.getLoan().getId())) { if (releaseLoanIds.containsKey(loanTransaction.getLoan().getId())) {
completeGuarantorFund(loanTransaction); completeGuarantorFund(loanTransaction);
} else { } else {
@@ -545,12 +549,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private class ReverseFundsOnBusinessEvent implements BusinessEventListner { private class ReverseFundsOnBusinessEvent implements BusinessEventListner {
@Override @Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {} public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override @Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) { public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
if (businessEventEntity instanceof LoanTransaction) { Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity; if (entity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) entity;
List<Long> reersedTransactions = new ArrayList<>(1); List<Long> reersedTransactions = new ArrayList<>(1);
reersedTransactions.add(loanTransaction.getId()); reersedTransactions.add(loanTransaction.getId());
reverseTransaction(reersedTransactions); reverseTransaction(reersedTransactions);
@@ -558,15 +563,38 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
} }
} }
private class AdjustFundsOnBusinessEvent implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION);
if (entity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) entity;
List<Long> reersedTransactions = new ArrayList<>(1);
reersedTransactions.add(loanTransaction.getId());
reverseTransaction(reersedTransactions);
}
Object transactionentity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
if (transactionentity != null && transactionentity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) transactionentity;
releaseGuarantorFunds(loanTransaction);
}
}
}
private class ReverseAllFundsOnBusinessEvent implements BusinessEventListner { private class ReverseAllFundsOnBusinessEvent implements BusinessEventListner {
@Override @Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {} public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override @Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) { public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
if (businessEventEntity instanceof Loan) { Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
Loan loan = (Loan) businessEventEntity; if (entity instanceof Loan) {
Loan loan = (Loan) entity;
List<Long> reersedTransactions = new ArrayList<>(1); List<Long> reersedTransactions = new ArrayList<>(1);
reersedTransactions.addAll(loan.findExistingTransactionIds()); reersedTransactions.addAll(loan.findExistingTransactionIds());
reverseTransaction(reersedTransactions); reverseTransaction(reersedTransactions);
@@ -577,12 +605,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private class UndoAllFundTransactions implements BusinessEventListner { private class UndoAllFundTransactions implements BusinessEventListner {
@Override @Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {} public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override @Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) { public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
if (businessEventEntity instanceof Loan) { Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
Loan loan = (Loan) businessEventEntity; if (entity instanceof Loan) {
Loan loan = (Loan) entity;
reverseAllFundTransaction(loan); reverseAllFundTransaction(loan);
} }
} }
@@ -591,12 +620,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private class ReleaseAllFunds implements BusinessEventListner { private class ReleaseAllFunds implements BusinessEventListner {
@Override @Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {} public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
@Override @Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) { public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
if (businessEventEntity instanceof LoanTransaction) { Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity; if (entity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) entity;
releaseAllGuarantors(loanTransaction); releaseAllGuarantors(loanTransaction);
} }
} }
@@ -19,13 +19,10 @@ public class LoanSchedulePeriodData {
private final Integer period; private final Integer period;
private final LocalDate fromDate; private final LocalDate fromDate;
private final LocalDate dueDate; private final LocalDate dueDate;
@SuppressWarnings("unused")
private final LocalDate obligationsMetOnDate; private final LocalDate obligationsMetOnDate;
@SuppressWarnings("unused")
private final Boolean complete; private final Boolean complete;
private final Integer daysInPeriod; private final Integer daysInPeriod;
private final BigDecimal principalDisbursed; private final BigDecimal principalDisbursed;
@SuppressWarnings("unused")
private final BigDecimal principalOriginalDue; private final BigDecimal principalOriginalDue;
private final BigDecimal principalDue; private final BigDecimal principalDue;
private final BigDecimal principalPaid; private final BigDecimal principalPaid;
@@ -52,21 +49,14 @@ public class LoanSchedulePeriodData {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final BigDecimal totalOriginalDueForPeriod; private final BigDecimal totalOriginalDueForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalDueForPeriod; private final BigDecimal totalDueForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalPaidForPeriod; private final BigDecimal totalPaidForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalPaidInAdvanceForPeriod; private final BigDecimal totalPaidInAdvanceForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalPaidLateForPeriod; private final BigDecimal totalPaidLateForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalWaivedForPeriod; private final BigDecimal totalWaivedForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalWrittenOffForPeriod; private final BigDecimal totalWrittenOffForPeriod;
private final BigDecimal totalOutstandingForPeriod; private final BigDecimal totalOutstandingForPeriod;
private final BigDecimal totalOverdue; private final BigDecimal totalOverdue;
@SuppressWarnings("unused")
private final BigDecimal totalActualCostOfLoanForPeriod; private final BigDecimal totalActualCostOfLoanForPeriod;
public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed, public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed,
@@ -108,6 +98,26 @@ public class LoanSchedulePeriodData {
totalActualCostOfLoanForPeriod); totalActualCostOfLoanForPeriod);
} }
public static LoanSchedulePeriodData WithPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData, final boolean complete,
final BigDecimal principalPaid, final BigDecimal interestPaid, final BigDecimal feeChargesPaid,
final BigDecimal penaltyChargesPaid) {
return new LoanSchedulePeriodData(loanSchedulePeriodData.period, loanSchedulePeriodData.fromDate, loanSchedulePeriodData.dueDate,
loanSchedulePeriodData.obligationsMetOnDate, complete, loanSchedulePeriodData.principalOriginalDue, principalPaid,
loanSchedulePeriodData.principalWrittenOff, loanSchedulePeriodData.principalOutstanding,
loanSchedulePeriodData.principalLoanBalanceOutstanding, loanSchedulePeriodData.interestDue, interestPaid,
loanSchedulePeriodData.interestWaived, loanSchedulePeriodData.interestWrittenOff,
loanSchedulePeriodData.interestOutstanding, loanSchedulePeriodData.feeChargesDue, feeChargesPaid,
loanSchedulePeriodData.feeChargesWaived, loanSchedulePeriodData.feeChargesWrittenOff,
loanSchedulePeriodData.feeChargesOutstanding, loanSchedulePeriodData.penaltyChargesDue, penaltyChargesPaid,
loanSchedulePeriodData.penaltyChargesWaived, loanSchedulePeriodData.penaltyChargesWrittenOff,
loanSchedulePeriodData.penaltyChargesOutstanding, loanSchedulePeriodData.totalDueForPeriod,
loanSchedulePeriodData.totalPaidForPeriod, loanSchedulePeriodData.totalPaidInAdvanceForPeriod,
loanSchedulePeriodData.totalPaidLateForPeriod, loanSchedulePeriodData.totalWaivedForPeriod,
loanSchedulePeriodData.totalWrittenOffForPeriod, loanSchedulePeriodData.totalOutstandingForPeriod,
loanSchedulePeriodData.totalActualCostOfLoanForPeriod);
}
/* /*
* constructor used for creating period on loan schedule that is only a * constructor used for creating period on loan schedule that is only a
* disbursement (typically first period) * disbursement (typically first period)
@@ -417,4 +427,9 @@ public class LoanSchedulePeriodData {
public BigDecimal principalLoanBalanceOutstanding() { public BigDecimal principalLoanBalanceOutstanding() {
return this.principalLoanBalanceOutstanding; return this.principalLoanBalanceOutstanding;
} }
public Boolean getComplete() {
return this.complete;
}
} }
@@ -48,6 +48,7 @@ import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.collateral.domain.LoanCollateral; import org.mifosplatform.portfolio.collateral.domain.LoanCollateral;
import org.mifosplatform.portfolio.collateral.service.CollateralAssembler; import org.mifosplatform.portfolio.collateral.service.CollateralAssembler;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.fund.domain.Fund;
@@ -762,7 +763,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.noteRepository.save(note); this.noteRepository.save(note);
} }
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_APPROVED, loan); this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_APPROVED,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -806,7 +808,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Note note = Note.loanNote(loan, noteText); final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL, loan); this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -962,4 +965,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
return user; return user;
} }
private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
} }
@@ -0,0 +1,18 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.service;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
public interface LoanArrearsAgingService {
void updateLoanArrearsAgeingDetails();
void updateLoanArrearsAgeingDetailsWithOriginalSchedule(Loan loan);
void updateLoanArrearsAgeingDetails(Loan loan);
}
@@ -0,0 +1,503 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.service.BusinessEventListner;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.mifosplatform.scheduledjobs.service.ScheduledJobRunnerServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService, BusinessEventListner {
private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class);
private final BusinessEventNotifierService businessEventNotifierService;
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
private final JdbcTemplate jdbcTemplate;
@Autowired
public LoanArrearsAgingServiceImpl(final RoutingDataSource dataSource, final BusinessEventNotifierService businessEventNotifierService) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.businessEventNotifierService = businessEventNotifierService;
}
@PostConstruct
public void registerForNotification() {
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_REFUND, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_ADD_CHARGE, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE, this);
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_DISBURSAL, new DisbursementEventListner());
}
@Transactional
@Override
@CronTarget(jobName = JobName.UPDATE_LOAN_ARREARS_AGEING)
public void updateLoanArrearsAgeingDetails() {
this.jdbcTemplate.execute("truncate table m_loan_arrears_aging");
final StringBuilder updateSqlBuilder = new StringBuilder(900);
updateSqlBuilder
.append("INSERT INTO m_loan_arrears_aging(`loan_id`,`principal_overdue_derived`,`interest_overdue_derived`,`fee_charges_overdue_derived`,`penalty_charges_overdue_derived`,`total_overdue_derived`,`overdue_since_date_derived`)");
updateSqlBuilder.append("select ml.id as loanId,");
updateSqlBuilder
.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) as principal_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.interest_amount,0) - ifnull(mr.interest_completed_derived, 0))) as interest_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.fee_charges_amount,0) - ifnull(mr.fee_charges_completed_derived, 0))) as fee_charges_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as penalty_charges_overdue_derived,");
updateSqlBuilder.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) +");
updateSqlBuilder.append("SUM((ifnull(mr.interest_amount,0) - ifnull(mr.interest_completed_derived, 0))) +");
updateSqlBuilder.append("SUM((ifnull(mr.fee_charges_amount,0) - ifnull(mr.fee_charges_completed_derived, 0))) +");
updateSqlBuilder
.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as total_overdue_derived,");
updateSqlBuilder.append("MIN(mr.duedate) as overdue_since_date_derived ");
updateSqlBuilder.append(" FROM m_loan ml ");
updateSqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
updateSqlBuilder.append(" left join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id ");
updateSqlBuilder.append(" WHERE ml.loan_status_id = 300 "); // active
updateSqlBuilder.append(" and mr.completed_derived is false ");
updateSqlBuilder.append(" and mr.duedate < SUBDATE(CURDATE(),INTERVAL ifnull(ml.grace_on_arrears_ageing,0) day) ");
updateSqlBuilder.append(" and (prd.arrears_based_on_original_schedule = 0 or prd.arrears_based_on_original_schedule is null) ");
updateSqlBuilder.append(" GROUP BY ml.id");
List<String> insertStatements = updateLoanArrearsAgeingDetailsWithOriginalSchedule();
insertStatements.add(0, updateSqlBuilder.toString());
final int[] results = this.jdbcTemplate.batchUpdate(insertStatements.toArray(new String[0]));
int result = 0;
for (int i : results) {
result += i;
}
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
}
@Override
public void updateLoanArrearsAgeingDetailsWithOriginalSchedule(final Loan loan) {
int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
Integer.class, loan.getId());
List<String> updateStatement = new ArrayList<>();
OriginalScheduleExtractor originalScheduleExtractor = new OriginalScheduleExtractor(loan.getId().toString());
Map<Long, List<LoanSchedulePeriodData>> scheduleDate = this.jdbcTemplate.query(originalScheduleExtractor.schema,
originalScheduleExtractor);
if (scheduleDate.size() > 0) {
List<Map<String, Object>> transactions = getLoanSummary(loan.getId(), loan.getLoanSummary());
updateSchheduleWithPaidDetail(scheduleDate, transactions);
createInsertStatements(updateStatement, scheduleDate, count == 0);
if (updateStatement.size() == 1) {
this.jdbcTemplate.update(updateStatement.get(0));
} else {
String deletestatement = "DELETE FROM `m_loan_arrears_aging` WHERE `loan_id`=" + loan.getId();
this.jdbcTemplate.update(deletestatement);
}
}
}
@Override
public void updateLoanArrearsAgeingDetails(final Loan loan) {
int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
Integer.class, loan.getId());
String updateStatement = constructUpdateStatement(loan, count == 0);
if (updateStatement == null) {
String deletestatement = "DELETE FROM `m_loan_arrears_aging` WHERE `loan_id`=" + loan.getId();
this.jdbcTemplate.update(deletestatement);
} else {
this.jdbcTemplate.update(updateStatement);
}
}
private String constructUpdateStatement(final Loan loan, boolean isInsertStatement) {
String updateSql = null;
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
BigDecimal principalOverdue = BigDecimal.ZERO;
BigDecimal interestOverdue = BigDecimal.ZERO;
BigDecimal feeOverdue = BigDecimal.ZERO;
BigDecimal penaltyOverdue = BigDecimal.ZERO;
LocalDate overDueSince = LocalDate.now();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getDueDate().isBefore(LocalDate.now())) {
principalOverdue = principalOverdue.add(installment.getPrincipalOutstanding(loan.getCurrency()).getAmount());
interestOverdue = interestOverdue.add(installment.getInterestOutstanding(loan.getCurrency()).getAmount());
feeOverdue = feeOverdue.add(installment.getFeeChargesOutstanding(loan.getCurrency()).getAmount());
penaltyOverdue = penaltyOverdue.add(installment.getPenaltyChargesOutstanding(loan.getCurrency()).getAmount());
if (installment.isNotFullyPaidOff() && overDueSince.isAfter(installment.getDueDate())) {
overDueSince = installment.getDueDate();
}
}
}
BigDecimal totalOverDue = principalOverdue.add(interestOverdue).add(feeOverdue).add(penaltyOverdue);
if (totalOverDue.compareTo(BigDecimal.ZERO) == 1) {
if (isInsertStatement) {
updateSql = constructInsertStatement(loan.getId(), principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
overDueSince);
} else {
updateSql = constructUpdateStatement(loan.getId(), principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
overDueSince);
}
}
return updateSql;
}
private List<String> updateLoanArrearsAgeingDetailsWithOriginalSchedule() {
List<String> insertStatement = new ArrayList<>();
final StringBuilder loanIdentifier = new StringBuilder();
loanIdentifier.append("select ml.id as loanId FROM m_loan ml ");
loanIdentifier.append("INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
loanIdentifier
.append("inner join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id and prd.arrears_based_on_original_schedule = 1 ");
loanIdentifier
.append("WHERE ml.loan_status_id = 300 and mr.completed_derived is false and mr.duedate < SUBDATE(CURDATE(),INTERVAL ifnull(ml.grace_on_arrears_ageing,0) day) group by ml.id");
List<Long> loanIds = this.jdbcTemplate.queryForList(loanIdentifier.toString(), Long.class);
if (!loanIds.isEmpty()) {
String loanIdsAsString = loanIds.toString();
loanIdsAsString = loanIdsAsString.substring(1, loanIdsAsString.length() - 1);
OriginalScheduleExtractor originalScheduleExtractor = new OriginalScheduleExtractor(loanIdsAsString);
Map<Long, List<LoanSchedulePeriodData>> scheduleDate = this.jdbcTemplate.query(originalScheduleExtractor.schema,
originalScheduleExtractor);
List<Map<String, Object>> loanSummary = getLoanSummary(loanIdsAsString);
updateSchheduleWithPaidDetail(scheduleDate, loanSummary);
createInsertStatements(insertStatement, scheduleDate, true);
}
return insertStatement;
}
private List<Map<String, Object>> getLoanSummary(final String loanIdsAsString) {
final StringBuilder transactionsSql = new StringBuilder();
transactionsSql.append("select ml.id as loanId, ");
transactionsSql
.append("ml.principal_repaid_derived as principalAmtPaid, ml.principal_writtenoff_derived as principalAmtWrittenoff, ");
transactionsSql.append(" ml.interest_repaid_derived as interestAmtPaid, ml.interest_waived_derived as interestAmtWaived, ");
transactionsSql.append("ml.fee_charges_repaid_derived as feeAmtPaid, ml.fee_charges_waived_derived as feeAmtWaived, ");
transactionsSql
.append("ml.penalty_charges_repaid_derived as penaltyAmtPaid, ml.penalty_charges_waived_derived as penaltyAmtWaived ");
transactionsSql.append("from m_loan ml ");
transactionsSql.append("where ml.id IN (").append(loanIdsAsString).append(") order by ml.id");
List<Map<String, Object>> loanSummary = this.jdbcTemplate.queryForList(transactionsSql.toString());
return loanSummary;
}
private List<Map<String, Object>> getLoanSummary(final Long loanId, final LoanSummary loanSummary) {
List<Map<String, Object>> transactionDetail = new ArrayList<>();
Map<String, Object> transactionMap = new HashMap<>();
transactionMap.put("loanId", loanId);
transactionMap.put("principalAmtPaid", loanSummary.getTotalPrincipalRepaid());
transactionMap.put("principalAmtWrittenoff", loanSummary.getTotalPrincipalWrittenOff());
transactionMap.put("interestAmtPaid", loanSummary.getTotalInterestRepaid());
transactionMap.put("interestAmtWaived", loanSummary.getTotalInterestWaived());
transactionMap.put("feeAmtPaid", loanSummary.getTotalFeeChargesRepaid());
transactionMap.put("feeAmtWaived", loanSummary.getTotalFeeChargesWaived());
transactionMap.put("penaltyAmtPaid", loanSummary.getTotalPenaltyChargesRepaid());
transactionMap.put("penaltyAmtWaived", loanSummary.getTotalPenaltyChargesWaived());
transactionDetail.add(transactionMap);
return transactionDetail;
}
private void createInsertStatements(List<String> insertStatement, Map<Long, List<LoanSchedulePeriodData>> scheduleDate,
boolean isInsertStatement) {
for (Map.Entry<Long, List<LoanSchedulePeriodData>> entry : scheduleDate.entrySet()) {
final Long loanId = entry.getKey();
BigDecimal principalOverdue = BigDecimal.ZERO;
BigDecimal interestOverdue = BigDecimal.ZERO;
BigDecimal feeOverdue = BigDecimal.ZERO;
BigDecimal penaltyOverdue = BigDecimal.ZERO;
LocalDate overDueSince = LocalDate.now();
for (LoanSchedulePeriodData loanSchedulePeriodData : entry.getValue()) {
if (!loanSchedulePeriodData.getComplete()) {
principalOverdue = principalOverdue.add(loanSchedulePeriodData.principalDue().subtract(
loanSchedulePeriodData.principalPaid()));
interestOverdue = interestOverdue.add(loanSchedulePeriodData.interestDue().subtract(
loanSchedulePeriodData.interestPaid()));
feeOverdue = feeOverdue.add(loanSchedulePeriodData.feeChargesDue().subtract(loanSchedulePeriodData.feeChargesPaid()));
penaltyOverdue = penaltyOverdue.add(loanSchedulePeriodData.penaltyChargesDue().subtract(
loanSchedulePeriodData.penaltyChargesPaid()));
if (overDueSince.isAfter(loanSchedulePeriodData.periodDueDate())
&& loanSchedulePeriodData.principalDue().subtract(loanSchedulePeriodData.principalPaid())
.compareTo(BigDecimal.ZERO) == 1) {
overDueSince = loanSchedulePeriodData.periodDueDate();
}
}
}
if (principalOverdue.compareTo(BigDecimal.ZERO) == 1) {
String sqlStatement = null;
if (isInsertStatement) {
sqlStatement = constructInsertStatement(loanId, principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
overDueSince);
} else {
sqlStatement = constructUpdateStatement(loanId, principalOverdue, interestOverdue, feeOverdue, penaltyOverdue,
overDueSince);
}
insertStatement.add(sqlStatement);
}
}
}
private String constructInsertStatement(final Long loanId, BigDecimal principalOverdue, BigDecimal interestOverdue,
BigDecimal feeOverdue, BigDecimal penaltyOverdue, LocalDate overDueSince) {
final StringBuilder insertStatementBuilder = new StringBuilder(900);
insertStatementBuilder
.append("INSERT INTO m_loan_arrears_aging(`loan_id`,`principal_overdue_derived`,`interest_overdue_derived`,")
.append("`fee_charges_overdue_derived`,`penalty_charges_overdue_derived`,`total_overdue_derived`,`overdue_since_date_derived`) VALUES(");
insertStatementBuilder.append(loanId).append(",");
insertStatementBuilder.append(principalOverdue).append(",");
insertStatementBuilder.append(interestOverdue).append(",");
insertStatementBuilder.append(feeOverdue).append(",");
insertStatementBuilder.append(penaltyOverdue).append(",");
BigDecimal totalOverDue = principalOverdue.add(interestOverdue).add(feeOverdue).add(penaltyOverdue);
insertStatementBuilder.append(totalOverDue).append(",'");
insertStatementBuilder.append(this.formatter.print(overDueSince)).append("')");
return insertStatementBuilder.toString();
}
private String constructUpdateStatement(final Long loanId, BigDecimal principalOverdue, BigDecimal interestOverdue,
BigDecimal feeOverdue, BigDecimal penaltyOverdue, LocalDate overDueSince) {
final StringBuilder insertStatementBuilder = new StringBuilder(900);
insertStatementBuilder.append("UPDATE m_loan_arrears_aging mla SET mla.principal_overdue_derived=");
insertStatementBuilder.append(principalOverdue).append(", mla.interest_overdue_derived=");
insertStatementBuilder.append(interestOverdue).append(", mla.fee_charges_overdue_derived=");
insertStatementBuilder.append(feeOverdue).append(", mla.penalty_charges_overdue_derived=");
insertStatementBuilder.append(penaltyOverdue).append(", mla.total_overdue_derived=");
BigDecimal totalOverDue = principalOverdue.add(interestOverdue).add(feeOverdue).add(penaltyOverdue);
insertStatementBuilder.append(totalOverDue).append(",mla.overdue_since_date_derived= '");
insertStatementBuilder.append(this.formatter.print(overDueSince)).append("' ");
insertStatementBuilder.append("WHERE mla.loan_id=").append(loanId);
return insertStatementBuilder.toString();
}
private void updateSchheduleWithPaidDetail(Map<Long, List<LoanSchedulePeriodData>> scheduleDate, List<Map<String, Object>> loanSummary) {
for (Map<String, Object> transactionMap : loanSummary) {
Long loanId = (Long) transactionMap.get("loanId");
BigDecimal principalAmtPaid = (BigDecimal) transactionMap.get("principalAmtPaid");
BigDecimal principalAmtWrittenoff = (BigDecimal) transactionMap.get("principalAmtWrittenoff");
BigDecimal interestAmtPaid = (BigDecimal) transactionMap.get("interestAmtPaid");
BigDecimal interestAmtWaived = (BigDecimal) transactionMap.get("interestAmtWaived");
BigDecimal feeAmtPaid = (BigDecimal) transactionMap.get("feeAmtPaid");
BigDecimal feeAmtWaived = (BigDecimal) transactionMap.get("feeAmtWaived");
BigDecimal penaltyAmtPaid = (BigDecimal) transactionMap.get("penaltyAmtPaid");
BigDecimal penaltyAmtWaived = (BigDecimal) transactionMap.get("penaltyAmtWaived");
BigDecimal principalAmt = principalAmtPaid.add(principalAmtWrittenoff);
BigDecimal interestAmt = interestAmtPaid.add(interestAmtWaived);
BigDecimal feeAmt = feeAmtPaid.add(feeAmtWaived);
BigDecimal penaltyAmt = penaltyAmtPaid.add(penaltyAmtWaived);
List<LoanSchedulePeriodData> loanSchedulePeriodDatas = scheduleDate.get(loanId);
if (loanSchedulePeriodDatas != null) {
List<LoanSchedulePeriodData> updatedPeriodData = new ArrayList<>(loanSchedulePeriodDatas.size());
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
BigDecimal principalPaid = null;
BigDecimal interestPaid = null;
BigDecimal feeChargesPaid = null;
BigDecimal penaltyChargesPaid = null;
Boolean isComplete = true;
if (loanSchedulePeriodData.principalDue().compareTo(principalAmt) == 1) {
principalPaid = principalAmt;
principalAmt = BigDecimal.ZERO;
isComplete = false;
} else {
principalPaid = loanSchedulePeriodData.principalDue();
principalAmt = principalAmt.subtract(loanSchedulePeriodData.principalDue());
}
if (loanSchedulePeriodData.interestDue().compareTo(interestAmt) == 1) {
interestPaid = interestAmt;
interestAmt = BigDecimal.ZERO;
isComplete = false;
} else {
interestPaid = loanSchedulePeriodData.interestDue();
interestAmt = interestAmt.subtract(loanSchedulePeriodData.interestDue());
}
if (loanSchedulePeriodData.feeChargesDue().compareTo(feeAmt) == 1) {
feeChargesPaid = feeAmt;
feeAmt = BigDecimal.ZERO;
isComplete = false;
} else {
feeChargesPaid = loanSchedulePeriodData.feeChargesDue();
feeAmt = feeAmt.subtract(loanSchedulePeriodData.feeChargesDue());
}
if (loanSchedulePeriodData.penaltyChargesDue().compareTo(penaltyAmt) == 1) {
penaltyChargesPaid = penaltyAmt;
penaltyAmt = BigDecimal.ZERO;
isComplete = false;
} else {
penaltyChargesPaid = loanSchedulePeriodData.penaltyChargesDue();
penaltyAmt = penaltyAmt.subtract(loanSchedulePeriodData.penaltyChargesDue());
}
LoanSchedulePeriodData periodData = LoanSchedulePeriodData.WithPaidDetail(loanSchedulePeriodData, isComplete,
principalPaid, interestPaid, feeChargesPaid, penaltyChargesPaid);
updatedPeriodData.add(periodData);
}
loanSchedulePeriodDatas.clear();
loanSchedulePeriodDatas.addAll(updatedPeriodData);
}
}
}
private static final class OriginalScheduleExtractor implements ResultSetExtractor<Map<Long, List<LoanSchedulePeriodData>>> {
private final String schema;
public OriginalScheduleExtractor(final String loanIdsAsString) {
final StringBuilder scheduleDetail = new StringBuilder();
scheduleDetail.append("select ml.id as loanId, mr.duedate as dueDate, mr.principal_amount as principalAmount, ");
scheduleDetail
.append("mr.interest_amount as interestAmount, mr.fee_charges_amount as feeAmount, mr.penalty_charges_amount as penaltyAmount ");
scheduleDetail.append("from m_loan ml INNER JOIN m_loan_repayment_schedule_history mr on mr.loan_id = ml.id ");
scheduleDetail.append("where mr.duedate < SUBDATE(CURDATE(),INTERVAL ifnull(ml.grace_on_arrears_ageing,0) day) and ");
scheduleDetail.append("ml.id IN(").append(loanIdsAsString).append(") and mr.version = (");
scheduleDetail.append("select max(lrs.version) from m_loan_repayment_schedule_history lrs where mr.loan_id = lrs.loan_id");
scheduleDetail.append(") order by ml.id,mr.duedate");
this.schema = scheduleDetail.toString();
}
@Override
public Map<Long, List<LoanSchedulePeriodData>> extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<Long, List<LoanSchedulePeriodData>> scheduleDate = new HashMap<>();
while (rs.next()) {
Long loanId = rs.getLong("loanId");
List<LoanSchedulePeriodData> periodDatas = new ArrayList<>();
LoanSchedulePeriodData loanSchedulePeriodData = fetchLoanSchedulePeriodData(rs);
periodDatas.add(loanSchedulePeriodData);
while (rs.next()) {
Long tempLoanId = rs.getLong("loanId");
if (loanId.equals(tempLoanId)) {
periodDatas.add(fetchLoanSchedulePeriodData(rs));
} else {
rs.previous();
break;
}
}
scheduleDate.put(loanId, periodDatas);
}
return scheduleDate;
}
private LoanSchedulePeriodData fetchLoanSchedulePeriodData(ResultSet rs) throws SQLException {
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalAmount");
final BigDecimal interestDueOnPrincipalOutstanding = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestAmount");
final BigDecimal feeChargesDueForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeAmount");
final BigDecimal penaltyChargesDueForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyAmount");
final Integer periodNumber = null;
final LocalDate fromDate = null;
final BigDecimal principalOutstanding = null;
final BigDecimal totalDueForPeriod = null;
return LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, fromDate, dueDate, principalDue, principalOutstanding,
interestDueOnPrincipalOutstanding, feeChargesDueForPeriod, penaltyChargesDueForPeriod, totalDueForPeriod);
}
}
@SuppressWarnings("unused")
@Override
public void businessEventToBeExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
// TODO Auto-generated method stub
}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Loan loan = null;
Object loanEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
Object loanTransactionEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
Object loanAdjustTransactionEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION);
Object loanChargeEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_CHARGE);
if (loanEntity != null) {
loan = (Loan) loanEntity;
} else if (loanTransactionEntity != null) {
LoanTransaction loanTransaction = (LoanTransaction) loanTransactionEntity;
loan = loanTransaction.getLoan();
} else if (loanAdjustTransactionEntity != null) {
LoanTransaction loanTransaction = (LoanTransaction) loanAdjustTransactionEntity;
loan = loanTransaction.getLoan();
} else if (loanChargeEntity != null) {
LoanCharge loanCharge = (LoanCharge) loanChargeEntity;
loan = loanCharge.getLoan();
}
if (loan != null && loan.isOpen() && loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& loan.loanProduct().isArrearsBasedOnOriginalSchedule()) {
updateLoanArrearsAgeingDetailsWithOriginalSchedule(loan);
} else {
updateLoanArrearsAgeingDetails(loan);
}
}
private class DisbursementEventListner implements BusinessEventListner {
@SuppressWarnings("unused")
@Override
public void businessEventToBeExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
// TODO Auto-generated method stub
}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Object loanEntity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
if (loanEntity != null) {
Loan loan = (Loan) loanEntity;
updateLoanArrearsAgeingDetails(loan);
}
}
}
}
@@ -159,7 +159,8 @@ public class LoanAssembler {
final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element); final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", element); final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", element);
final Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element); final Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper.extractBooleanNamed("createStandingInstructionAtDisbursement", element); final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper.extractBooleanNamed(
"createStandingInstructionAtDisbursement", element);
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
@@ -265,7 +266,8 @@ public class LoanAssembler {
} }
} }
final LocalDate recalculationRestFrequencyDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanProductConstants.recalculationRestFrequencyDateParamName, element); final LocalDate recalculationRestFrequencyDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanProductConstants.recalculationRestFrequencyDateParamName, element);
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element); final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
@@ -9,7 +9,6 @@ import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -93,6 +92,7 @@ import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDi
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand; import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentCommand; import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentCommand;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
@@ -124,7 +124,6 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException; import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
@@ -291,6 +290,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance); this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance);
} }
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final MonetaryCurrency currency = loan.getCurrency(); final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
@@ -405,6 +407,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
updateRecurringCalendarDatesForInterestRecalculation(loan); updateRecurringCalendarDatesForInterestRecalculation(loan);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loan.getId()) // .withEntityId(loan.getId()) //
@@ -540,6 +544,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId()); final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId());
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final MonetaryCurrency currency = loan.getCurrency(); final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
@@ -648,6 +654,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
updateRecurringCalendarDatesForInterestRecalculation(loan); updateRecurringCalendarDatesForInterestRecalculation(loan);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
return changes; return changes;
@@ -661,6 +669,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
removeLoanCycle(loan); removeLoanCycle(loan);
// //
@@ -703,7 +713,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(), final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL, loan); this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -789,10 +800,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId); final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId);
if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(transactionId); } if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(transactionId); }
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION,
constructEntityMap(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION, transactionToAdjust));
if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { throw new PlatformServiceUnavailableException( if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { throw new PlatformServiceUnavailableException(
"error.msg.loan.transfer.transaction.update.not.allowed", "Loan transaction:" + transactionId "error.msg.loan.transfer.transaction.update.not.allowed", "Loan transaction:" + transactionId
+ " update not allowed as it involves in account transfer", transactionId); } + " update not allowed as it involves in account transfer", transactionId); }
@@ -914,10 +925,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_TRANSACTION, transactionToAdjust); Map<BUSINESS_ENTITY, Object> entityMap = constructEntityMap(BUSINESS_ENTITY.LOAN_ADJUSTED_TRANSACTION, transactionToAdjust);
if (newTransactionDetail.isRepayment() && newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (newTransactionDetail.isRepayment() && newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, newTransactionDetail); entityMap.put(BUSINESS_ENTITY.LOAN_TRANSACTION, newTransactionDetail);
} }
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION, entityMap);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -964,7 +976,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney,
transactionDate, interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser); transactionDate, interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, waiveInterestTransaction));
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null; ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null; LocalDate calculatedRepaymentsStartingFromDate = null;
@@ -1027,6 +1040,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, waiveInterestTransaction));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(waiveInterestTransaction.getId()) // .withEntityId(waiveInterestTransaction.getId()) //
@@ -1052,6 +1067,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WRITTEN_OFF,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
removeLoanCycle(loan); removeLoanCycle(loan);
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
@@ -1102,7 +1119,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WRITTEN_OFF, writeoff); this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WRITTEN_OFF,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeoff));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(writeoff.getId()) // .withEntityId(writeoff.getId()) //
@@ -1124,6 +1142,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CLOSE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
@@ -1184,6 +1204,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
} }
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CLOSE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
CommandProcessingResult result = null; CommandProcessingResult result = null;
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
@@ -1220,6 +1242,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
removeLoanCycle(loan); removeLoanCycle(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CLOSE_AS_RESCHEDULE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
@@ -1236,7 +1260,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Note note = Note.loanNote(loan, noteText); final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CLOSE_AS_RESCHEDULE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanId) // .withEntityId(loanId) //
@@ -1261,6 +1286,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command); final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
validateAddLoanCharge(loan, chargeDefinition, loanCharge); validateAddLoanCharge(loan, chargeDefinition, loanCharge);
@@ -1283,6 +1310,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) { && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
} }
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanCharge.getId()) // .withEntityId(loanCharge.getId()) //
@@ -1455,10 +1484,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException( if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException(
LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); } LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UPDATE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command); final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UPDATE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
@@ -1491,6 +1524,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_WAIVED, loanCharge.getId()); throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaid()) { throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_PAID, } else if (loanCharge.isPaid()) { throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_PAID,
loanCharge.getId()); } loanCharge.getId()); }
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
Integer loanInstallmentNumber = null; Integer loanInstallmentNumber = null;
if (loanCharge.isInstalmentFee()) { if (loanCharge.isInstalmentFee()) {
LoanInstallmentCharge chargePerInstallment = null; LoanInstallmentCharge chargePerInstallment = null;
@@ -1566,6 +1601,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
@@ -1589,10 +1627,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// yet to be approved (are in submitted and pending status) // yet to be approved (are in submitted and pending status)
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeDeletedException( if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeDeletedException(
LOAN_CHARGE_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); } LOAN_CHARGE_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DELETE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
loan.removeLoanCharge(loanCharge); loan.removeLoanCharge(loanCharge);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DELETE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
@@ -1670,7 +1711,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
isExceptionForBalanceCheck); isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
@@ -1787,6 +1827,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(accountId); final Loan loan = this.loanAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INITIATE_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
@@ -1800,7 +1842,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_INITIATE_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
return newTransferTransaction; return newTransferTransaction;
} }
@@ -1812,7 +1855,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
final Loan loan = this.loanAssembler.assembleFrom(accountId); final Loan loan = this.loanAssembler.assembleFrom(accountId);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ACCEPT_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
@@ -1832,6 +1876,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ACCEPT_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
return newTransferAcceptanceTransaction; return newTransferAcceptanceTransaction;
} }
@@ -1843,6 +1889,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
final Loan loan = this.loanAssembler.assembleFrom(accountId); final Loan loan = this.loanAssembler.assembleFrom(accountId);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WITHDRAW_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
@@ -1856,6 +1904,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WITHDRAW_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
return newTransferAcceptanceTransaction; return newTransferAcceptanceTransaction;
} }
@@ -1864,8 +1914,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Override @Override
public void rejectLoanTransfer(final Long accountId) { public void rejectLoanTransfer(final Long accountId) {
final Loan loan = this.loanAssembler.assembleFrom(accountId); final Loan loan = this.loanAssembler.assembleFrom(accountId);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REJECT_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue()); loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue());
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REJECT_TRANSFER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
@Transactional @Transactional
@@ -1883,12 +1937,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); } if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment); loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -1918,12 +1975,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
for (final String loanIdString : loanIds) { for (final String loanIdString : loanIds) {
final Long loanId = Long.valueOf(loanIdString); final Long loanId = Long.valueOf(loanIdString);
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); } if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment); loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
this.loanRepository.flush(); this.loanRepository.flush();
@@ -1946,10 +2007,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
if (loan.getLoanOfficer() == null) { throw new LoanOfficerUnassignmentException(loanId); } if (loan.getLoanOfficer() == null) { throw new LoanOfficerUnassignmentException(loanId); }
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REMOVE_OFFICER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
loan.removeLoanOfficer(dateOfLoanOfficerunAssigned); loan.removeLoanOfficer(dateOfLoanOfficerunAssigned);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REMOVE_OFFICER,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -1984,7 +2049,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
@Transactional @Transactional
@Override @Override
public void applyMeetingDateChanges(final Calendar calendar, final Collection<CalendarInstance> loanCalendarInstances, public void applyMeetingDateChanges(final Calendar calendar, final Collection<CalendarInstance> loanCalendarInstances,
@@ -2012,15 +2076,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
if(reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates ){ if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) {
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate); holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
}else{ } else {
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays); holidays, workingDays);
} }
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
} }
} }
@@ -2337,6 +2400,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) { && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
} }
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
} }
@@ -2387,7 +2453,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
installment = loan.fetchRepaymentScheduleInstallment(periodNumber); installment = loan.fetchRepaymentScheduleInstallment(periodNumber);
} }
if (loan != null) { if (loan != null) {
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
for (Map.Entry<Integer, LocalDate> entry : scheduleDates.entrySet()) { for (Map.Entry<Integer, LocalDate> entry : scheduleDates.entrySet()) {
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue()); final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue());
@@ -2425,6 +2492,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Long overdurPenaltyWaitPeriod = null; Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null; LocalDate lastTransactionDate = null;
LoanTransaction writeOffTransaction = loan.findWriteOffTransaction(); LoanTransaction writeOffTransaction = loan.findWriteOffTransaction();
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeOffTransaction));
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -2460,7 +2529,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
if (writeOffTransaction != null) { if (writeOffTransaction != null) {
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF, writeOffTransaction); this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF,
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeOffTransaction));
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()) // .withOfficeId(loan.getOfficeId()) //
@@ -2617,6 +2687,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public void recalculateInterest(final long loanId) { public void recalculateInterest(final long loanId) {
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
Loan loan = this.loanAssembler.assembleFrom(loanId); Loan loan = this.loanAssembler.assembleFrom(loanId);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null; ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null; LocalDate calculatedRepaymentsStartingFromDate = null;
@@ -2662,6 +2734,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
} }
@Override @Override
@@ -2718,7 +2792,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
//checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate); // checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate);
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
checkIfLoanIsPaidInAdvance(loanId, transactionAmount); checkIfLoanIsPaidInAdvance(loanId, transactionAmount);
@@ -2748,15 +2822,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
private void checkIfLoanIsPaidInAdvance(final Long loanId,final BigDecimal transactionAmount) { private void checkIfLoanIsPaidInAdvance(final Long loanId, final BigDecimal transactionAmount) {
BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId).getPaidInAdvance(); BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId).getPaidInAdvance();
if(overpaid == null || overpaid.equals(new BigDecimal(0)) || transactionAmount.floatValue() > overpaid.floatValue()) { if (overpaid == null || overpaid.equals(new BigDecimal(0)) || transactionAmount.floatValue() > overpaid.floatValue()) {
if(overpaid == null) if (overpaid == null) overpaid = BigDecimal.ZERO;
overpaid = BigDecimal.ZERO;
throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString()); throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
} }
} }
private AppUser getAppUserIfPresent() { private AppUser getAppUserIfPresent() {
AppUser user = null; AppUser user = null;
@@ -2766,4 +2839,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return user; return user;
} }
private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
} }
@@ -61,6 +61,7 @@ public interface LoanProductConstants {
public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing"; public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing";
public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA"; public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA";
public static final String minimumDaysBetweenDisbursalAndFirstRepayment = "minimumDaysBetweenDisbursalAndFirstRepayment"; public static final String minimumDaysBetweenDisbursalAndFirstRepayment = "minimumDaysBetweenDisbursalAndFirstRepayment";
public static final String accountMovesOutOfNPAOnlyOnArrearsCompletionParamName = "accountMovesOutOfNPAOnlyOnArrearsCompletion";
// Interest recalculation related // Interest recalculation related
public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled"; public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
@@ -71,6 +72,7 @@ public interface LoanProductConstants {
public static final String recalculationRestFrequencyTypeParameterName = "recalculationRestFrequencyType"; public static final String recalculationRestFrequencyTypeParameterName = "recalculationRestFrequencyType";
public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval"; public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval";
public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate"; public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate";
public static final String isArrearsBasedOnOriginalScheduleParamName = "isArrearsBasedOnOriginalSchedule";
// Guarantee related // Guarantee related
public static final String holdGuaranteeFundsParamName = "holdGuaranteeFunds"; public static final String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
@@ -137,6 +137,7 @@ public class LoanProductData {
private final Boolean holdGuaranteeFunds; private final Boolean holdGuaranteeFunds;
private final LoanProductGuaranteeData productGuaranteeData; private final LoanProductGuaranteeData productGuaranteeData;
private final Boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
/** /**
* Used when returning lookup information about loan product for dropdowns. * Used when returning lookup information about loan product for dropdowns.
@@ -187,6 +188,7 @@ public class LoanProductData {
final BigDecimal outstandingLoanBalance = null; final BigDecimal outstandingLoanBalance = null;
final LoanProductGuaranteeData productGuaranteeData = null; final LoanProductGuaranteeData productGuaranteeData = null;
final Boolean holdGuaranteeFunds = false; final Boolean holdGuaranteeFunds = false;
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
final EnumOptionData daysInMonthType = null; final EnumOptionData daysInMonthType = null;
final EnumOptionData daysInYearType = null; final EnumOptionData daysInYearType = null;
@@ -202,7 +204,8 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData); interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
accountMovesOutOfNPAOnlyOnArrearsCompletion);
} }
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) { public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
@@ -258,6 +261,7 @@ public class LoanProductData {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null; final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
final Boolean holdGuaranteeFunds = false; final Boolean holdGuaranteeFunds = false;
final LoanProductGuaranteeData productGuaranteeData = null; final LoanProductGuaranteeData productGuaranteeData = null;
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -267,7 +271,8 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData); interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
accountMovesOutOfNPAOnlyOnArrearsCompletion);
} }
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
@@ -330,6 +335,7 @@ public class LoanProductData {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null; final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
final Boolean holdGuaranteeFunds = false; final Boolean holdGuaranteeFunds = false;
final LoanProductGuaranteeData productGuaranteeData = null; final LoanProductGuaranteeData productGuaranteeData = null;
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -340,7 +346,7 @@ public class LoanProductData {
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuaranteeFunds, productGuaranteeData); holdGuaranteeFunds, productGuaranteeData, accountMovesOutOfNPAOnlyOnArrearsCompletion);
} }
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings, public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -371,8 +377,8 @@ public class LoanProductData {
Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData, final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds, final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuaranteeFunds,
LoanProductGuaranteeData loanProductGuaranteeData) { final LoanProductGuaranteeData loanProductGuaranteeData, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -445,6 +451,7 @@ public class LoanProductData {
this.interestRecalculationData = interestRecalculationData; this.interestRecalculationData = interestRecalculationData;
this.holdGuaranteeFunds = holdGuaranteeFunds; this.holdGuaranteeFunds = holdGuaranteeFunds;
this.productGuaranteeData = loanProductGuaranteeData; this.productGuaranteeData = loanProductGuaranteeData;
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
this.daysInMonthTypeOptions = null; this.daysInMonthTypeOptions = null;
this.daysInYearTypeOptions = null; this.daysInYearTypeOptions = null;
@@ -548,6 +555,7 @@ public class LoanProductData {
this.interestRecalculationData = productData.interestRecalculationData; this.interestRecalculationData = productData.interestRecalculationData;
this.holdGuaranteeFunds = productData.holdGuaranteeFunds; this.holdGuaranteeFunds = productData.holdGuaranteeFunds;
this.productGuaranteeData = productData.productGuaranteeData; this.productGuaranteeData = productData.productGuaranteeData;
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = productData.accountMovesOutOfNPAOnlyOnArrearsCompletion;
this.daysInMonthTypeOptions = daysInMonthTypeOptions; this.daysInMonthTypeOptions = daysInMonthTypeOptions;
this.daysInYearTypeOptions = daysInYearTypeOptions; this.daysInYearTypeOptions = daysInYearTypeOptions;
@@ -24,11 +24,13 @@ public class LoanProductInterestRecalculationData {
private final EnumOptionData recalculationRestFrequencyType; private final EnumOptionData recalculationRestFrequencyType;
private final Integer recalculationRestFrequencyInterval; private final Integer recalculationRestFrequencyInterval;
private final LocalDate recalculationRestFrequencyDate; private final LocalDate recalculationRestFrequencyDate;
@SuppressWarnings("unused")
private final boolean isArrearsBasedOnOriginalSchedule;
public LoanProductInterestRecalculationData(final Long id, final Long productId, public LoanProductInterestRecalculationData(final Long id, final Long productId,
final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType, final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType,
final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval, final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval,
final LocalDate recalculationRestFrequencyDate) { final LocalDate recalculationRestFrequencyDate, boolean isArrearsBasedOnOriginalSchedule) {
this.id = id; this.id = id;
this.productId = productId; this.productId = productId;
this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
@@ -36,6 +38,7 @@ public class LoanProductInterestRecalculationData {
this.recalculationRestFrequencyType = recalculationRestFrequencyType; this.recalculationRestFrequencyType = recalculationRestFrequencyType;
this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval; this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval;
this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; this.recalculationRestFrequencyDate = recalculationRestFrequencyDate;
this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule;
} }
public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() { public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() {
@@ -46,8 +49,10 @@ public class LoanProductInterestRecalculationData {
final EnumOptionData recalculationRestFrequencyType = null; final EnumOptionData recalculationRestFrequencyType = null;
final Integer recalculationRestFrequencyInterval = null; final Integer recalculationRestFrequencyInterval = null;
final LocalDate recalculationRestFrequencyDate = null; final LocalDate recalculationRestFrequencyDate = null;
final boolean isArrearsBasedOnOriginalSchedule = false;
return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType, return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType,
recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate); recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate,
isArrearsBasedOnOriginalSchedule);
} }
public EnumOptionData getInterestRecalculationCompoundingType() { public EnumOptionData getInterestRecalculationCompoundingType() {
@@ -136,6 +136,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true) @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
private LoanProductGuaranteeDetails loanProductGuaranteeDetails; private LoanProductGuaranteeDetails loanProductGuaranteeDetails;
@Column(name = "account_moves_out_of_npa_only_on_arrears_completion")
private boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) { final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
@@ -225,6 +228,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command); loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command);
} }
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName);
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType,
@@ -233,7 +239,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount, startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment, isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuarantorFunds, loanProductGuaranteeDetails); holdGuarantorFunds, loanProductGuaranteeDetails, accountMovesOutOfNPAOnlyOnArrearsCompletion);
} }
@@ -458,7 +464,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
final boolean isInterestRecalculationEnabled, final boolean isInterestRecalculationEnabled,
final LoanProductInterestRecalculationDetails productInterestRecalculationDetails, final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds, final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
final LoanProductGuaranteeDetails loanProductGuaranteeDetails) { final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion) {
this.fund = fund; this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy; this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim(); this.name = name.trim();
@@ -510,6 +516,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment; this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
this.holdGuaranteeFunds = holdGuarantorFunds; this.holdGuaranteeFunds = holdGuarantorFunds;
this.loanProductGuaranteeDetails = loanProductGuaranteeDetails; this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
} }
public MonetaryCurrency getCurrency() { public MonetaryCurrency getCurrency() {
@@ -737,6 +744,14 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.loanProductGuaranteeDetails.update(command, actualChanges); this.loanProductGuaranteeDetails.update(command, actualChanges);
} }
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName,
this.accountMovesOutOfNPAOnlyOnArrearsCompletion)) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName);
actualChanges.put(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, newValue);
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = newValue;
}
return actualChanges; return actualChanges;
} }
@@ -989,4 +1004,12 @@ public class LoanProduct extends AbstractPersistable<Long> {
return this.shortName; return this.shortName;
} }
public boolean isArrearsBasedOnOriginalSchedule() {
boolean isBasedOnOriginalSchedule = false;
if (getProductInterestRecalculationDetails() != null) {
isBasedOnOriginalSchedule = getProductInterestRecalculationDetails().isArrearsBasedOnOriginalSchedule();
}
return isBasedOnOriginalSchedule;
}
} }
@@ -57,6 +57,9 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
@Column(name = "rest_freqency_date") @Column(name = "rest_freqency_date")
private Date restFrequencyDate; private Date restFrequencyDate;
@Column(name = "arrears_based_on_original_schedule")
private boolean isArrearsBasedOnOriginalSchedule;
protected LoanProductInterestRecalculationDetails() { protected LoanProductInterestRecalculationDetails() {
// //
} }
@@ -75,6 +78,8 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
.localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName);
Integer recurrenceInterval = command Integer recurrenceInterval = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName); .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName);
final boolean isArrearsBasedOnOriginalSchedule = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName);
RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency); RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency);
Date recurrenceOnDate = null; Date recurrenceOnDate = null;
if (recurrenceOnLocalDate != null) { if (recurrenceOnLocalDate != null) {
@@ -87,17 +92,18 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
} }
return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod, return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod,
recurrenceFrequency, recurrenceInterval, recurrenceOnDate); recurrenceFrequency, recurrenceInterval, recurrenceOnDate, isArrearsBasedOnOriginalSchedule);
} }
private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod,
final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval, final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval,
final Date restFrequencyDate) { final Date restFrequencyDate, final boolean isArrearsBasedOnOriginalSchedule) {
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.restFrequencyType = restFrequencyType; this.restFrequencyType = restFrequencyType;
this.restInterval = restInterval; this.restInterval = restInterval;
this.restFrequencyDate = restFrequencyDate; this.restFrequencyDate = restFrequencyDate;
this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule;
} }
public void updateProduct(final LoanProduct loanProduct) { public void updateProduct(final LoanProduct loanProduct) {
@@ -169,6 +175,14 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
this.restFrequencyDate = recurrenceOnDate; this.restFrequencyDate = recurrenceOnDate;
} }
} }
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName,
this.isArrearsBasedOnOriginalSchedule)) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName);
actualChanges.put(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, newValue);
this.isArrearsBasedOnOriginalSchedule = newValue;
}
} }
public LocalDate getRestFrequencyLocalDate() { public LocalDate getRestFrequencyLocalDate() {
@@ -186,4 +200,9 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
public Integer getRestInterval() { public Integer getRestInterval() {
return this.restInterval; return this.restInterval;
} }
public boolean isArrearsBasedOnOriginalSchedule() {
return this.isArrearsBasedOnOriginalSchedule;
}
} }
@@ -71,9 +71,10 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationRestFrequencyDateParamName, LoanProductConstants.recalculationRestFrequencyDateParamName,
LoanProductConstants.recalculationRestFrequencyIntervalParameterName, LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
LoanProductConstants.recalculationRestFrequencyTypeParameterName, LoanProductConstants.recalculationRestFrequencyTypeParameterName,
LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName,
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName, LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName,
LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName, LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName)); LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName));
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@@ -299,6 +300,13 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull() baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull()
.isOneOfTheseValues(1, 30); .isOneOfTheseValues(1, 30);
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element)) {
Boolean npaChangeConfig = this.fromApiJsonHelper.extractBooleanNamed(
LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element);
baseDataValidator.reset().parameter(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName).value(npaChangeConfig)
.notNull().isOneOfTheseValues(true, false);
}
// Interest recalculation settings // Interest recalculation settings
final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed( final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
LoanProductConstants.isInterestRecalculationEnabledParameterName, element); LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
@@ -503,6 +511,12 @@ public final class LoanProductDataValidator {
} }
} }
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, element)) {
final Boolean isArrearsBasedOnOriginalSchedule = this.fromApiJsonHelper.extractBooleanNamed(
LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, element);
baseDataValidator.reset().parameter(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName)
.value(isArrearsBasedOnOriginalSchedule).notNull().isOneOfTheseValues(true, false);
}
} }
public void validateForUpdate(final String json, final LoanProduct loanProduct) { public void validateForUpdate(final String json, final LoanProduct loanProduct) {
@@ -719,6 +733,13 @@ public final class LoanProductDataValidator {
.isOneOfTheseValues(1, 30); .isOneOfTheseValues(1, 30);
} }
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element)) {
Boolean npaChangeConfig = this.fromApiJsonHelper.extractBooleanNamed(
LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, element);
baseDataValidator.reset().parameter(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName).value(npaChangeConfig)
.notNull().isOneOfTheseValues(true, false);
}
// Interest recalculation settings // Interest recalculation settings
Boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled(); Boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) {
@@ -86,13 +86,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "select " + rm.loanProductSchema(); String sql = "select " + rm.loanProductSchema();
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office // Check if branch specific products are enabled. If yes, fetch only
String inClause = mifosEntityAccessUtil. // products mapped to current user's office
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( String inClause = mifosEntityAccessUtil
MifosEntityType.LOAN_PRODUCT); .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
sql += " where lp.id in ( " + inClause + " ) "; sql += " where lp.id in ( " + inClause + " ) ";
} }
return this.jdbcTemplate.query(sql, rm, new Object[] {}); return this.jdbcTemplate.query(sql, rm, new Object[] {});
} }
@@ -109,18 +109,18 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final LoanProductLookupMapper rm = new LoanProductLookupMapper(); final LoanProductLookupMapper rm = new LoanProductLookupMapper();
String sql = "select "; String sql = "select ";
if(activeOnly) { if (activeOnly) {
sql += rm.activeOnlySchema(); sql += rm.activeOnlySchema();
} else { } else {
sql += rm.schema(); sql += rm.schema();
} }
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office // Check if branch specific products are enabled. If yes, fetch only
String inClause = mifosEntityAccessUtil. // products mapped to current user's office
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( String inClause = mifosEntityAccessUtil
MifosEntityType.LOAN_PRODUCT); .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
if(activeOnly) { if (activeOnly) {
sql += " and id in ( " + inClause + " )"; sql += " and id in ( " + inClause + " )";
} else { } else {
sql += " where id in ( " + inClause + " ) "; sql += " where id in ( " + inClause + " ) ";
@@ -162,10 +162,11 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, " + "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, "
+ "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, " + "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, "
+ "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, " + "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
+ "lpr.rest_freqency_date as restFrequencyDate, " + "lpr.rest_freqency_date as restFrequencyDate, lpr.arrears_based_on_original_schedule as isArrearsBasedOnOriginalSchedule, "
+ "lp.hold_guarantee_funds as holdGuaranteeFunds, " + "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+ "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, " + "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, "
+ "lpg.minimum_guarantee_from_own_funds as minimumGuaranteeFromOwnFunds, lpg.minimum_guarantee_from_guarantor_funds as minimumGuaranteeFromGuarantor, " + "lpg.minimum_guarantee_from_own_funds as minimumGuaranteeFromOwnFunds, lpg.minimum_guarantee_from_guarantor_funds as minimumGuaranteeFromGuarantor, "
+ "lp.account_moves_out_of_npa_only_on_arrears_completion as accountMovesOutOfNPAOnlyOnArrearsCompletion, "
+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId " + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId "
+ " from m_product_loan lp " + " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id " + " left join m_fund f on f.id = lp.fund_id "
@@ -290,10 +291,10 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue); final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue);
final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval"); final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval");
final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate"); final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate");
final boolean isArrearsBasedOnOriginalSchedule = rs.getBoolean("isArrearsBasedOnOriginalSchedule");
interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId, interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId,
interestRecalculationCompoundingType, rescheduleStrategyType, restFrequencyType, restFrequencyInterval, interestRecalculationCompoundingType, rescheduleStrategyType, restFrequencyType, restFrequencyInterval,
restFrequencyDate); restFrequencyDate, isArrearsBasedOnOriginalSchedule);
} }
final boolean holdGuaranteeFunds = rs.getBoolean("holdGuaranteeFunds"); final boolean holdGuaranteeFunds = rs.getBoolean("holdGuaranteeFunds");
@@ -307,6 +308,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
minimumGuaranteeFromGuarantor); minimumGuaranteeFromGuarantor);
} }
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = rs.getBoolean("accountMovesOutOfNPAOnlyOnArrearsCompletion");
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
@@ -316,7 +319,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData); interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData,
accountMovesOutOfNPAOnlyOnArrearsCompletion);
} }
} }
@@ -390,13 +394,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "select " + rm.loanProductSchema() + " where lp.currency_code='" + currencyCode + "'"; String sql = "select " + rm.loanProductSchema() + " where lp.currency_code='" + currencyCode + "'";
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office // Check if branch specific products are enabled. If yes, fetch only
String inClause = mifosEntityAccessUtil. // products mapped to current user's office
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( String inClause = mifosEntityAccessUtil
MifosEntityType.LOAN_PRODUCT); .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
sql += " and id in ( " + inClause + " ) "; sql += " and id in ( " + inClause + " ) ";
} }
return this.jdbcTemplate.query(sql, rm, new Object[] {}); return this.jdbcTemplate.query(sql, rm, new Object[] {});
} }
@@ -410,13 +414,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "Select " + rm.productMixSchema(); String sql = "Select " + rm.productMixSchema();
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office // Check if branch specific products are enabled. If yes, fetch only
String inClause = mifosEntityAccessUtil. // products mapped to current user's office
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( String inClause = mifosEntityAccessUtil
MifosEntityType.LOAN_PRODUCT); .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
sql += " and lp.id in ( " + inClause + " ) "; sql += " and lp.id in ( " + inClause + " ) ";
} }
return this.jdbcTemplate.query(sql, rm, new Object[] {}); return this.jdbcTemplate.query(sql, rm, new Object[] {});
} }
@@ -429,24 +433,23 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final LoanProductLookupMapper rm = new LoanProductLookupMapper(); final LoanProductLookupMapper rm = new LoanProductLookupMapper();
String sql = "Select " + rm.restrictedProductsSchema() + " where pm.product_id=? "; String sql = "Select " + rm.restrictedProductsSchema() + " where pm.product_id=? ";
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office // Check if branch specific products are enabled. If yes, fetch only
String inClause1 = mifosEntityAccessUtil. // products mapped to current user's office
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( String inClause1 = mifosEntityAccessUtil
MifosEntityType.LOAN_PRODUCT); .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
if ( (inClause1 != null) && (!(inClause1.trim().isEmpty())) ) { if ((inClause1 != null) && (!(inClause1.trim().isEmpty()))) {
sql += " and rp.id in ( " + inClause1 + " ) "; sql += " and rp.id in ( " + inClause1 + " ) ";
} }
sql += " UNION Select " sql += " UNION Select " + rm.derivedRestrictedProductsSchema() + " where pm.restricted_product_id=?";
+ rm.derivedRestrictedProductsSchema() + " where pm.restricted_product_id=?";
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office // Check if branch specific products are enabled. If yes, fetch only
String inClause2 = mifosEntityAccessUtil. // products mapped to current user's office
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( String inClause2 = mifosEntityAccessUtil
MifosEntityType.LOAN_PRODUCT); .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
if ( (inClause2 != null) && (!(inClause2.trim().isEmpty())) ) { if ((inClause2 != null) && (!(inClause2.trim().isEmpty()))) {
sql += " and lp.id in ( " + inClause2 + " ) "; sql += " and lp.id in ( " + inClause2 + " ) ";
} }
return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId }); return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
} }
@@ -460,16 +463,15 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "Select " + rm.schema() + " where "; String sql = "Select " + rm.schema() + " where ";
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office // Check if branch specific products are enabled. If yes, fetch only
String inClause = mifosEntityAccessUtil. // products mapped to current user's office
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( String inClause = mifosEntityAccessUtil
MifosEntityType.LOAN_PRODUCT); .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
sql += " lp.id in ( " + inClause + " ) and "; sql += " lp.id in ( " + inClause + " ) and ";
} }
sql += "lp.id not in (" sql += "lp.id not in (" + "Select pm.restricted_product_id from m_product_mix pm where pm.product_id=? " + "UNION "
+ "Select pm.restricted_product_id from m_product_mix pm where pm.product_id=? " + "UNION "
+ "Select pm.product_id from m_product_mix pm where pm.restricted_product_id=?)"; + "Select pm.product_id from m_product_mix pm where pm.restricted_product_id=?)";
return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId }); return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
@@ -11,8 +11,6 @@ public interface ScheduledJobRunnerService {
void updateLoanSummaryDetails(); void updateLoanSummaryDetails();
void updateLoanArrearsAgeingDetails();
void updateLoanPaidInAdvance(); void updateLoanPaidInAdvance();
void applyAnnualFeeForSavings(); void applyAnnualFeeForSavings();
@@ -136,46 +136,6 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result); logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
} }
@Transactional
@Override
@CronTarget(jobName = JobName.UPDATE_LOAN_ARREARS_AGEING)
public void updateLoanArrearsAgeingDetails() {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
jdbcTemplate.execute("truncate table m_loan_arrears_aging");
final StringBuilder updateSqlBuilder = new StringBuilder(900);
updateSqlBuilder
.append("INSERT INTO m_loan_arrears_aging(`loan_id`,`principal_overdue_derived`,`interest_overdue_derived`,`fee_charges_overdue_derived`,`penalty_charges_overdue_derived`,`total_overdue_derived`,`overdue_since_date_derived`)");
updateSqlBuilder.append("select ml.id as loanId,");
updateSqlBuilder
.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) as principal_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.interest_amount,0) - ifnull(mr.interest_completed_derived, 0))) as interest_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.fee_charges_amount,0) - ifnull(mr.fee_charges_completed_derived, 0))) as fee_charges_overdue_derived,");
updateSqlBuilder
.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as penalty_charges_overdue_derived,");
updateSqlBuilder.append("SUM((ifnull(mr.principal_amount,0) - ifnull(mr.principal_completed_derived, 0))) +");
updateSqlBuilder.append("SUM((ifnull(mr.interest_amount,0) - ifnull(mr.interest_completed_derived, 0))) +");
updateSqlBuilder.append("SUM((ifnull(mr.fee_charges_amount,0) - ifnull(mr.fee_charges_completed_derived, 0))) +");
updateSqlBuilder
.append("SUM((ifnull(mr.penalty_charges_amount,0) - ifnull(mr.penalty_charges_completed_derived, 0))) as total_overdue_derived,");
updateSqlBuilder.append("MIN(mr.duedate) as overdue_since_date_derived ");
updateSqlBuilder.append(" FROM m_loan ml ");
updateSqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
updateSqlBuilder.append(" WHERE ml.loan_status_id = 300 "); // active
updateSqlBuilder.append(" and mr.completed_derived is false ");
updateSqlBuilder.append(" and mr.duedate < SUBDATE(CURDATE(),INTERVAL ifnull(ml.grace_on_arrears_ageing,0) day) ");
updateSqlBuilder.append(" GROUP BY ml.id");
final int result = jdbcTemplate.update(updateSqlBuilder.toString());
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result);
}
@Transactional @Transactional
@Override @Override
@CronTarget(jobName = JobName.UPDATE_LOAN_PAID_IN_ADVANCE) @CronTarget(jobName = JobName.UPDATE_LOAN_PAID_IN_ADVANCE)
@@ -273,18 +233,28 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource()); final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
jdbcTemplate.update("UPDATE m_loan ml SET ml.is_npa=0"); final StringBuilder resetNPASqlBuilder = new StringBuilder(900);
resetNPASqlBuilder.append("update m_loan loan ");
resetNPASqlBuilder.append("left join m_loan_arrears_aging laa on laa.loan_id = loan.id ");
resetNPASqlBuilder.append("inner join m_product_loan mpl on mpl.id = loan.product_id and mpl.overdue_days_for_npa is not null ");
resetNPASqlBuilder.append("set loan.is_npa = 0 ");
resetNPASqlBuilder.append("where loan.loan_status_id = 300 and mpl.account_moves_out_of_npa_only_on_arrears_completion = 0 ");
resetNPASqlBuilder
.append("or (mpl.account_moves_out_of_npa_only_on_arrears_completion = 1 and laa.overdue_since_date_derived is null)");
jdbcTemplate.update(resetNPASqlBuilder.toString());
final StringBuilder updateSqlBuilder = new StringBuilder(900); final StringBuilder updateSqlBuilder = new StringBuilder(900);
updateSqlBuilder.append("UPDATE m_loan as ml,"); updateSqlBuilder.append("UPDATE m_loan as ml,");
updateSqlBuilder.append(" (select loan.id from m_loan_repayment_schedule mr "); updateSqlBuilder.append(" (select loan.id ");
updateSqlBuilder updateSqlBuilder.append("from m_loan_arrears_aging laa");
.append(" INNER JOIN m_loan loan on mr.loan_id = loan.id INNER JOIN m_product_loan mpl on mpl.id = loan.product_id AND mpl.overdue_days_for_npa is not null "); updateSqlBuilder.append(" INNER JOIN m_loan loan on laa.loan_id = loan.id ");
updateSqlBuilder.append("WHERE loan.loan_status_id = 300 and mr.completed_derived is false "); updateSqlBuilder.append(" INNER JOIN m_product_loan mpl on mpl.id = loan.product_id AND mpl.overdue_days_for_npa is not null ");
updateSqlBuilder updateSqlBuilder.append("WHERE loan.loan_status_id = 300 and ");
.append(" and mr.duedate < SUBDATE(CURDATE(),INTERVAL ifnull(mpl.overdue_days_for_npa,0) day) group by loan.id) as sl "); updateSqlBuilder.append("laa.overdue_since_date_derived < SUBDATE(CURDATE(),INTERVAL ifnull(mpl.overdue_days_for_npa,0) day) ");
updateSqlBuilder.append("SET ml.is_npa=1 where ml.id=sl.id"); updateSqlBuilder.append("group by loan.id) as sl ");
updateSqlBuilder.append("SET ml.is_npa=1 where ml.id=sl.id ");
final int result = jdbcTemplate.update(updateSqlBuilder.toString()); final int result = jdbcTemplate.update(updateSqlBuilder.toString());
@@ -0,0 +1,5 @@
ALTER TABLE `m_product_loan_recalculation_details`
ADD COLUMN `arrears_based_on_original_schedule` TINYINT(1) NOT NULL DEFAULT '0';
ALTER TABLE `m_product_loan`
ADD COLUMN `account_moves_out_of_npa_only_on_arrears_completion` TINYINT(1) NOT NULL DEFAULT '0';