From 40b005cbfa836175a8548b38c60a53b672dbed75 Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Mon, 14 Jul 2014 22:51:12 +0530 Subject: [PATCH] MIFOSX-1416 : added batch job for periodic accrual --- .../V186__added_periodic_accrual_job.sql | 3 + .../AccountingScenarioIntegrationTest.java | 78 +++++++++ .../infrastructure/jobs/service/JobName.java | 3 +- .../common/domain/DaysInMonthType.java | 67 ++++++++ .../common/domain/DaysInYearType.java | 74 +++++++++ .../data/LoanScheduleAccrualData.java | 57 ++++++- .../LoanAccrualWritePlatformService.java | 5 + .../LoanAccrualWritePlatformServiceImpl.java | 150 +++++++++++++++++- .../service/LoanReadPlatformService.java | 3 + .../service/LoanReadPlatformServiceImpl.java | 107 ++++++++++++- 10 files changed, 535 insertions(+), 12 deletions(-) create mode 100755 mifosng-db/migrations/core_db/V186__added_periodic_accrual_job.sql create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInMonthType.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInYearType.java diff --git a/mifosng-db/migrations/core_db/V186__added_periodic_accrual_job.sql b/mifosng-db/migrations/core_db/V186__added_periodic_accrual_job.sql new file mode 100755 index 000000000..724123e4e --- /dev/null +++ b/mifosng-db/migrations/core_db/V186__added_periodic_accrual_job.sql @@ -0,0 +1,3 @@ +INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `scheduler_group`) VALUES ('Add Periodic Accrual Transactions', 'Add Periodic Accrual Transactions', '0 2 0 1/1 * ? *', now(), 4, 3); + +UPDATE `job` SET `scheduler_group`=3 WHERE `name`='Add Accrual Transactions'; \ No newline at end of file diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java index a19581ec0..8230c9fd2 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java @@ -8,6 +8,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; +import java.util.List; import org.joda.time.LocalDate; import org.junit.Assert; @@ -645,6 +646,82 @@ public class AccountingScenarioIntegrationTest { System.out.println("Repayment 5 Done ......"); } + @Test + public void checkPeriodicAccrualAccountingTillCurrentDateFlow() { + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingEnabled(assetAccount, incomeAccount, expenseAccount, + overpaymentAccount); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, this.DATE_OF_JOINING); + final Integer loanID = applyForLoanApplication(clientID, loanProductID); + + final float FEE_PORTION = 50.0f; + final float PENALTY_PORTION = 100.0f; + Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(FEE_PORTION), false)); + Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateJSON( + ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(PENALTY_PORTION), true)); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = this.loanTransactionHelper.approveLoan(this.EXPECTED_DISBURSAL_DATE, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + + Calendar todayDate = Calendar.getInstance(); + final String currentDate = dateFormat.format(todayDate.getTime()); + + todayDate.add(Calendar.DATE, -4); + + final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todayDate.getTime()); + + todayDate.add(Calendar.MONTH, 2); + final String FIRST_REPAYMENT_DATE = dateFormat.format(todayDate.getTime()); + + todayDate = Calendar.getInstance(); + todayDate.add(Calendar.DATE, -2); + + loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + this.loanTransactionHelper.addChargesForLoan( + loanID, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate), + dateFormat.format(todayDate.getTime()), String.valueOf(PENALTY_PORTION))); + todayDate.add(Calendar.DATE, 1); + this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( + String.valueOf(flat), dateFormat.format(todayDate.getTime()), String.valueOf(FEE_PORTION))); + + // CHECK ACCOUNT ENTRIES + System.out.println("Entries ......"); + final float PRINCIPAL_VALUE_FOR_EACH_PERIOD = 2000.0f; + final float TOTAL_INTEREST = 1000.0f; + final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.CREDIT), + new JournalEntry(this.LP_PRINCIPAL, JournalEntry.TransactionType.DEBIT), }; + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, LOAN_DISBURSEMENT_DATE, assetAccountInitialEntry); + + final String jobName = "Add Periodic Accrual Transactions"; + try { + this.schedulerJobHelper.excuteJob(jobName); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // MAKE 1 + final float FIRST_INTEREST = 200.0f; + final float INTEREST_4_DAYS = 13.33F; + this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(currentDate), INTEREST_4_DAYS, FEE_PORTION, + PENALTY_PORTION, loanID); + + } + private Integer createLoanProductWithPeriodicAccrualAccountingEnabled(final Account... accounts) { System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(this.LP_PRINCIPAL.toString()).withRepaymentTypeAsMonth() @@ -774,4 +851,5 @@ public class AccountingScenarioIntegrationTest { } return date; } + } 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 82c6f29d7..a8a413bc7 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 @@ -16,7 +16,8 @@ public enum JobName { ADD_ACCRUAL_ENTRIES("Add Accrual Transactions"), UPDATE_NPA("Update Non Performing Assets"), UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"), - TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"); + TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"), + ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"); private final String name; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInMonthType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInMonthType.java new file mode 100755 index 000000000..e80269fdb --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInMonthType.java @@ -0,0 +1,67 @@ +/** + * 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.common.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public enum DaysInMonthType { + + INVALID(0, "DaysInMonthType.invalid"), // + ACTUAL(1, "DaysInMonthType.actual"), // + DAYS_30(30, "DaysInMonthType.days360"); + + private final Integer value; + private final String code; + + private DaysInMonthType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } + + public static Object[] integerValues() { + final List values = new ArrayList<>(); + for (final DaysInMonthType enumType : values()) { + if (enumType.getValue() > 0) { + values.add(enumType.getValue()); + } + } + + return values.toArray(); + } + + public static DaysInMonthType fromInt(final Integer type) { + DaysInMonthType repaymentFrequencyType = DaysInMonthType.INVALID; + if (type != null) { + switch (type) { + case 1: + repaymentFrequencyType = DaysInMonthType.ACTUAL; + break; + case 30: + repaymentFrequencyType = DaysInMonthType.DAYS_30; + break; + + } + } + return repaymentFrequencyType; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInYearType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInYearType.java new file mode 100755 index 000000000..ffcb648d5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/domain/DaysInYearType.java @@ -0,0 +1,74 @@ +/** + * 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.common.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public enum DaysInYearType { + + INVALID(0, "DaysInYearType.invalid"), // + ACTUAL(1, "DaysInYearType.actual"), // + DAYS_360(360, "DaysInYearType.days360"), // + DAYS_364(364, "DaysInYearType.days364"), // + DAYS_365(365, "DaysInYearType.days365"); + + private final Integer value; + private final String code; + + private DaysInYearType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } + + public static Object[] integerValues() { + final List values = new ArrayList<>(); + for (final DaysInYearType enumType : values()) { + if (enumType.getValue() > 0) { + values.add(enumType.getValue()); + } + } + + return values.toArray(); + } + + public static DaysInYearType fromInt(final Integer type) { + DaysInYearType repaymentFrequencyType = DaysInYearType.INVALID; + if (type != null) { + switch (type) { + case 1: + repaymentFrequencyType = DaysInYearType.ACTUAL; + break; + case 360: + repaymentFrequencyType = DaysInYearType.DAYS_360; + break; + case 364: + repaymentFrequencyType = DaysInYearType.DAYS_364; + break; + case 365: + repaymentFrequencyType = DaysInYearType.DAYS_365; + break; + } + } + return repaymentFrequencyType; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanScheduleAccrualData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanScheduleAccrualData.java index 57e06fdc0..54c406493 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanScheduleAccrualData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanScheduleAccrualData.java @@ -5,12 +5,19 @@ import java.util.Date; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; public class LoanScheduleAccrualData { private final Long loanId; private final Long officeId; + private final LocalDate accruedTill; + private final PeriodFrequencyType repaymentFrequency; + private final Integer repayEvery; + private final Integer numberOfDaysInYear; + private final Integer numberOfDaysInMonth; private final LocalDate dueDate; + private final LocalDate fromDate; private final Long repaymentScheduleId; private final Long loanProductId; private final BigDecimal interestIncome; @@ -20,14 +27,22 @@ public class LoanScheduleAccrualData { private final BigDecimal accruedFeeIncome; private final BigDecimal accruedPenaltyIncome; private final CurrencyData currencyData; + private final BigDecimal dueDateFeeIncome; + private final BigDecimal dueDatePenaltyIncome; - public LoanScheduleAccrualData(final Long loanId, final Long officeId, final LocalDate dueDate, final Long repaymentScheduleId, - final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome, final BigDecimal penaltyIncome, + public LoanScheduleAccrualData(final Long loanId, final Long officeId, final LocalDate accruedTill, final Integer numberOfDaysInMonth, + final Integer numberOfDaysInYear, final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, + final LocalDate dueDate, final LocalDate fromDate, final Long repaymentScheduleId, final Long loanProductId, + final BigDecimal interestIncome, final BigDecimal feeIncome, final BigDecimal penaltyIncome, final BigDecimal accruedInterestIncome, final BigDecimal accruedFeeIncome, final BigDecimal accruedPenaltyIncome, - final CurrencyData currencyData) { + final CurrencyData currencyData, final BigDecimal dueDateFeeIncome, final BigDecimal dueDatePenaltyIncome) { this.loanId = loanId; this.officeId = officeId; + this.accruedTill = accruedTill; + this.numberOfDaysInMonth = numberOfDaysInMonth; + this.numberOfDaysInYear = numberOfDaysInYear; this.dueDate = dueDate; + this.fromDate = fromDate; this.repaymentScheduleId = repaymentScheduleId; this.loanProductId = loanProductId; this.interestIncome = interestIncome; @@ -37,6 +52,10 @@ public class LoanScheduleAccrualData { this.accruedInterestIncome = accruedInterestIncome; this.accruedPenaltyIncome = accruedPenaltyIncome; this.currencyData = currencyData; + this.repaymentFrequency = repaymentFrequency; + this.repayEvery = repayEvery; + this.dueDateFeeIncome = dueDateFeeIncome; + this.dueDatePenaltyIncome = dueDatePenaltyIncome; } public Long getLoanId() { @@ -91,4 +110,36 @@ public class LoanScheduleAccrualData { return this.currencyData; } + public LocalDate getAccruedTill() { + return this.accruedTill; + } + + public Integer getNumberOfDaysInYear() { + return this.numberOfDaysInYear; + } + + public Integer getNumberOfDaysInMonth() { + return this.numberOfDaysInMonth; + } + + public LocalDate getFromDateAsLocaldate() { + return this.fromDate; + } + + public PeriodFrequencyType getRepaymentFrequency() { + return this.repaymentFrequency; + } + + public Integer getRepayEvery() { + return this.repayEvery; + } + + public BigDecimal getDueDateFeeIncome() { + return this.dueDateFeeIncome; + } + + public BigDecimal getDueDatePenaltyIncome() { + return this.dueDatePenaltyIncome; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java index a6eb34db2..658648231 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java @@ -1,5 +1,6 @@ package org.mifosplatform.portfolio.loanaccount.service; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException; @@ -7,4 +8,8 @@ public interface LoanAccrualWritePlatformService { void addAccrualAccounting() throws JobExecutionException; + void addPeriodicAccruals() throws JobExecutionException; + + String addPeriodicAccruals(LocalDate tilldate); + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java index 4924dee22..5918a86d5 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java @@ -1,6 +1,7 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -11,11 +12,15 @@ import java.util.Set; import javax.sql.DataSource; +import org.joda.time.Days; +import org.joda.time.LocalDate; import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException; import org.mifosplatform.infrastructure.jobs.service.JobName; +import org.mifosplatform.portfolio.common.domain.DaysInMonthType; +import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; @@ -72,11 +77,138 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } } + @Override + @CronTarget(jobName = JobName.ADD_PERIODIC_ACCRUAL_ENTRIES) + public void addPeriodicAccruals() throws JobExecutionException { + String errors = addPeriodicAccruals(LocalDate.now()); + if (errors.length() > 0) { throw new JobExecutionException(errors); } + } + + @Override + public String addPeriodicAccruals(final LocalDate tilldate) { + Collection loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate); + return addPeriodicAccruals(tilldate, loanScheduleAccrualDatas); + } + + private String addPeriodicAccruals(final LocalDate tilldate, Collection loanScheduleAccrualDatas) { + StringBuilder sb = new StringBuilder(); + Set loansIds = new HashSet<>(); + for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) { + try { + if (!loansIds.contains(accrualData.getLoanId())) { + if (accrualData.getDueDateAsLocaldate().isAfter(tilldate)) { + addAccrualTillSpecificDate(tilldate, accrualData); + } else { + addAccrualAccounting(accrualData); + } + } + } catch (Exception e) { + loansIds.add(accrualData.getLoanId()); + Throwable realCause = e; + if (e.getCause() != null) { + realCause = e.getCause(); + } + sb.append("failed to add accural transaction for repayment with id " + accrualData.getRepaymentScheduleId() + + " with message " + realCause.getMessage()); + } + } + return sb.toString(); + } + + private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanScheduleAccrualData accrualData) throws Exception { + int totalNumberOfDays = Days.daysBetween(accrualData.getFromDateAsLocaldate(), accrualData.getDueDateAsLocaldate()).getDays(); + LocalDate startDate = accrualData.getAccruedTill(); + if (startDate == null) { + startDate = accrualData.getFromDateAsLocaldate(); + } + int daysToBeAccrued = Days.daysBetween(startDate, tilldate).getDays(); + switch (accrualData.getRepaymentFrequency()) { + case MONTHS: + totalNumberOfDays = calculateTotalNumberOfDaysForMonth(totalNumberOfDays, accrualData); + break; + case YEARS: + totalNumberOfDays = calculateTotalNumberOfDaysForYear(totalNumberOfDays, accrualData); + break; + default: + break; + } + + double interestPerDay = accrualData.getInterestIncome().doubleValue() / totalNumberOfDays; + BigDecimal amount = BigDecimal.ZERO; + BigDecimal interestportion = null; + BigDecimal feeportion = accrualData.getDueDateFeeIncome(); + BigDecimal penaltyportion = accrualData.getDueDatePenaltyIncome(); + if (daysToBeAccrued >= totalNumberOfDays) { + interestportion = accrualData.getInterestIncome(); + } else { + double iterest = interestPerDay * daysToBeAccrued; + interestportion = BigDecimal.valueOf(iterest); + } + interestportion = interestportion.setScale(accrualData.getCurrencyData().decimalPlaces(), RoundingMode.HALF_EVEN); + amount = amount.add(interestportion); + + BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome(); + if (totalAccInterest == null) { + totalAccInterest = BigDecimal.ZERO; + } + totalAccInterest = totalAccInterest.add(interestportion); + BigDecimal totalAccFee = accrualData.getAccruedFeeIncome(); + + if (feeportion != null) { + if (totalAccFee == null) { + totalAccFee = BigDecimal.ZERO; + } + amount = amount.add(feeportion); + totalAccFee = totalAccFee.add(feeportion); + } + BigDecimal totalAccPenalty = accrualData.getAccruedPenaltyIncome(); + if (penaltyportion != null) { + if (totalAccPenalty == null) { + totalAccPenalty = BigDecimal.ZERO; + } + amount = amount.add(penaltyportion); + totalAccFee = totalAccFee.add(penaltyportion); + } + + addAccrualAccounting(accrualData, amount, interestportion, totalAccInterest, feeportion, totalAccFee, penaltyportion, + totalAccPenalty, tilldate); + } + + private int calculateTotalNumberOfDaysForMonth(final int totalNumberOfDays, final LoanScheduleAccrualData accrualData) { + int numberOfDays = totalNumberOfDays; + DaysInMonthType type = DaysInMonthType.fromInt(accrualData.getNumberOfDaysInMonth()); + switch (type) { + case DAYS_30: + numberOfDays = 30 * accrualData.getRepayEvery(); + break; + default: + break; + } + return numberOfDays; + } + + private int calculateTotalNumberOfDaysForYear(final int totalNumberOfDays, final LoanScheduleAccrualData accrualData) { + int numberOfDays = totalNumberOfDays; + DaysInYearType type = DaysInYearType.fromInt(accrualData.getNumberOfDaysInMonth()); + switch (type) { + case DAYS_360: + numberOfDays = 360 * accrualData.getRepayEvery(); + break; + case DAYS_364: + numberOfDays = 364 * accrualData.getRepayEvery(); + break; + case DAYS_365: + numberOfDays = 365 * accrualData.getRepayEvery(); + break; + default: + break; + } + return numberOfDays; + } + @Transactional public void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData) throws Exception { - String transactionSql = "INSERT INTO m_loan_transaction (loan_id,office_id,is_reversed,transaction_type_enum,transaction_date,amount,interest_portion_derived," - + "fee_charges_portion_derived,penalty_charges_portion_derived) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?)"; BigDecimal amount = BigDecimal.ZERO; BigDecimal interestportion = null; BigDecimal totalAccInterest = null; @@ -119,11 +251,19 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat penaltyportion = null; } } + addAccrualAccounting(scheduleAccrualData, amount, interestportion, totalAccInterest, feeportion, totalAccFee, penaltyportion, + totalAccPenalty, scheduleAccrualData.getDueDateAsLocaldate()); + } + + private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, BigDecimal amount, BigDecimal interestportion, + BigDecimal totalAccInterest, BigDecimal feeportion, BigDecimal totalAccFee, BigDecimal penaltyportion, + BigDecimal totalAccPenalty, final LocalDate accruredTill) throws Exception { TransactionStatus transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); try { + String transactionSql = "INSERT INTO m_loan_transaction (loan_id,office_id,is_reversed,transaction_type_enum,transaction_date,amount,interest_portion_derived," + + "fee_charges_portion_derived,penalty_charges_portion_derived) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?)"; this.jdbcTemplate.update(transactionSql, scheduleAccrualData.getLoanId(), scheduleAccrualData.getOfficeId(), - LoanTransactionType.ACCRUAL.getValue(), scheduleAccrualData.getDueDate(), amount, interestportion, feeportion, - penaltyportion); + LoanTransactionType.ACCRUAL.getValue(), accruredTill.toDate(), amount, interestportion, feeportion, penaltyportion); final Long transactonId = this.jdbcTemplate.queryForLong("SELECT LAST_INSERT_ID()"); Map transactionMap = toMapData(transactonId, amount, interestportion, feeportion, penaltyportion, @@ -135,7 +275,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat scheduleAccrualData.getRepaymentScheduleId()); String updateLoan = "UPDATE m_loan SET accrued_till=? WHERE id=?"; - this.jdbcTemplate.update(updateLoan, scheduleAccrualData.getDueDate(), scheduleAccrualData.getLoanId()); + this.jdbcTemplate.update(updateLoan, accruredTill.toDate(), scheduleAccrualData.getLoanId()); final Map accountingBridgeData = deriveAccountingBridgeData(scheduleAccrualData, transactionMap); this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); } catch (Exception e) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java index b4c133790..5e7d930af 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.util.Collection; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.portfolio.calendar.data.CalendarData; @@ -84,4 +85,6 @@ public interface LoanReadPlatformService { LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId); LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId); + + Collection retrivePeriodicAccrualData(LocalDate tillDate); } \ No newline at end of file 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 6ae419faa..61f6a4b3e 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 @@ -11,11 +11,15 @@ import java.sql.SQLException; 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 org.apache.commons.lang.StringUtils; import org.joda.time.Days; import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.codes.service.CodeValueReadPlatformService; @@ -45,6 +49,7 @@ import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.domain.ClientEnumerations; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; +import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; @@ -88,6 +93,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -110,6 +116,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private final StaffReadPlatformService staffReadPlatformService; private final PaginationHelper paginationHelper = new PaginationHelper<>(); private final LoanMapper loaanLoanMapper = new LoanMapper(); + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd"); @Autowired public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, @@ -134,6 +142,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.calendarReadPlatformService = calendarReadPlatformService; this.staffReadPlatformService = staffReadPlatformService; this.jdbcTemplate = new JdbcTemplate(dataSource); + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } @Override @@ -1328,6 +1337,90 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { AccountingRuleType.ACCRUAL_PERIODIC.getValue() }); } + @Override + public Collection retrivePeriodicAccrualData(final LocalDate tillDate) { + + LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper(); + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder + .append("select ") + .append(mapper.schema()) + .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=:active and mpl.accounting_type=:type and loan.is_npa=0 and (ls.duedate <= :tilldate or (ls.duedate > :tilldate and ls.fromdate < :tilldate)) order by loan.id,ls.duedate"); + Map paramMap = new HashMap<>(3); + paramMap.put("active", LoanStatus.ACTIVE.getValue()); + paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue()); + paramMap.put("tilldate", formatter.print(tillDate)); + + return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper); + } + + private static final class LoanSchedulePeriodicAccrualMapper implements RowMapper { + + public String schema() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder + .append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,") + .append("loan.accrued_till as accruedTill, loan.repayment_period_frequency_enum as frequencyEnum, ") + .append("loan.repay_every as repayEvery,") + .append("(select sum(lc.amount) as dueamount from m_loan_charge lc where lc.loan_id = loan.id and lc.is_active = 1 and lc.waived = 0 and lc.is_penalty = 0 and lc.due_for_collection_as_of_date > ls.fromdate and lc.due_for_collection_as_of_date<= if(:tilldate < ls.duedate, :tilldate , ls.duedate)) as feedueIn,") + .append("(select sum(lc.amount) as dueamount from m_loan_charge lc where lc.loan_id = loan.id and lc.is_active = 1 and lc.waived = 0 and lc.is_penalty = 1 and lc.due_for_collection_as_of_date > ls.fromdate and lc.due_for_collection_as_of_date<= if(:tilldate < ls.duedate, :tilldate , ls.duedate)) as penalitydueIn,") + .append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,") + .append("if(ls.interest_waived_derived is null,ls.interest_amount,(ls.interest_amount-ls.interest_waived_derived)) as interest,") + .append("if(ls.penalty_charges_waived_derived is null , ls.penalty_charges_amount,(ls.penalty_charges_amount-ls.penalty_charges_waived_derived)) as penalty,") + .append("if(ls.fee_charges_waived_derived is null , ls.fee_charges_amount,(ls.fee_charges_amount-ls.fee_charges_waived_derived)) as charges,") + .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,") + .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,") + .append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode") + .append(" from m_loan_repayment_schedule ls ").append(" left join m_loan loan on loan.id=ls.loan_id ") + .append(" left join m_product_loan mpl on mpl.id = loan.product_id") + .append(" left join m_client mc on mc.id = loan.client_id ").append(" left join m_group mg on mg.id = loan.group_id") + .append(" left join m_currency curr on curr.code = loan.currency_code"); + return sqlBuilder.toString(); + } + + @Override + public LoanScheduleAccrualData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + + final Long loanId = rs.getLong("loanId"); + final Long officeId = rs.getLong("officeId"); + final LocalDate accruedTill = JdbcSupport.getLocalDate(rs, "accruedTill"); + final Integer numberOfDaysInMonth = 30; + final Integer numberOfDaysInYear = 360; + final Integer frequencyEnum = JdbcSupport.getInteger(rs, "frequencyEnum"); + final Integer repayEvery = JdbcSupport.getInteger(rs, "repayEvery"); + final PeriodFrequencyType frequency = PeriodFrequencyType.fromInt(frequencyEnum); + final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "duedate"); + final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromdate"); + final Long repaymentScheduleId = rs.getLong("scheduleId"); + final Long loanProductId = rs.getLong("productId"); + final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest"); + final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges"); + final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty"); + final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest"); + final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege"); + final BigDecimal accruedPenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accpenalty"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + final BigDecimal dueDateFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "feedueIn"); + final BigDecimal dueDatePenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalitydueIn"); + return new LoanScheduleAccrualData(loanId, officeId, accruedTill, numberOfDaysInMonth, numberOfDaysInYear, frequency, + repayEvery, dueDate, fromDate, repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, + accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData, dueDateFeeIncome, dueDatePenaltyIncome); + } + + } + private static final class LoanScheduleAccrualMapper implements RowMapper { public String schema() { @@ -1371,10 +1464,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, currencyNameCode); - return new LoanScheduleAccrualData(loanId, officeId, dueDate, repaymentScheduleId, loanProductId, interestIncome, feeIncome, - penaltyIncome, accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData); + final LocalDate accruedTill = null; + final Integer numberOfDaysInMonth = null; + final Integer numberOfDaysInYear = null; + final PeriodFrequencyType frequency = null; + final Integer repayEvery = null; + final LocalDate fromDate = null; + final BigDecimal dueDateFeeincome = null; + final BigDecimal dueDatePenaltyIncome = null; + return new LoanScheduleAccrualData(loanId, officeId, accruedTill, numberOfDaysInMonth, numberOfDaysInYear, frequency, + repayEvery, dueDate, fromDate, repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, + accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData, dueDateFeeincome, dueDatePenaltyIncome); } - } @Override