Skip to content

Commit

Permalink
FINERACT-2060: Accrual reverse replay logic and Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Marta Jankovics committed Nov 11, 2024
1 parent 39da688 commit 7cac479
Show file tree
Hide file tree
Showing 47 changed files with 1,682 additions and 1,232 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.charge.service;

import java.util.Collection;
import java.util.List;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;

Expand Down Expand Up @@ -53,7 +54,7 @@ public interface ChargeReadPlatformService {
* Excludes Given List of Charge Types from the response
* @return
*/
Collection<ChargeData> retrieveLoanAccountApplicableCharges(Long loanId, ChargeTimeType[] excludeChargeTimes);
List<ChargeData> retrieveLoanAccountApplicableCharges(Long loanId, ChargeTimeType[] excludeChargeTimes);

/**
* Returns all charges applicable for a given loan product (filter based on Currency of Selected Loan Product)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ public static BigDecimal subtract(BigDecimal first, BigDecimal second) {
return subtract(first, second, MoneyHelper.getMathContext());
}

/** @return first minus the others considering null values, maybe negative */
public static BigDecimal subtract(BigDecimal first, BigDecimal second, BigDecimal third) {
return subtract(subtract(first, second), third);
}

/** @return first minus second considering null values, maybe negative */
public static BigDecimal subtract(BigDecimal first, BigDecimal second, MathContext mc) {
return first == null ? null : second == null ? first : first.subtract(second, mc);
Expand Down Expand Up @@ -454,6 +459,16 @@ public static Money min(Money first, Money second, Money third, boolean notNull)
return min(min(first, second, notNull), third, notNull);
}

/** @return Money null safe negate */
public static Money negate(Money amount) {
return negate(amount, MoneyHelper.getMathContext());
}

/** @return Money null safe negate */
public static Money negate(Money amount, MathContext mc) {
return isEmpty(amount) ? amount : amount.negated(mc);
}

/**
* Calculate percentage of a value
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.event.business.domain.loan.transaction;

import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;

public class LoanAccrualAdjustmentTransactionBusinessEvent extends LoanTransactionBusinessEvent {

private static final String TYPE = "LoanAccrualAdjustmentTransactionBusinessEvent";

public LoanAccrualAdjustmentTransactionBusinessEvent(LoanTransaction value) {
super(value);
}

@Override
public String getType() {
return TYPE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.data;

import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import org.apache.fineract.organisation.monetary.domain.Money;

@Data
@Accessors(chain = true)
@RequiredArgsConstructor
public class AccrualChargeData {

private final Long loanChargeId;
private final Long loanInstallmentChargeId;
private final boolean isPenalty;
private Money chargeAmount;
private Money chargeAccruable;
private Money chargeAccrued;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.data;

import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money;

@Data
@Accessors(chain = true)
@RequiredArgsConstructor
public class AccrualPeriodData {

private final Integer installmentNumber;
private final boolean isFirstPeriod;
private Money interestAmount;
private Money interestAccruable;
private Money interestAccrued;
private List<AccrualChargeData> charges;

public AccrualPeriodData addCharge(AccrualChargeData charge) {
if (charges == null) {
charges = new ArrayList<>();
}
charges.add(charge);
return this;
}

public Money getChargeAmount() {
return charges.stream().map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
}

public Money getFeeAmount() {
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
}

public Money getPenaltyAmount() {
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
}

public Money getChargeAccrued() {
return charges.stream().map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
}

public Money getFeeAccrued() {
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
}

public Money getPenaltyAccrued() {
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
}

public Money getChargeAccruable() {
return charges.stream().map(AccrualChargeData::getChargeAccruable).reduce(null, MathUtil::plus);
}

public Money getFeeAccruable() {
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccruable).reduce(null,
MathUtil::plus);
}

public Money getPenaltyAccruable() {
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccruable).reduce(null,
MathUtil::plus);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.data;

import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;

@Data
@Accessors(chain = true)
@RequiredArgsConstructor
public class AccrualPeriodsData {

private final MonetaryCurrency currency;
private List<AccrualPeriodData> periods = new ArrayList<>();

public AccrualPeriodsData addPeriod(AccrualPeriodData period) {
periods.add(period);
return this;
}

public static AccrualPeriodsData create(@NotNull List<LoanRepaymentScheduleInstallment> installments, Integer firstInstallmentNumber,
MonetaryCurrency currency) {
AccrualPeriodsData accrualPeriods = new AccrualPeriodsData(currency);
for (LoanRepaymentScheduleInstallment installment : installments) {
Integer installmentNumber = installment.getInstallmentNumber();
boolean isFirst = installmentNumber.equals(firstInstallmentNumber);
accrualPeriods.addPeriod(new AccrualPeriodData(installmentNumber, isFirst));
}
return accrualPeriods;
}

public AccrualPeriodData getPeriodByInstallmentNumber(Integer installmentNumber) {
return installmentNumber == null ? null
: periods.stream().filter(p -> installmentNumber.equals(p.getInstallmentNumber())).findFirst().orElse(null);
}

public Integer getFirstInstallmentNumber() {
return periods.stream().filter(AccrualPeriodData::isFirstPeriod).map(AccrualPeriodData::getInstallmentNumber).findFirst()
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -66,7 +66,7 @@ public class LoanChargeData {

private final BigDecimal amountOrPercentage;

private final Collection<ChargeData> chargeOptions;
private final List<ChargeData> chargeOptions;

private final boolean penalty;

Expand All @@ -84,7 +84,7 @@ public class LoanChargeData {

private final BigDecimal maxCap;

private final Collection<LoanInstallmentChargeData> installmentChargeData;
private final List<LoanInstallmentChargeData> installmentChargeData;

private BigDecimal amountAccrued;

Expand All @@ -94,7 +94,7 @@ public class LoanChargeData {

private final ExternalId externalLoanId;

public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
public static LoanChargeData template(final List<ChargeData> chargeOptions) {
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null,
ExternalId.empty(), null, null, null, null, ExternalId.empty());
}
Expand All @@ -116,7 +116,7 @@ public LoanChargeData(final Long id, final Long chargeId, final String name, fin
final LocalDate dueDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
final BigDecimal amountOrPercentage, List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
Expand Down Expand Up @@ -160,9 +160,9 @@ public LoanChargeData(final Long id, final Long chargeId, final String name, fin

private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
final BigDecimal percentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
final Collection<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
final List<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
final BigDecimal amountOrPercentage, List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
Expand Down Expand Up @@ -207,7 +207,7 @@ private LoanChargeData(final Long id, final Long chargeId, final String name, fi

public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final LocalDate submittedOnDate, final BigDecimal amountOutstanding,
EnumOptionData chargeTimeType, final Long loanId, final ExternalId externalLoanId,
Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
this.id = id;
this.chargeId = null;
this.name = null;
Expand Down Expand Up @@ -308,7 +308,7 @@ public LoanChargeData(final BigDecimal amountUnrecognized, final LoanChargeData
this.externalId = chargeData.externalId;
}

public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
public LoanChargeData(LoanChargeData chargeData, List<LoanInstallmentChargeData> installmentChargeData) {
this.id = chargeData.id;
this.chargeId = chargeData.chargeId;
this.name = chargeData.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class LoanTransactionEnumData {
private final boolean reAmortize;
private final boolean accrualActivity;
private final boolean interestRefund;
private final boolean accrualAdjustment;

public LoanTransactionEnumData(final Long id, final String code, final String value) {
this.id = id;
Expand Down Expand Up @@ -96,6 +97,7 @@ public LoanTransactionEnumData(final Long id, final String code, final String va
this.reAge = Long.valueOf(LoanTransactionType.REAGE.getValue()).equals(this.id);
this.reAmortize = Long.valueOf(LoanTransactionType.REAMORTIZE.getValue()).equals(this.id);
this.interestRefund = Long.valueOf(LoanTransactionType.INTEREST_REFUND.getValue()).equals(this.id);
this.accrualAdjustment = Long.valueOf(LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue()).equals(this.id);
}

public boolean isRepaymentType() {
Expand Down
Loading

0 comments on commit 7cac479

Please sign in to comment.