From a11c56741322b4756c96ecf1afbcdfd8bedff837 Mon Sep 17 00:00:00 2001 From: mariiaKraievska Date: Thu, 17 Apr 2025 11:57:02 +0300 Subject: [PATCH] FINERACT-2210: Fix journal entries creation issue when amount is null --- .../data/loanproduct/DefaultLoanProduct.java | 1 + .../LoanProductGlobalInitializerStep.java | 34 +++ .../fineract/test/support/TestContextKey.java | 1 + .../resources/features/LoanChargeOff.feature | 222 +++++++++++++++ ...edPaymentScheduleTransactionProcessor.java | 51 ++-- .../service/AccountingProcessorHelper.java | 19 +- ...ccrualBasedAccountingProcessorForLoan.java | 256 ++++++++++-------- 7 files changed, 442 insertions(+), 142 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java index bcdc24d8e..a3fad3639 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java @@ -106,6 +106,7 @@ public enum DefaultLoanProduct implements LoanProduct { LP2_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR_LAST_INSTALLMENT_STRATEGY, // LP2_ADV_PYMNT_INTEREST_DAILY_INT_RECALCULATION_ZERO_INT_CHARGE_OFF_INT_RECOGNITION_FROM_DISB_DATE, // LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON, // + LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC, // LP2_ADV_DP_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, // LP2_ADV_DP_IR_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_DISBURSEMENT_CHARGES, // diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java index fc47f6b47..ead1740d8 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java @@ -2227,6 +2227,40 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ TestContext.INSTANCE.set( TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CASH_ACCOUNTING_DISBURSEMENT_CHARGES, responseLoanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyCashAccountingDisbursementCharge); + + // LP2 + zero-interest chargeOff behaviour + progressive loan schedule + horizontal + delinquent charge-off + // reason to GL account mapping + interest recalculation + // (LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC) + final String name90 = DefaultLoanProduct.LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC.getName(); + + final PostLoanProductsRequest loanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReasonIntRecalc = loanProductsRequestFactory + .defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappings()// + .name(name90)// + .enableDownPayment(false)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .enableAutoRepaymentForDownPayment(null)// + .disbursedAmountPercentageForDownPayment(null)// + .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())// + .loanScheduleType("PROGRESSIVE") // + .loanScheduleProcessingType("HORIZONTAL")// + .interestRateFrequencyType(3)// + .maxInterestRatePerPeriod(10.0)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))// + .chargeOffBehaviour("ZERO_INTEREST");// + final Response responseLoanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReasonIntRecalc = loanProductsApi + .createLoanProduct(loanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReasonIntRecalc).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC, + responseLoanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReasonIntRecalc); } public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index eecb1ab1a..71af37693 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -117,6 +117,7 @@ public abstract class TestContextKey { public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffBehaviour"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOff"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffDelinquentReason"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffDelinquentReasonInterestRecalculation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_DP_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE = "loanProductCreateResponseLP2ProgressiveLoanScheduleDPCustomPaymentAllocation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_DP_IR_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE = "loanProductCreateResponseLP2ProgressiveLoanScheduleDPIRCustomPaymentAllocation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_ALLOW_PARTIAL_PERIOD = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyAllowPartialPeriod"; diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature index 4eb31752c..ea7a7173d 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature @@ -8123,3 +8123,225 @@ Feature: Charge-off And Loan Charges tab has the following data: | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | | Snooze fee | false | Specified due date | 23 January 2024 | Flat | 5.0 | 0.0 | 5.0 | 0.0 | + + @TestRailId:C3611 + Scenario: As a user I want to verify that backdated transactions are allowed after charge-off and backdated full repayment, interestRecalculation = false + When Admin sets the business date to "11 March 2025" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON | 11 March 2025 | 500 | 7 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "11 March 2025" with "500" amount and expected disbursement date on "11 March 2025" + And Admin successfully disburse the loan on "11 March 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 2 | 30 | 11 May 2025 | | 335.37 | 82.6 | 2.4 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 3 | 31 | 11 June 2025 | | 252.36 | 83.01 | 1.99 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 4 | 30 | 11 July 2025 | | 168.81 | 83.55 | 1.45 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 5 | 31 | 11 August 2025 | | 84.81 | 84.0 | 1.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 6 | 31 | 11 September 2025| | 0.0 | 84.81 | 0.5 | 0.0 | 0.0 | 85.31 | 0.0 | 0.0 | 0.0 | 0.0 | 85.31 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 10.31 | 0.0 | 0 | 510.31 | 0.0 | 0 | 0 | 0.0 | 510.31 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + Then Admin can successfully set Fraud flag to the loan + When Admin runs inline COB job for Loan + When Admin sets the business date to "14 April 2025" + And Admin does charge-off the loan with reason "DELINQUENT" on "14 April 2025" + Then Loan marked as charged-off on "14 April 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 2 | 30 | 11 May 2025 | | 333.21 | 84.76 | 0.24 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 3 | 31 | 11 June 2025 | | 248.21 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 4 | 30 | 11 July 2025 | | 163.21 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 5 | 31 | 11 August 2025 | | 78.21 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 6 | 31 | 11 September 2025 | | 0.0 | 78.21 | 0.0 | 0.0 | 0.0 | 78.21 | 0.0 | 0.0 | 0.0 | 0.0 | 78.21 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 3.21 | 0.0 | 0 | 503.21 | 0 | 0 | 0 | 0.0 | 503.21 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 14 April 2025 | Accrual | 3.21 | 0.0 | 3.21 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 503.21 | 500.0 | 3.21 | 0.0 | 0.0 | 0.0 | false | false | + When Admin runs inline COB job for Loan + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "13 April 2025" with 500 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | 13 April 2025 | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 85.0 | 0.0 | 85.0 | 0.0 | 0.0 | + | 2 | 30 | 11 May 2025 | 13 April 2025 | 335.37 | 82.6 | 2.4 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 11 June 2025 | 13 April 2025 | 252.36 | 83.01 | 1.99 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 11 July 2025 | 13 April 2025 | 168.81 | 83.55 | 1.45 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 11 August 2025 | 13 April 2025 | 84.81 | 84.0 | 1.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 6 | 31 | 11 September 2025 | | 0.0 | 84.81 | 0.0 | 0.0 | 0.0 | 84.81 | 75.0 | 75.0 | 0.0 | 0.0 | 9.81 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 9.81 | 0.0 | 0 | 509.81 | 500.0 | 415.0 | 85.0 | 0.0 | 9.81 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 13 April 2025 | Repayment | 500.0 | 490.19 | 9.81 | 0.0 | 0.0 | 9.81 | false | false | + | 14 April 2025 | Accrual | 3.21 | 0.0 | 3.21 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual Adjustment | 0.24 | 0.0 | 0.24 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 9.81 | 9.81 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "13 April 2025" with 500 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | 13 April 2025 | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 85.0 | 0.0 | 85.0 | 0.0 | 0.0 | + | 2 | 30 | 11 May 2025 | 13 April 2025 | 335.37 | 82.6 | 2.4 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 11 June 2025 | 13 April 2025 | 252.36 | 83.01 | 1.99 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 11 July 2025 | 13 April 2025 | 168.81 | 83.55 | 1.45 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 11 August 2025 | 13 April 2025 | 84.81 | 84.0 | 1.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 6 | 31 | 11 September 2025 | 13 April 2025 | 0.0 | 84.81 | 0.5 | 0.0 | 0.0 | 85.31 | 85.31 | 85.31 | 0.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 10.31 | 0.0 | 0 | 510.31 | 510.31 | 425.31 | 85.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 13 April 2025 | Repayment | 500.0 | 490.19 | 9.81 | 0.0 | 0.0 | 9.81 | false | false | + | 13 April 2025 | Merchant Issued Refund | 500.0 | 9.81 | 0.5 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual | 3.21 | 0.0 | 3.21 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual Adjustment | 0.24 | 0.0 | 0.24 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "13 April 2025" with 500 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | 13 April 2025 | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 85.0 | 0.0 | 85.0 | 0.0 | 0.0 | + | 2 | 30 | 11 May 2025 | 13 April 2025 | 335.37 | 82.6 | 2.4 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 11 June 2025 | 13 April 2025 | 252.36 | 83.01 | 1.99 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 11 July 2025 | 13 April 2025 | 168.81 | 83.55 | 1.45 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 11 August 2025 | 13 April 2025 | 84.81 | 84.0 | 1.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 6 | 31 | 11 September 2025 | 13 April 2025 | 0.0 | 84.81 | 0.5 | 0.0 | 0.0 | 85.31 | 85.31 | 85.31 | 0.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 10.31 | 0.0 | 0 | 510.31 | 510.31 | 425.31 | 85.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 13 April 2025 | Repayment | 500.0 | 490.19 | 9.81 | 0.0 | 0.0 | 9.81 | false | false | + | 13 April 2025 | Merchant Issued Refund | 500.0 | 9.81 | 0.5 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2025 | Goodwill Credit | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual | 3.21 | 0.0 | 3.21 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual Adjustment | 0.24 | 0.0 | 0.24 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + + @TestRailId:C3612 + Scenario: As a user I want to verify that backdated transactions are allowed after charge-off and backdated full repayment, interestRecalculation = true + When Admin sets the business date to "11 March 2025" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC | 11 March 2025 | 500 | 7 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "11 March 2025" with "500" amount and expected disbursement date on "11 March 2025" + And Admin successfully disburse the loan on "11 March 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 2 | 30 | 11 May 2025 | | 335.37 | 82.6 | 2.4 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 3 | 31 | 11 June 2025 | | 252.36 | 83.01 | 1.99 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 4 | 30 | 11 July 2025 | | 168.81 | 83.55 | 1.45 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 5 | 31 | 11 August 2025 | | 84.81 | 84.0 | 1.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 6 | 31 | 11 September 2025| | 0.0 | 84.81 | 0.5 | 0.0 | 0.0 | 85.31 | 0.0 | 0.0 | 0.0 | 0.0 | 85.31 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 10.31 | 0.0 | 0 | 510.31 | 0.0 | 0 | 0 | 0.0 | 510.31 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + Then Admin can successfully set Fraud flag to the loan + When Admin runs inline COB job for Loan + When Admin sets the business date to "14 April 2025" + And Admin does charge-off the loan with reason "DELINQUENT" on "14 April 2025" + Then Loan marked as charged-off on "14 April 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 2 | 30 | 11 May 2025 | | 333.26 | 84.71 | 0.29 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 3 | 31 | 11 June 2025 | | 248.26 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 4 | 30 | 11 July 2025 | | 163.26 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 5 | 31 | 11 August 2025 | | 78.26 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 0.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 6 | 31 | 11 September 2025 | | 0.0 | 78.26 | 0.0 | 0.0 | 0.0 | 78.26 | 0.0 | 0.0 | 0.0 | 0.0 | 78.26 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 3.26 | 0.0 | 0 | 503.26 | 0.0 | 0 | 0 | 0.0 | 503.26 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 14 April 2025 | Accrual | 3.26 | 0.0 | 3.26 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 503.26 | 500.0 | 3.26 | 0.0 | 0.0 | 0.0 | false | false | + When Admin runs inline COB job for Loan + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "13 April 2025" with 500 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | 13 April 2025 | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 85.0 | 0.0 | 85.0 | 0.0 | 0.0 | + | 2 | 30 | 11 May 2025 | 13 April 2025 | 333.16 | 84.81 | 0.19 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 11 June 2025 | 13 April 2025 | 248.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 11 July 2025 | 13 April 2025 | 163.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 11 August 2025 | 13 April 2025 | 78.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 6 | 31 | 11 September 2025 | | 0.0 | 78.16 | 0.0 | 0.0 | 0.0 | 78.16 | 75.0 | 75.0 | 0.0 | 0.0 | 3.16 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 3.16 | 0.0 | 0 | 503.16 | 500.0 | 415.0 | 85.0 | 0.0 | 3.16 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 13 April 2025 | Repayment | 500.0 | 496.84 | 3.16 | 0.0 | 0.0 | 3.16 | false | false | + | 14 April 2025 | Accrual | 3.26 | 0.0 | 3.26 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual Adjustment | 0.1 | 0.0 | 0.1 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 3.16 | 3.16 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "13 April 2025" with 500 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | 13 April 2025 | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 85.0 | 0.0 | 85.0 | 0.0 | 0.0 | + | 2 | 30 | 11 May 2025 | 13 April 2025 | 333.16 | 84.81 | 0.19 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 11 June 2025 | 13 April 2025 | 248.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 11 July 2025 | 13 April 2025 | 163.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 11 August 2025 | 13 April 2025 | 78.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 6 | 31 | 11 September 2025 | 13 April 2025 | 0.0 | 78.16 | 0.0 | 0.0 | 0.0 | 78.16 | 78.16 | 78.16 | 0.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 3.16 | 0.0 | 0 | 503.16 | 503.16 | 418.16 | 85.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 13 April 2025 | Repayment | 500.0 | 496.84 | 3.16 | 0.0 | 0.0 | 3.16 | false | false | + | 13 April 2025 | Merchant Issued Refund | 500.0 | 3.16 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual | 3.26 | 0.0 | 3.26 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual Adjustment | 0.1 | 0.0 | 0.1 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "13 April 2025" with 500 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | | | 11 March 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | | + | 1 | 31 | 11 April 2025 | 13 April 2025 | 417.97 | 82.03 | 2.97 | 0.0 | 0.0 | 85.0 | 85.0 | 0.0 | 85.0 | 0.0 | 0.0 | + | 2 | 30 | 11 May 2025 | 13 April 2025 | 333.16 | 84.81 | 0.19 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 11 June 2025 | 13 April 2025 | 248.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 11 July 2025 | 13 April 2025 | 163.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 11 August 2025 | 13 April 2025 | 78.16 | 85.0 | 0.0 | 0.0 | 0.0 | 85.0 | 85.0 | 85.0 | 0.0 | 0.0 | 0.0 | + | 6 | 31 | 11 September 2025 | 13 April 2025 | 0.0 | 78.16 | 0.0 | 0.0 | 0.0 | 78.16 | 78.16 | 78.16 | 0.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Waived | Outstanding | + | 500.0 | 3.16 | 0.0 | 0 | 503.16 | 503.16 | 418.16 | 85.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 11 March 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 13 April 2025 | Repayment | 500.0 | 496.84 | 3.16 | 0.0 | 0.0 | 3.16 | false | false | + | 13 April 2025 | Merchant Issued Refund | 500.0 | 3.16 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2025 | Goodwill Credit | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual | 3.26 | 0.0 | 3.26 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Accrual Adjustment | 0.1 | 0.0 | 0.1 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2025 | Charge-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index e3a5ae453..b83ae1fc8 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -1400,20 +1400,25 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate())) { if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx && loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) { + final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount(); final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), currentInstallment.getDueDate(), transactionDate, true).getAmount(); - currentInstallment.updateInterestCharged(newInterest); + if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) { + currentInstallment.updateInterestCharged(newInterest); + } } else { final BigDecimal totalInterest = currentInstallment.getInterestOutstanding(transactionCtx.getCurrency()).getAmount(); - final long totalDaysInPeriod = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), currentInstallment.getDueDate()); - final long daysTillChargeOff = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), transactionDate); + if (totalInterest.compareTo(BigDecimal.ZERO) > 0) { + final long totalDaysInPeriod = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), + currentInstallment.getDueDate()); + final long daysTillChargeOff = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), transactionDate); - final MathContext mc = MoneyHelper.getMathContext(); - final Money interestTillChargeOff = Money.of(transactionCtx.getCurrency(), - totalInterest.divide(BigDecimal.valueOf(totalDaysInPeriod), mc).multiply(BigDecimal.valueOf(daysTillChargeOff), mc), - mc); + final MathContext mc = MoneyHelper.getMathContext(); + final Money interestTillChargeOff = Money.of(transactionCtx.getCurrency(), totalInterest + .divide(BigDecimal.valueOf(totalDaysInPeriod), mc).multiply(BigDecimal.valueOf(daysTillChargeOff), mc), mc); - currentInstallment.updateInterestCharged(interestTillChargeOff.getAmount()); + currentInstallment.updateInterestCharged(interestTillChargeOff.getAmount()); + } } currentInstallment.updateDueDate(transactionDate); @@ -1478,25 +1483,27 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep private void handleZeroInterestChargeOff(final LoanTransaction loanTransaction, final TransactionCtx transactionCtx) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final List installments = transactionCtx.getInstallments(); + final MonetaryCurrency currency = loanTransaction.getLoan().getCurrency(); if (!installments.isEmpty()) { if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx && loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) { installments.stream().filter(installment -> !installment.getFromDate().isAfter(transactionDate) && installment.getDueDate().isAfter(transactionDate)).forEach(installment -> { + final BigDecimal interestOutstanding = installment.getInterestOutstanding(currency).getAmount(); final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), installment.getDueDate(), transactionDate, true).getAmount(); - final BigDecimal interestRemoved = installment.getInterestCharged().subtract(newInterest); - installment.updatePrincipal(MathUtil.nullToZero(installment.getPrincipal()).add(interestRemoved)); - installment.updateInterestCharged(newInterest); + if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) { + final BigDecimal interestRemoved = installment.getInterestCharged().subtract(newInterest); + installment.updatePrincipal(MathUtil.nullToZero(installment.getPrincipal()).add(interestRemoved)); + installment.updateInterestCharged(newInterest); + } }); progressiveTransactionCtx.setChargedOff(true); } else { calculatePartialPeriodInterest(transactionCtx, transactionDate); } - final MonetaryCurrency currency = loanTransaction.getLoan().getCurrency(); - installments.stream() .filter(installment -> installment.getFromDate().isAfter(transactionDate) && !installment.isObligationsMet()) .forEach(installment -> { @@ -1538,16 +1545,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep .filter(installment -> !installment.getFromDate().isAfter(chargeOffDate) && installment.getDueDate().isAfter(chargeOffDate)) .forEach(installment -> { final BigDecimal totalInterest = installment.getInterestOutstanding(transactionCtx.getCurrency()).getAmount(); - final long totalDaysInPeriod = ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate()); - final long daysTillChargeOff = ChronoUnit.DAYS.between(installment.getFromDate(), chargeOffDate); + if (totalInterest.compareTo(BigDecimal.ZERO) > 0) { + final long totalDaysInPeriod = ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate()); + final long daysTillChargeOff = ChronoUnit.DAYS.between(installment.getFromDate(), chargeOffDate); - final MathContext mc = MoneyHelper.getMathContext(); - final Money interestTillChargeOff = Money.of(transactionCtx.getCurrency(), totalInterest - .divide(BigDecimal.valueOf(totalDaysInPeriod), mc).multiply(BigDecimal.valueOf(daysTillChargeOff), mc), mc); + final MathContext mc = MoneyHelper.getMathContext(); + final Money interestTillChargeOff = Money.of(transactionCtx.getCurrency(), totalInterest + .divide(BigDecimal.valueOf(totalDaysInPeriod), mc).multiply(BigDecimal.valueOf(daysTillChargeOff), mc), mc); - final BigDecimal interestRemoved = totalInterest.subtract(interestTillChargeOff.getAmount()); - installment.updatePrincipal(MathUtil.nullToZero(installment.getPrincipal()).add(interestRemoved)); - installment.updateInterestCharged(interestTillChargeOff.getAmount()); + final BigDecimal interestRemoved = totalInterest.subtract(interestTillChargeOff.getAmount()); + installment.updatePrincipal(MathUtil.nullToZero(installment.getPrincipal()).add(interestRemoved)); + installment.updateInterestCharged(interestTillChargeOff.getAmount()); + } }); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index 6be56815f..b4e26d448 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -60,6 +60,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductT import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.event.business.domain.journalentry.LoanJournalEntryCreatedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.office.domain.Office; @@ -453,13 +454,19 @@ public class AccountingProcessorHelper { JournalAmountHolder totalAccountHolder, Long loanProductId, Long paymentTypeId, Long loanId, String transactionId, LocalDate transactionDate) { splitAccountsHolder.forEach(journalItemHolder -> { - final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, journalItemHolder.getAccountType(), paymentTypeId); - createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, - journalItemHolder.getAmount()); + if (MathUtil.isGreaterThanZero(journalItemHolder.getAmount())) { + final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, journalItemHolder.getAccountType(), + paymentTypeId); + createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, + journalItemHolder.getAmount()); + } }); - final GLAccount totalAccount = getLinkedGLAccountForLoanProduct(loanProductId, totalAccountHolder.getAccountType(), paymentTypeId); - createCreditJournalEntryForLoan(office, currencyCode, totalAccount, loanId, transactionId, transactionDate, - totalAccountHolder.getAmount()); + if (MathUtil.isGreaterThanZero(totalAccountHolder.getAmount())) { + final GLAccount totalAccount = getLinkedGLAccountForLoanProduct(loanProductId, totalAccountHolder.getAccountType(), + paymentTypeId); + createCreditJournalEntryForLoan(office, currencyCode, totalAccount, loanId, transactionId, transactionDate, + totalAccountHolder.getAmount()); + } } public void createCreditJournalEntryForLoan(final Office office, final String currencyCode, diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java index 852554001..53f498143 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java @@ -147,75 +147,79 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess if (isMarkedAsChargeOff) { // ChargeOFF // principal payment - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // interest payment - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // handle fees payment - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // handle penalty payment - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { populateCreditDebitMaps(loanProductId, penaltiesAmount, paymentTypeId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // handle overpayment - if (overPayment != null && overPayment.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overPayment)) { populateCreditDebitMaps(loanProductId, overPayment, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } } else { // principal payment - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // interest payment - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // handle fees payment - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // handle penalty payment - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { populateCreditDebitMaps(loanProductId, penaltiesAmount, paymentTypeId, AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } // handle overpayment - if (overPayment != null && overPayment.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overPayment)) { populateCreditDebitMaps(loanProductId, overPayment, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), glAccountBalanceHolder); } } // create credit entries for (Map.Entry creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) { - GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey()); - this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, - creditEntry.getValue(), glAccount); + if (MathUtil.isGreaterThanZero(creditEntry.getValue())) { + GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey()); + this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, + creditEntry.getValue(), glAccount); + } } // create debit entries for (Map.Entry debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) { - GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey()); - this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, debitEntry.getValue(), - glAccount); + if (MathUtil.isGreaterThanZero(debitEntry.getValue())) { + GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey()); + this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, + debitEntry.getValue(), glAccount); + } } } @@ -241,14 +245,15 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess ProductToGLAccountMapping mapping = chargeOffReasonCodeValue != null ? helper.getChargeOffMappingByCodeValue(loanProductId, PortfolioProductType.LOAN, chargeOffReasonCodeValue) : null; - if (mapping != null) { - GLAccount accountCredit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, - AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId); - glAccountBalanceHolder.addToCredit(accountCredit, principalAmount); - glAccountBalanceHolder.addToDebit(mapping.getGlAccount(), principalAmount); - } else { - // principal payment - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + + if (MathUtil.isGreaterThanZero(principalAmount)) { + if (mapping != null) { + GLAccount accountCredit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId); + glAccountBalanceHolder.addToCredit(accountCredit, principalAmount); + glAccountBalanceHolder.addToDebit(mapping.getGlAccount(), principalAmount); + } else { + // principal payment if (isMarkedFraud) { populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), glAccountBalanceHolder); @@ -259,43 +264,48 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } } // interest payment - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { - + if (MathUtil.isGreaterThanZero(interestAmount)) { populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), glAccountBalanceHolder); } // handle fees payment - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), glAccountBalanceHolder); } // handle penalty payment - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { populateCreditDebitMaps(loanProductId, penaltiesAmount, paymentTypeId, AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), glAccountBalanceHolder); } // create credit entries for (Map.Entry creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) { - GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey()); - this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, - creditEntry.getValue(), glAccount); + if (MathUtil.isGreaterThanZero(creditEntry.getValue())) { + GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey()); + this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, + creditEntry.getValue(), glAccount); + } } // create debit entries for (Map.Entry debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) { - GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey()); - this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, debitEntry.getValue(), - glAccount); + if (MathUtil.isGreaterThanZero(debitEntry.getValue())) { + GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey()); + this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, + debitEntry.getValue(), glAccount); + } } } private void populateCreditDebitMaps(Long loanProductId, BigDecimal transactionPartAmount, Long paymentTypeId, Integer creditAccountType, Integer debitAccountType, GLAccountBalanceHolder glAccountBalanceHolder) { - // Resolve Credit - GLAccount accountCredit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, creditAccountType, paymentTypeId); - glAccountBalanceHolder.addToCredit(accountCredit, transactionPartAmount); - // Resolve Debit - GLAccount accountDebit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, debitAccountType, paymentTypeId); - glAccountBalanceHolder.addToDebit(accountDebit, transactionPartAmount); + if (MathUtil.isGreaterThanZero(transactionPartAmount)) { + // Resolve Credit + GLAccount accountCredit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, creditAccountType, paymentTypeId); + glAccountBalanceHolder.addToCredit(accountCredit, transactionPartAmount); + // Resolve Debit + GLAccount accountDebit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, debitAccountType, paymentTypeId); + glAccountBalanceHolder.addToDebit(accountDebit, transactionPartAmount); + } } private void createJournalEntriesForChargeAdjustment(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) { @@ -329,7 +339,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess Map accountMap = new LinkedHashMap<>(); // handle principal payment - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { totalDebitAmount = totalDebitAmount.add(principalAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId); @@ -337,7 +347,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle interest payment - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { totalDebitAmount = totalDebitAmount.add(interestAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId); @@ -351,7 +361,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle fees payment - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { totalDebitAmount = totalDebitAmount.add(feesAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId); @@ -364,7 +374,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle penalty payment - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), paymentTypeId); @@ -377,7 +387,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle overpayment - if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overPaymentAmount)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), paymentTypeId); @@ -390,11 +400,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } for (Map.Entry entry : accountMap.entrySet()) { - this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(), - entry.getKey()); + if (MathUtil.isGreaterThanZero(entry.getValue())) { + this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(), + entry.getKey()); + } } - if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(totalDebitAmount)) { Long chargeId = loanTransactionDTO.getLoanChargeData().getChargeId(); Integer accountMappingTypeId; if (loanTransactionDTO.getLoanChargeData().isPenalty()) { @@ -428,7 +440,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess Map accountMap = new LinkedHashMap<>(); // handle principal payment - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { totalDebitAmount = totalDebitAmount.add(principalAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId); @@ -436,7 +448,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle interest payment - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { totalDebitAmount = totalDebitAmount.add(interestAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), paymentTypeId); @@ -449,7 +461,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle fees payment - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { totalDebitAmount = totalDebitAmount.add(feesAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), paymentTypeId); @@ -462,7 +474,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle penalties payment - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), paymentTypeId); @@ -475,7 +487,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle overpayment - if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overPaymentAmount)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), paymentTypeId); @@ -488,11 +500,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } for (Map.Entry entry : accountMap.entrySet()) { - this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(), - entry.getKey()); + if (MathUtil.isGreaterThanZero(entry.getValue())) { + this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(), + entry.getKey()); + } } - if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(totalDebitAmount)) { Long chargeId = loanTransactionDTO.getLoanChargeData().getChargeId(); Integer accountMappingTypeId; if (loanTransactionDTO.getLoanChargeData().isPenalty()) { @@ -538,10 +552,12 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess final BigDecimal penaltyPaid = Objects.isNull(loanTransactionDTO.getPenaltyPaid()) ? BigDecimal.ZERO : loanTransactionDTO.getPenaltyPaid(); - helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE, loanProductId, paymentTypeId, - loanId, transactionId, transactionDate, amount); + if (MathUtil.isGreaterThanZero(amount)) { + helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE, loanProductId, paymentTypeId, + loanId, transactionId, transactionDate, amount); + } - if (overpaidAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overpaidAmount)) { helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overpaidAmount); } @@ -617,7 +633,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess final LocalDate transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal overpaymentPortion = loanTransactionDTO.getOverPayment() != null ? loanTransactionDTO.getOverPayment() : BigDecimal.ZERO; - final BigDecimal principalPortion = loanTransactionDTO.getAmount().subtract(overpaymentPortion); + final BigDecimal loanTransactionDTOAmount = loanTransactionDTO.getAmount(); + final BigDecimal principalPortion = loanTransactionDTOAmount.subtract(overpaymentPortion); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); // create journal entries for the disbursement @@ -630,15 +647,17 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overpaymentPortion); } - if (loanTransactionDTO.isLoanToLoanTransfer()) { - this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId, - paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount()); - } else if (loanTransactionDTO.isAccountTransfer()) { - this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(), - loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount()); - } else { - this.helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, - paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount()); + if (MathUtil.isGreaterThanZero(loanTransactionDTOAmount)) { + if (loanTransactionDTO.isLoanToLoanTransfer()) { + this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), + loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTOAmount); + } else if (loanTransactionDTO.isAccountTransfer()) { + this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(), + loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTOAmount); + } else { + this.helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), + loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTOAmount); + } } } @@ -705,7 +724,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess BigDecimal totalDebitAmount = new BigDecimal(0); // principal payment - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { totalDebitAmount = totalDebitAmount.add(principalAmount); if (loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) { if (isMarkedFraud) { @@ -747,7 +766,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // interest payment - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { totalDebitAmount = totalDebitAmount.add(interestAmount); if (loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) { populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, @@ -777,7 +796,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle fees payment - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { totalDebitAmount = totalDebitAmount.add(feesAmount); if (loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) { populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, @@ -816,7 +835,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle penalties - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); if (loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) { populateCreditDebitMaps(loanProductId, penaltiesAmount, paymentTypeId, @@ -853,7 +872,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // overpayment - if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overPaymentAmount)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); if (loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) { populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), @@ -876,12 +895,14 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess // create credit entries for (Map.Entry creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) { - GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey()); - this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, - creditEntry.getValue(), glAccount); + if (MathUtil.isGreaterThanZero(creditEntry.getValue())) { + GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey()); + this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, + creditEntry.getValue(), glAccount); + } } - if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(totalDebitAmount)) { if (writeOff) { this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount); @@ -907,7 +928,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess * Charge Refunds have an extra refund related pair of journal entries in addition to those related to the * repayment above ***/ - if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(totalDebitAmount)) { if (loanTransactionDTO.getTransactionType().isChargeRefund()) { Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType()); this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, AccrualAccountsForLoan.FUND_SOURCE.getValue(), @@ -940,7 +961,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess Map debitAccountMapForGoodwillCredit = new LinkedHashMap<>(); // handle principal payment or writeOff - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { totalDebitAmount = totalDebitAmount.add(principalAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId); @@ -952,7 +973,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle interest payment of writeOff - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { totalDebitAmount = totalDebitAmount.add(interestAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), paymentTypeId); @@ -970,10 +991,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle fees payment of writeOff - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { - + if (MathUtil.isGreaterThanZero(feesAmount)) { totalDebitAmount = totalDebitAmount.add(feesAmount); - if (isIncomeFromFee) { this.helper.createCreditJournalEntryForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount, loanTransactionDTO.getFeePayments()); @@ -994,7 +1013,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } // handle penalties payment of writeOff - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); if (isIncomeFromFee) { GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, @@ -1023,7 +1042,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } } - if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overPaymentAmount)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), paymentTypeId); @@ -1040,14 +1059,16 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } for (Map.Entry entry : accountMap.entrySet()) { - this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(), - entry.getKey()); + if (MathUtil.isGreaterThanZero(entry.getValue())) { + this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(), + entry.getKey()); + } } /** * Single DEBIT transaction for write-offs or Repayments ***/ - if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(totalDebitAmount)) { if (writeOff) { this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount); @@ -1078,7 +1099,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess * Charge Refunds have an extra refund related pair of journal entries in addition to those related to the * repayment above ***/ - if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0 && loanTransactionDTO.getTransactionType().isChargeRefund()) { + if (MathUtil.isGreaterThanZero(totalDebitAmount) && loanTransactionDTO.getTransactionType().isChargeRefund()) { Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType()); this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount); @@ -1122,10 +1143,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess final BigDecimal amount = loanTransactionDTO.getAmount(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); - this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), - AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), loanProductId, paymentTypeId, loanId, transactionId, - transactionDate, amount); - + if (MathUtil.isGreaterThanZero(amount)) { + this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), + AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), loanProductId, paymentTypeId, loanId, transactionId, + transactionDate, amount); + } } /** @@ -1156,7 +1178,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); // create journal entries for recognizing interest - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { if (transactionType.isAccrualAdjustment()) { this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, @@ -1168,7 +1190,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } } // create journal entries for the fees application - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { if (transactionType.isAccrualAdjustment()) { this.helper.createJournalEntriesForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), loanProductId, loanId, transactionId, transactionDate, @@ -1180,7 +1202,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } } // create journal entries for the penalties application - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { if (transactionType.isAccrualAdjustment()) { this.helper.createJournalEntriesForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), @@ -1205,14 +1227,16 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess final BigDecimal refundAmount = loanTransactionDTO.getAmount(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); - if (loanTransactionDTO.isAccountTransfer()) { - this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), - FinancialActivity.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, - refundAmount); - } else { - this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), - AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, - refundAmount); + if (MathUtil.isGreaterThanZero(refundAmount)) { + if (loanTransactionDTO.isAccountTransfer()) { + this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), + FinancialActivity.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, + transactionDate, refundAmount); + } else { + this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), + AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, + refundAmount); + } } } @@ -1241,12 +1265,12 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess BigDecimal totalAmount = BigDecimal.ZERO; List journalAmountHolders = new ArrayList<>(); - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { totalAmount = totalAmount.add(principalAmount); journalAmountHolders .add(new JournalAmountHolder(determineAccrualAccountForCBR(isMarkedChargeOff, isMarkedFraud, false), principalAmount)); } - if (overpaymentAmount != null && overpaymentAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overpaymentAmount)) { totalAmount = totalAmount.add(overpaymentAmount); journalAmountHolders .add(new JournalAmountHolder(determineAccrualAccountForCBR(isMarkedChargeOff, isMarkedFraud, true), overpaymentAmount)); @@ -1293,19 +1317,19 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess BigDecimal totalDebitAmount = new BigDecimal(0); - if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(principalAmount)) { totalDebitAmount = totalDebitAmount.add(principalAmount); this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount); } - if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(interestAmount)) { totalDebitAmount = totalDebitAmount.add(interestAmount); this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount); } - if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(feesAmount)) { totalDebitAmount = totalDebitAmount.add(feesAmount); List chargePaymentDTOs = new ArrayList<>(); @@ -1320,7 +1344,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess loanProductId, loanId, transactionId, transactionDate, feesAmount, chargePaymentDTOs); } - if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(penaltiesAmount)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); List chargePaymentDTOs = new ArrayList<>(); @@ -1334,14 +1358,16 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, chargePaymentDTOs); } - if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) { + if (MathUtil.isGreaterThanZero(overPaymentAmount)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount); } - /*** create a single debit entry (or reversal) for the entire amount **/ - this.helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, - paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount); + if (MathUtil.isGreaterThanZero(totalDebitAmount)) { + /*** create a single debit entry (or reversal) for the entire amount **/ + this.helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, + paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount); + } } }