MIFOSX-1613 : changes for overdue status and npa
This commit is contained in:
+24
-2
@@ -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)
|
||||
</td>
|
||||
</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>
|
||||
<td>accountingRule</td>
|
||||
</tr>
|
||||
@@ -11356,6 +11363,12 @@ An enumeration that indicates the type of transaction processing strategy to be
|
||||
<tr>
|
||||
<td class=fielddesc>Specifies rest frequency start date for interest recalculation.<br>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -11470,7 +11483,8 @@ GET https://DomainName/api/v1/loanproducts
|
||||
"id":1,
|
||||
"code":"interestRecalculationFrequencyType.same.as.repayment.period",
|
||||
"value":"Same as repayment period"
|
||||
}
|
||||
},
|
||||
"isArrearsBasedOnOriginalSchedule" : true
|
||||
},
|
||||
"accountingRule": {
|
||||
"id": 2,
|
||||
@@ -12598,7 +12612,7 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
numberOfRepaymentVariationsForBorrowerCycle,
|
||||
interestRateVariationsForBorrowerCycle,
|
||||
multiDisburseLoan,maxTrancheCount,
|
||||
outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds
|
||||
outstandingLoanBalance,overdueDaysForNPA,accountMovesOutOfNPAOnlyOnArrearsCompletion,holdGuaranteeFunds
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -12631,6 +12645,14 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
<td>interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType</td>
|
||||
</tr>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
+156
-3
@@ -3584,7 +3584,7 @@ public class ClientLoanIntegrationTest {
|
||||
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY,
|
||||
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
|
||||
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,
|
||||
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,
|
||||
String interestDue, String feeChargesDue, String penaltyChargesDue) {
|
||||
Map<String, Object> values = new HashMap<>(3);
|
||||
@@ -3795,13 +3945,14 @@ public class ClientLoanIntegrationTest {
|
||||
final String recalculationRestFrequencyDate, final Account[] accounts) {
|
||||
return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
|
||||
rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval,
|
||||
recalculationRestFrequencyDate, accounts, null);
|
||||
recalculationRestFrequencyDate, accounts, null, false);
|
||||
}
|
||||
|
||||
private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
|
||||
final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
|
||||
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 ---------------------------------------");
|
||||
LoanProductTestBuilder builder = new LoanProductTestBuilder()
|
||||
.withPrincipal("10000000.00")
|
||||
@@ -3820,6 +3971,8 @@ public class ClientLoanIntegrationTest {
|
||||
builder = builder.withAccountingRulePeriodicAccrual(accounts);
|
||||
}
|
||||
|
||||
if (isArrearsBasedOnOriginalSchedule) builder = builder.withArrearsConfiguration();
|
||||
|
||||
final String loanProductJSON = builder.build(chargeId);
|
||||
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||
}
|
||||
|
||||
+9
@@ -92,6 +92,7 @@ public class LoanProductTestBuilder {
|
||||
private String mandatoryGuarantee = null;
|
||||
private String minimumGuaranteeFromOwnFunds = null;
|
||||
private String minimumGuaranteeFromGuarantor = null;
|
||||
private String isArrearsBasedOnOriginalSchedule = null;
|
||||
|
||||
public String build(final String chargeId) {
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
@@ -148,6 +149,9 @@ public class LoanProductTestBuilder {
|
||||
map.put("recalculationRestFrequencyType", recalculationRestFrequencyType);
|
||||
map.put("recalculationRestFrequencyInterval", recalculationRestFrequencyInterval);
|
||||
map.put("recalculationRestFrequencyDate", recalculationRestFrequencyDate);
|
||||
if (isArrearsBasedOnOriginalSchedule != null) {
|
||||
map.put("isArrearsBasedOnOriginalSchedule", isArrearsBasedOnOriginalSchedule);
|
||||
}
|
||||
}
|
||||
if (holdGuaranteeFunds != null) {
|
||||
map.put("holdGuaranteeFunds", this.holdGuaranteeFunds);
|
||||
@@ -398,6 +402,11 @@ public class LoanProductTestBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanProductTestBuilder withArrearsConfiguration() {
|
||||
this.isArrearsBasedOnOriginalSchedule = "true";
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanProductTestBuilder withOnHoldFundDetails(final String mandatoryGuarantee, final String minimumGuaranteeFromGuarantor,
|
||||
final String minimumGuaranteeFromOwnFunds) {
|
||||
this.holdGuaranteeFunds = true;
|
||||
|
||||
-2
@@ -13,7 +13,6 @@ import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstan
|
||||
import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
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.LoanTransactionType;
|
||||
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.LoanReadPlatformService;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
|
||||
+23
-2
@@ -12,8 +12,14 @@ public class BusinessEventNotificationConstants {
|
||||
|
||||
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_undo_transaction"), LOAN_MAKE_REPAYMENT("loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF(
|
||||
"loan_undo_writtenoff");
|
||||
"loan_undo_transaction"), LOAN_ADJUST_TRANSACTION("loan_adjust_transaction"), LOAN_MAKE_REPAYMENT(
|
||||
"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;
|
||||
|
||||
@@ -36,4 +42,19 @@ public class BusinessEventNotificationConstants {
|
||||
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
-3
@@ -5,7 +5,9 @@
|
||||
*/
|
||||
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
|
||||
@@ -18,11 +20,11 @@ public interface BusinessEventListner {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity);
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity);
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -6,7 +6,8 @@
|
||||
package org.mifosplatform.portfolio.common.service;
|
||||
|
||||
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
|
||||
@@ -19,13 +20,13 @@ public interface BusinessEventNotifierService {
|
||||
* Method should be called to notify listeners before Business 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
|
||||
* 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
|
||||
|
||||
+3
-3
@@ -10,8 +10,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
|
||||
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@@ -31,7 +31,7 @@ public class BusinessEventNotifierServiceImpl implements BusinessEventNotifierSe
|
||||
* org.springframework.data.jpa.domain.AbstractPersistable)
|
||||
*/
|
||||
@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);
|
||||
if (businessEventListners != null) {
|
||||
for (BusinessEventListner eventListner : businessEventListners) {
|
||||
@@ -51,7 +51,7 @@ public class BusinessEventNotifierServiceImpl implements BusinessEventNotifierSe
|
||||
* org.springframework.data.jpa.domain.AbstractPersistable)
|
||||
*/
|
||||
@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);
|
||||
if (businessEventListners != null) {
|
||||
for (BusinessEventListner eventListner : businessEventListners) {
|
||||
|
||||
+3
-13
@@ -299,9 +299,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
@Embedded
|
||||
private LoanSummary summary;
|
||||
|
||||
@OneToOne(mappedBy = "loan", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
private LoanSummaryArrearsAging summaryArrearsAging;
|
||||
|
||||
@Transient
|
||||
private boolean accountNumberRequiresAutoGeneration = false;
|
||||
@Transient
|
||||
@@ -1146,7 +1143,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
if (isNotDisbursed()) {
|
||||
this.summary.zeroFields();
|
||||
this.summaryArrearsAging = null;
|
||||
this.totalOverpaid = null;
|
||||
} else {
|
||||
final Money overpaidBy = calculateTotalOverpayment();
|
||||
@@ -1158,13 +1154,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
|
||||
this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper,
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1928,7 +1917,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, 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.";
|
||||
throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded",
|
||||
errorMessage, this.proposedPrincipal, approvedLoanAmount);
|
||||
@@ -3377,7 +3366,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return status().isRejected();
|
||||
}
|
||||
|
||||
private boolean isOpen() {
|
||||
public boolean isOpen() {
|
||||
return status().isActive();
|
||||
}
|
||||
|
||||
@@ -5113,4 +5102,5 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
return lastTransactionDate == null ? now : lastTransactionDate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+24
-5
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.client.domain.Client;
|
||||
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.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
|
||||
@@ -126,6 +128,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
final String txnExternalId, final boolean isRecoveryRepayment, boolean isAccountTransfer) {
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
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
|
||||
// if repayments is synced with meeting?
|
||||
@@ -221,7 +225,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
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()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
@@ -276,7 +281,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
boolean isAccountTransfer = true;
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||
|
||||
@@ -311,6 +317,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
recalculateAccruals(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newPaymentTransaction));
|
||||
return newPaymentTransaction;
|
||||
}
|
||||
|
||||
@@ -349,7 +357,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
boolean isAccountTransfer = true;
|
||||
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||
|
||||
@@ -374,7 +383,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction));
|
||||
builderResult.withEntityId(newRefundTransaction.getId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
@@ -556,7 +566,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
|
||||
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
@@ -584,6 +595,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
recalculateAccruals(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction));
|
||||
|
||||
builderResult.withEntityId(newRefundTransaction.getId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
@@ -592,4 +605,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+5
@@ -927,4 +927,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
}
|
||||
return paidChargePerInstallment;
|
||||
}
|
||||
|
||||
|
||||
public Loan getLoan() {
|
||||
return this.loan;
|
||||
}
|
||||
}
|
||||
+25
@@ -306,4 +306,29 @@ public final class LoanSummary {
|
||||
public BigDecimal getTotalPrincipalWrittenOff() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
-130
@@ -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();
|
||||
}
|
||||
}
|
||||
+63
-33
@@ -26,6 +26,7 @@ import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
|
||||
import org.mifosplatform.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.mifosplatform.portfolio.account.domain.AccountTransferType;
|
||||
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.service.BusinessEventListner;
|
||||
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.exception.InsufficientAccountBalanceException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
import org.springframework.stereotype.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_DISBURSAL,
|
||||
new ReverseAllFundsOnBusinessEvent());
|
||||
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_TRANSACTION,
|
||||
new ReverseFundsOnBusinessEvent());
|
||||
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_ADJUST_TRANSACTION,
|
||||
new AdjustFundsOnBusinessEvent());
|
||||
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
|
||||
new ReleaseFundsOnBusinessEvent());
|
||||
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WRITTEN_OFF, new ReleaseAllFunds());
|
||||
@@ -236,8 +236,9 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
releaseLoanIds.put(loanId, guarantorFundingDetails.getId());
|
||||
try {
|
||||
BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining();
|
||||
if(loan.getGuaranteeAmount().compareTo(loan.getPrincpal().getAmount()) == 1){
|
||||
remainingAmount = remainingAmount.multiply(loan.getPrincpal().getAmount()).divide(loan.getGuaranteeAmount(), roundingMode);
|
||||
if (loan.getGuaranteeAmount().compareTo(loan.getPrincpal().getAmount()) == 1) {
|
||||
remainingAmount = remainingAmount.multiply(loan.getPrincpal().getAmount()).divide(loan.getGuaranteeAmount(),
|
||||
roundingMode);
|
||||
}
|
||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
|
||||
toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType,
|
||||
@@ -499,12 +500,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
private class ValidateOnBusinessEvent implements BusinessEventListner {
|
||||
|
||||
@Override
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
|
||||
|
||||
@Override
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
|
||||
if (businessEventEntity instanceof Loan) {
|
||||
Loan loan = (Loan) businessEventEntity;
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
|
||||
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
|
||||
if (entity instanceof Loan) {
|
||||
Loan loan = (Loan) entity;
|
||||
validateGuarantorBusinessRules(loan);
|
||||
}
|
||||
}
|
||||
@@ -513,12 +515,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
private class HoldFundsOnBusinessEvent implements BusinessEventListner {
|
||||
|
||||
@Override
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
|
||||
|
||||
@Override
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
|
||||
if (businessEventEntity instanceof Loan) {
|
||||
Loan loan = (Loan) businessEventEntity;
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
|
||||
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
|
||||
if (entity instanceof Loan) {
|
||||
Loan loan = (Loan) entity;
|
||||
holdGuarantorFunds(loan);
|
||||
}
|
||||
}
|
||||
@@ -527,12 +530,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
private class ReleaseFundsOnBusinessEvent implements BusinessEventListner {
|
||||
|
||||
@Override
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
|
||||
|
||||
@Override
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
|
||||
if (businessEventEntity instanceof LoanTransaction) {
|
||||
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity;
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
|
||||
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
|
||||
if (entity instanceof LoanTransaction) {
|
||||
LoanTransaction loanTransaction = (LoanTransaction) entity;
|
||||
if (releaseLoanIds.containsKey(loanTransaction.getLoan().getId())) {
|
||||
completeGuarantorFund(loanTransaction);
|
||||
} else {
|
||||
@@ -545,12 +549,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
private class ReverseFundsOnBusinessEvent implements BusinessEventListner {
|
||||
|
||||
@Override
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
|
||||
|
||||
@Override
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
|
||||
if (businessEventEntity instanceof LoanTransaction) {
|
||||
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity;
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
|
||||
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
|
||||
if (entity instanceof LoanTransaction) {
|
||||
LoanTransaction loanTransaction = (LoanTransaction) entity;
|
||||
List<Long> reersedTransactions = new ArrayList<>(1);
|
||||
reersedTransactions.add(loanTransaction.getId());
|
||||
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 {
|
||||
|
||||
@Override
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
|
||||
|
||||
@Override
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
|
||||
if (businessEventEntity instanceof Loan) {
|
||||
Loan loan = (Loan) businessEventEntity;
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
|
||||
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
|
||||
if (entity instanceof Loan) {
|
||||
Loan loan = (Loan) entity;
|
||||
List<Long> reersedTransactions = new ArrayList<>(1);
|
||||
reersedTransactions.addAll(loan.findExistingTransactionIds());
|
||||
reverseTransaction(reersedTransactions);
|
||||
@@ -577,12 +605,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
private class UndoAllFundTransactions implements BusinessEventListner {
|
||||
|
||||
@Override
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
|
||||
|
||||
@Override
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
|
||||
if (businessEventEntity instanceof Loan) {
|
||||
Loan loan = (Loan) businessEventEntity;
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
|
||||
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
|
||||
if (entity instanceof Loan) {
|
||||
Loan loan = (Loan) entity;
|
||||
reverseAllFundTransaction(loan);
|
||||
}
|
||||
}
|
||||
@@ -591,12 +620,13 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
private class ReleaseAllFunds implements BusinessEventListner {
|
||||
|
||||
@Override
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
|
||||
public void businessEventToBeExecuted(@SuppressWarnings("unused") Map<BUSINESS_ENTITY, Object> businessEventEntity) {}
|
||||
|
||||
@Override
|
||||
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
|
||||
if (businessEventEntity instanceof LoanTransaction) {
|
||||
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity;
|
||||
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
|
||||
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_TRANSACTION);
|
||||
if (entity instanceof LoanTransaction) {
|
||||
LoanTransaction loanTransaction = (LoanTransaction) entity;
|
||||
releaseAllGuarantors(loanTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-10
@@ -19,13 +19,10 @@ public class LoanSchedulePeriodData {
|
||||
private final Integer period;
|
||||
private final LocalDate fromDate;
|
||||
private final LocalDate dueDate;
|
||||
@SuppressWarnings("unused")
|
||||
private final LocalDate obligationsMetOnDate;
|
||||
@SuppressWarnings("unused")
|
||||
private final Boolean complete;
|
||||
private final Integer daysInPeriod;
|
||||
private final BigDecimal principalDisbursed;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal principalOriginalDue;
|
||||
private final BigDecimal principalDue;
|
||||
private final BigDecimal principalPaid;
|
||||
@@ -52,21 +49,14 @@ public class LoanSchedulePeriodData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalOriginalDueForPeriod;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalDueForPeriod;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalPaidForPeriod;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalPaidInAdvanceForPeriod;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalPaidLateForPeriod;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalWaivedForPeriod;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalWrittenOffForPeriod;
|
||||
private final BigDecimal totalOutstandingForPeriod;
|
||||
private final BigDecimal totalOverdue;
|
||||
@SuppressWarnings("unused")
|
||||
private final BigDecimal totalActualCostOfLoanForPeriod;
|
||||
|
||||
public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed,
|
||||
@@ -108,6 +98,26 @@ public class LoanSchedulePeriodData {
|
||||
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
|
||||
* disbursement (typically first period)
|
||||
@@ -417,4 +427,9 @@ public class LoanSchedulePeriodData {
|
||||
public BigDecimal principalLoanBalanceOutstanding() {
|
||||
return this.principalLoanBalanceOutstanding;
|
||||
}
|
||||
|
||||
|
||||
public Boolean getComplete() {
|
||||
return this.complete;
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -48,6 +48,7 @@ import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.mifosplatform.portfolio.collateral.domain.LoanCollateral;
|
||||
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.service.BusinessEventNotifierService;
|
||||
import org.mifosplatform.portfolio.fund.domain.Fund;
|
||||
@@ -762,7 +763,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
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() //
|
||||
@@ -806,7 +808,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
final Note note = Note.loanNote(loan, noteText);
|
||||
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() //
|
||||
@@ -962,4 +965,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+18
@@ -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);
|
||||
|
||||
}
|
||||
+503
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -159,8 +159,9 @@ public class LoanAssembler {
|
||||
final Long transactionProcessingStrategyId = this.fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
|
||||
final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", 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);
|
||||
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
||||
|
||||
@@ -234,14 +235,14 @@ public class LoanAssembler {
|
||||
|
||||
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
|
||||
loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
|
||||
collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
|
||||
collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
|
||||
createStandingInstructionAtDisbursement);
|
||||
|
||||
} else if (group != null) {
|
||||
|
||||
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
|
||||
loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
|
||||
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
|
||||
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
|
||||
createStandingInstructionAtDisbursement);
|
||||
|
||||
} else if (client != null) {
|
||||
@@ -264,8 +265,9 @@ public class LoanAssembler {
|
||||
loanDisbursementDetails.updateLoan(loanApplication);
|
||||
}
|
||||
}
|
||||
|
||||
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 boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
|
||||
+117
-38
@@ -9,7 +9,6 @@ import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
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.SingleDisbursalCommand;
|
||||
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.domain.PeriodFrequencyType;
|
||||
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.LoanTransactionType;
|
||||
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.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||
@@ -291,6 +290,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance);
|
||||
}
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
|
||||
@@ -405,6 +407,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loan.getId()) //
|
||||
@@ -540,6 +544,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId());
|
||||
checkClientOrGroupActive(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
|
||||
@@ -648,6 +654,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
}
|
||||
|
||||
return changes;
|
||||
@@ -661,6 +669,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
removeLoanCycle(loan);
|
||||
|
||||
//
|
||||
@@ -703,7 +713,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
|
||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
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() //
|
||||
@@ -789,10 +800,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(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(
|
||||
"error.msg.loan.transfer.transaction.update.not.allowed", "Loan transaction:" + transactionId
|
||||
+ " update not allowed as it involves in account transfer", transactionId); }
|
||||
@@ -914,10 +925,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
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())) {
|
||||
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() //
|
||||
.withCommandId(command.commandId()) //
|
||||
@@ -964,7 +976,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney,
|
||||
transactionDate, interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, waiveInterestTransaction));
|
||||
CalendarInstance restCalendarInstance = null;
|
||||
ApplicationCurrency applicationCurrency = null;
|
||||
LocalDate calculatedRepaymentsStartingFromDate = null;
|
||||
@@ -1027,6 +1040,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, waiveInterestTransaction));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(waiveInterestTransaction.getId()) //
|
||||
@@ -1052,6 +1067,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WRITTEN_OFF,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
removeLoanCycle(loan);
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
@@ -1102,7 +1119,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
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() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(writeoff.getId()) //
|
||||
@@ -1124,6 +1142,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CLOSE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
@@ -1184,6 +1204,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CLOSE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
CommandProcessingResult result = null;
|
||||
if (possibleClosingTransaction != null) {
|
||||
|
||||
@@ -1220,6 +1242,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
removeLoanCycle(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CLOSE_AS_RESCHEDULE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
@@ -1236,7 +1260,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Note note = Note.loanNote(loan, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CLOSE_AS_RESCHEDULE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loanId) //
|
||||
@@ -1261,6 +1286,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
|
||||
|
||||
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);
|
||||
|
||||
@@ -1283,6 +1310,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
}
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loanCharge.getId()) //
|
||||
@@ -1455,10 +1484,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException(
|
||||
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);
|
||||
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UPDATE_CHARGE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loanChargeId) //
|
||||
@@ -1491,6 +1524,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
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,
|
||||
loanCharge.getId()); }
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
|
||||
Integer loanInstallmentNumber = null;
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
LoanInstallmentCharge chargePerInstallment = null;
|
||||
@@ -1566,6 +1601,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WAIVE_CHARGE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loanChargeId) //
|
||||
@@ -1589,10 +1627,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
// yet to be approved (are in submitted and pending status)
|
||||
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeDeletedException(
|
||||
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);
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_DELETE_CHARGE,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loanChargeId) //
|
||||
@@ -1670,7 +1711,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
|
||||
isExceptionForBalanceCheck);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loanChargeId) //
|
||||
@@ -1787,6 +1827,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(accountId);
|
||||
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> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
@@ -1800,7 +1842,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_INITIATE_TRANSFER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
return newTransferTransaction;
|
||||
}
|
||||
|
||||
@@ -1812,7 +1855,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
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> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
|
||||
@@ -1832,6 +1876,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_ACCEPT_TRANSFER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
return newTransferAcceptanceTransaction;
|
||||
}
|
||||
@@ -1843,6 +1889,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
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> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
@@ -1856,6 +1904,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WITHDRAW_TRANSFER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
return newTransferAcceptanceTransaction;
|
||||
}
|
||||
@@ -1864,8 +1914,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public void rejectLoanTransfer(final Long 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());
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REJECT_TRANSFER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -1883,12 +1937,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
|
||||
|
||||
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
|
||||
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
@@ -1918,12 +1975,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
for (final String loanIdString : loanIds) {
|
||||
final Long loanId = Long.valueOf(loanIdString);
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
|
||||
|
||||
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REASSIGN_OFFICER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
}
|
||||
this.loanRepository.flush();
|
||||
|
||||
@@ -1946,10 +2007,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
if (loan.getLoanOfficer() == null) { throw new LoanOfficerUnassignmentException(loanId); }
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REMOVE_OFFICER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
loan.removeLoanOfficer(dateOfLoanOfficerunAssigned);
|
||||
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REMOVE_OFFICER,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
@@ -1971,19 +2036,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void applyMeetingDateChanges(final Calendar calendar, final Collection<CalendarInstance> loanCalendarInstances) {
|
||||
|
||||
final Boolean reschedulebasedOnMeetingDates = null;
|
||||
final Boolean reschedulebasedOnMeetingDates = null;
|
||||
final LocalDate presentMeetingDate = null;
|
||||
final LocalDate newMeetingDate = null;
|
||||
|
||||
|
||||
applyMeetingDateChanges(calendar, loanCalendarInstances, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -2011,16 +2075,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
throw new CalendarParameterUpdateNotSupportedException("jlg.loan.recalculation", defaultUserMessage);
|
||||
}
|
||||
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
|
||||
|
||||
if(reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates ){
|
||||
|
||||
if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) {
|
||||
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
|
||||
holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
|
||||
}else{
|
||||
} else {
|
||||
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
|
||||
holidays, workingDays);
|
||||
holidays, workingDays);
|
||||
}
|
||||
|
||||
|
||||
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
}
|
||||
}
|
||||
@@ -2337,6 +2400,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
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);
|
||||
}
|
||||
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()) {
|
||||
|
||||
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue());
|
||||
@@ -2425,6 +2492,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
Long overdurPenaltyWaitPeriod = null;
|
||||
LocalDate lastTransactionDate = null;
|
||||
LoanTransaction writeOffTransaction = loan.findWriteOffTransaction();
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeOffTransaction));
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
|
||||
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
|
||||
@@ -2460,7 +2529,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
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() //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
@@ -2617,6 +2687,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
public void recalculateInterest(final long loanId) {
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
CalendarInstance restCalendarInstance = null;
|
||||
ApplicationCurrency applicationCurrency = null;
|
||||
LocalDate calculatedRepaymentsStartingFromDate = null;
|
||||
@@ -2662,6 +2734,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION,
|
||||
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2708,7 +2782,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
return installments;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
|
||||
@@ -2718,11 +2792,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
|
||||
//checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate);
|
||||
// checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate);
|
||||
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
checkIfLoanIsPaidInAdvance(loanId, transactionAmount);
|
||||
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
|
||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
changes.put("transactionAmount", command.stringValueOfParameterNamed("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();
|
||||
|
||||
if(overpaid == null || overpaid.equals(new BigDecimal(0)) || transactionAmount.floatValue() > overpaid.floatValue()) {
|
||||
if(overpaid == null)
|
||||
overpaid = BigDecimal.ZERO;
|
||||
|
||||
if (overpaid == null || overpaid.equals(new BigDecimal(0)) || transactionAmount.floatValue() > overpaid.floatValue()) {
|
||||
if (overpaid == null) overpaid = BigDecimal.ZERO;
|
||||
throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AppUser getAppUserIfPresent() {
|
||||
AppUser user = null;
|
||||
@@ -2766,4 +2839,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -61,6 +61,7 @@ public interface LoanProductConstants {
|
||||
public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing";
|
||||
public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA";
|
||||
public static final String minimumDaysBetweenDisbursalAndFirstRepayment = "minimumDaysBetweenDisbursalAndFirstRepayment";
|
||||
public static final String accountMovesOutOfNPAOnlyOnArrearsCompletionParamName = "accountMovesOutOfNPAOnlyOnArrearsCompletion";
|
||||
|
||||
// Interest recalculation related
|
||||
public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
|
||||
@@ -71,6 +72,7 @@ public interface LoanProductConstants {
|
||||
public static final String recalculationRestFrequencyTypeParameterName = "recalculationRestFrequencyType";
|
||||
public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval";
|
||||
public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate";
|
||||
public static final String isArrearsBasedOnOriginalScheduleParamName = "isArrearsBasedOnOriginalSchedule";
|
||||
|
||||
// Guarantee related
|
||||
public static final String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
|
||||
|
||||
+13
-5
@@ -137,6 +137,7 @@ public class LoanProductData {
|
||||
|
||||
private final Boolean holdGuaranteeFunds;
|
||||
private final LoanProductGuaranteeData productGuaranteeData;
|
||||
private final Boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
|
||||
|
||||
/**
|
||||
* Used when returning lookup information about loan product for dropdowns.
|
||||
@@ -187,6 +188,7 @@ public class LoanProductData {
|
||||
final BigDecimal outstandingLoanBalance = null;
|
||||
final LoanProductGuaranteeData productGuaranteeData = null;
|
||||
final Boolean holdGuaranteeFunds = false;
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
|
||||
|
||||
final EnumOptionData daysInMonthType = null;
|
||||
final EnumOptionData daysInYearType = null;
|
||||
@@ -202,7 +204,8 @@ public class LoanProductData {
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
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) {
|
||||
@@ -258,6 +261,7 @@ public class LoanProductData {
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
|
||||
final Boolean holdGuaranteeFunds = false;
|
||||
final LoanProductGuaranteeData productGuaranteeData = null;
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -267,7 +271,8 @@ public class LoanProductData {
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData);
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
|
||||
accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
}
|
||||
|
||||
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
|
||||
@@ -330,6 +335,7 @@ public class LoanProductData {
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
|
||||
final Boolean holdGuaranteeFunds = false;
|
||||
final LoanProductGuaranteeData productGuaranteeData = null;
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -340,7 +346,7 @@ public class LoanProductData {
|
||||
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
|
||||
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
|
||||
holdGuaranteeFunds, productGuaranteeData);
|
||||
holdGuaranteeFunds, productGuaranteeData, accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
}
|
||||
|
||||
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,
|
||||
final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds,
|
||||
LoanProductGuaranteeData loanProductGuaranteeData) {
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuaranteeFunds,
|
||||
final LoanProductGuaranteeData loanProductGuaranteeData, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.shortName = shortName;
|
||||
@@ -445,6 +451,7 @@ public class LoanProductData {
|
||||
this.interestRecalculationData = interestRecalculationData;
|
||||
this.holdGuaranteeFunds = holdGuaranteeFunds;
|
||||
this.productGuaranteeData = loanProductGuaranteeData;
|
||||
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
|
||||
|
||||
this.daysInMonthTypeOptions = null;
|
||||
this.daysInYearTypeOptions = null;
|
||||
@@ -548,6 +555,7 @@ public class LoanProductData {
|
||||
this.interestRecalculationData = productData.interestRecalculationData;
|
||||
this.holdGuaranteeFunds = productData.holdGuaranteeFunds;
|
||||
this.productGuaranteeData = productData.productGuaranteeData;
|
||||
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = productData.accountMovesOutOfNPAOnlyOnArrearsCompletion;
|
||||
|
||||
this.daysInMonthTypeOptions = daysInMonthTypeOptions;
|
||||
this.daysInYearTypeOptions = daysInYearTypeOptions;
|
||||
|
||||
+7
-2
@@ -24,11 +24,13 @@ public class LoanProductInterestRecalculationData {
|
||||
private final EnumOptionData recalculationRestFrequencyType;
|
||||
private final Integer recalculationRestFrequencyInterval;
|
||||
private final LocalDate recalculationRestFrequencyDate;
|
||||
@SuppressWarnings("unused")
|
||||
private final boolean isArrearsBasedOnOriginalSchedule;
|
||||
|
||||
public LoanProductInterestRecalculationData(final Long id, final Long productId,
|
||||
final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType,
|
||||
final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval,
|
||||
final LocalDate recalculationRestFrequencyDate) {
|
||||
final LocalDate recalculationRestFrequencyDate, boolean isArrearsBasedOnOriginalSchedule) {
|
||||
this.id = id;
|
||||
this.productId = productId;
|
||||
this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
|
||||
@@ -36,6 +38,7 @@ public class LoanProductInterestRecalculationData {
|
||||
this.recalculationRestFrequencyType = recalculationRestFrequencyType;
|
||||
this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval;
|
||||
this.recalculationRestFrequencyDate = recalculationRestFrequencyDate;
|
||||
this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule;
|
||||
}
|
||||
|
||||
public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() {
|
||||
@@ -46,8 +49,10 @@ public class LoanProductInterestRecalculationData {
|
||||
final EnumOptionData recalculationRestFrequencyType = null;
|
||||
final Integer recalculationRestFrequencyInterval = null;
|
||||
final LocalDate recalculationRestFrequencyDate = null;
|
||||
final boolean isArrearsBasedOnOriginalSchedule = false;
|
||||
return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType,
|
||||
recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate);
|
||||
recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate,
|
||||
isArrearsBasedOnOriginalSchedule);
|
||||
}
|
||||
|
||||
public EnumOptionData getInterestRecalculationCompoundingType() {
|
||||
|
||||
+25
-2
@@ -136,6 +136,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
|
||||
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,
|
||||
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
|
||||
|
||||
@@ -225,6 +228,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command);
|
||||
}
|
||||
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = command
|
||||
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName);
|
||||
|
||||
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
|
||||
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
|
||||
annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType,
|
||||
@@ -233,7 +239,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount,
|
||||
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment,
|
||||
holdGuarantorFunds, loanProductGuaranteeDetails);
|
||||
holdGuarantorFunds, loanProductGuaranteeDetails, accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
|
||||
}
|
||||
|
||||
@@ -458,7 +464,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
final boolean isInterestRecalculationEnabled,
|
||||
final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
|
||||
final LoanProductGuaranteeDetails loanProductGuaranteeDetails) {
|
||||
final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion) {
|
||||
this.fund = fund;
|
||||
this.transactionProcessingStrategy = transactionProcessingStrategy;
|
||||
this.name = name.trim();
|
||||
@@ -510,6 +516,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
this.holdGuaranteeFunds = holdGuarantorFunds;
|
||||
this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
|
||||
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
@@ -737,6 +744,14 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -989,4 +1004,12 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
return this.shortName;
|
||||
}
|
||||
|
||||
public boolean isArrearsBasedOnOriginalSchedule() {
|
||||
boolean isBasedOnOriginalSchedule = false;
|
||||
if (getProductInterestRecalculationDetails() != null) {
|
||||
isBasedOnOriginalSchedule = getProductInterestRecalculationDetails().isArrearsBasedOnOriginalSchedule();
|
||||
}
|
||||
return isBasedOnOriginalSchedule;
|
||||
}
|
||||
|
||||
}
|
||||
+21
-2
@@ -57,6 +57,9 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
|
||||
@Column(name = "rest_freqency_date")
|
||||
private Date restFrequencyDate;
|
||||
|
||||
@Column(name = "arrears_based_on_original_schedule")
|
||||
private boolean isArrearsBasedOnOriginalSchedule;
|
||||
|
||||
protected LoanProductInterestRecalculationDetails() {
|
||||
//
|
||||
}
|
||||
@@ -75,6 +78,8 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
|
||||
.localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName);
|
||||
Integer recurrenceInterval = command
|
||||
.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName);
|
||||
final boolean isArrearsBasedOnOriginalSchedule = command
|
||||
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName);
|
||||
RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency);
|
||||
Date recurrenceOnDate = null;
|
||||
if (recurrenceOnLocalDate != null) {
|
||||
@@ -87,17 +92,18 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
|
||||
}
|
||||
|
||||
return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod,
|
||||
recurrenceFrequency, recurrenceInterval, recurrenceOnDate);
|
||||
recurrenceFrequency, recurrenceInterval, recurrenceOnDate, isArrearsBasedOnOriginalSchedule);
|
||||
}
|
||||
|
||||
private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod,
|
||||
final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval,
|
||||
final Date restFrequencyDate) {
|
||||
final Date restFrequencyDate, final boolean isArrearsBasedOnOriginalSchedule) {
|
||||
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
|
||||
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
|
||||
this.restFrequencyType = restFrequencyType;
|
||||
this.restInterval = restInterval;
|
||||
this.restFrequencyDate = restFrequencyDate;
|
||||
this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule;
|
||||
}
|
||||
|
||||
public void updateProduct(final LoanProduct loanProduct) {
|
||||
@@ -169,6 +175,14 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
|
||||
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() {
|
||||
@@ -186,4 +200,9 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
|
||||
public Integer getRestInterval() {
|
||||
return this.restInterval;
|
||||
}
|
||||
|
||||
|
||||
public boolean isArrearsBasedOnOriginalSchedule() {
|
||||
return this.isArrearsBasedOnOriginalSchedule;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -71,9 +71,10 @@ public final class LoanProductDataValidator {
|
||||
LoanProductConstants.recalculationRestFrequencyDateParamName,
|
||||
LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
|
||||
LoanProductConstants.recalculationRestFrequencyTypeParameterName,
|
||||
LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName,
|
||||
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName,
|
||||
LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
|
||||
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName));
|
||||
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@@ -299,6 +300,13 @@ public final class LoanProductDataValidator {
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull()
|
||||
.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
|
||||
final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
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) {
|
||||
@@ -719,6 +733,13 @@ public final class LoanProductDataValidator {
|
||||
.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
|
||||
Boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) {
|
||||
|
||||
+59
-57
@@ -86,13 +86,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
|
||||
String sql = "select " + rm.loanProductSchema();
|
||||
|
||||
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil.
|
||||
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
|
||||
MifosEntityType.LOAN_PRODUCT);
|
||||
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
|
||||
sql += " where lp.id in ( " + inClause + " ) ";
|
||||
}
|
||||
// Check if branch specific products are enabled. If yes, fetch only
|
||||
// products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil
|
||||
.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
|
||||
if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
|
||||
sql += " where lp.id in ( " + inClause + " ) ";
|
||||
}
|
||||
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] {});
|
||||
}
|
||||
@@ -109,18 +109,18 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
final LoanProductLookupMapper rm = new LoanProductLookupMapper();
|
||||
|
||||
String sql = "select ";
|
||||
if(activeOnly) {
|
||||
if (activeOnly) {
|
||||
sql += rm.activeOnlySchema();
|
||||
} else {
|
||||
sql += rm.schema();
|
||||
}
|
||||
|
||||
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil.
|
||||
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
|
||||
MifosEntityType.LOAN_PRODUCT);
|
||||
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
|
||||
if(activeOnly) {
|
||||
// Check if branch specific products are enabled. If yes, fetch only
|
||||
// products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil
|
||||
.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
|
||||
if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
|
||||
if (activeOnly) {
|
||||
sql += " and id in ( " + inClause + " )";
|
||||
} else {
|
||||
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, "
|
||||
+ "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_freqency_date as restFrequencyDate, "
|
||||
+ "lpr.rest_freqency_date as restFrequencyDate, lpr.arrears_based_on_original_schedule as isArrearsBasedOnOriginalSchedule, "
|
||||
+ "lp.hold_guarantee_funds as holdGuaranteeFunds, "
|
||||
+ "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, "
|
||||
+ "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 "
|
||||
+ " from m_product_loan lp "
|
||||
+ " 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 int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval");
|
||||
final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate");
|
||||
|
||||
final boolean isArrearsBasedOnOriginalSchedule = rs.getBoolean("isArrearsBasedOnOriginalSchedule");
|
||||
interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId,
|
||||
interestRecalculationCompoundingType, rescheduleStrategyType, restFrequencyType, restFrequencyInterval,
|
||||
restFrequencyDate);
|
||||
restFrequencyDate, isArrearsBasedOnOriginalSchedule);
|
||||
}
|
||||
|
||||
final boolean holdGuaranteeFunds = rs.getBoolean("holdGuaranteeFunds");
|
||||
@@ -307,6 +308,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
minimumGuaranteeFromGuarantor);
|
||||
}
|
||||
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = rs.getBoolean("accountMovesOutOfNPAOnlyOnArrearsCompletion");
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
|
||||
@@ -316,7 +319,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
|
||||
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
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 + "'";
|
||||
|
||||
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil.
|
||||
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
|
||||
MifosEntityType.LOAN_PRODUCT);
|
||||
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
|
||||
sql += " and id in ( " + inClause + " ) ";
|
||||
}
|
||||
// Check if branch specific products are enabled. If yes, fetch only
|
||||
// products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil
|
||||
.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
|
||||
if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
|
||||
sql += " and id in ( " + inClause + " ) ";
|
||||
}
|
||||
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] {});
|
||||
}
|
||||
@@ -410,13 +414,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
|
||||
String sql = "Select " + rm.productMixSchema();
|
||||
|
||||
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil.
|
||||
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
|
||||
MifosEntityType.LOAN_PRODUCT);
|
||||
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
|
||||
sql += " and lp.id in ( " + inClause + " ) ";
|
||||
}
|
||||
// Check if branch specific products are enabled. If yes, fetch only
|
||||
// products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil
|
||||
.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
|
||||
if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
|
||||
sql += " and lp.id in ( " + inClause + " ) ";
|
||||
}
|
||||
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] {});
|
||||
}
|
||||
@@ -429,24 +433,23 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
final LoanProductLookupMapper rm = new LoanProductLookupMapper();
|
||||
|
||||
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
|
||||
String inClause1 = mifosEntityAccessUtil.
|
||||
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
|
||||
MifosEntityType.LOAN_PRODUCT);
|
||||
if ( (inClause1 != null) && (!(inClause1.trim().isEmpty())) ) {
|
||||
sql += " and rp.id in ( " + inClause1 + " ) ";
|
||||
}
|
||||
// Check if branch specific products are enabled. If yes, fetch only
|
||||
// products mapped to current user's office
|
||||
String inClause1 = mifosEntityAccessUtil
|
||||
.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
|
||||
if ((inClause1 != null) && (!(inClause1.trim().isEmpty()))) {
|
||||
sql += " and rp.id in ( " + inClause1 + " ) ";
|
||||
}
|
||||
|
||||
sql += " UNION Select "
|
||||
+ rm.derivedRestrictedProductsSchema() + " where pm.restricted_product_id=?";
|
||||
sql += " UNION Select " + 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
|
||||
String inClause2 = mifosEntityAccessUtil.
|
||||
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
|
||||
MifosEntityType.LOAN_PRODUCT);
|
||||
if ( (inClause2 != null) && (!(inClause2.trim().isEmpty())) ) {
|
||||
sql += " and lp.id in ( " + inClause2 + " ) ";
|
||||
}
|
||||
// Check if branch specific products are enabled. If yes, fetch only
|
||||
// products mapped to current user's office
|
||||
String inClause2 = mifosEntityAccessUtil
|
||||
.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
|
||||
if ((inClause2 != null) && (!(inClause2.trim().isEmpty()))) {
|
||||
sql += " and lp.id in ( " + inClause2 + " ) ";
|
||||
}
|
||||
|
||||
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 ";
|
||||
|
||||
// Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil.
|
||||
getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
|
||||
MifosEntityType.LOAN_PRODUCT);
|
||||
if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
|
||||
sql += " lp.id in ( " + inClause + " ) and ";
|
||||
}
|
||||
// Check if branch specific products are enabled. If yes, fetch only
|
||||
// products mapped to current user's office
|
||||
String inClause = mifosEntityAccessUtil
|
||||
.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
|
||||
if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
|
||||
sql += " lp.id in ( " + inClause + " ) and ";
|
||||
}
|
||||
|
||||
sql += "lp.id not in ("
|
||||
+ "Select pm.restricted_product_id from m_product_mix pm where pm.product_id=? " + "UNION "
|
||||
sql += "lp.id not in (" + "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=?)";
|
||||
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
|
||||
|
||||
-2
@@ -11,8 +11,6 @@ public interface ScheduledJobRunnerService {
|
||||
|
||||
void updateLoanSummaryDetails();
|
||||
|
||||
void updateLoanArrearsAgeingDetails();
|
||||
|
||||
void updateLoanPaidInAdvance();
|
||||
|
||||
void applyAnnualFeeForSavings();
|
||||
|
||||
+18
-48
@@ -136,46 +136,6 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
|
||||
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
|
||||
@Override
|
||||
@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());
|
||||
|
||||
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);
|
||||
|
||||
updateSqlBuilder.append("UPDATE m_loan as ml,");
|
||||
updateSqlBuilder.append(" (select loan.id from m_loan_repayment_schedule mr ");
|
||||
updateSqlBuilder
|
||||
.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("WHERE loan.loan_status_id = 300 and mr.completed_derived is false ");
|
||||
updateSqlBuilder
|
||||
.append(" and mr.duedate < SUBDATE(CURDATE(),INTERVAL ifnull(mpl.overdue_days_for_npa,0) day) group by loan.id) as sl ");
|
||||
updateSqlBuilder.append("SET ml.is_npa=1 where ml.id=sl.id");
|
||||
updateSqlBuilder.append(" (select loan.id ");
|
||||
updateSqlBuilder.append("from m_loan_arrears_aging laa");
|
||||
updateSqlBuilder.append(" INNER JOIN m_loan loan on laa.loan_id = loan.id ");
|
||||
updateSqlBuilder.append(" INNER JOIN m_product_loan mpl on mpl.id = loan.product_id AND mpl.overdue_days_for_npa is not null ");
|
||||
updateSqlBuilder.append("WHERE loan.loan_status_id = 300 and ");
|
||||
updateSqlBuilder.append("laa.overdue_since_date_derived < SUBDATE(CURDATE(),INTERVAL ifnull(mpl.overdue_days_for_npa,0) day) ");
|
||||
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());
|
||||
|
||||
|
||||
+5
@@ -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';
|
||||
Reference in New Issue
Block a user