Merge pull request #662 from pramodn02/MIFOSX-730

Mifosx 730
This commit is contained in:
Vishwas Babu A J
2013-11-12 23:35:00 -08:00
20 changed files with 844 additions and 153 deletions
@@ -0,0 +1,19 @@
CREATE TABLE `m_loan_installment_charge` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_charge_id` BIGINT(20) NOT NULL,
`loan_schedule_id` BIGINT(20) NOT NULL,
`due_date` DATE NULL DEFAULT NULL,
`amount` DECIMAL(19,6) NOT NULL,
`amount_paid_derived` DECIMAL(19,6) NULL DEFAULT NULL,
`amount_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL,
`amount_writtenoff_derived` DECIMAL(19,6) NULL DEFAULT NULL,
`amount_outstanding_derived` DECIMAL(19,6) NOT NULL DEFAULT '0.000000',
`is_paid_derived` TINYINT(1) NOT NULL DEFAULT '0',
`waived` TINYINT(1) NOT NULL DEFAULT '0',
`amount_through_charge_payment` DECIMAL(19,6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_loan_charge_id_charge_schedule` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
CONSTRAINT `FK_loan_schedule_id_charge_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
@@ -23,11 +23,12 @@ public class AccountTransferDTO {
private final Integer fromTransferType;
private final Integer toTransferType;
private final Long chargeId;
private final Integer loanInstallmentNumber;
public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount,
final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId,
final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt,
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId) {
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId, Integer loanInstallmentNumber) {
this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount;
this.fromAccountType = fromAccountType;
@@ -41,6 +42,7 @@ public class AccountTransferDTO {
this.fromTransferType = fromTransferType;
this.toTransferType = toTransferType;
this.chargeId = chargeId;
this.loanInstallmentNumber = loanInstallmentNumber;
}
public LocalDate getTransactionDate() {
@@ -95,4 +97,9 @@ public class AccountTransferDTO {
return this.chargeId;
}
public Integer getLoanInstallmentNumber() {
return this.loanInstallmentNumber;
}
}
@@ -229,7 +229,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeChargePayment(toLoanAccount,
accountTransferDTO.getChargeId(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType());
accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType(), accountTransferDTO.getLoanInstallmentNumber());
final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO,
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
@@ -36,6 +36,7 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.mifosplatform.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
@@ -48,7 +49,7 @@ public class LoanChargesApiResource {
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "chargeId", "name", "penalty",
"chargeTimeType", "dueAsOfDate", "chargeCalculationType", "percentage", "amountPercentageAppliedTo", "currency",
"amountWaived", "amountWrittenOff", "amountOutstanding", "amountOrPercentage", "amount", "amountPaid", "chargeOptions"));
"amountWaived", "amountWrittenOff", "amountOutstanding", "amountOrPercentage", "amount", "amountPaid", "chargeOptions","installmentChargeData"));
private final String resourceNameForPermissions = "LOAN";
@@ -116,9 +117,13 @@ public class LoanChargesApiResource {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final LoanChargeData loanCharge = this.loanChargeReadPlatformService.retrieveLoanChargeDetails(loanChargeId, loanId);
final Collection<LoanInstallmentChargeData> installmentChargeData = this.loanChargeReadPlatformService.retrieveInstallmentLoanCharges(loanChargeId, true);
final LoanChargeData loanChargeData = new LoanChargeData(loanCharge, installmentChargeData);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanCharge, this.RESPONSE_DATA_PARAMETERS);
return this.toApiJsonSerializer.serialize(settings, loanChargeData, this.RESPONSE_DATA_PARAMETERS);
}
@POST
@@ -13,6 +13,7 @@ import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
/**
* Immutable data object for loan charge data.
@@ -20,45 +21,32 @@ import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
public class LoanChargeData {
private final Long id;
@SuppressWarnings("unused")
private final Long chargeId;
@SuppressWarnings("unused")
private final String name;
@SuppressWarnings("unused")
private final EnumOptionData chargeTimeType;
private final LocalDate dueDate;
@SuppressWarnings("unused")
private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage;
@SuppressWarnings("unused")
private final BigDecimal amountPercentageAppliedTo;
@SuppressWarnings("unused")
private final CurrencyData currency;
@SuppressWarnings("unused")
private final BigDecimal amount;
@SuppressWarnings("unused")
private final BigDecimal amountPaid;
@SuppressWarnings("unused")
private final BigDecimal amountWaived;
@SuppressWarnings("unused")
private final BigDecimal amountWrittenOff;
private final BigDecimal amountOutstanding;
@SuppressWarnings("unused")
private final BigDecimal amountOrPercentage;
@SuppressWarnings("unused")
private final Collection<ChargeData> chargeOptions;
@SuppressWarnings("unused")
private final boolean penalty;
private final EnumOptionData chargePaymentMode;
@@ -67,20 +55,20 @@ public class LoanChargeData {
private final boolean waived;
@SuppressWarnings("unused")
private final boolean chargePayable;
private final Long loanId;
@SuppressWarnings("unused")
private final BigDecimal minCap;
@SuppressWarnings("unused")
private final BigDecimal maxCap;
@SuppressWarnings("unused")
private final Collection<LoanInstallmentChargeData> installmentChargeData;
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,null,null);
}
/**
@@ -92,7 +80,7 @@ public class LoanChargeData {
final EnumOptionData chargeCalculationType, final boolean penalty, final EnumOptionData chargePaymentMode,
final BigDecimal minCap, final BigDecimal maxCap) {
return new LoanChargeData(null, chargeId, name, currency, amount, percentage, chargeTimeType, chargeCalculationType, null, penalty,
chargePaymentMode, false, false, null, minCap, maxCap,null);
chargePaymentMode, false, false, null, minCap, maxCap,null, null);
}
public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
@@ -100,7 +88,7 @@ public class LoanChargeData {
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueDate,
final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid, final boolean waived, final Long loanId,
final BigDecimal minCap, final BigDecimal maxCap,final BigDecimal amountOrPercentage) {
final BigDecimal minCap, final BigDecimal maxCap,final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
@@ -134,12 +122,13 @@ public class LoanChargeData {
this.chargeOptions = null;
this.chargePayable = isChargePayable();
this.loanId = loanId;
this.installmentChargeData = installmentChargeData;
}
private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
final BigDecimal percentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
final Collection<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
final boolean waived, final Long loanId, final BigDecimal minCap, final BigDecimal maxCap,final BigDecimal amountOrPercentage) {
final boolean waived, final Long loanId, final BigDecimal minCap, final BigDecimal maxCap,final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
@@ -174,9 +163,10 @@ public class LoanChargeData {
this.loanId = loanId;
this.minCap = minCap;
this.maxCap = maxCap;
this.installmentChargeData = installmentChargeData;
}
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, final Long loanId) {
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, EnumOptionData chargeTimeType, final Long loanId, Collection<LoanInstallmentChargeData> installmentChargeData) {
this.id = id;
this.chargeId = null;
this.name = null;
@@ -186,7 +176,7 @@ public class LoanChargeData {
this.amountWaived = null;
this.amountWrittenOff = null;
this.amountOutstanding = amountOutstanding;
this.chargeTimeType = null;
this.chargeTimeType = chargeTimeType;
this.dueDate = dueAsOfDate;
this.chargeCalculationType = null;
this.percentage = null;
@@ -201,7 +191,37 @@ public class LoanChargeData {
this.loanId = loanId;
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = installmentChargeData;
}
public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
this.id = chargeData.id;
this.chargeId = chargeData.chargeId;
this.name = chargeData.name;
this.currency = chargeData.currency;
this.amount = chargeData.amount;
this.amountPaid = chargeData.amountPaid;
this.amountWaived = chargeData.amountWaived;
this.amountWrittenOff = chargeData.amountWrittenOff;
this.amountOutstanding = chargeData.amountOutstanding;
this.chargeTimeType = chargeData.chargeTimeType;
this.dueDate = chargeData.dueDate;
this.chargeCalculationType = chargeData.chargeCalculationType;
this.percentage = chargeData.percentage;
this.amountPercentageAppliedTo = chargeData.amountPercentageAppliedTo;
this.penalty = chargeData.penalty;
this.chargePaymentMode = chargeData.chargePaymentMode;
this.paid = chargeData.paid;
this.waived = chargeData.waived;
this.minCap = chargeData.minCap;
this.maxCap = chargeData.maxCap;
this.amountOrPercentage = chargeData.amountOrPercentage;
this.chargeOptions = chargeData.chargeOptions;
this.chargePayable = chargeData.chargePayable;
this.loanId = chargeData.loanId;
this.installmentChargeData = installmentChargeData;
}
public boolean isChargePayable() {
boolean isAccountTransfer = false;
@@ -226,4 +246,12 @@ public class LoanChargeData {
public BigDecimal getAmountOutstanding() {
return this.amountOutstanding;
}
public boolean isInstallmentFee() {
boolean isInstalmentFee = false;
if (this.chargeTimeType != null) {
isInstalmentFee = ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isInstalmentFee();
}
return isInstalmentFee;
}
}
@@ -0,0 +1,46 @@
package org.mifosplatform.portfolio.loanaccount.data;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
public class LoanInstallmentChargeData {
private final Integer installmentNumber;
private final LocalDate dueDate;
@SuppressWarnings("unused")
private final BigDecimal amount;
@SuppressWarnings("unused")
private final BigDecimal amountOutstanding;
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) {
this.installmentNumber = installmentNumber;
this.dueDate = dueDate;
this.amount = amount;
this.amountOutstanding = amountOutstanding;
this.paid = paid;
this.waived = waived;
}
public Integer getInstallmentNumber() {
return this.installmentNumber;
}
public LocalDate getDueDate() {
return this.dueDate;
}
public boolean isPaymentPending(){
return !(paid || waived);
}
public BigDecimal getAmountOutstanding() {
return this.amountOutstanding;
}
}
@@ -520,7 +520,7 @@ public class Loan extends AbstractPersistable<Long> {
}
private void handleChargePaidTransaction(final LoanCharge charge, final LoanTransaction chargesPayment,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final LoanLifecycleStateMachine loanLifecycleStateMachine, Integer installmentNumber) {
chargesPayment.updateLoan(this);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, chargesPayment.getAmount(getCurrency())
.getAmount());
@@ -535,7 +535,10 @@ public class Loan extends AbstractPersistable<Long> {
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<LoanRepaymentScheduleInstallment>();
LocalDate startDate = getDisbursementDate();
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
if (charge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
if (installmentNumber == null && charge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
chargePaymentInstallments.add(installment);
break;
}else if(installmentNumber != null && installment.getInstallmentNumber().equals(installmentNumber)){
chargePaymentInstallments.add(installment);
break;
}
@@ -708,31 +711,45 @@ public class Loan extends AbstractPersistable<Long> {
public BigDecimal calculatePerInstallmentChargeAmount(final ChargeCalculationType calculationType, final BigDecimal percentage) {
Money amount = Money.zero(getCurrency());
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
Money percentOf = Money.zero(getCurrency());
switch (calculationType) {
case PERCENT_OF_AMOUNT:
percentOf = installment.getPrincipal(getCurrency());
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
percentOf = installment.getPrincipal(getCurrency()).plus(installment.getInterestCharged(getCurrency()));
break;
case PERCENT_OF_INTEREST:
percentOf = installment.getInterestCharged(getCurrency());
break;
default:
break;
}
amount = amount.plus(LoanCharge.percentageOf(percentOf.getAmount(), percentage));
amount = amount.plus(calculateInstallmentChargeAmount(calculationType, percentage, installment));
}
return amount.getAmount();
}
/**
* @param calculationType
* @param percentage
* @param installment
* @return
*/
private Money calculateInstallmentChargeAmount(final ChargeCalculationType calculationType, final BigDecimal percentage,
final LoanRepaymentScheduleInstallment installment) {
Money amount = Money.zero(getCurrency());
Money percentOf = Money.zero(getCurrency());
switch (calculationType) {
case PERCENT_OF_AMOUNT:
percentOf = installment.getPrincipal(getCurrency());
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
percentOf = installment.getPrincipal(getCurrency()).plus(installment.getInterestCharged(getCurrency()));
break;
case PERCENT_OF_INTEREST:
percentOf = installment.getInterestCharged(getCurrency());
break;
default:
break;
}
amount = amount.plus(LoanCharge.percentageOf(percentOf.getAmount(), percentage));
return amount;
}
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
Integer loanInstallmentNumber) {
validateLoanIsNotClosed(loanCharge);
final Money amountWaived = loanCharge.waive(loanCurrency());
final Money amountWaived = loanCharge.waive(loanCurrency(), loanInstallmentNumber);
changes.put("amount", amountWaived.getAmount());
@@ -868,8 +885,9 @@ public class Loan extends AbstractPersistable<Long> {
}
public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) {
clearInstallmentLoanCharge();
this.repaymentScheduleInstallments.clear();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
@@ -884,6 +902,15 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanScheduleDependentDerivedFields();
updateLoanSummaryDerivedFields();
}
private void clearInstallmentLoanCharge(){
for(LoanCharge loanCharge:this.charges){
if(loanCharge.isInstalmentFee()){
loanCharge.clearLoanInstallmentCharges();
}
}
}
private void updateLoanScheduleDependentDerivedFields() {
@@ -1723,7 +1750,7 @@ public class Loan extends AbstractPersistable<Long> {
public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean allowTransactionsOnNonWorkingDay, final LoanTransaction paymentTransaction) {
final boolean allowTransactionsOnNonWorkingDay, final LoanTransaction paymentTransaction, Integer installmentNumber) {
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
@@ -1742,7 +1769,7 @@ public class Loan extends AbstractPersistable<Long> {
charge = loanCharge;
}
}
handleChargePaidTransaction(charge, paymentTransaction, loanLifecycleStateMachine);
handleChargePaidTransaction(charge, paymentTransaction, loanLifecycleStateMachine, installmentNumber);
}
public void makeRefund(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine,
@@ -2961,4 +2988,22 @@ public class Loan extends AbstractPersistable<Long> {
public Set<LoanCharge> charges() {
return this.charges;
}
public Set<LoanInstallmentCharge> generateInstallmentLoanCharges(final LoanCharge loanCharge){
Set<LoanInstallmentCharge> loanChargePerInstallments = new HashSet<LoanInstallmentCharge>();
if(loanCharge.isInstalmentFee()){
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
BigDecimal amount = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isFlat()){
amount = loanCharge.amount().divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
}else{
amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), installment).getAmount();
}
LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment);
loanChargePerInstallments.add(loanInstallmentCharge);
}
}
return loanChargePerInstallments;
}
}
@@ -22,5 +22,5 @@ public interface LoanAccountDomainService {
void reverseTransfer(LoanTransaction loanTransaction);
LoanTransaction makeChargePayment(Loan loan, Long chargeId, LocalDate transactionDate, BigDecimal transactionAmount,
PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType);
PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType, Integer installmentNumber);
}
@@ -163,7 +163,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Transactional
public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate,
final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final String txnExternalId,
final Integer transactionType) {
final Integer transactionType, Integer installmentNumber) {
checkClientOrGroupActive(loan);
@@ -184,7 +184,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds);
} else {
loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction);
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction, installmentNumber);
}
this.loanTransactionRepository.save(newPaymentTransaction);
this.loanRepository.saveAndFlush(loan);
@@ -8,20 +8,27 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
@@ -65,6 +72,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal amountPercentageAppliedTo;
@SuppressWarnings("unused")
@Column(name = "charge_amount_or_percentage", scale = 6, precision = 19, nullable = false)
private BigDecimal amountOrPercentage;
@@ -97,6 +105,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Column(name = "max_cap", scale = 6, precision = 19, nullable = true)
private BigDecimal maxCap;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loancharge", orphanRemoval = true)
private Set<LoanInstallmentCharge> loanInstallmentCharge = new HashSet<LoanInstallmentCharge>();
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
@@ -236,6 +248,19 @@ public class LoanCharge extends AbstractPersistable<Long> {
break;
}
this.amountOrPercentage = chargeAmount;
if(this.loan!=null && this.isInstalmentFee()){
Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if(this.loanInstallmentCharge.isEmpty()){
this.loanInstallmentCharge.addAll(chargePerInstallments);
}else{
int index = 0;
LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for(LoanInstallmentCharge chargePerInstallment:this.loanInstallmentCharge){
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
}
}
}
}
public void markAsFullyPaid() {
@@ -251,20 +276,41 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
this.waived = false;
for(LoanInstallmentCharge installmentCharge:loanInstallmentCharge){
installmentCharge.resetPaidAmount(currency);
}
}
public void resetPaidAmount(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
for(LoanInstallmentCharge installmentCharge:loanInstallmentCharge){
installmentCharge.resetPaidAmount(currency);
}
}
public Money waive(final MonetaryCurrency currency) {
public Money waive(final MonetaryCurrency currency, Integer loanInstallmentNumber) {
if(isInstalmentFee()){
LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber);
Money amountWaived = chargePerInstallment.waive(currency);
if(this.amountWaived == null){
this.amountWaived = BigDecimal.ZERO;
}
this.amountWaived = this.amountWaived.add(amountWaived.getAmount());
this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount());
if(determineIfFullyPaid()){
this.paid = false;
this.waived = true;
}
return amountWaived;
}
this.amountWaived = this.amountOutstanding;
this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true;
return getAmountWaived(currency);
}
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
@@ -307,6 +353,19 @@ public class LoanCharge extends AbstractPersistable<Long> {
break;
}
this.amountOrPercentage = amount;
if(this.loan !=null && this.isInstalmentFee()){
Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if(this.loanInstallmentCharge.isEmpty()){
this.loanInstallmentCharge.addAll(chargePerInstallments);
}else{
int index = 0;
LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for(LoanInstallmentCharge chargePerInstallment:this.loanInstallmentCharge){
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
}
}
}
}
}
@@ -357,6 +416,19 @@ public class LoanCharge extends AbstractPersistable<Long> {
break;
}
this.amountOrPercentage = newValue;
if(this.isInstalmentFee()){
Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if(this.loanInstallmentCharge.isEmpty()){
this.loanInstallmentCharge.addAll(chargePerInstallments);
}else{
int index = 0;
LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for(LoanInstallmentCharge chargePerInstallment:this.loanInstallmentCharge){
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
}
}
}
}
return actualChanges;
}
@@ -538,28 +610,36 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
/**
* @param incrementBy
* @param feeAmount TODO
* @param processAmount
* Amount used to pay off this charge
* @return Actual amount paid on this charge
*/
public Money updatePaidAmountBy(final Money incrementBy) {
public Money updatePaidAmountBy(final Money incrementBy,final Integer installmentNumber, Money feeAmount) {
Money processAmount = Money.zero(incrementBy.getCurrency());
if(this.isInstalmentFee()){
if(installmentNumber == null){
processAmount = getUnpaidInstallmentLoanCharge().updatePaidAmountBy(incrementBy, feeAmount);
}else{
processAmount = getInstallmentLoanCharge(installmentNumber).updatePaidAmountBy(incrementBy, feeAmount);
}
}else{
processAmount = incrementBy;
}
Money amountPaidToDate = Money.of(processAmount.getCurrency(), this.amountPaid);
final Money amountOutstanding = Money.of(processAmount.getCurrency(), this.amountOutstanding);
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
Money amountPaidOnThisCharge = Money.zero(processAmount.getCurrency());
if (processAmount.isGreaterThanOrEqualTo(amountOutstanding)) {
amountPaidOnThisCharge = amountOutstanding;
amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = BigDecimal.ZERO;
} else {
amountPaidOnThisCharge = incrementBy;
amountPaidToDate = amountPaidToDate.plus(incrementBy);
amountPaidOnThisCharge = processAmount;
amountPaidToDate = amountPaidToDate.plus(processAmount);
this.amountPaid = amountPaidToDate.getAmount();
final Money amountExpected = Money.of(incrementBy.getCurrency(), this.amount);
this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
}
this.paid = determineIfFullyPaid();
@@ -606,19 +686,68 @@ public class LoanCharge extends AbstractPersistable<Long> {
return ChargePaymentMode.fromInt(this.chargePaymentMode);
}
public ChargeCalculationType getChargeCalculation() {
return ChargeCalculationType.fromInt(this.chargeCalculation);
}
public BigDecimal getPercentage() {
return this.percentage;
}
public void updateAmount(final BigDecimal amount) {
this.amount = amount;
calculateOutstanding();
}
public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() {
LoanInstallmentCharge unpaidChargePerInstallment = null;
for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending()
&& (unpaidChargePerInstallment == null || unpaidChargePerInstallment.getRepaymentInstallment().getDueDate()
.isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
unpaidChargePerInstallment = loanChargePerInstallment;
}
}
return unpaidChargePerInstallment;
}
public LoanInstallmentCharge getInstallmentLoanCharge(LocalDate periodDueDate) {
for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (periodDueDate.isEqual(loanChargePerInstallment.getRepaymentInstallment().getDueDate())) {
return loanChargePerInstallment;
}
}
return null;
}
public LoanInstallmentCharge getInstallmentLoanCharge(Integer installmentNumber) {
for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue()) ){
return loanChargePerInstallment;
}
}
return null;
}
public void clearLoanInstallmentCharges(){
this.loanInstallmentCharge.clear();
}
public void addLoanInstallmentCharges(Collection<LoanInstallmentCharge> installmentCharges){
this.loanInstallmentCharge.addAll(installmentCharges);
}
public boolean hasNoLoanInstallmentCharges(){
return this.loanInstallmentCharge.isEmpty();
}
public LoanRepaymentScheduleInstallment fetchRepaymentInstallment(Money trasferedAmount){
for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending() &&
trasferedAmount.getAmount().equals(loanChargePerInstallment.getAmountThroughChargePayment(trasferedAmount.getCurrency()).getAmount()) ){
return loanChargePerInstallment.getRepaymentInstallment();
}
}
return null;
}
}
@@ -0,0 +1,211 @@
/**
* 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.loanaccount.domain;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_installment_charge")
public class LoanInstallmentCharge extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne(optional = false)
@JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
private LoanCharge loancharge;
@ManyToOne(optional = false)
@JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false)
private LoanRepaymentScheduleInstallment installment;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountPaid;
@Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountWaived;
@Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountWrittenOff;
@Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
private BigDecimal amountOutstanding;
@Column(name = "amount_through_charge_payment", scale = 6, precision = 19, nullable = true)
private BigDecimal amountThroughChargePayment;
@Column(name = "is_paid_derived", nullable = false)
private boolean paid = false;
@Column(name = "waived", nullable = false)
private boolean waived = false;
public LoanInstallmentCharge() {
// TODO Auto-generated constructor stub
}
public LoanInstallmentCharge(BigDecimal amount,LoanCharge loanCharge,LoanRepaymentScheduleInstallment installment) {
this.loancharge = loanCharge;
this.installment = installment;
this.amount = amount;
this.amountOutstanding = amount;
this.amountPaid = null;
this.amountWaived = null;
this.amountWrittenOff = null;
}
public void copyFrom(LoanInstallmentCharge loanChargePerInstallment){
this.amount = loanChargePerInstallment.amount;
this.installment = loanChargePerInstallment.installment;
this.amountOutstanding = calculateOutstanding();
this.paid = determineIfFullyPaid();
}
public Money waive(final MonetaryCurrency currency) {
this.amountWaived = this.amountOutstanding;
this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true;
return getAmountWaived(currency);
}
public Money getAmountWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.amountWaived);
}
private boolean determineIfFullyPaid() {
if(this.amount == null){
return true;
}
return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
}
private BigDecimal calculateOutstanding() {
if(this.amount == null){
return null;
}
BigDecimal amountPaidLocal = BigDecimal.ZERO;
if (this.amountPaid != null) {
amountPaidLocal = this.amountPaid;
}
BigDecimal amountWaivedLocal = BigDecimal.ZERO;
if (this.amountWaived != null) {
amountWaivedLocal = this.amountWaived;
}
BigDecimal amountWrittenOffLocal = BigDecimal.ZERO;
if (this.amountWrittenOff != null) {
amountWrittenOffLocal = this.amountWrittenOff;
}
final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal);
return this.amount.subtract(totalAccountedFor);
}
public BigDecimal getAmount() {
return this.amount;
}
public Money getAmount(final MonetaryCurrency currency) {
return Money.of(currency, this.amount);
}
private Money getAmountPaid(final MonetaryCurrency currency) {
return Money.of(currency, this.amountPaid);
}
public BigDecimal getAmountOutstanding() {
return this.amountOutstanding;
}
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
}
public boolean isPaid() {
return this.paid;
}
public boolean isWaived() {
return this.waived;
}
public boolean isPending(){
return !(isPaid() || isWaived());
}
public LoanRepaymentScheduleInstallment getRepaymentInstallment() {
return this.installment;
}
public Money updatePaidAmountBy(final Money incrementBy, Money feeAmount) {
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
amountPaidOnThisCharge = amountOutstanding;
amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = BigDecimal.ZERO;
} else {
amountPaidOnThisCharge = incrementBy;
amountPaidToDate = amountPaidToDate.plus(incrementBy);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
}
this.amountThroughChargePayment = feeAmount.getAmount();
this.paid = determineIfFullyPaid();
return amountPaidOnThisCharge;
}
public Money getAmountWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.amountWrittenOff);
}
public void resetPaidAmount(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
}
public void resetToOriginal(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountWaived = BigDecimal.ZERO;
this.amountWrittenOff = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
this.waived = false;
}
public Money getAmountThroughChargePayment(final MonetaryCurrency currency) {
return Money.of(currency, this.amountThroughChargePayment);
}
}
@@ -7,15 +7,22 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
@@ -98,7 +105,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
@Temporal(TemporalType.DATE)
@Column(name = "obligations_met_on_date")
private Date obligationsMetOnDate;
protected LoanRepaymentScheduleInstallment() {
this.installmentNumber = null;
this.fromDate = null;
@@ -70,7 +70,8 @@ public class LoanRepaymentScheduleProcessingWrapper {
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) &&
loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getLoan().getSummary().getTotalPrincipalDisbursed()).add(period.getLoan().getSummary().getTotalInterestCharged());
@@ -96,8 +97,13 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
if(loanCharge.isFeeCharge()){
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
}
}
}
@@ -110,8 +116,13 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) {
if(loanCharge.isFeeCharge()){
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
}
}
}
@@ -141,7 +152,8 @@ public class LoanRepaymentScheduleProcessingWrapper {
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) &&
loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getLoan().getSummary().getTotalPrincipalDisbursed()).add(period.getLoan().getSummary().getTotalInterestCharged());
@@ -167,8 +179,13 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
if (loanCharge.isPenaltyCharge()) {
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
}
}
}
@@ -181,8 +198,13 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) {
if (loanCharge.isPenaltyCharge()) {
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
}
}
}
@@ -16,6 +16,7 @@ import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -68,12 +69,24 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<LoanTransaction>();
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
if (loanTransaction.isChargePayment()) {
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<LoanRepaymentScheduleInstallment>();
final Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
final Set<LoanCharge> transferCharges = new HashSet<LoanCharge>();
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
transferCharges.add(chargePaidBy.getLoanCharge());
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
transferCharges.add(loanCharge);
if(loanCharge.isInstalmentFee()){
LoanRepaymentScheduleInstallment installment = null;
if(loanCharge.isFeeCharge()){
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency));
}else{
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency));
}
if(installment !=null){
chargePaymentInstallments.add(installment);
}
}
}
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<LoanRepaymentScheduleInstallment>();
LocalDate startDate = disbursementDate;
for (final LoanRepaymentScheduleInstallment installment : installments) {
for (final LoanCharge loanCharge : transferCharges) {
@@ -172,16 +185,20 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
Integer installmentNumber = null;
if(loanTransaction.isChargePayment() && installments.size() == 1){
installmentNumber = installments.get(0).getInstallmentNumber();
}
if (loanTransaction.isNotWaiver()) {
final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
if (feeCharges.isGreaterThanZero()) {
updateFeeChargesPaidAmountBy(loanTransaction, feeCharges, loanFees);
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees,installmentNumber);
}
final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (penaltyCharges.isGreaterThanZero()) {
updatePenaltyChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties);
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties,installmentNumber);
}
}
@@ -211,65 +228,46 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return penaltyCharges;
}
private void updateFeeChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges) {
private void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,final Integer installmentNumber) {
Money amountRemaining = feeCharges;
for (final LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.isFeeCharge() && loanCharge.isNotFullyPaid() && amountRemaining.isGreaterThanZero()) {
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining);
if (!amountPaidTowardsCharge.isZero()) {
if (!loanTransaction.isChargePayment()) {
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, loanCharge,
amountPaidTowardsCharge.getAmount());
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
}
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
}
}
while(amountRemaining.isGreaterThanZero()) {
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
Money feeAmount = feeCharges.zero();
if(loanTransaction.isChargePayment()){
feeAmount = feeCharges;
}
}
}
private void updatePenaltyChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges,
final Set<LoanCharge> charges) {
Money amountRemaining = feeCharges;
for (final LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.isPenaltyCharge() && loanCharge.isNotFullyPaid() && amountRemaining.isGreaterThanZero()) {
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining);
if (!amountPaidTowardsCharge.isZero()) {
if (!loanTransaction.isChargePayment()) {
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, loanCharge,
amountPaidTowardsCharge.getAmount());
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
}
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
}
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining,installmentNumber, feeAmount);
if (!amountPaidTowardsCharge.isZero()) {
if (!loanTransaction.isChargePayment()) {
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge,
amountPaidTowardsCharge.getAmount());
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
}
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
}
}
}
private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) {
LoanCharge earliestUnpaidCharge = null;
LoanCharge installemntCharge = null;
LoanInstallmentCharge chargePerInstallment = null;
for (final LoanCharge loanCharge : charges) {
if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) {
if(loanCharge.isInstalmentFee()){
if(installemntCharge == null){
LoanInstallmentCharge unpaidLoanChargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
if(chargePerInstallment == null || chargePerInstallment.getRepaymentInstallment().getDueDate().isAfter(unpaidLoanChargePerInstallment.getRepaymentInstallment().getDueDate())){
installemntCharge = loanCharge;
chargePerInstallment = unpaidLoanChargePerInstallment;
}
}else if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
earliestUnpaidCharge = loanCharge;
}
}
}
if(earliestUnpaidCharge == null || earliestUnpaidCharge.getDueLocalDate().isAfter(new LocalDate())){
if(earliestUnpaidCharge == null || (chargePerInstallment!=null && earliestUnpaidCharge.getDueLocalDate().isAfter(chargePerInstallment.getRepaymentInstallment().getDueDate()))){
earliestUnpaidCharge = installemntCharge;
}
@@ -198,7 +198,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
@@ -243,7 +244,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
@@ -300,9 +300,9 @@ public final class LoanEventApiJsonValidator {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
Set<String> transactionParameters = null;
if (isChargeIdIncluded) {
transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "locale", "dateFormat", "chargeId"));
transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "locale", "dateFormat", "chargeId","dueDate","installmentNumber"));
} else {
transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "locale", "dateFormat"));
transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "locale", "dateFormat","dueDate","installmentNumber"));
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
@@ -319,6 +319,28 @@ public final class LoanEventApiJsonValidator {
baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
}
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateInstallmentChargeTransaction(final String json) {
if (StringUtils.isBlank(json)) { return;}
Set<String> transactionParameters = new HashSet<String>(Arrays.asList("dueDate", "locale", "dateFormat", "installmentNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.waive.transaction");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final Integer installmentNumber = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("installmentNumber", element);
baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -5,8 +5,10 @@
*/
package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -47,6 +49,8 @@ import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
@@ -109,6 +113,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final CalendarInstanceRepository calendarInstanceRepository;
private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository;
private final LoanChargeRepository loanChargeRepository;
@Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -122,7 +127,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanSummaryWrapper loanSummaryWrapper, final GroupRepositoryWrapper groupRepository,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository) {
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
final LoanChargeRepository loanChargeRepository) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -145,6 +151,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.calendarInstanceRepository = calendarInstanceRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository;
this.loanChargeRepository = loanChargeRepository;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -333,7 +340,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule");
final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json());
final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper);
@@ -354,6 +361,14 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Note note = Note.loanNote(existingLoanApplication, submittedOnNote);
this.noteRepository.save(note);
}
for (final LoanCharge loanCharge : existingLoanApplication.charges()) {
if(loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()){
Set<LoanInstallmentCharge> chargePerInstallments = existingLoanApplication.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
}
final Long calendarId = command.longValueOfParameterNamed("calendarId");
Calendar calendar = null;
@@ -9,6 +9,7 @@ import java.util.Collection;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
public interface LoanChargeReadPlatformService {
@@ -19,4 +20,6 @@ public interface LoanChargeReadPlatformService {
LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId);
Collection<LoanChargeData> retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus);
Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId,boolean onlyPaymentPendingCharges);
}
@@ -21,6 +21,7 @@ import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeDropdownReadPlatformService;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@@ -104,13 +105,13 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final EnumOptionData paymentMode = ChargeEnumerations.chargePaymentMode(chargePaymentMode);
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
final BigDecimal minCap = rs.getBigDecimal("minCap");
final BigDecimal maxCap = rs.getBigDecimal("maxCap");
final BigDecimal minCap = rs.getBigDecimal("minCap");
final BigDecimal maxCap = rs.getBigDecimal("maxCap");
final BigDecimal amountOrPercentage = rs.getBigDecimal("amountOrPercentage");
return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding,
chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, paymentMode,
paid, waived, null,minCap,maxCap,amountOrPercentage);
paid, waived, null, minCap, maxCap, amountOrPercentage, null);
}
}
@@ -127,7 +128,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
.retrieveSavingsCalculationTypes();
final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCollectionTimeTypes();
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions);
@@ -168,7 +169,10 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
public String schema() {
return "lc.id as id, lc.due_for_collection_as_of_date as dueAsOfDate, "
+ "lc.amount_outstanding_derived as amountOutstanding, " + "loan.id as loanId " + "from m_loan_charge lc "
+ "lc.amount_outstanding_derived as amountOutstanding, "
+ "lc.charge_time_enum as chargeTime, "
+ "loan.id as loanId "
+ "from m_loan_charge lc "
+ "join m_loan loan on loan.id = lc.loan_id ";
}
@@ -179,7 +183,46 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final Long loanId = rs.getLong("loanId");
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
return new LoanChargeData(id, dueAsOfDate, amountOutstanding, loanId);
final int chargeTime = rs.getInt("chargeTime");
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
return new LoanChargeData(id, dueAsOfDate, amountOutstanding, chargeTimeType, loanId, null);
}
}
@Override
public Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId,boolean onlyPaymentPendingCharges) {
final LoanInstallmentChargeMapper rm = new LoanInstallmentChargeMapper();
String sql = "select " + rm.schema()
+ "where lic.loan_charge_id= ? ";
if(onlyPaymentPendingCharges){
sql = sql +"and lic.waived =0 and lic.is_paid_derived=0";
}
return this.jdbcTemplate.query(sql, rm, new Object[] { loanChargeId });
}
private static final class LoanInstallmentChargeMapper implements RowMapper<LoanInstallmentChargeData> {
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 "
+ "join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ";
}
@Override
public LoanInstallmentChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Integer installmentNumber = rs.getInt("installmentNumber");
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
final BigDecimal amount = rs.getBigDecimal("amount");
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, paid, waived);
}
}
}
@@ -77,11 +77,13 @@ import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
@@ -264,6 +266,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amount());
}else if(loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()){
Set<LoanInstallmentCharge> chargePerInstallments = loan.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
}
final Locale locale = command.extractLocale();
@@ -272,7 +278,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loanId, "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey());
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
@@ -382,6 +388,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
final Set<LoanCharge> loanCharges = loan.charges();
final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<Long, BigDecimal>();
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amount());
}else if(loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()){
Set<LoanInstallmentCharge> chargePerInstallments = loan.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
}
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan.getId());
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loan.getId(), "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
}
return changes;
}
@@ -914,6 +940,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
this.loanEventApiJsonValidator.validateInstallmentChargeTransaction(command.json());
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
// Charges may be waived only when the loan associated with them are
@@ -926,9 +953,28 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaid()) { throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_PAID,
loanCharge.getId());
} else if (loanCharge.isInstalmentFee()){
throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.WAIVE_NOT_ALLOWED_FOR_CHARGE,
loanCharge.getId());
}
Integer loanInstallmentNumber = null;
if (loanCharge.isInstalmentFee()){
LoanInstallmentCharge chargePerInstallment = null;
if(!StringUtils.isBlank(command.json())){
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
if(dueDate !=null){
chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
}else if(installmentNumber!=null){
chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
}
}
if(chargePerInstallment == null){
chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
}
if(chargePerInstallment.isWaived()){
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
}else if(chargePerInstallment.isPaid()){
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID, loanCharge.getId());
}
loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
}
final Map<String, Object> changes = new LinkedHashMap<String, Object>(3);
@@ -937,7 +983,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds);
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber);
this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan);
@@ -1006,9 +1052,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaid()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID,
loanCharge.getId());
}else if (loanCharge.isInstalmentFee()){
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_PAYABLE,
loanCharge.getId());
}
if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { throw new LoanChargeCannotBePayedException(
@@ -1018,15 +1061,37 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
Integer loanInstallmentNumber = null;
BigDecimal amount = loanCharge.amountOutstanding();
if (loanCharge.isInstalmentFee()){
LoanInstallmentCharge chargePerInstallment = null;
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
if(dueDate !=null){
chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
}else if(installmentNumber!=null){
chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
}
if(chargePerInstallment == null){
chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
}
if(chargePerInstallment.isWaived()){
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
}else if(chargePerInstallment.isPaid()){
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID, loanCharge.getId());
}
loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
amount= chargePerInstallment.getAmountOutstanding();
}
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
if (portfolioAccountData == null) {
final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
}
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, loanCharge.amountOutstanding(),
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount,
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId);
locale, fmt, null, null, LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
return new CommandProcessingResultBuilder() //
@@ -1047,29 +1112,53 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final StringBuilder sb = new StringBuilder();
if (chargeDatas != null) {
for (final LoanChargeData chargeData : chargeDatas) {
if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(new LocalDate())) {
if(chargeData.isInstallmentFee()){
Collection<LoanInstallmentChargeData> chargePerInstallments = this.LoanChargeReadPlatformService.retrieveInstallmentLoanCharges(chargeData.getId(), true);
PortfolioAccountData portfolioAccountData = null;
for(LoanInstallmentChargeData installmentChargeData : chargePerInstallments){
if(!installmentChargeData.getDueDate().isAfter(new LocalDate())){
if(portfolioAccountData == null){
portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanAssociation(chargeData.getLoanId());
}
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
installmentChargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), installmentChargeData.getInstallmentNumber());
transferFeeCharge(sb, accountTransferDTO);
}
}
}else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(new LocalDate())) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanAssociation(chargeData.getLoanId());
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId());
try {
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} catch (final PlatformApiDataValidationException e) {
sb.append("Validation exception while paying charge ").append(chargeData.getId()).append(" for loan id:")
.append(chargeData.getLoanId()).append("--------");
} catch (final InsufficientAccountBalanceException e) {
sb.append("InsufficientAccountBalance Exception while paying charge ").append(chargeData.getId())
.append("for loan id:").append(chargeData.getLoanId()).append("--------");
}
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null);
transferFeeCharge(sb, accountTransferDTO);
}
}
}
if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
}
/**
* @param sb
* @param accountTransferDTO
*/
private void transferFeeCharge(final StringBuilder sb, final AccountTransferDTO accountTransferDTO) {
try {
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} catch (final PlatformApiDataValidationException e) {
sb.append("Validation exception while paying charge ").append(accountTransferDTO.getChargeId()).append(" for loan id:")
.append(accountTransferDTO.getToAccountId()).append("--------");
} catch (final InsufficientAccountBalanceException e) {
sb.append("InsufficientAccountBalance Exception while paying charge ").append(accountTransferDTO.getChargeId())
.append("for loan id:").append(accountTransferDTO.getToAccountId()).append("--------");
}
}
private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) {
final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId);
if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); }