Merge pull request #1086 from pramodn02/develop

MIFOSX-1557 : corrected journal entries for advanced accounting with acc...
This commit is contained in:
Vishwas Babu A J
2014-09-26 19:21:26 -07:00
11 changed files with 406 additions and 120 deletions
@@ -237,6 +237,67 @@ public class AccountingProcessorHelper {
loanId, transactionId, transactionDate, amount);
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Accrual Based accounting.
*
* The target accounts for debits and credits are switched in case of a
* reversal
*
* @param office
* @param accountTypeToBeDebited
* Enum of the placeholder GLAccount to be debited
* @param accountTypeToBeCredited
* Enum of the placeholder of the GLAccount to be credited
* @param loanProductId
* @param paymentTypeId
* @param loanId
* @param transactionId
* @param transactionDate
* @param amount
* @param isReversal
*/
public void createAccrualBasedJournalEntriesAndReversalsForLoanCharges(final Office office, final String currencyCode,
final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long loanId,
final String transactionId, final Date transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
final List<ChargePaymentDTO> chargePaymentDTOs) {
GLAccount receivableAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountTypeToBeDebited, null);
final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) {
final Long chargeId = chargePaymentDTO.getChargeId();
final GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountTypeToBeCredited, chargeId);
BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount();
// adjust net credit amount if the account is already present in the
// map
if (creditDetailsMap.containsKey(chargeSpecificAccount)) {
final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount);
chargeSpecificAmount = chargeSpecificAmount.add(existingAmount);
}
creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount);
}
BigDecimal totalCreditedAmount = BigDecimal.ZERO;
for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) {
final GLAccount account = entry.getKey();
final BigDecimal amount = entry.getValue();
totalCreditedAmount = totalCreditedAmount.add(amount);
if (isReversal) {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
createCreditJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount);
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
}
if (totalAmount.compareTo(totalCreditedAmount) != 0) { throw new PlatformDataIntegrityException(
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
totalCreditedAmount, totalAmount); }
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Cash Based accounting.
@@ -294,15 +294,17 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
// create journal entries for the fees application (or reversal)
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
this.helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
this.helper.createAccrualBasedJournalEntriesAndReversalsForLoanCharges(office, currencyCode,
ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, feesAmount, isReversed);
loanProductId, loanId, transactionId, transactionDate, feesAmount, isReversed, loanTransactionDTO.getFeePayments());
}
// create journal entries for the penalties application (or reversal)
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
this.helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, currencyCode,
this.helper.createAccrualBasedJournalEntriesAndReversalsForLoanCharges(office, currencyCode,
ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, penaltiesAmount, isReversed);
loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, isReversed,
loanTransactionDTO.getPenaltyPayments());
}
}
@@ -63,9 +63,10 @@ public class LoanChargeData {
private final BigDecimal maxCap;
@SuppressWarnings("unused")
private final Collection<LoanInstallmentChargeData> installmentChargeData;
private BigDecimal amountAccrued;
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null, null,
null, null, null);
@@ -124,6 +125,7 @@ public class LoanChargeData {
this.chargePayable = isChargePayable();
this.loanId = loanId;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
}
private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
@@ -166,6 +168,7 @@ public class LoanChargeData {
this.minCap = minCap;
this.maxCap = maxCap;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
}
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, EnumOptionData chargeTimeType,
@@ -195,6 +198,37 @@ public class LoanChargeData {
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
}
public LoanChargeData(final Long id, final Long chargeId, final LocalDate dueAsOfDate, EnumOptionData chargeTimeType,
final BigDecimal amount, final BigDecimal amountAccrued, final BigDecimal amountWaived, final boolean penalty) {
this.id = id;
this.chargeId = chargeId;
this.name = null;
this.currency = null;
this.amount = amount;
this.amountPaid = null;
this.amountWaived = amountWaived;
this.amountWrittenOff = null;
this.amountOutstanding = null;
this.chargeTimeType = chargeTimeType;
this.dueDate = dueAsOfDate;
this.chargeCalculationType = null;
this.percentage = null;
this.amountPercentageAppliedTo = null;
this.penalty = penalty;
this.chargePaymentMode = null;
this.paid = false;
this.waived = false;
this.amountOrPercentage = null;
this.chargeOptions = null;
this.chargePayable = false;
this.loanId = null;
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = null;
this.amountAccrued = amountAccrued;
}
public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
@@ -223,6 +257,7 @@ public class LoanChargeData {
this.chargePayable = chargeData.chargePayable;
this.loanId = chargeData.loanId;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = chargeData.amountAccrued;
}
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOrPercentage) {
@@ -251,6 +286,7 @@ public class LoanChargeData {
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = null;
this.amountAccrued = null;
}
public boolean isChargePayable() {
@@ -288,4 +324,33 @@ public class LoanChargeData {
public BigDecimal amountOrPercentage() {
return this.amountOrPercentage;
}
public Collection<LoanInstallmentChargeData> getInstallmentChargeData() {
return this.installmentChargeData;
}
public boolean isPenalty() {
return this.penalty;
}
public BigDecimal getAmount() {
return this.amount;
}
public BigDecimal getAmountAccrued() {
return this.amountAccrued;
}
public void updateAmountAccrued(BigDecimal amountAccrued) {
this.amountAccrued = amountAccrued;
}
public Long getChargeId() {
return this.chargeId;
}
public BigDecimal getAmountWaived() {
return this.amountWaived;
}
}
@@ -13,20 +13,21 @@ public class LoanInstallmentChargeData {
private final Integer installmentNumber;
private final LocalDate dueDate;
@SuppressWarnings("unused")
private final BigDecimal amount;
private final BigDecimal amountOutstanding;
private final BigDecimal amountWaived;
private final boolean paid;
private final boolean waived;
public LoanInstallmentChargeData(final Integer installmentNumber, final LocalDate dueDate, final BigDecimal amount,
final BigDecimal amountOutstanding, final boolean paid, final boolean waived) {
final BigDecimal amountOutstanding, BigDecimal amountWaived, final boolean paid, final boolean waived) {
this.installmentNumber = installmentNumber;
this.dueDate = dueDate;
this.amount = amount;
this.amountOutstanding = amountOutstanding;
this.paid = paid;
this.waived = waived;
this.amountWaived = amountWaived;
}
public Integer getInstallmentNumber() {
@@ -45,4 +46,12 @@ public class LoanInstallmentChargeData {
return this.amountOutstanding;
}
public BigDecimal getAmount() {
return this.amount;
}
public BigDecimal getAmountWaived() {
return this.amountWaived;
}
}
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
@@ -19,8 +20,7 @@ public class LoanScheduleAccrualData {
private final LocalDate accruedTill;
private final PeriodFrequencyType repaymentFrequency;
private final Integer repayEvery;
private final Integer numberOfDaysInYear;
private final Integer numberOfDaysInMonth;
private final Integer installmentNumber;
private final LocalDate dueDate;
private final LocalDate fromDate;
private final Long repaymentScheduleId;
@@ -32,22 +32,21 @@ public class LoanScheduleAccrualData {
private final BigDecimal accruedFeeIncome;
private final BigDecimal accruedPenaltyIncome;
private final CurrencyData currencyData;
private final BigDecimal dueDateFeeIncome;
private final BigDecimal dueDatePenaltyIncome;
private final LocalDate interestCalculatedFrom;
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 BigDecimal dueDateFeeIncome, final BigDecimal dueDatePenaltyIncome,
final LocalDate interestCalculatedFrom) {
private Map<LoanChargeData, BigDecimal> applicableCharges;
private BigDecimal dueDateFeeIncome;
private BigDecimal dueDatePenaltyIncome;
public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill,
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 LocalDate interestCalculatedFrom) {
this.loanId = loanId;
this.installmentNumber = installmentNumber;
this.officeId = officeId;
this.accruedTill = accruedTill;
this.numberOfDaysInMonth = numberOfDaysInMonth;
this.numberOfDaysInYear = numberOfDaysInYear;
this.dueDate = dueDate;
this.fromDate = fromDate;
this.repaymentScheduleId = repaymentScheduleId;
@@ -61,8 +60,6 @@ public class LoanScheduleAccrualData {
this.currencyData = currencyData;
this.repaymentFrequency = repaymentFrequency;
this.repayEvery = repayEvery;
this.dueDateFeeIncome = dueDateFeeIncome;
this.dueDatePenaltyIncome = dueDatePenaltyIncome;
this.interestCalculatedFrom = interestCalculatedFrom;
}
@@ -122,14 +119,6 @@ public class LoanScheduleAccrualData {
return this.accruedTill;
}
public Integer getNumberOfDaysInYear() {
return this.numberOfDaysInYear;
}
public Integer getNumberOfDaysInMonth() {
return this.numberOfDaysInMonth;
}
public LocalDate getFromDateAsLocaldate() {
return this.fromDate;
}
@@ -142,6 +131,18 @@ public class LoanScheduleAccrualData {
return this.repayEvery;
}
public LocalDate getInterestCalculatedFrom() {
return this.interestCalculatedFrom;
}
public Integer getInstallmentNumber() {
return this.installmentNumber;
}
public Map<LoanChargeData, BigDecimal> getApplicableCharges() {
return this.applicableCharges;
}
public BigDecimal getDueDateFeeIncome() {
return this.dueDateFeeIncome;
}
@@ -150,9 +151,11 @@ public class LoanScheduleAccrualData {
return this.dueDatePenaltyIncome;
}
public LocalDate getInterestCalculatedFrom() {
return this.interestCalculatedFrom;
public void updateChargeDetails(final Map<LoanChargeData, BigDecimal> applicableCharges, final BigDecimal dueDateFeeIncome,
final BigDecimal dueDatePenaltyIncome) {
this.applicableCharges = applicableCharges;
this.dueDateFeeIncome = dueDateFeeIncome;
this.dueDatePenaltyIncome = dueDatePenaltyIncome;
}
}
@@ -592,6 +592,9 @@ public class Loan extends AbstractPersistable<Long> {
final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount,
transactionDate, feeCharges, penaltyCharges);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, loanCharge.getAmount(
getCurrency()).getAmount());
applyLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
this.loanTransactions.add(applyLoanChargeTransaction);
return applyLoanChargeTransaction;
}
@@ -40,8 +40,6 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
import org.mifosplatform.portfolio.common.domain.DaysInYearType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
@@ -414,8 +412,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return calculatedRepaymentsStartingFromDate;
}
/* (non-Javadoc)
* @see org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService#recalculateAccruals(org.mifosplatform.portfolio.loanaccount.domain.Loan)
/*
* (non-Javadoc)
*
* @see
* org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService
* #recalculateAccruals(org.mifosplatform.portfolio.loanaccount.domain.Loan)
*/
@Override
public void recalculateAccruals(Loan loan) {
@@ -424,8 +426,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>();
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
Long loanId = loan.getId();
DaysInMonthType daysInMonth = loan.repaymentScheduleDetail().fetchDaysInMonthType();
DaysInYearType daysInYear = loan.repaymentScheduleDetail().fetchDaysInYearType();
Long officeId = loan.getOfficeId();
LocalDate accrualStartDate = null;
PeriodFrequencyType repaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType();
@@ -459,12 +459,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
}
}
LoanScheduleAccrualData accrualData = new LoanScheduleAccrualData(loanId, officeId, accrualStartDate,
daysInMonth.getValue(), daysInYear.getValue(), repaymentFrequency, repayEvery, installment.getDueDate(),
installment.getFromDate(), installment.getId(), loanProductId,
installment.getInterestCharged(currency).getAmount(), installment.getFeeChargesCharged(currency).getAmount(),
installment.getPenaltyChargesCharged(currency).getAmount(), accruedInterestIncome, accruedFeeIncome,
accruedPenaltyIncome, currencyData, dueDateFeeIncome, dueDatePenaltyIncome, interestCalculatedFrom);
LoanScheduleAccrualData accrualData = new LoanScheduleAccrualData(loanId, officeId, installment.getInstallmentNumber(),
accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(),
installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment
.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData, interestCalculatedFrom);
loanScheduleAccrualDatas.add(accrualData);
}
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -24,6 +25,8 @@ 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.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
@@ -40,6 +43,7 @@ import org.springframework.transaction.support.DefaultTransactionDefinition;
public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlatformService {
private final LoanReadPlatformService loanReadPlatformService;
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final JdbcTemplate jdbcTemplate;
private final DataSource dataSource;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
@@ -47,12 +51,14 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
@Autowired
public LoanAccrualWritePlatformServiceImpl(final RoutingDataSource dataSource, final LoanReadPlatformService loanReadPlatformService,
final JournalEntryWritePlatformService journalEntryWritePlatformService, final JpaTransactionManager transactionManager) {
final JournalEntryWritePlatformService journalEntryWritePlatformService, final JpaTransactionManager transactionManager,
final LoanChargeReadPlatformService loanChargeReadPlatformService) {
this.loanReadPlatformService = loanReadPlatformService;
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.transactionManager = transactionManager;
this.loanChargeReadPlatformService = loanChargeReadPlatformService;
}
@Override
@@ -61,9 +67,17 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retriveScheduleAccrualData();
StringBuilder sb = new StringBuilder();
Set<Long> loansIds = new HashSet<>();
Map<Long, Collection<LoanChargeData>> loanChargeMap = new HashMap<>();
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
try {
if (!loansIds.contains(accrualData.getLoanId())) {
if (!loanChargeMap.containsKey(accrualData.getLoanId())) {
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService
.retrieveLoanChargesForAccural(accrualData.getLoanId());
loanChargeMap.put(accrualData.getLoanId(), chargeData);
}
updateCharges(loanChargeMap.get(accrualData.getLoanId()), accrualData, accrualData.getFromDateAsLocaldate(),
accrualData.getDueDateAsLocaldate());
addAccrualAccounting(accrualData);
}
} catch (Exception e) {
@@ -99,17 +113,27 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
Set<Long> loansIds = new HashSet<>();
LocalDate accruredTill = null;
Long lastLoanId = null;
Map<Long, Collection<LoanChargeData>> loanChargeMap = new HashMap<>();
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
try {
if (!loansIds.contains(accrualData.getLoanId())) {
if (!loanChargeMap.containsKey(accrualData.getLoanId())) {
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService
.retrieveLoanChargesForAccural(accrualData.getLoanId());
loanChargeMap.put(accrualData.getLoanId(), chargeData);
}
if (accrualData.getDueDateAsLocaldate().isAfter(tilldate)) {
if (accruredTill == null || lastLoanId == null || !lastLoanId.equals(accrualData.getLoanId())) {
accruredTill = accrualData.getAccruedTill();
}
if (accruredTill == null || accruredTill.isBefore(tilldate)) {
if (accruredTill == null || accruredTill.isBefore(tilldate)) {
updateCharges(loanChargeMap.get(accrualData.getLoanId()), accrualData, accrualData.getFromDateAsLocaldate(),
tilldate);
addAccrualTillSpecificDate(tilldate, accrualData, accruredTill);
}
} else {
updateCharges(loanChargeMap.get(accrualData.getLoanId()), accrualData, accrualData.getFromDateAsLocaldate(),
accrualData.getDueDateAsLocaldate());
addAccrualAccounting(accrualData);
accruredTill = accrualData.getDueDateAsLocaldate();
}
@@ -131,10 +155,8 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanScheduleAccrualData accrualData,
final LocalDate accruredTill) throws Exception {
LocalDate interestStartDate = accrualData.getFromDateAsLocaldate();
// boolean isInterestStartDateEqulsInstallmentStartdate = true;
if (accrualData.getInterestCalculatedFrom() != null
&& accrualData.getFromDateAsLocaldate().isBefore(accrualData.getInterestCalculatedFrom())) {
// isInterestStartDateEqulsInstallmentStartdate = false;
if (accrualData.getInterestCalculatedFrom().isBefore(accrualData.getDueDateAsLocaldate())) {
interestStartDate = accrualData.getInterestCalculatedFrom();
} else {
@@ -155,21 +177,6 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
}
}
int daysToBeAccrued = Days.daysBetween(startDate, tilldate).getDays();
// if (daysToBeAccrued < 1) { return; }
// int daysInSchedule = Days.daysBetween(interestStartDate, tilldate).getDays();
/*
* switch (accrualData.getRepaymentFrequency()) { case MONTHS: int
* numberOfDaysInMonth =
* calculateTotalNumberOfDaysForMonth(totalNumberOfDays, accrualData);
* if (!(!isInterestStartDateEqulsInstallmentStartdate &&
* totalNumberOfDays < numberOfDaysInMonth)) { totalNumberOfDays =
* numberOfDaysInMonth; } break; case YEARS: int numberOfDaysinYear =
* calculateTotalNumberOfDaysForYear(totalNumberOfDays, accrualData); if
* (!(!isInterestStartDateEqulsInstallmentStartdate && totalNumberOfDays
* < numberOfDaysinYear)) { totalNumberOfDays = numberOfDaysinYear; }
* break; default: break; }
*/
double interestPerDay = accrualData.getInterestIncome().doubleValue() / totalNumberOfDays;
BigDecimal amount = BigDecimal.ZERO;
BigDecimal interestportion = null;
@@ -182,23 +189,22 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
interestportion = BigDecimal.valueOf(iterest);
}
interestportion = interestportion.setScale(accrualData.getCurrencyData().decimalPlaces(), RoundingMode.HALF_EVEN);
amount = amount.add(interestportion);
BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome();
BigDecimal totalAccPenalty = accrualData.getAccruedPenaltyIncome();
BigDecimal totalAccFee = accrualData.getAccruedFeeIncome();
if (startDate.isEqual(accrualData.getFromDateAsLocaldate())) {
totalAccInterest = null;
totalAccPenalty = null;
totalAccFee = null;
if (interestportion != null) {
if (totalAccInterest == null) {
totalAccInterest = BigDecimal.ZERO;
}
interestportion = interestportion.subtract(totalAccInterest);
amount = amount.add(interestportion);
totalAccInterest = totalAccInterest.add(interestportion);
if (interestportion.compareTo(BigDecimal.ZERO) == 0) {
interestportion = null;
}
}
if (totalAccInterest == null) {
totalAccInterest = BigDecimal.ZERO;
}
totalAccInterest = totalAccInterest.add(interestportion);
if (feeportion != null) {
if (totalAccFee == null) {
totalAccFee = BigDecimal.ZERO;
@@ -228,24 +234,6 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
}
}
/*
* 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 {
@@ -306,6 +294,13 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
LoanTransactionType.ACCRUAL.getValue(), accruredTill.toDate(), amount, interestportion, feeportion, penaltyportion);
final Long transactonId = this.jdbcTemplate.queryForLong("SELECT LAST_INSERT_ID()");
Map<LoanChargeData, BigDecimal> applicableCharges = scheduleAccrualData.getApplicableCharges();
String chargespaidSql = "INSERT INTO m_loan_charge_paid_by (loan_transaction_id, loan_charge_id, amount) VALUES (?,?,?)";
for (Map.Entry<LoanChargeData, BigDecimal> entry : applicableCharges.entrySet()) {
LoanChargeData chargeData = entry.getKey();
this.jdbcTemplate.update(chargespaidSql, transactonId, chargeData.getId(), entry.getValue());
}
Map<String, Object> transactionMap = toMapData(transactonId, amount, interestportion, feeportion, penaltyportion,
scheduleAccrualData, accruredTill);
@@ -365,7 +360,86 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
thisTransactionData.put("penaltyChargesPortion", penaltyportion);
thisTransactionData.put("overPaymentPortion", null);
Map<LoanChargeData, BigDecimal> applicableCharges = loanScheduleAccrualData.getApplicableCharges();
if (applicableCharges != null && !applicableCharges.isEmpty()) {
final List<Map<String, Object>> loanChargesPaidData = new ArrayList<>();
for (Map.Entry<LoanChargeData, BigDecimal> entry : applicableCharges.entrySet()) {
LoanChargeData chargeData = entry.getKey();
final Map<String, Object> loanChargePaidData = new LinkedHashMap<>();
loanChargePaidData.put("chargeId", chargeData.getChargeId());
loanChargePaidData.put("isPenalty", chargeData.isPenalty());
loanChargePaidData.put("loanChargeId", chargeData.getId());
loanChargePaidData.put("amount", entry.getValue());
loanChargesPaidData.add(loanChargePaidData);
}
thisTransactionData.put("loanChargesPaid", loanChargesPaidData);
}
return thisTransactionData;
}
private void updateCharges(final Collection<LoanChargeData> chargesData, final LoanScheduleAccrualData accrualData,
final LocalDate startDate, final LocalDate endDate) {
final Map<LoanChargeData, BigDecimal> applicableCharges = new HashMap<>();
BigDecimal dueDateFeeIncome = BigDecimal.ZERO;
BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO;
for (LoanChargeData loanCharge : chargesData) {
BigDecimal chargeAmount = BigDecimal.ZERO;
if (loanCharge.getDueDate() == null) {
if (loanCharge.isInstallmentFee() && accrualData.getDueDateAsLocaldate().isEqual(endDate)) {
BigDecimal accruedAmount = BigDecimal.ZERO;
Collection<LoanInstallmentChargeData> installmentData = loanCharge.getInstallmentChargeData();
for (LoanInstallmentChargeData installmentChargeData : installmentData) {
BigDecimal accruableForInstallment = installmentChargeData.getAmount();
if (installmentChargeData.getAmountWaived() != null) {
accruableForInstallment = accruableForInstallment.subtract(installmentChargeData.getAmountWaived());
}
if (installmentChargeData.getInstallmentNumber().equals(accrualData.getInstallmentNumber())) {
chargeAmount = accruableForInstallment;
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
if (canAddCharge
&& (loanCharge.getAmountAccrued() == null || loanCharge.getAmountAccrued().compareTo(accruedAmount) == 0)) {
applicableCharges.put(loanCharge, chargeAmount);
BigDecimal amountAccrued = chargeAmount;
if (loanCharge.getAmountAccrued() != null) {
amountAccrued = amountAccrued.add(loanCharge.getAmountAccrued());
}
loanCharge.updateAmountAccrued(amountAccrued);
}
break;
}
accruedAmount = accruedAmount.add(accruableForInstallment);
}
}
} else if (loanCharge.getDueDate().isAfter(startDate) && !loanCharge.getDueDate().isAfter(endDate)) {
chargeAmount = loanCharge.getAmount();
if (loanCharge.getAmountWaived() != null) {
chargeAmount = chargeAmount.subtract(loanCharge.getAmountWaived());
}
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
if (canAddCharge && loanCharge.getAmountAccrued() == null) {
applicableCharges.put(loanCharge, chargeAmount);
}
}
if (loanCharge.isPenalty()) {
dueDatePenaltyIncome = dueDatePenaltyIncome.add(chargeAmount);
} else {
dueDateFeeIncome = dueDateFeeIncome.add(chargeAmount);
}
}
if (dueDateFeeIncome.compareTo(BigDecimal.ZERO) == 0) {
dueDateFeeIncome = null;
}
if (dueDatePenaltyIncome.compareTo(BigDecimal.ZERO) == 0) {
dueDatePenaltyIncome = null;
}
accrualData.updateChargeDetails(applicableCharges, dueDateFeeIncome, dueDatePenaltyIncome);
}
}
@@ -24,4 +24,6 @@ public interface LoanChargeReadPlatformService {
Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges);
Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(Long loanId, Long chargeId, Integer periodNumber);
Collection<LoanChargeData> retrieveLoanChargesForAccural(Long loanId);
}
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -23,6 +24,7 @@ import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@@ -208,7 +210,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
public String schema() {
return " lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, "
+ "lic.amount_outstanding_derived as amountOutstanding," + "lic.amount as amount, " + "lic.is_paid_derived as paid, "
+ "lic.waived as waied " + "from m_loan_installment_charge lic "
+ "lic.amount_waived_derived as amountWaived, " + "lic.waived as waied " + "from m_loan_installment_charge lic "
+ "join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ";
}
@@ -218,10 +220,11 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
final BigDecimal amount = rs.getBigDecimal("amount");
final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, paid, waived);
return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, amountWaived, paid, waived);
}
}
@@ -237,4 +240,80 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
}
return this.jdbcTemplate.queryForList(sql, Integer.class, params);
}
@Override
public Collection<LoanChargeData> retrieveLoanChargesForAccural(final Long loanId) {
final LoanChargeAccrualMapper rm = new LoanChargeAccrualMapper();
final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = 1 group by lc.id "
+ " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
Collection<LoanChargeData> charges = this.jdbcTemplate.query(sql, rm,
new Object[] { LoanTransactionType.ACCRUAL.getValue(), loanId });
Collection<LoanChargeData> removeCharges = new ArrayList<>();
for (LoanChargeData loanChargeData : charges) {
if (loanChargeData.isInstallmentFee()) {
removeCharges.add(loanChargeData);
}
}
charges.removeAll(removeCharges);
for (LoanChargeData loanChargeData : removeCharges) {
if (loanChargeData.isInstallmentFee()) {
Collection<LoanInstallmentChargeData> installmentChargeDatas = retrieveInstallmentLoanCharges(loanChargeData.getId(), false);
LoanChargeData modifiedChargeData = new LoanChargeData(loanChargeData, installmentChargeDatas);
charges.add(modifiedChargeData);
}
}
return charges;
}
private static final class LoanChargeAccrualMapper implements RowMapper<LoanChargeData> {
private final String schemaSql;
public LoanChargeAccrualMapper() {
StringBuilder sb = new StringBuilder(50);
sb.append("lc.id as id, lc.charge_id as chargeId, ");
sb.append("lc.amount as amountDue, ");
sb.append("lc.amount_waived_derived as amountWaived, ");
sb.append("lc.charge_time_enum as chargeTime, ");
sb.append(" sum(cp.amount) as amountAccrued, ");
sb.append("lc.is_penalty as penalty, ");
sb.append("lc.due_for_collection_as_of_date as dueAsOfDate ");
sb.append(" from m_loan_charge lc ");
sb.append("left join (");
sb.append("select lcp.loan_charge_id, lcp.amount");
sb.append(" from m_loan_charge_paid_by lcp ");
sb.append("inner join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed = 0 and lt.transaction_type_enum = ?");
sb.append(") cp on cp.loan_charge_id= lc.id ");
schemaSql = sb.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long chargeId = rs.getLong("chargeId");
final BigDecimal amount = rs.getBigDecimal("amountDue");
final BigDecimal amountAccrued = rs.getBigDecimal("amountAccrued");
final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
final int chargeTime = rs.getInt("chargeTime");
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final boolean penalty = rs.getBoolean("penalty");
return new LoanChargeData(id, chargeId, dueAsOfDate, chargeTimeType, amount, amountAccrued, amountWaived, penalty);
}
}
}
@@ -1448,11 +1448,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
.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.interest_calculated_from_date as interestCalculatedFrom, ")
.append("loan.days_in_month_enum as daysInMonth, ")
.append("loan.days_in_year_enum as daysInYear, ")
.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.installment as installmentNumber, ")
.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,")
@@ -1474,10 +1471,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Long officeId = rs.getLong("officeId");
final LocalDate accruedTill = JdbcSupport.getLocalDate(rs, "accruedTill");
final LocalDate interestCalculatedFrom = JdbcSupport.getLocalDate(rs, "interestCalculatedFrom");
final Integer numberOfDaysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");
;
final Integer numberOfDaysInYear = JdbcSupport.getInteger(rs, "daysInYear");
;
final Integer installmentNumber = JdbcSupport.getInteger(rs, "installmentNumber");
final Integer frequencyEnum = JdbcSupport.getInteger(rs, "frequencyEnum");
final Integer repayEvery = JdbcSupport.getInteger(rs, "repayEvery");
final PeriodFrequencyType frequency = PeriodFrequencyType.fromInt(frequencyEnum);
@@ -1501,12 +1496,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
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,
interestCalculatedFrom);
return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromDate,
repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
accruedPenaltyIncome, currencyData, interestCalculatedFrom);
}
}
@@ -1517,7 +1509,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
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("ls.duedate as duedate,ls.id as scheduleId,loan.product_id as productId,")
.append("ls.duedate as duedate,ls.fromdate as fromdate,ls.id as scheduleId,loan.product_id as productId,")
.append("ls.installment as installmentNumber, ")
.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,")
@@ -1536,7 +1529,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Long loanId = rs.getLong("loanId");
final Long officeId = rs.getLong("officeId");
final Integer installmentNumber = JdbcSupport.getInteger(rs, "installmentNumber");
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");
@@ -1555,18 +1550,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
currencyDisplaySymbol, currencyNameCode);
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;
final LocalDate interestCalculatedFrom = null;
return new LoanScheduleAccrualData(loanId, officeId, accruedTill, numberOfDaysInMonth, numberOfDaysInYear, frequency,
repayEvery, dueDate, fromDate, repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome,
accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData, dueDateFeeincome, dueDatePenaltyIncome,
interestCalculatedFrom);
return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromdate,
repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
accruedPenaltyIncome, currencyData, interestCalculatedFrom);
}
}
@@ -1609,7 +1598,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
sqlBuilder.append(" group by ml.id");
try {
return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class,
new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()),formatter.print(LocalDate.now()) });
new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()), formatter.print(LocalDate.now()) });
} catch (final EmptyResultDataAccessException e) {
return null;
}