MIFOSX-1416 : added batch job for periodic accrual

This commit is contained in:
Pramod Nuthakki
2014-07-15 21:45:50 +05:30
parent ec7b7c0fd6
commit 40b005cbfa
10 changed files with 535 additions and 12 deletions
@@ -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';
@@ -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;
}
}
@@ -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;
@@ -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;
/**
* <ul>
* People typically use either of the following settings when calculating there
* interest using the daily method:
* <li>360 and</li>
* <li>365</li>
* </ul>
*/
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<Integer> 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;
}
}
@@ -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;
/**
* <ul>
* People typically use either of the following settings when calculating there
* interest using the daily method:
* <li>360 and</li>
* <li>365</li>
* </ul>
*/
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<Integer> 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;
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate);
return addPeriodicAccruals(tilldate, loanScheduleAccrualDatas);
}
private String addPeriodicAccruals(final LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) {
StringBuilder sb = new StringBuilder();
Set<Long> 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<String, Object> 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<String, Object> accountingBridgeData = deriveAccountingBridgeData(scheduleAccrualData, transactionMap);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
} catch (Exception e) {
@@ -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<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate);
}
@@ -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<LoanAccountData> 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<LoanScheduleAccrualData> 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<String, Object> 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<LoanScheduleAccrualData> {
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<LoanScheduleAccrualData> {
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