Hi Hysnije, Yes, it’s intentional. You can define the allocation rule for the Charge adjustment transaction to be allocated first to any charges. However, there’s no such restriction because there could be situations where there are no outstanding charges. So, it’s just easier and more reliable to process it as a regular transaction.
Yes, it’s also intentional to allow paying any other outstanding balances and to have any effect on the repayment schedule and calculated interest. Allocating the charge adjustment transaction amount across the outstanding balances would lead to the correct outcome that would have been the case if the charge amount was correct. Let’s consider the following scenarios: Scenario 1: - A charge is created with $100. - A repayment occurs with $100, and the full $100 is allocated to fully pay the charge, resulting in $100 charge income. - Later, if a decision is made to create a $50 charge adjustment, $50 from the charge income is deducted, and $50 is allocated to the principal. Scenario 2: - A charge is created with $50, and no charge adjustment is made. - A repayment occurs with $100, and $50 goes to charge income (fully paid), while $50 goes to the principal. In both scenarios, the outcome is the same. I hope this information and example help you understand the functionality better. Regards, Adam > On Jul 2, 2026, at 4:58 PM, Hysnije Zllanoga <[email protected]> > wrote: > > Hi Adam, > > Thank you for the detailed explanation and for testing it locally. The > walkthrough makes the intent clear, so the repayment schedule obligation > stays at 1100, the adjustment is a separate credit, and > cumulativeTotalPaidOnInstallments already reflects both. I had misread what > that field accumulates. > > One follow-up question on the allocation behavior: > > Is it intentional that a charge adjustment can be allocated across multiple > portions, including interest and principal, rather than being restricted to > the fee or penalty portion of the charge it targets? > > From a correction-of-charge perspective, I would have expected the credit to > land exclusively on the fee or penalty component (depending on whether the > targeted charge is a fee or a penalty), since the adjustment is semantically > a correction of a specific charge amount and not a general payment against > the loan. > > Allowing it to allocate to principal has side effects on the repayment > schedule: it can trigger a transaction reprocessing and, for interest-bearing > loans, affect the interest calculation for upcoming installments. Was that > cross-component allocation a deliberate design choice, or is it an artifact > of routing the transaction through the general repayment-like allocation path? > > Best regards, > Hysnije > > On Wed, Jul 1, 2026 at 4:48 PM Ádám Sághy <[email protected] > <mailto:[email protected]>> wrote: >> Hi Hysnije, >> >> Thanks for tracing this. >> >> My understanding of the original FINERACT-1761 intent is that including >> CHARGE_ADJUSTMENT in the repayment-like group was intentional, but not >> because it represents cash received from the customer. >> >> Before charge adjustment, the main option for reducing loan charges was >> waiver. Waiver does not cover all cases, especially where the charge has >> already been paid or partially paid. Its accounting treatment is also >> different, as it moves the amount through the expense/write-off path. >> >> Charge adjustment was introduced for cases where a fee or penalty amount was >> later found to be incorrect and needed to be corrected without changing the >> original charge directly, and without recognizing the correction as an >> expense. >> >> Conceptually, it is a credit against the customer’s loan account. It reduces >> what is owed and participates in the same allocation/reprocessing mechanics >> as other credit transactions. From the accounting side it is handled >> separately from a normal repayment; for accrual accounting it reduces the >> relevant fee/penalty income and related receivable as applicable. >> >> So yes, the repayment-like classification was intentional for >> balance/allocation/reprocessing behavior. It should not be interpreted only >> as “actual customer cash payment”. >> >> For your example, I agree that the expected overpayment is 50, and that is >> what I see today. >> >> The important distinction is that the repayment schedule obligation is not >> rewritten from 1100 to 1050. The charge adjustment is represented as a >> separate credit transaction. >> >> So in this case: >> >> borrower cash repayment: 1100 >> charge adjustment credit: 50 >> total repayment-like credits: 1150 >> cumulative paid on installments: 1100 >> calculated overpayment: 1150 - 1100 = 50 >> This is why I do not think the proposed formula is correct: >> >> totalPaidInRepayments - cumulativeTotalPaidOnInstallments - >> totalFeeAdjustments - totalPenaltyAdjustments >> >> That would deduct the adjustment again even though it is already reflected >> in the schedule/balance side. In the scenario above, the values are: >> >> totalPaidInRepayments: 1150 >> cumulativeTotalPaidOnInstallments: 1100 >> So the current result is 50, not 100 >> >> >> >> I tested this locally as well, using the Mifos UI to make the result easier >> to follow.: >> A progressive loan was created and disbursed $1000 (no interest) >> $100 charge was added to the loan >> All due balance is $1100 >> <Screenshot 2026-07-01 at 4.21.16 PM.png> >> Charge adjustment with $50 was issued (act like a repayment) >> All due balance is still $1100, but $50 is paid already! >> <Screenshot 2026-07-01 at 4.21.49 PM.png> >> Only $50 dollar is outstanding of the initial charge (due amount is not >> changed, but $50 paid by Charge adjustment) >> <Screenshot 2026-07-01 at 4.21.38 PM.png> >> On repayment schedule similarly $50 is paid from the $1100 >> <Screenshot 2026-07-01 at 4.21.56 PM.png> >> $1100 repayment from customer was issued, which fully repays remaining >> outstanding balance ($1050) and the remaining $50 recognized as overpayment. >> <Screenshot 2026-07-01 at 4.22.14 PM.png> >> >> Repayment schedule reflect the same: $1100 is paid on the repayment schedule >> (overpayment is not reflected on repayment schedule, it is a separate >> balance) >> <Screenshot 2026-07-01 at 4.22.23 PM.png> >> >> Overpayment amount is $50 (on the loan level) >> <Screenshot 2026-07-01 at 4.22.17 PM.png> >> >> Regarding getLastRepaymentDate() and getLastPaymentTransaction(): There >> might be some discrepancies, but in my understanding getLastRepaymentDate >> returns the last repayment date (not repayment like) and >> getLastPaymentTransaction (return the last “repayment like” transaction). We >> are happy to discuss further if you have any concrete use cases and places >> where incorrect one is used and we can review them. >> >> I hope you find my explanation useful! >> >> Regards, >> Adam >> >>> On Jul 1, 2026, at 3:11 PM, Hysnije Zllanoga <[email protected] >>> <mailto:[email protected]>> wrote: >>> >>> Hi Fineract community, >>> >>> Looking at LoanTransaction.isRepaymentLikeType(), CHARGE_ADJUSTMENT has >>> been included since the original FINERACT-1761 >>> <https://github.com/apache/fineract/pull/2724/changes>implementation. I'd >>> like to understand the intent behind that decision. >>> >>> When a charge adjustment is included in isRepaymentLikeType(), it gets >>> counted in getTotalPaidInRepayments(), which feeds into >>> calculateTotalOverpayment(). Since a charge adjustment reduces what is owed >>> rather than bringing in actual cash, the overpayment formula reports an >>> inflated amount. For example: >>> >>> Principal = 1000, fee = 100 >>> Repayment = 1100 (loan fully paid) >>> Charge adjustment = 50 (fee reduced after the fact) >>> Expected overpayment: 50 >>> Actual result: totalPaidInRepayments (1150) − >>> cumulativeTotalPaidOnInstallments (1050) = 100 >>> Additionally, getLastRepaymentDate() and getLastPaymentTransaction() return >>> the adjustment rather than the last actual payment, which could affect >>> delinquency tagging and UI display. >>> >>> Was the inclusion in isRepaymentLikeType() intentional, accepting these >>> side effects in exchange for reprocessing loop participation? Or was an >>> alternative classification ever considered so similar to how WAIVE_INTEREST >>> is handled, explicitly included in the reprocessing guards but not in >>> isRepaymentLikeType() itself? >>> >>> If the inclusion is intentional, would the correct fix for the overpayment >>> formula be: >>> >>> totalPaidInRepayments − cumulativeTotalPaidOnInstallments − >>> totalFeeAdjustments − totalPenaltyAdjustments >>> >>> Any insight into the original design rationale would be very helpful. >>> -- >>> Best regards, >>> Hysnije >> > > > > -- > Gjithë të mirat, > Hysnije Zllanoga > > Email: [email protected] <mailto:[email protected]>
