From 025e727757eef8849b4f8834de8ffd2436829e5c Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Tue, 15 Apr 2014 00:16:14 +0530 Subject: [PATCH] MIFOSX-1081 - Added Non Performing assets to Loan --- api-docs/apiLive.htm | 10 +++-- .../core_db/V163__added_npa_for_loans.sql | 7 ++++ .../infrastructure/jobs/service/JobName.java | 3 +- .../loanaccount/data/LoanAccountData.java | 38 ++++++++++++------- .../service/LoanReadPlatformServiceImpl.java | 8 ++-- .../loanproduct/LoanProductConstants.java | 2 + .../loanproduct/data/LoanProductData.java | 23 ++++++----- .../loanproduct/domain/LoanProduct.java | 18 ++++++++- .../LoanProductDataValidator.java | 19 ++++++++-- .../LoanProductReadPlatformServiceImpl.java | 6 ++- .../service/ScheduledJobRunnerService.java | 2 + .../ScheduledJobRunnerServiceImpl.java | 35 +++++++++++++++-- 12 files changed, 131 insertions(+), 40 deletions(-) create mode 100755 mifosng-db/migrations/core_db/V163__added_npa_for_loans.sql diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 3a6912c8e..24604f132 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -5800,6 +5800,7 @@ GET https://DomainName/api/v1/loans/{loanId} "canDisburse":false, "emiAmountVariations": [], "inArrears": true, + "isNPA":false, "overdueCharges": [ { "id": 20, @@ -6087,7 +6088,8 @@ GET https://DomainName/mifosng-provider/api/v1/loans ] }, "feeChargesAtDisbursementCharged": 0, - "inArrears":false + "inArrears":false, + "isNPA":false } ] } @@ -9656,7 +9658,8 @@ GET https://DomainName/api/v1/loanproducts/{productId} "glCode": "11" } }, - "multiDisburseLoan":true,"maxTrancheCount":3,"outstandingLoanBalance":36000.000000 + "multiDisburseLoan":true,"maxTrancheCount":3,"outstandingLoanBalance":36000.000000, + "overdueDaysForNPA":2 ] } @@ -9698,7 +9701,7 @@ GET https://DomainName/api/v1/loanproducts/{productId} numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, - outstandingLoanBalance + outstandingLoanBalance,overdueDaysForNPA @@ -9769,6 +9772,7 @@ Request Body: "feeToIncomeAccountMappings":[], "penaltyToIncomeAccountMappings":[], "charges":[], +"overdueDaysForNPA":2, "dateFormat":"dd MMMM yyyy", "locale":"en", "fundSourceAccountId":1, diff --git a/mifosng-db/migrations/core_db/V163__added_npa_for_loans.sql b/mifosng-db/migrations/core_db/V163__added_npa_for_loans.sql new file mode 100755 index 000000000..2bb528a11 --- /dev/null +++ b/mifosng-db/migrations/core_db/V163__added_npa_for_loans.sql @@ -0,0 +1,7 @@ +ALTER TABLE `m_product_loan` + ADD COLUMN `overdue_days_for_npa` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_arrears_ageing`; + +ALTER TABLE `m_loan` + ADD COLUMN `is_npa` TINYINT(1) NOT NULL DEFAULT '0' AFTER `grace_on_arrears_ageing`; + +INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Update Non Performing Assets', 'Update Non Performing Assets', '0 0 0 1/1 * ? *', now()); \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java index 66e677e2d..7698be461 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java @@ -13,7 +13,8 @@ public enum JobName { PAY_DUE_SAVINGS_CHARGES("Pay Due Savings Charges"), // APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT("Apply penalty to overdue loans"), EXECUTE_STANDING_INSTRUCTIONS("Execute Standing Instruction"), - ADD_ACCRUAL_ENTRIES("Add Accrual Transactions"); + ADD_ACCRUAL_ENTRIES("Add Accrual Transactions"), + UPDATE_NPA("Update Non Performing Assets"); private final String name; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index 7e906a689..e6118d103 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -158,6 +158,8 @@ public class LoanAccountData { private final Boolean inArrears; + private final Boolean isNPA; + private final Collection overdueCharges; /** @@ -244,6 +246,7 @@ public class LoanAccountData { final Map memberVariations = null; final LoanProductData product = null; final Boolean inArrears = null; + final Boolean isNPA = null; final Collection overdueCharges = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, @@ -258,7 +261,7 @@ public class LoanAccountData { chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations, - product, inArrears, graceOnArrearsAgeing, overdueCharges); + product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA); } /** @@ -345,6 +348,7 @@ public class LoanAccountData { final Map memberVariations = null; final LoanProductData product = null; final Boolean inArrears = null; + final Boolean isNPA = null; final Collection overdueCharges = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, @@ -359,7 +363,8 @@ public class LoanAccountData { interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, - maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges); + maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, + isNPA); } public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) { @@ -380,7 +385,8 @@ public class LoanAccountData { acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, - acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges); + acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, + acc.isNPA); } /** @@ -468,6 +474,7 @@ public class LoanAccountData { final Map memberVariations = null; final LoanProductData product = null; final Boolean inArrears = null; + final Boolean isNPA = null; final Collection overdueCharges = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, @@ -482,7 +489,7 @@ public class LoanAccountData { interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance, - emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges); + emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA); } public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) { @@ -502,7 +509,8 @@ public class LoanAccountData { acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, - acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges); + acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, + acc.isNPA); } public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product, @@ -609,6 +617,7 @@ public class LoanAccountData { final BigDecimal fixedEmi = null; Map memberVariations = null; final Boolean inArrears = null; + final Boolean isNPA = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId, @@ -625,7 +634,7 @@ public class LoanAccountData { loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, product.getMultiDisburseLoan(), fixedEmi, product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears, - product.getGraceOnArrearsAgeing(), product.overdueFeeCharges()); + product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA); } public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) { @@ -677,7 +686,7 @@ public class LoanAccountData { acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears, - product.getGraceOnArrearsAgeing(), product.overdueFeeCharges()); + product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA); } /* @@ -699,7 +708,7 @@ public class LoanAccountData { final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting, final Integer loancounter, final Integer loanProductCounter, final Boolean multiDisburseLoan, final BigDecimal fixedEmiAmont, final BigDecimal outstandingLoanBalance, - final Boolean inArrears, final Integer graceOnArrearsAgeing) { + final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA) { final LoanScheduleData repaymentSchedule = null; final Collection transactions = null; @@ -743,7 +752,7 @@ public class LoanAccountData { interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance, - emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges); + emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA); } /* @@ -779,7 +788,7 @@ public class LoanAccountData { loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, - acc.graceOnArrearsAgeing, overdueCharges); + acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA); } public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions, @@ -809,7 +818,8 @@ public class LoanAccountData { acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, - acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges); + acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, + acc.isNPA); } public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map memberLoanCycle) { @@ -865,7 +875,8 @@ public class LoanAccountData { acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, - acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges); + acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, + acc.isNPA); } private LoanAccountData( @@ -919,7 +930,7 @@ public class LoanAccountData { final Boolean multiDisburseLoan, BigDecimal fixedEmiAmount, final BigDecimal maxOutstandingLoanBalance, final Collection emiAmountVariations, final Map memberVariations, final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing, - final Collection overdueCharges) { + final Collection overdueCharges, final Boolean isNPA) { this.id = id; this.accountNo = accountNo; this.status = status; @@ -1057,6 +1068,7 @@ public class LoanAccountData { this.inArrears = inArrears; this.graceOnArrearsAgeing = graceOnArrearsAgeing; this.overdueCharges = overdueCharges; + this.isNPA = isNPA; } public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 064eb08c8..79e9473d4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -487,7 +487,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " la.total_overdue_derived as totalOverdue," + " la.overdue_since_date_derived as overdueSinceDate," + " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting," - + " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter" + + " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter," + + " l.is_npa as isNPA " + " from m_loan l" // + " join m_product_loan lp on lp.id = l.product_id" // + " join m_currency rc on rc.`code` = l.currency_code" // @@ -709,6 +710,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Integer loanCounter = JdbcSupport.getInteger(rs, "loanCounter"); final Integer loanProductCounter = JdbcSupport.getInteger(rs, "loanProductCounter"); final BigDecimal fixedEmiAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "fixedEmiAmount"); + final Boolean isNPA = rs.getBoolean("isNPA"); return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, @@ -718,7 +720,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, - fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing); + fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA); } } @@ -1319,7 +1321,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { .append(" where ((if(ls.fee_charges_waived_derived is null , ls.fee_charges_amount,(ls.fee_charges_amount-ls.fee_charges_waived_derived)) <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))") .append(" or (if(ls.penalty_charges_waived_derived is null , ls.penalty_charges_amount,(ls.penalty_charges_amount-ls.penalty_charges_waived_derived)) <> if(ls.accrual_penalty_charges_derived is null,0,ls.accrual_penalty_charges_derived))") .append(" or (if(ls.interest_waived_derived is null , ls.interest_amount,(ls.interest_amount-ls.interest_waived_derived)) <> if(ls.accrual_interest_derived is null,0,ls.accrual_interest_derived)))") - .append(" and loan.loan_status_id=? and mpl.accounting_type=?"); + .append(" and loan.loan_status_id=? and mpl.accounting_type=? and loan.is_npa=0"); return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, new Object[] { LoanStatus.ACTIVE.getValue(), AccountingRuleType.ACCRUAL_PERIODIC.getValue() }); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java index 08845358a..4b2bf41e4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java @@ -54,5 +54,7 @@ public interface LoanProductConstants { public static final String outstandingLoanBalanceParameterName = "outstandingLoanBalance"; public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing"; + + public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA"; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index 2633074c0..829f3883e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -72,6 +72,7 @@ public class LoanProductData { private final Integer graceOnInterestPayment; private final Integer graceOnInterestCharged; private final Integer graceOnArrearsAgeing; + private final Integer overdueDaysForNPA; // charges private final Collection charges; @@ -144,6 +145,7 @@ public class LoanProductData { final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; + final Integer overdueDaysForNPA = null; final Collection charges = null; final Collection principalVariations = new ArrayList( 1); @@ -168,7 +170,7 @@ public class LoanProductData { transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, - graceOnArrearsAgeing); + graceOnArrearsAgeing, overdueDaysForNPA); } public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) { @@ -199,6 +201,7 @@ public class LoanProductData { final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; + final Integer overdueDaysForNPA = null; final Collection charges = null; final EnumOptionData accountingType = null; @@ -226,7 +229,7 @@ public class LoanProductData { transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, - graceOnArrearsAgeing); + graceOnArrearsAgeing, overdueDaysForNPA); } public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { @@ -263,6 +266,7 @@ public class LoanProductData { final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; + final Integer overdueDaysForNPA = null; final Collection charges = null; final Collection principalVariationsForBorrowerCycle = new ArrayList( @@ -290,7 +294,7 @@ public class LoanProductData { transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, - multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing); + multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA); } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, @@ -318,7 +322,7 @@ public class LoanProductData { Collection principalVariations, Collection interestRateVariations, Collection numberOfRepaymentVariations, Boolean multiDisburseLoan, - Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing) { + Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, final Integer overdueDaysForNPA) { this.id = id; this.name = name; this.shortName = shortName; @@ -383,6 +387,7 @@ public class LoanProductData { this.outstandingLoanBalance = outstandingLoanBalance; this.maxTrancheCount = maxTrancheCount; this.graceOnArrearsAgeing = graceOnArrearsAgeing; + this.overdueDaysForNPA = overdueDaysForNPA; } public LoanProductData(final LoanProductData productData, final Collection chargeOptions, @@ -469,6 +474,7 @@ public class LoanProductData { this.accountingRuleOptions = accountingRuleOptions; this.valueConditionTypeOptions = valueConditionTypeOptions; this.graceOnArrearsAgeing = productData.graceOnArrearsAgeing; + this.overdueDaysForNPA = productData.overdueDaysForNPA; } @@ -655,16 +661,15 @@ public class LoanProductData { return this.outstandingLoanBalance; } - public Integer getGraceOnArrearsAgeing() { return this.graceOnArrearsAgeing; } - - public Collection overdueFeeCharges(){ + + public Collection overdueFeeCharges() { Collection overdueFeeCharges = new ArrayList(); Collection charges = charges(); - for(ChargeData chargeData : charges){ - if(chargeData.isOverdueInstallmentCharge()){ + for (ChargeData chargeData : charges) { + if (chargeData.isOverdueInstallmentCharge()) { overdueFeeCharges.add(chargeData); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 006d596c3..62c99766c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -116,6 +116,9 @@ public class LoanProduct extends AbstractPersistable { @OneToMany(cascade = CascadeType.ALL, mappedBy = "loanProduct", orphanRemoval = true) private Set borrowerCycleVariations = new HashSet(); + @Column(name = "overdue_days_for_npa", nullable = true) + private Integer overdueDaysForNPA; + public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, final List productCharges, final JsonCommand command, final AprCalculator aprCalculator) { @@ -180,13 +183,15 @@ public class LoanProduct extends AbstractPersistable { final Integer graceOnArrearsAgeing = command.integerValueOfParameterNamed(LoanProductConstants.graceOnArrearsAgeingParameterName); + final Integer overdueDaysForNPA = command.integerValueOfParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName); + return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount, - outstandingLoanBalance, graceOnArrearsAgeing); + outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA); } /** @@ -396,7 +401,8 @@ public class LoanProduct extends AbstractPersistable { final AccountingRuleType accountingRuleType, final boolean includeInBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle, final Set loanProductBorrowerCycleVariations, final boolean multiDisburseLoan, - final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing) { + final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, + final Integer overdueDaysForNPA) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -442,6 +448,7 @@ public class LoanProduct extends AbstractPersistable { borrowerCycleVariations.updateLoanProduct(this); } this.loanProducTrancheDetails = new LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount, outstandingLoanBalance); + this.overdueDaysForNPA = overdueDaysForNPA; } public MonetaryCurrency getCurrency() { @@ -606,6 +613,13 @@ public class LoanProduct extends AbstractPersistable { } loanProducTrancheDetails.update(command, actualChanges, localeAsInput); + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName, this.overdueDaysForNPA)) { + final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName); + actualChanges.put(LoanProductConstants.overdueDaysForNPAParameterName, newValue); + actualChanges.put("locale", localeAsInput); + this.overdueDaysForNPA = newValue; + } + return actualChanges; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 3e2b07474..85b713e2e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -60,7 +60,8 @@ public final class LoanProductDataValidator { LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName, LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName, LoanProductConstants.shortName, LoanProductConstants.multiDisburseLoanParameterName, LoanProductConstants.outstandingLoanBalanceParameterName, - LoanProductConstants.maxTrancheCountParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName)); + LoanProductConstants.maxTrancheCountParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName, + LoanProductConstants.overdueDaysForNPAParameterName)); private final FromJsonHelper fromApiJsonHelper; @@ -255,7 +256,12 @@ public final class LoanProductDataValidator { final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( LoanProductConstants.graceOnArrearsAgeingParameterName, element); baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing) - .zeroOrPositiveAmount(); + .integerZeroOrGreater(); + + final Integer overdueDaysForNPA = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( + LoanProductConstants.overdueDaysForNPAParameterName, element); + baseDataValidator.reset().parameter(LoanProductConstants.overdueDaysForNPAParameterName).value(overdueDaysForNPA) + .integerZeroOrGreater(); // accounting related data validation final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); @@ -528,7 +534,14 @@ public final class LoanProductDataValidator { final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( LoanProductConstants.graceOnArrearsAgeingParameterName, element); baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing) - .zeroOrPositiveAmount(); + .integerZeroOrGreater(); + } + + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.overdueDaysForNPAParameterName, element)) { + final Integer overdueDaysForNPA = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( + LoanProductConstants.overdueDaysForNPAParameterName, element); + baseDataValidator.reset().parameter(LoanProductConstants.overdueDaysForNPAParameterName).value(overdueDaysForNPA) + .integerZeroOrGreater(); } // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index c37b4c4d1..993a381ee 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -117,7 +117,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, " + "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod," + "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, " - + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing, " + + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing,lp.overdue_days_for_npa as overdueDaysForNPA, " + "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, " + "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, " + "lp.allow_multiple_disbursals as multiDisburseLoan, lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as outstandingLoanBalance, " @@ -164,6 +164,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment"); final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged"); final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing"); + final Integer overdueDaysForNPA = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overdueDaysForNPA"); final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType"); final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId); @@ -227,7 +228,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo transactionStrategyId, transactionStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, - numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing); + numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, + graceOnArrearsAgeing, overdueDaysForNPA); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java index 6266d47bc..6187fbf86 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerService.java @@ -18,4 +18,6 @@ public interface ScheduledJobRunnerService { void applyAnnualFeeForSavings(); void applyDueChargesForSavings() throws JobExecutionException; + + void updateNPA(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java index b695088ae..136202e5b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java @@ -61,8 +61,10 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService updateSqlBuilder.append("SUM(IFNULL(mr.interest_completed_derived,0)) as interest_repaid_derived,"); updateSqlBuilder.append("SUM(IFNULL(mr.interest_waived_derived,0)) as interest_waived_derived,"); updateSqlBuilder.append("SUM(IFNULL(mr.interest_writtenoff_derived,0)) as interest_writtenoff_derived,"); - updateSqlBuilder.append("SUM(IFNULL(mr.fee_charges_amount,0)) + IFNULL((select SUM(lc.amount) from m_loan_charge lc where lc.loan_id=ml.id and lc.is_active=1 and lc.charge_id=1),0) as fee_charges_charged_derived,"); - updateSqlBuilder.append("SUM(IFNULL(mr.fee_charges_completed_derived,0)) + IFNULL((select SUM(lc.amount_paid_derived) from m_loan_charge lc where lc.loan_id=ml.id and lc.is_active=1 and lc.charge_id=1),0) as fee_charges_repaid_derived,"); + updateSqlBuilder + .append("SUM(IFNULL(mr.fee_charges_amount,0)) + IFNULL((select SUM(lc.amount) from m_loan_charge lc where lc.loan_id=ml.id and lc.is_active=1 and lc.charge_id=1),0) as fee_charges_charged_derived,"); + updateSqlBuilder + .append("SUM(IFNULL(mr.fee_charges_completed_derived,0)) + IFNULL((select SUM(lc.amount_paid_derived) from m_loan_charge lc where lc.loan_id=ml.id and lc.is_active=1 and lc.charge_id=1),0) as fee_charges_repaid_derived,"); updateSqlBuilder.append("SUM(IFNULL(mr.fee_charges_waived_derived,0)) as fee_charges_waived_derived,"); updateSqlBuilder.append("SUM(IFNULL(mr.fee_charges_writtenoff_derived,0)) as fee_charges_writtenoff_derived,"); updateSqlBuilder.append("SUM(IFNULL(mr.penalty_charges_amount,0)) as penalty_charges_charged_derived,"); @@ -216,8 +218,8 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService logger.error("Apply annual fee failed for account:" + savingsAccountReference.getAccountNo() + " with message " + error.getDeveloperMessage()); } - } catch (final Exception ex){ - //need to handle this scenario + } catch (final Exception ex) { + // need to handle this scenario } } @@ -254,4 +256,29 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService if (errorMsg.length() > 0) { throw new JobExecutionException(errorMsg.toString()); } } + @Transactional + @Override + @CronTarget(jobName = JobName.UPDATE_NPA) + public void updateNPA() { + + final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource()); + + jdbcTemplate.update("UPDATE m_loan ml SET ml.is_npa=0"); + + 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 "); + 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"); + + final int result = jdbcTemplate.update(updateSqlBuilder.toString()); + + logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + result); + } + } \ No newline at end of file