@@ -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;
|
||||
+8
-1
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+7
-2
@@ -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
|
||||
|
||||
+50
-22
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+67
-22
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+145
-16
@@ -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;
|
||||
}
|
||||
}
|
||||
+211
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+8
-1
@@ -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;
|
||||
|
||||
+30
-8
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-42
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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());
|
||||
|
||||
+24
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+17
-2
@@ -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;
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
|
||||
+49
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+112
-23
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user