FINERACT-2232: add income capitalization principal handling and repayment schedule calculations on loans

This commit is contained in:
mark.vituska
2025-05-27 10:02:25 +02:00
committed by Adam Saghy
parent 07839951b9
commit e5329f6fe3
53 changed files with 1007 additions and 516 deletions
@@ -464,6 +464,7 @@ public class LoanImportHandler implements ImportHandler {
JsonObject loanRepaymentJsonob = gsonBuilder.create().toJsonTree(loanRepayments.get(rowIndex)).getAsJsonObject();
loanRepaymentJsonob.remove("manuallyReversed");
loanRepaymentJsonob.remove("numberOfRepayments");
loanRepaymentJsonob.remove("reversed");
String payload = loanRepaymentJsonob.toString();
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
.loanRepaymentTransaction(result.getLoanId()) //
@@ -139,6 +139,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeTyp
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
import org.apache.fineract.portfolio.loanaccount.exception.NotSupportedLoanTemplateTypeException;
@@ -150,6 +151,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
@@ -297,6 +299,7 @@ public class LoansApiResource {
private final ClientReadPlatformService clientReadPlatformService;
private final LoanTermVariationsRepository loanTermVariationsRepository;
private final LoanSummaryProviderDelegate loanSummaryProviderDelegate;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
/*
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
@@ -498,11 +501,13 @@ public class LoansApiResource {
loanBasicDetails.getPageItems().forEach(i -> {
if (i.getSummary() != null) {
Collection<DisbursementData> disbursementData = this.loanReadPlatformService.retrieveLoanDisbursementDetails(i.getId());
List<LoanTransactionRepaymentPeriodData> capitalizedIncomeData = this.loanCapitalizedIncomeBalanceRepository
.findRepaymentPeriodDataByLoanId(i.getId());
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
i.getTimeline().getExpectedDisbursementDate(), i.getTimeline().getActualDisbursementDate(), i.getCurrency(),
i.getPrincipal(), i.getInArrearsTolerance(), i.getFeeChargesAtDisbursementCharged());
final LoanScheduleData repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(i.getId(),
repaymentScheduleRelatedData, disbursementData, i.isInterestRecalculationEnabled(),
repaymentScheduleRelatedData, disbursementData, capitalizedIncomeData, i.isInterestRecalculationEnabled(),
LoanScheduleType.fromEnumOptionData(i.getLoanScheduleType()));
LoanSummaryDataProvider loanSummaryDataProvider = loanSummaryProviderDelegate
.resolveLoanSummaryDataProvider(i.getTransactionProcessingStrategyCode());
@@ -981,13 +986,15 @@ public class LoansApiResource {
if (associationParameters.contains(DataTableApiConstant.repaymentScheduleAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.repaymentScheduleAssociateParamName);
List<LoanTransactionRepaymentPeriodData> capitalizedIncomeData = this.loanCapitalizedIncomeBalanceRepository
.findRepaymentPeriodDataByLoanId(resolvedLoanId);
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
loanBasicDetails.getTimeline().getExpectedDisbursementDate(),
loanBasicDetails.getTimeline().getActualDisbursementDate(), loanBasicDetails.getCurrency(),
loanBasicDetails.getPrincipal(), loanBasicDetails.getInArrearsTolerance(),
loanBasicDetails.getFeeChargesAtDisbursementCharged());
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(resolvedLoanId, repaymentScheduleRelatedData,
disbursementData, loanBasicDetails.isInterestRecalculationEnabled(),
disbursementData, capitalizedIncomeData, loanBasicDetails.isInterestRecalculationEnabled(),
LoanScheduleType.fromEnumOptionData(loanBasicDetails.getLoanScheduleType()));
if (associationParameters.contains(DataTableApiConstant.futureScheduleAssociateParamName)
@@ -42,9 +42,9 @@ final class LoansApiResourceSwagger {
@Schema(example = "[2012, 4, 3]")
public LocalDate approvalDate;
@Schema(example = "200.000000")
public Double approvalAmount;
public BigDecimal approvalAmount;
@Schema(example = "200.000000")
public Double netDisbursalAmount;
public BigDecimal netDisbursalAmount;
public GetLoanCurrency currency;
}
@@ -289,29 +289,29 @@ final class LoansApiResourceSwagger {
@Schema(example = "30")
public Long loanTermInDays;
@Schema(example = "200.000000")
public Double totalPrincipalDisbursed;
public BigDecimal totalPrincipalDisbursed;
@Schema(example = "200.00")
public Double totalPrincipalExpected;
public BigDecimal totalPrincipalExpected;
@Schema(example = "200.00")
public Double totalPrincipalPaid;
public BigDecimal totalPrincipalPaid;
@Schema(example = "0.00")
public Double totalInterestCharged;
public BigDecimal totalInterestCharged;
@Schema(example = "0.00")
public Double totalFeeChargesCharged;
public BigDecimal totalFeeChargesCharged;
@Schema(example = "0.00")
public Double totalPenaltyChargesCharged;
public BigDecimal totalPenaltyChargesCharged;
@Schema(example = "0.00")
public Double totalWaived;
public BigDecimal totalWaived;
@Schema(example = "0.00")
public Double totalWrittenOff;
public BigDecimal totalWrittenOff;
@Schema(example = "200.00")
public Double totalRepaymentExpected;
public BigDecimal totalRepaymentExpected;
@Schema(example = "200.00")
public Double totalPaidInAdvance;
public BigDecimal totalPaidInAdvance;
@Schema(example = "0.00")
public Double totalPaidLate;
public BigDecimal totalPaidLate;
@Schema(example = "0.00")
public Double totalOutstanding;
public BigDecimal totalOutstanding;
public List<GetLoansLoanIdRepaymentPeriod> periods;
}
@@ -332,71 +332,71 @@ final class LoansApiResourceSwagger {
@Schema(example = "30")
public Long daysInPeriod;
@Schema(example = "200.000000")
public Double principalOriginalDue;
public BigDecimal principalOriginalDue;
@Schema(example = "200.000000")
public Double principalDue;
public BigDecimal principalDue;
@Schema(example = "200.000000")
public Double principalPaid;
public BigDecimal principalPaid;
@Schema(example = "0.000000")
public Double principalWrittenOff;
public BigDecimal principalWrittenOff;
@Schema(example = "20.000000")
public Double principalOutstanding;
public BigDecimal principalOutstanding;
@Schema(example = "20.000000")
public Double principalLoanBalanceOutstanding;
public BigDecimal principalLoanBalanceOutstanding;
@Schema(example = "0.000000")
public Double interestOriginalDue;
public BigDecimal interestOriginalDue;
@Schema(example = "0.000000")
public Double interestDue;
public BigDecimal interestDue;
@Schema(example = "0.000000")
public Double interestPaid;
public BigDecimal interestPaid;
@Schema(example = "0.000000")
public Double interestWaived;
public BigDecimal interestWaived;
@Schema(example = "0.000000")
public Double interestWrittenOff;
public BigDecimal interestWrittenOff;
@Schema(example = "0.000000")
public Double interestOutstanding;
public BigDecimal interestOutstanding;
@Schema(example = "0.000000")
public Double feeChargesDue;
public BigDecimal feeChargesDue;
@Schema(example = "20.000000")
public Double feeChargesPaid;
public BigDecimal feeChargesPaid;
@Schema(example = "20.000000")
public Double feeChargesWaived;
public BigDecimal feeChargesWaived;
@Schema(example = "20.000000")
public Double feeChargesWrittenOff;
public BigDecimal feeChargesWrittenOff;
@Schema(example = "20.000000")
public Double feeChargesOutstanding;
public BigDecimal feeChargesOutstanding;
@Schema(example = "20.000000")
public Double penaltyChargesDue;
public BigDecimal penaltyChargesDue;
@Schema(example = "20.000000")
public Double penaltyChargesPaid;
public BigDecimal penaltyChargesPaid;
@Schema(example = "20.000000")
public Double penaltyChargesWaived;
public BigDecimal penaltyChargesWaived;
@Schema(example = "20.000000")
public Double penaltyChargesWrittenOff;
public BigDecimal penaltyChargesWrittenOff;
@Schema(example = "20.000000")
public Double penaltyChargesOutstanding;
public BigDecimal penaltyChargesOutstanding;
@Schema(example = "20.000000")
public Double totalOriginalDueForPeriod;
public BigDecimal totalOriginalDueForPeriod;
@Schema(example = "20.000000")
public Double totalDueForPeriod;
public BigDecimal totalDueForPeriod;
@Schema(example = "20.000000")
public Double totalPaidForPeriod;
public BigDecimal totalPaidForPeriod;
@Schema(example = "20.000000")
public Double totalPaidInAdvanceForPeriod;
public BigDecimal totalPaidInAdvanceForPeriod;
@Schema(example = "20.000000")
public Double totalPaidLateForPeriod;
public BigDecimal totalPaidLateForPeriod;
@Schema(example = "20.000000")
public Double totalWaivedForPeriod;
public BigDecimal totalWaivedForPeriod;
@Schema(example = "20.000000")
public Double totalWrittenOffForPeriod;
public BigDecimal totalWrittenOffForPeriod;
@Schema(example = "200.000000")
public Double totalOutstandingForPeriod;
public BigDecimal totalOutstandingForPeriod;
@Schema(example = "20.000000")
public Double totalActualCostOfLoanForPeriod;
public BigDecimal totalActualCostOfLoanForPeriod;
@Schema(example = "200.000000")
public Double totalInstallmentAmountForPeriod;
public BigDecimal totalInstallmentAmountForPeriod;
@Schema(example = "2.000000")
public Double totalCredits;
public BigDecimal totalCredits;
@Schema(example = "true")
public Boolean downPaymentPeriod;
}
@@ -412,15 +412,15 @@ final class LoansApiResourceSwagger {
@Schema(example = "[2022, 07, 01]")
public LocalDate actualDisbursementDate;
@Schema(example = "22000.000000")
public Double principal;
public BigDecimal principal;
@Schema(example = "22000.000000")
public Double netDisbursalAmount;
public BigDecimal netDisbursalAmount;
@Schema(example = "1")
public String loanChargeId;
@Schema(example = "22000.000000")
public Double chargeAmount;
public BigDecimal chargeAmount;
@Schema(example = "22000.000000")
public Double waivedChargeAmount;
public BigDecimal waivedChargeAmount;
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
@Schema(example = "de_DE")
@@ -517,77 +517,81 @@ final class LoansApiResourceSwagger {
public GetLoansLoanIdCurrency currency;
@Schema(example = "1000000.000000")
public Double principalDisbursed;
@Schema(example = "0.000000")
public Double principalPaid;
@Schema(example = "0.00")
public Double principalAdjustments;
@Schema(example = "0.000000")
public Double principalWrittenOff;
public BigDecimal totalPrincipal;
@Schema(example = "1000000.000000")
public Double principalOutstanding;
public BigDecimal principalDisbursed;
@Schema(example = "1000000.000000")
public BigDecimal totalPrincipalFromCapitalizedIncome;
@Schema(example = "0.000000")
public BigDecimal principalPaid;
@Schema(example = "0.00")
public BigDecimal principalAdjustments;
@Schema(example = "0.000000")
public BigDecimal principalWrittenOff;
@Schema(example = "1000000.000000")
public BigDecimal principalOutstanding;
@Schema(example = "833333.300000")
public Double principalOverdue;
public BigDecimal principalOverdue;
@Schema(example = "240000.000000")
public Double interestCharged;
public BigDecimal interestCharged;
@Schema(example = "0.000000")
public Double interestPaid;
public BigDecimal interestPaid;
@Schema(example = "0.000000")
public Double interestWaived;
public BigDecimal interestWaived;
@Schema(example = "0.000000")
public Double interestWrittenOff;
public BigDecimal interestWrittenOff;
@Schema(example = "240000.000000")
public Double interestOutstanding;
public BigDecimal interestOutstanding;
@Schema(example = "200000.000000")
public Double interestOverdue;
public BigDecimal interestOverdue;
@Schema(example = "0.00")
public Double feeAdjustments;
public BigDecimal feeAdjustments;
@Schema(example = "18000.000000")
public Double feeChargesCharged;
public BigDecimal feeChargesCharged;
@Schema(example = "0.000000")
public Double feeChargesDueAtDisbursementCharged;
public BigDecimal feeChargesDueAtDisbursementCharged;
@Schema(example = "0.000000")
public Double feeChargesPaid;
public BigDecimal feeChargesPaid;
@Schema(example = "0.000000")
public Double feeChargesWaived;
public BigDecimal feeChargesWaived;
@Schema(example = "0.000000")
public Double feeChargesWrittenOff;
public BigDecimal feeChargesWrittenOff;
@Schema(example = "18000.000000")
public Double feeChargesOutstanding;
public BigDecimal feeChargesOutstanding;
@Schema(example = "15000.000000")
public Double feeChargesOverdue;
public BigDecimal feeChargesOverdue;
@Schema(example = "0.00")
public Double penaltyAdjustments;
public BigDecimal penaltyAdjustments;
@Schema(example = "0.000000")
public Double penaltyChargesCharged;
public BigDecimal penaltyChargesCharged;
@Schema(example = "0.000000")
public Double penaltyChargesPaid;
public BigDecimal penaltyChargesPaid;
@Schema(example = "0.000000")
public Double penaltyChargesWaived;
public BigDecimal penaltyChargesWaived;
@Schema(example = "0.000000")
public Double penaltyChargesWrittenOff;
public BigDecimal penaltyChargesWrittenOff;
@Schema(example = "0.000000")
public Double penaltyChargesOutstanding;
public BigDecimal penaltyChargesOutstanding;
@Schema(example = "0.000000")
public Double penaltyChargesOverdue;
public BigDecimal penaltyChargesOverdue;
@Schema(example = "1258000.000000")
public Double totalExpectedRepayment;
public BigDecimal totalExpectedRepayment;
@Schema(example = "0.000000")
public Double totalRepayment;
public BigDecimal totalRepayment;
@Schema(example = "258000.000000")
public Double totalExpectedCostOfLoan;
public BigDecimal totalExpectedCostOfLoan;
@Schema(example = "0.000000")
public Double totalCostOfLoan;
public BigDecimal totalCostOfLoan;
@Schema(example = "0.000000")
public Double totalWaived;
public BigDecimal totalWaived;
@Schema(example = "0.000000")
public Double totalWrittenOff;
public BigDecimal totalWrittenOff;
@Schema(example = "1258000.000000")
public Double totalOutstanding;
public BigDecimal totalOutstanding;
@Schema(example = "1048333.30000")
public Double totalOverdue;
public BigDecimal totalOverdue;
@Schema(example = "2456.30000")
public Double totalRecovered;
public BigDecimal totalRecovered;
@Schema(example = "[2012, 5, 10]")
public LocalDate overdueSinceDate;
@Schema(example = "1")
@@ -597,9 +601,9 @@ final class LoansApiResourceSwagger {
public GetLoansLoanIdLinkedAccount linkedAccount;
public Set<GetLoansLoanIdDisbursementDetails> disbursementDetails;
@Schema(example = "1100.000000")
public Double fixedEmiAmount;
public BigDecimal fixedEmiAmount;
@Schema(example = "35000.000000")
public Double maxOutstandingLoanBalance;
public BigDecimal maxOutstandingLoanBalance;
@Schema(example = "false")
public Boolean canDisburse;
@Schema(example = "true")
@@ -607,33 +611,33 @@ final class LoansApiResourceSwagger {
@Schema(example = "false")
public Boolean isNPA;
@Schema(example = "0.000000")
public Double totalMerchantRefund;
public BigDecimal totalMerchantRefund;
@Schema(example = "0.000000")
public Double totalMerchantRefundReversed;
public BigDecimal totalMerchantRefundReversed;
@Schema(example = "0.000000")
public Double totalPayoutRefund;
public BigDecimal totalPayoutRefund;
@Schema(example = "0.000000")
public Double totalPayoutRefundReversed;
public BigDecimal totalPayoutRefundReversed;
@Schema(example = "0.000000")
public Double totalGoodwillCredit;
public BigDecimal totalGoodwillCredit;
@Schema(example = "0.000000")
public Double totalGoodwillCreditReversed;
public BigDecimal totalGoodwillCreditReversed;
@Schema(example = "0.000000")
public Double totalChargeAdjustment;
public BigDecimal totalChargeAdjustment;
@Schema(example = "0.000000")
public Double totalChargeAdjustmentReversed;
public BigDecimal totalChargeAdjustmentReversed;
@Schema(example = "0.000000")
public Double totalChargeback;
public BigDecimal totalChargeback;
@Schema(example = "0.000000")
public Double totalCreditBalanceRefund;
public BigDecimal totalCreditBalanceRefund;
@Schema(example = "0.000000")
public Double totalCreditBalanceRefundReversed;
public BigDecimal totalCreditBalanceRefundReversed;
@Schema(example = "0.000000")
public Double totalRepaymentTransaction;
public BigDecimal totalRepaymentTransaction;
@Schema(example = "0.000000")
public Double totalInterestPaymentWaiver;
public BigDecimal totalInterestPaymentWaiver;
@Schema(example = "0.000000")
public Double totalRepaymentTransactionReversed;
public BigDecimal totalRepaymentTransactionReversed;
@Schema(example = "0.000000")
public BigDecimal totalUnpaidPayableDueInterest;
@Schema(example = "0.000000")
@@ -754,7 +758,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "11")
public Long id;
@Schema(example = "100.000000")
public Double amount;
public BigDecimal amount;
@Schema(example = "9679")
public Integer installmentNumber;
@Schema(example = "1")
@@ -810,7 +814,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "CHARGEBACK")
public String relationType;
@Schema(example = "100.00")
public Double amount;
public BigDecimal amount;
@Schema(example = "Repayment Adjustment Chargeback")
public String paymentType;
@@ -831,27 +835,27 @@ final class LoansApiResourceSwagger {
@Schema(description = "Payment detail")
public GetLoansLoanIdPaymentDetailData paymentDetailData;
@Schema(example = "100.000000")
public Double amount;
public BigDecimal amount;
@Schema(example = "100.000000")
public Double netDisbursalAmount;
public BigDecimal netDisbursalAmount;
@Schema(example = "100.000000")
public Double principalPortion;
public BigDecimal principalPortion;
@Schema(example = "100.000000")
public Double interestPortion;
public BigDecimal interestPortion;
@Schema(example = "100.000000")
public Double feeChargesPortion;
public BigDecimal feeChargesPortion;
@Schema(example = "100.000000")
public Double penaltyChargesPortion;
public BigDecimal penaltyChargesPortion;
@Schema(example = "100.000000")
public Double overpaymentPortion;
public BigDecimal overpaymentPortion;
@Schema(example = "100.000000")
public Double unrecognizedIncomePortion;
public BigDecimal unrecognizedIncomePortion;
@Schema(example = "3")
public String externalId;
@Schema(example = "100.000000")
public Double fixedEmiAmount;
public BigDecimal fixedEmiAmount;
@Schema(example = "100.000000")
public Double outstandingLoanBalance;
public BigDecimal outstandingLoanBalance;
@Schema(example = "[2022, 07, 01]")
public LocalDate submittedOnDate;
public boolean manuallyReversed;
@@ -870,7 +874,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "de_DE")
public String locale;
@Schema(example = "100.000000")
public Double transactionAmount;
public BigDecimal transactionAmount;
@Schema(example = "[2022, 07, 01]")
public LocalDate transactionDate;
@Schema(example = "101")
@@ -924,19 +928,19 @@ final class LoansApiResourceSwagger {
@Schema(example = "[2022, 07, 01]")
public LocalDate dueDate;
@Schema(example = "13.560000")
public Double amount;
public BigDecimal amount;
@Schema(example = "13.560000")
public Double amountOutstanding;
public BigDecimal amountOutstanding;
@Schema(example = "13.560000")
public Double amountWaived;
public BigDecimal amountWaived;
@Schema(example = "false")
public boolean paid;
@Schema(example = "false")
public boolean waived;
@Schema(example = "13.560000")
public Double amountAccrued;
public BigDecimal amountAccrued;
@Schema(example = "13.560000")
public Double amountUnrecognized;
public BigDecimal amountUnrecognized;
}
@Schema(example = "3")
@@ -952,23 +956,23 @@ final class LoansApiResourceSwagger {
@Schema(description = "Enum option data")
public GetLoansLoanIdEnumOptionData chargeCalculationType;
@Schema(example = "3.400000")
public Double percentage;
public BigDecimal percentage;
@Schema(example = "13.560000")
public Double amountPercentageAppliedTo;
public BigDecimal amountPercentageAppliedTo;
@Schema(description = "currency")
public GetLoansLoanIdCurrency currency;
@Schema(example = "102.000000")
public Double amount;
public BigDecimal amount;
@Schema(example = "12.000000")
public Double amountPaid;
public BigDecimal amountPaid;
@Schema(example = "14.000000")
public Double amountWaived;
public BigDecimal amountWaived;
@Schema(example = "102.000000")
public Double amountWrittenOff;
public BigDecimal amountWrittenOff;
@Schema(example = "102.000000")
public Double amountOutstanding;
public BigDecimal amountOutstanding;
@Schema(example = "102.000000")
public Double amountOrPercentage;
public BigDecimal amountOrPercentage;
@Schema(example = "false")
public boolean penalty;
@Schema(description = "Enum option data")
@@ -982,15 +986,15 @@ final class LoansApiResourceSwagger {
@Schema(example = "3")
public Long loanId;
@Schema(example = "30.000000")
public Double minCap;
public BigDecimal minCap;
@Schema(example = "30.000000")
public Double maxCap;
public BigDecimal maxCap;
@Schema(description = "List of GetLoansLoanIdLoanInstallmentChargeData")
public List<GetLoansLoanIdLoanInstallmentChargeData> installmentChargeData;
@Schema(example = "30.000000")
private Double amountAccrued;
private BigDecimal amountAccrued;
@Schema(example = "30.000000")
private Double amountUnrecognized;
private BigDecimal amountUnrecognized;
@Schema(example = "3ert3453")
private String externalId;
}
@@ -1000,7 +1004,7 @@ final class LoansApiResourceSwagger {
private GetLoansLoanIdDelinquencySummary() {}
@Schema(example = "100.000000")
public Double availableDisbursementAmount;
public BigDecimal availableDisbursementAmount;
@Schema(example = "12")
public Integer pastDueDays;
@Schema(example = "[2022, 07, 01]")
@@ -1010,7 +1014,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "[2022, 07, 01]")
public LocalDate delinquentDate;
@Schema(example = "100.000000")
public Double delinquentAmount;
public BigDecimal delinquentAmount;
@Schema(example = "80.000000")
public BigDecimal delinquentPrincipal;
@Schema(example = "10.000000")
@@ -1022,12 +1026,12 @@ final class LoansApiResourceSwagger {
@Schema(example = "[2022, 07, 01]")
public LocalDate lastPaymentDate;
@Schema(example = "100.000000")
public Double lastPaymentAmount;
public BigDecimal lastPaymentAmount;
@Schema(example = "[2022, 07, 01]")
public LocalDate lastRepaymentDate;
@Schema(example = "100.000000")
public Double lastRepaymentAmount;
public BigDecimal lastRepaymentAmount;
@Schema(description = "List of GetLoansLoanIdDelinquencyPausePeriod")
public List<GetLoansLoanIdDelinquencyPausePeriod> delinquencyPausePeriods;
@@ -1094,7 +1098,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "[2024, 1, 1]")
public LocalDate termVariationApplicableFrom;
@Schema(example = "200.000000")
public Double decimalValue;
public BigDecimal decimalValue;
@Schema(example = "[2024, 1, 1]")
public LocalDate dateValue;
@Schema(example = "false")
@@ -1139,11 +1143,11 @@ final class LoansApiResourceSwagger {
@Schema(example = "1000000.00")
public BigDecimal principal;
@Schema(example = "1000.000000")
public Double approvedPrincipal;
public BigDecimal approvedPrincipal;
@Schema(example = "1001.000000")
public Double proposedPrincipal;
public BigDecimal proposedPrincipal;
@Schema(example = "200.000000")
public Double netDisbursalAmount;
public BigDecimal netDisbursalAmount;
@Schema(example = "12")
public Integer termFrequency;
public GetLoansLoanIdTermPeriodFrequencyType termPeriodFrequencyType;
@@ -1185,7 +1189,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "false")
public Boolean enableInstallmentLevelDelinquency;
@Schema(example = "250.000000")
public Double totalOverpaid;
public BigDecimal totalOverpaid;
public LocalDate lastClosedBusinessDate;
@Schema(example = "[2013, 11, 1]")
public LocalDate overpaidOnDate;
@@ -1240,7 +1244,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "1 November 2023")
public String expectedDisbursementDate;
@Schema(example = "1000.00")
public Double principal;
public BigDecimal principal;
}
private PostLoansRequest() {}
@@ -1387,7 +1391,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "0")
public Long totalPrincipalPaid;
@Schema(example = "13471.52")
public Double totalInterestCharged;
public BigDecimal totalInterestCharged;
@Schema(example = "0")
public Long totalFeeChargesCharged;
@Schema(example = "0")
@@ -1397,7 +1401,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "0")
public Long totalWrittenOff;
@Schema(example = "113471.52")
public Double totalRepaymentExpected;
public BigDecimal totalRepaymentExpected;
@Schema(example = "0")
public Long totalRepayment;
@Schema(example = "0")
@@ -1616,7 +1620,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "[2012, 4, 3]")
public LocalDate expectedDisbursementDate;
@Schema(example = "22000")
public Double principal;
public BigDecimal principal;
}
@Schema(example = "2")
@@ -0,0 +1,32 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.data;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class LoanSchedulePeriodDataWrapper {
private final LoanPrincipalRelatedDataHolder data;
private final LocalDate date;
private final boolean isDisbursement;
}
@@ -97,7 +97,8 @@ public abstract class CommonLoanSummaryDataProvider implements LoanSummaryDataPr
}
return LoanSummaryData.builder().currency(defaultSummaryData.getCurrency())
.principalDisbursed(defaultSummaryData.getPrincipalDisbursed())
.principalDisbursed(defaultSummaryData.getPrincipalDisbursed()).totalPrincipal(defaultSummaryData.getTotalPrincipal())
.totalPrincipalFromCapitalizedIncome(defaultSummaryData.getTotalPrincipalFromCapitalizedIncome())
.principalAdjustments(defaultSummaryData.getPrincipalAdjustments()).principalPaid(defaultSummaryData.getPrincipalPaid())
.principalWrittenOff(defaultSummaryData.getPrincipalWrittenOff())
.principalOutstanding(defaultSummaryData.getPrincipalOutstanding())
@@ -21,10 +21,12 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
@@ -51,9 +53,10 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
@Transactional
public void processCapitalizedIncomeAmortizationOnLoanClosure(@NotNull final Loan loan) {
final LocalDate transactionDate = getFinalCapitalizedIncomeAmortizationTransactionDate(loan);
final LoanTransaction amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate, false);
businessEventNotifierService
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction));
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
false);
amortizationTransaction.ifPresent(loanTransaction -> businessEventNotifierService
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(loanTransaction)));
}
@Override
@@ -67,13 +70,16 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
transactionDate = DateUtils.getBusinessLocalDate();
}
final LoanTransaction amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate, true);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction));
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
true);
if (amortizationTransaction.isPresent()) {
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(
new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction.get()));
}
}
private LoanTransaction createCapitalizedIncomeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
private Optional<LoanTransaction> createCapitalizedIncomeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
final boolean isChargeOff) {
ExternalId externalId = ExternalId.empty();
BigDecimal totalUnrecognizedAmount = BigDecimal.ZERO;
@@ -92,13 +98,17 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
balance.setUnrecognizedAmount(BigDecimal.ZERO);
}
if (MathUtil.isLessThanOrEqualZero(totalUnrecognizedAmount)) {
return Optional.empty();
}
final LoanTransaction amortizationTransaction = LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(),
transactionDate, totalUnrecognizedAmount, externalId);
loan.addLoanTransaction(amortizationTransaction);
loanTransactionRepository.saveAndFlush(amortizationTransaction);
return amortizationTransaction;
return Optional.of(amortizationTransaction);
}
@Override
@@ -98,6 +98,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSchedulePeriodDataWrapper;
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
@@ -118,6 +119,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
@@ -249,8 +251,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
accountData.getFeeChargesAtDisbursementCharged());
final Collection<DisbursementData> disbursementData = retrieveLoanDisbursementDetails(accountData.getId());
List<LoanTransactionRepaymentPeriodData> capitalizedIncomeData = loanCapitalizedIncomeBalanceRepository
.findRepaymentPeriodDataByLoanId(accountData.getId());
final LoanScheduleData repaymentSchedule = retrieveRepaymentSchedule(accountData.getId(), repaymentScheduleRelatedData,
disbursementData, accountData.isInterestRecalculationEnabled(),
disbursementData, capitalizedIncomeData, accountData.isInterestRecalculationEnabled(),
LoanScheduleType.fromEnumOptionData(accountData.getLoanScheduleType()));
accountData.setRepaymentSchedule(repaymentSchedule);
return accountData;
@@ -259,13 +263,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
@Override
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData,
boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
Collection<LoanTransactionRepaymentPeriodData> capitalizedIncomeData, boolean isInterestRecalculationEnabled,
LoanScheduleType loanScheduleType) {
try {
this.context.authenticatedUser();
final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled, loanScheduleType);
repaymentScheduleRelatedLoanData, disbursementData, capitalizedIncomeData, isInterestRecalculationEnabled,
loanScheduleType);
final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
return this.jdbcTemplate.query(sql, fullResultsetExtractor, loanId); // NOSONAR
@@ -749,7 +755,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
+ " l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate, dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname,"
+ " l.closedon_date as closedOnDate, cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, l.writtenoffon_date as writtenOffOnDate, "
+ " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate, "
+ " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.approved_principal as approvedPrincipal, l.net_disbursal_amount as netDisbursalAmount, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
+ " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.total_principal_derived as totalPrincipal, l.approved_principal as approvedPrincipal, l.net_disbursal_amount as netDisbursalAmount, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
+ " l.grace_on_principal_periods as graceOnPrincipalPayment, l.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing,"
+ " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, "
+ " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, "
@@ -764,7 +770,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
+ sqlGenerator.escape("name")
+ " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "
+ " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName, "
+ " l.principal_disbursed_derived as principalDisbursed, l.principal_repaid_derived as principalPaid,"
+ " l.principal_disbursed_derived as principalDisbursed, l.capitalized_income_derived as totalPrincipalFromCapitalizedIncome, l.principal_repaid_derived as principalPaid,"
+ " l.principal_adjustments_derived as principalAdjustments, l.principal_writtenoff_derived as principalWrittenOff,"
+ " l.fee_adjustments_derived as feeAdjustments, l.penalty_adjustments_derived as penaltyAdjustments,"
+ " l.principal_outstanding_derived as principalOutstanding, l.interest_charged_derived as interestCharged,"
@@ -1023,6 +1029,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
// loan summary
final BigDecimal principalDisbursed = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDisbursed");
final BigDecimal totalPrincipal = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalPrincipal");
final BigDecimal totalPrincipalFromCapitalizedIncome = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
"totalPrincipalFromCapitalizedIncome");
final BigDecimal principalAdjustments = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalAdjustments");
final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
@@ -1066,6 +1075,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
inArrears = (overdueSinceDate != null);
loanSummary = LoanSummaryData.builder().currency(currencyData).principalDisbursed(principalDisbursed)
.totalPrincipal(totalPrincipal).totalPrincipalFromCapitalizedIncome(totalPrincipalFromCapitalizedIncome)
.principalAdjustments(principalAdjustments).principalPaid(principalPaid).principalWrittenOff(principalWrittenOff)
.principalOutstanding(principalOutstanding).principalOverdue(principalOverdue).interestCharged(interestCharged)
.interestPaid(interestPaid).interestWaived(interestWaived).interestWrittenOff(interestWrittenOff)
@@ -1271,15 +1281,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final DisbursementData disbursement;
private final BigDecimal totalFeeChargesDueAtDisbursement;
private final Collection<DisbursementData> disbursementData;
private final Collection<LoanTransactionRepaymentPeriodData> capitalizedIncomeData;
private final LoanScheduleType loanScheduleType;
private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance;
private boolean excludePastUnDisbursed;
LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
Collection<DisbursementData> disbursementData, Collection<LoanTransactionRepaymentPeriodData> capitalizedIncomeData,
boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.capitalizedIncomeData = capitalizedIncomeData;
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
this.lastDueDate = this.disbursement.disbursementDate();
this.outstandingLoanPrincipalBalance = this.disbursement.getPrincipal();
@@ -1359,10 +1372,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate");
final boolean complete = rs.getBoolean("complete");
BigDecimal disbursedAmount = BigDecimal.ZERO;
disbursedAmount = processDisbursementData(loanScheduleType, disbursementData, fromDate, dueDate, disbursementPeriodIds,
disbursementChargeAmount, waivedChargeAmount, periods);
List<LoanSchedulePeriodDataWrapper> combinedDataList = new ArrayList<>();
combinedDataList.addAll(
collectEligibleDisbursementData(loanScheduleType, disbursementData, fromDate, dueDate, disbursementPeriodIds));
combinedDataList.addAll(collectEligibleCapitalizedIncomeData(fromDate, dueDate, disbursementPeriodIds));
combinedDataList.sort(this::sortPeriodDataHolders);
fillLoanSchedulePeriodData(periods, combinedDataList, disbursementChargeAmount, waivedChargeAmount);
BigDecimal disbursedAmount = calculateDisbursedAmount(combinedDataList);
// Add the Charge back or Credits to the initial amount to avoid negative balance
final BigDecimal principalCredits = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalCredits");
@@ -1473,26 +1491,82 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
totalPaidLate.getAmount(), totalOutstanding.getAmount(), totalCredits.getAmount());
}
private BigDecimal processDisbursementData(LoanScheduleType loanScheduleType, Collection<DisbursementData> disbursementData,
LocalDate fromDate, LocalDate dueDate, Set<Long> disbursementPeriodIds, BigDecimal disbursementChargeAmount,
BigDecimal waivedChargeAmount, List<LoanSchedulePeriodData> periods) {
BigDecimal disbursedAmount = BigDecimal.ZERO;
private List<LoanSchedulePeriodDataWrapper> collectEligibleDisbursementData(LoanScheduleType loanScheduleType,
Collection<DisbursementData> disbursementData, LocalDate fromDate, LocalDate dueDate, Set<Long> disbursementPeriodIds) {
List<LoanSchedulePeriodDataWrapper> disbursementDataList = new ArrayList<>();
// Collect eligible disbursement data
for (final DisbursementData data : disbursementData) {
boolean isDueForDisbursement = data.isDueForDisbursement(loanScheduleType, fromDate, dueDate);
if (((fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate))
boolean isEligible = ((fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate))
|| (fromDate.equals(dueDate) && data.disbursementDate().equals(fromDate))
|| canAddDisbursementData(data, isDueForDisbursement, excludePastUnDisbursed))
&& !disbursementPeriodIds.contains(data.getId())) {
disbursedAmount = disbursedAmount.add(data.getPrincipal());
LoanSchedulePeriodData periodData = createLoanSchedulePeriodData(data, disbursementChargeAmount, waivedChargeAmount);
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(periodData.getPrincipalDisbursed());
&& !disbursementPeriodIds.contains(data.getId());
if (isEligible) {
disbursementDataList.add(new LoanSchedulePeriodDataWrapper(data, data.disbursementDate(), true));
disbursementPeriodIds.add(data.getId());
}
}
return disbursementDataList;
}
private List<LoanSchedulePeriodDataWrapper> collectEligibleCapitalizedIncomeData(LocalDate fromDate, LocalDate dueDate,
Set<Long> disbursementPeriodIds) {
List<LoanSchedulePeriodDataWrapper> capitalizedIncomeDataList = new ArrayList<>();
// Collect eligible capitalized income data
for (LoanTransactionRepaymentPeriodData data : capitalizedIncomeData) {
boolean isEligible = canAddCapitalizedIncomeData(data, fromDate, dueDate)
&& !disbursementPeriodIds.contains(data.getTransactionId());
if (isEligible) {
capitalizedIncomeDataList.add(new LoanSchedulePeriodDataWrapper(data, data.getDate(), false));
disbursementPeriodIds.add(data.getTransactionId());
}
}
return capitalizedIncomeDataList;
}
private void fillLoanSchedulePeriodData(List<LoanSchedulePeriodData> periods, List<LoanSchedulePeriodDataWrapper> combinedDataList,
BigDecimal disbursementChargeAmount, BigDecimal waivedChargeAmount) {
// Process all collected data in chronological order
for (LoanSchedulePeriodDataWrapper dataItem : combinedDataList) {
LoanSchedulePeriodData periodData;
if (dataItem.isDisbursement()) {
// Process disbursement data
DisbursementData data = (DisbursementData) dataItem.getData();
periodData = createLoanSchedulePeriodData(data, disbursementChargeAmount, waivedChargeAmount);
} else {
// Process capitalized income data
LoanTransactionRepaymentPeriodData data = (LoanTransactionRepaymentPeriodData) dataItem.getData();
periodData = createLoanSchedulePeriodData(data);
}
// Common processing for both data types
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(periodData.getPrincipalDisbursed());
}
}
private BigDecimal calculateDisbursedAmount(List<LoanSchedulePeriodDataWrapper> combinedDataList) {
BigDecimal disbursedAmount = BigDecimal.ZERO;
for (LoanSchedulePeriodDataWrapper dataItem : combinedDataList) {
if (dataItem.isDisbursement()) {
DisbursementData data = (DisbursementData) dataItem.getData();
disbursedAmount = disbursedAmount.add(data.getPrincipal());
}
}
return disbursedAmount;
}
private int sortPeriodDataHolders(LoanSchedulePeriodDataWrapper item1, LoanSchedulePeriodDataWrapper item2) {
int dateComparison = item1.getDate().compareTo(item2.getDate());
if (dateComparison == 0 && item1.isDisbursement() != item2.isDisbursement()) {
// If dates are equal, prioritize disbursement data
return item1.isDisbursement() ? -1 : 1;
}
return dateComparison;
}
private LoanSchedulePeriodData createLoanSchedulePeriodData(final DisbursementData data, BigDecimal disbursementChargeAmount,
BigDecimal waivedChargeAmount) {
BigDecimal chargeAmount = data.getChargeAmount() == null ? disbursementChargeAmount
@@ -1501,11 +1575,20 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
data.isDisbursed());
}
private LoanSchedulePeriodData createLoanSchedulePeriodData(final LoanTransactionRepaymentPeriodData data) {
BigDecimal feeCharges = Objects.isNull(data.getFeeChargesPortion()) ? BigDecimal.ZERO : data.getFeeChargesPortion();
return LoanSchedulePeriodData.disbursementOnlyPeriod(data.getDate(), data.getAmount(), feeCharges, !data.isReversed());
}
private boolean canAddDisbursementData(DisbursementData data, boolean isDueForDisbursement, boolean excludePastUnDisbursed) {
return (!excludePastUnDisbursed || data.isDisbursed() || !DateUtils.isBeforeBusinessDate(data.disbursementDate()))
&& isDueForDisbursement;
}
private boolean canAddCapitalizedIncomeData(LoanTransactionRepaymentPeriodData data, LocalDate fromDate, LocalDate dueDate) {
return !data.isReversed() && DateUtils.isDateInRangeFromInclusiveToExclusive(fromDate, dueDate, data.getDate());
}
}
private static final class LoanTransactionsMapper implements RowMapper<LoanTransactionData> {
@@ -2168,7 +2251,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount();
final Boolean isReversed = false;
final Boolean isManuallyReversed = false;
final Money outStandingAmount = loanRepaymentScheduleInstallment.getTotalOutstanding(currency);
@@ -2178,7 +2261,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(),
loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion,
paymentTypeOptions, ExternalId.empty(), null, null, outstandingLoanBalance, isReversed, loanId, loan.getExternalId());
paymentTypeOptions, ExternalId.empty(), null, null, outstandingLoanBalance, isManuallyReversed, loanId,
loan.getExternalId());
}
private static final class CurrencyMapper implements RowMapper<CurrencyData> {
@@ -64,6 +64,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
@@ -93,6 +94,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
private final LoanChargeValidator loanChargeValidator;
private final LoanJournalEntryPoster journalEntryPoster;
private final LoanBalanceService loanBalanceService;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
@Override
public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter,
@@ -245,8 +247,9 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
transactionForAdjustment.getTransactionDate());
if (!transactionForAdjustment.isAccrualRelated() && transactionForAdjustment.isNotRepaymentLikeType()
&& transactionForAdjustment.isNotWaiver() && transactionForAdjustment.isNotCreditBalanceRefund()) {
final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver, accrual or credit balance refund can be adjusted.";
&& transactionForAdjustment.isNotWaiver() && transactionForAdjustment.isNotCreditBalanceRefund()
&& !transactionForAdjustment.isCapitalizedIncome()) {
final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver, capitalized income, accrual or credit balance refund can be adjusted.";
throw new InvalidLoanTransactionTypeException("transaction",
"adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions", errorMessage);
}
@@ -285,6 +288,10 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, transactionForAdjustment,
scheduleGeneratorDTO);
}
if (transactionForAdjustment.getTypeOf().equals(LoanTransactionType.CAPITALIZED_INCOME)) {
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
}
private Loan saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
@@ -204,9 +204,13 @@ public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatf
}
@Override
public void createLoanNote(final Long loanId, final String note) {
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
final Note newNote = Note.loanNote(loan, note);
public void createLoanTransactionNote(final Long loanTransactionId, final String note) {
final LoanTransaction loanTransaction = this.loanTransactionRepository.findById(loanTransactionId)
.orElseThrow(() -> new LoanTransactionNotFoundException(loanTransactionId));
final Loan loan = loanTransaction.getLoan();
final Note newNote = Note.loanTransactionNote(loan, loanTransaction, note);
this.noteRepository.save(newNote);
}
@@ -196,4 +196,5 @@
<include file="parts/0175_add_fk_acc_product_mapping.xml" relativeToChangelogFile="true" />
<include file="parts/0176_add_capitalized_income_amortization_transaction.xml" relativeToChangelogFile="true" />
<include file="parts/0177_acc_journal_entry_index.xml" relativeToChangelogFile="true" />
<include file="parts/0178_add_principal_from_capitalized_income_to_loan_summary.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_loan">
<column defaultValueNumeric="0.000000" name="capitalized_income_derived" type="DECIMAL(19, 6)">
<constraints nullable="false"/>
</column>
<column defaultValueNumeric="0.000000" name="total_principal_derived" type="DECIMAL(19, 6)">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>