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;
+
+/**
+ *
+ * People typically use either of the following settings when calculating there
+ * interest using the daily method:
+ * - 360 and
+ * - 365
+ *
+ */
+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;
+
+/**
+ *
+ * People typically use either of the following settings when calculating there
+ * interest using the daily method:
+ * - 360 and
+ * - 365
+ *
+ */
+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