adamsaghy commented on code in PR #3544:
URL: https://github.com/apache/fineract/pull/3544#discussion_r1375231309


##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java:
##########
@@ -176,6 +178,153 @@ private void handleRepayment(LoanTransaction 
loanTransaction, MonetaryCurrency c
             loanTransaction.resetDerivedComponents();
         }
         Money transactionAmountUnprocessed = 
loanTransaction.getAmount(currency);
+        handleOverAmount(loanTransaction, currency, installments, 
transactionAmountUnprocessed, charges);
+    }
+
+    private LoanTransactionToRepaymentScheduleMapping getTransactionMapping(
+            List<LoanTransactionToRepaymentScheduleMapping> 
transactionMappings, LoanTransaction loanTransaction,
+            LoanRepaymentScheduleInstallment currentInstallment, 
MonetaryCurrency currency) {
+        Money zero = Money.zero(currency);
+        LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping = transactionMappings.stream()
+                .filter(e -> loanTransaction.equals(e.getLoanTransaction()))
+                .filter(e -> 
currentInstallment.equals(e.getLoanRepaymentScheduleInstallment())).findFirst().orElse(null);
+        if (loanTransactionToRepaymentScheduleMapping == null) {
+            loanTransactionToRepaymentScheduleMapping = 
LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
+                    currentInstallment, zero, zero, zero, zero);
+            transactionMappings.add(loanTransactionToRepaymentScheduleMapping);
+        }
+        return loanTransactionToRepaymentScheduleMapping;
+    }
+
+    private Money payAllocation(PaymentAllocationType paymentAllocationType, 
LoanRepaymentScheduleInstallment currentInstallment,
+            LoanTransaction loanTransaction, Money 
transactionAmountUnprocessed,
+            LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping, Balances balances) {
+        LocalDate transactionDate = loanTransaction.getTransactionDate();
+        Money zero = transactionAmountUnprocessed.zero();
+        return switch (paymentAllocationType.getAllocationType()) {
+            case PENALTY -> {
+                Money subPenaltyPortion = 
currentInstallment.payPenaltyChargesComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(subPenaltyPortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, 
zero, subPenaltyPortion);
+                yield subPenaltyPortion;
+            }
+            case FEE -> {
+                Money subFeePortion = 
currentInstallment.payFeeChargesComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(subFeePortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, 
subFeePortion, zero);
+                yield subFeePortion;
+            }
+            case INTEREST -> {
+                Money subInterestPortion = 
currentInstallment.payInterestComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(subInterestPortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, 
subInterestPortion, zero, zero);
+                yield subInterestPortion;
+            }
+            case PRINCIPAL -> {
+                Money subPrincipalPortion = 
currentInstallment.payPrincipalComponent(transactionDate, 
transactionAmountUnprocessed);
+                
balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(subPrincipalPortion));
+                
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, 
subPrincipalPortion, zero, zero, zero);
+                yield subPrincipalPortion;
+            }
+        };
+    }
+
+    private void 
addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping,
+            Money principalPortion, Money interestPortion, Money feePortion, 
Money penaltyPortion) {
+        BigDecimal aggregatedPenalty = ObjectUtils
+                
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPenaltyChargesPortion(),
 BigDecimal.ZERO)
+                .add(penaltyPortion.getAmount());
+        BigDecimal aggregatedFee = ObjectUtils
+                
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getFeeChargesPortion(),
 BigDecimal.ZERO)
+                .add(feePortion.getAmount());
+        BigDecimal aggregatedInterest = ObjectUtils
+                
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getInterestPortion(), 
BigDecimal.ZERO)
+                .add(interestPortion.getAmount());
+        BigDecimal aggregatedPrincipal = ObjectUtils
+                
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPrincipalPortion(), 
BigDecimal.ZERO)
+                .add(principalPortion.getAmount());
+        
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal, 
aggregatedInterest, aggregatedFee, aggregatedPenalty);
+    }
+
+    private void handleOverpayment(Money overpaymentPortion, LoanTransaction 
loanTransaction) {
+        if (overpaymentPortion.isGreaterThanZero()) {
+            onLoanOverpayment(loanTransaction, overpaymentPortion);
+            loanTransaction.updateOverPayments(overpaymentPortion);
+        }
+    }
+
+    private void handleChargeOff(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments) {
+        loanTransaction.resetDerivedComponents();
+        // determine how much is outstanding total and breakdown for 
principal, interest and charges
+        Money principalPortion = Money.zero(currency);
+        Money interestPortion = Money.zero(currency);
+        Money feeChargesPortion = Money.zero(currency);
+        Money penaltychargesPortion = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment currentInstallment : 
installments) {
+            if (currentInstallment.isNotFullyPaidOff()) {
+                principalPortion = 
principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
+                interestPortion = 
interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
+                feeChargesPortion = 
feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
+                penaltychargesPortion = 
penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
+            }
+        }
+
+        loanTransaction.updateComponentsAndTotal(principalPortion, 
interestPortion, feeChargesPortion, penaltychargesPortion);
+    }
+
+    private void handleChargePayment(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges) {
+        Money zero = Money.zero(currency);
+        Money feeChargesPortion = zero;
+        Money penaltyChargesPortion = zero;
+        List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = 
new ArrayList<>();
+        LoanChargePaidBy loanChargePaidBy = 
loanTransaction.getLoanChargesPaid().stream().findFirst().get();
+        LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
+        Money amountToBePaid = Money.of(currency, loanTransaction.getAmount());
+        if 
(loanCharge.getAmountOutstanding(currency).isLessThan(amountToBePaid)) {
+            amountToBePaid = loanCharge.getAmountOutstanding(currency);
+        }
+
+        LocalDate startDate = loanTransaction.getLoan().getDisbursementDate();
+
+        Money unprocessed = loanTransaction.getAmount(currency);
+        for (final LoanRepaymentScheduleInstallment installment : 
installments) {
+            boolean isDue = installment.isFirstPeriod()
+                    ? 
loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, 
installment.getDueDate())
+                    : 
loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, 
installment.getDueDate());
+            if (isDue) {
+                Integer installmentNumber = installment.getInstallmentNumber();
+                Money paidAmount = 
loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero);
+
+                LoanTransactionToRepaymentScheduleMapping 
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
+                        transactionMappings, loanTransaction, installment, 
currency);
+
+                if (loanTransaction.isPenaltyPayment()) {
+                    penaltyChargesPortion = 
installment.payPenaltyChargesComponent(loanTransaction.getTransactionDate(), 
paidAmount);
+                    loanTransaction.setLoanChargesPaid(Collections
+                            .singleton(new LoanChargePaidBy(loanTransaction, 
loanCharge, paidAmount.getAmount(), installmentNumber)));
+                    
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, 
zero, penaltyChargesPortion);
+                } else {
+                    feeChargesPortion = 
installment.payFeeChargesComponent(loanTransaction.getTransactionDate(), 
paidAmount);
+                    loanTransaction.setLoanChargesPaid(Collections
+                            .singleton(new LoanChargePaidBy(loanTransaction, 
loanCharge, paidAmount.getAmount(), installmentNumber)));
+                    
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, 
feeChargesPortion, zero);
+                }
+
+                loanTransaction.updateComponents(zero, zero, 
feeChargesPortion, penaltyChargesPortion);
+                unprocessed = 
loanTransaction.getAmount(currency).minus(paidAmount);
+                
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+            }
+        }
+
+        if (unprocessed.isGreaterThanZero()) {
+            handleOverAmount(loanTransaction, currency, installments, 
unprocessed, charges);

Review Comment:
   I think it would make sense to check what charge paid by relation is already 
on the loan transaction and the total amount of those are lower then the fee / 
penalty portion on the transaction. If so, we need to create the new charge 
paid by relations.
   
   Also it would make sense to consider moving this whole logic to the point 
where we are distributing the amount of the loan transaction and if a fee / 
penalty got partially or totally paid, we create these relations immediately!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to