Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
Expand Down Expand Up @@ -232,6 +233,8 @@ public void addLoanCharge(final Loan loan, final LoanCharge loanCharge) {
update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmentsAfterExceptions(),
totalChargeAmt);

validateChargeAmountNotZero(loanCharge);

// NOTE: must add new loan charge to set of loan charges before
// reprocessing the repayment schedule.
if (loan.getLoanCharges() == null) {
Expand Down Expand Up @@ -384,6 +387,7 @@ public Map<String, Object> update(final JsonCommand command, final BigDecimal am
case PERCENT_OF_AMOUNT_AND_INTEREST:
case PERCENT_OF_INTEREST:
case PERCENT_OF_DISBURSEMENT_AMOUNT:

loanCharge.setPercentage(newValue);
loanCharge.setAmountPercentageAppliedTo(amount);
loanChargeAmount = BigDecimal.ZERO;
Expand Down Expand Up @@ -818,13 +822,18 @@ private void update(final LoanCharge loanCharge, final BigDecimal amount, final
case INVALID:
break;
case FLAT:
BigDecimal roundedAmount;
if (loanCharge.isInstalmentFee()) {
if (numberOfRepayments == null) {
numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmentsAfterExceptions();
}
loanCharge.setAmount(amount.multiply(BigDecimal.valueOf(numberOfRepayments)));
roundedAmount = Money
.of(loanCharge.getLoan().getCurrency(), amount.multiply(BigDecimal.valueOf(numberOfRepayments)))
.getAmount();
loanCharge.setAmount(roundedAmount);
} else {
loanCharge.setAmount(amount);
roundedAmount = Money.of(loanCharge.getLoan().getCurrency(), amount).getAmount();
loanCharge.setAmount(roundedAmount);
}
break;
case PERCENT_OF_AMOUNT:
Expand Down Expand Up @@ -998,4 +1007,13 @@ private boolean doesLoanChargePaidByContainLoanCharge(Set<LoanChargePaidBy> loan
.anyMatch(loanChargePaidBy -> loanChargePaidBy.getLoanCharge().equals(loanCharge));
}

private void validateChargeAmountNotZero(final LoanCharge loanCharge) {
if (loanCharge.getAmount() != null && BigDecimal.ZERO.compareTo(loanCharge.getAmount()) == 0) {
final String defaultUserMessage = "This charge cannot be added because the calculated amount becomes zero after rounding.";

throw new LoanChargeCannotBeAddedException("loanCharge", "amount.rounded.to.zero", defaultUserMessage,
loanCharge.getLoan().getId(), loanCharge.name());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@
import jakarta.persistence.Transient;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.OrganisationCurrency;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.client.api.ClientApiConstants;

@Entity
@Table(name = "m_client_charge")
Expand Down Expand Up @@ -94,12 +92,8 @@ protected ClientCharge() {
//
}

public static ClientCharge createNew(final Client client, final Charge charge, final JsonCommand command) {
BigDecimal amount = command.bigDecimalValueOfParameterNamed(ClientApiConstants.amountParamName);
final LocalDate dueDate = command.localDateValueOfParameterNamed(ClientApiConstants.dueAsOfDateParamName);
public static ClientCharge createNew(final Client client, final Charge charge, final BigDecimal amount, final LocalDate dueDate) {
final boolean status = true;
// Derive from charge definition if not passed in as a parameter
amount = (amount == null) ? charge.getAmount() : amount;
return new ClientCharge(client, charge, amount, dueDate, status);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.Charge;
Expand Down Expand Up @@ -78,6 +81,7 @@ public class ClientChargeWritePlatformServiceImpl implements ClientChargeWritePl
private final ClientTransactionRepository clientTransactionRepository;
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;

@Override
public CommandProcessingResult addCharge(Long clientId, JsonCommand command) {
Expand All @@ -95,7 +99,10 @@ public CommandProcessingResult addCharge(Long clientId, JsonCommand command) {
throw new ChargeCannotBeAppliedToException("client", errorMessage, charge.getId());
}

final ClientCharge clientCharge = ClientCharge.createNew(client, charge, command);
Money roundedAmount = calculateRoundedChargeAmount(charge, command);
validateChargeAmountNotZero(roundedAmount);
final LocalDate date = command.localDateValueOfParameterNamed(ClientApiConstants.dueAsOfDateParamName);
final ClientCharge clientCharge = ClientCharge.createNew(client, charge, roundedAmount.getAmount(), date);
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat());
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
Expand Down Expand Up @@ -428,4 +435,23 @@ private void handleDataIntegrityIssues(@SuppressWarnings("unused") final Long cl
"Unknown data integrity issue with resource: " + realCause.getMessage());
}

private Money calculateRoundedChargeAmount(final Charge charge, final JsonCommand command) {
BigDecimal amount = command.bigDecimalValueOfParameterNamed(ClientApiConstants.amountParamName);
amount = (amount == null) ? charge.getAmount() : amount;

ApplicationCurrency currency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(charge.getCurrencyCode());

CurrencyData currencyData = currency.toData();
return Money.of(currencyData, amount);
}

private void validateChargeAmountNotZero(Money amount) {
if (amount.isZero()) {
List<ApiParameterError> errors = new ArrayList<>();
errors.add(ApiParameterError.parameterError("error.msg.client.charge.amount.rounded.to.zero",
"This charge cannot be added because the calculated amount becomes zero after rounding.", "amount"));
throw new PlatformApiDataValidationException(errors);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.client.domain.Client;
Expand Down Expand Up @@ -1236,6 +1237,8 @@ public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command
}
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command);

validateChargeAmountNotZero(savingsAccountCharge, dataValidationErrors);

if (chargeDefinition.isEnableFreeWithdrawal()) {
savingsAccountCharge.setFreeWithdrawalCount(0);
}
Expand Down Expand Up @@ -2086,4 +2089,18 @@ private void validateReasonForHold(String reasonForBlock) {
throw new PlatformDataIntegrityException("Reason For Block is Mandatory", "error.msg.reason.for.block.mandatory");
}
}

private void validateChargeAmountNotZero(final SavingsAccountCharge savingsAccountCharge,
final List<ApiParameterError> dataValidationErrors) {
if (!ChargeCalculationType.fromInt(savingsAccountCharge.getCharge().getChargeCalculation()).isFlat()) {
return;
}

if (savingsAccountCharge.getAmount(savingsAccountCharge.savingsAccount().getCurrency()).isZero()) {
final String defaultUserMessage = "This charge cannot be added because the calculated amount becomes zero after rounding.";

dataValidationErrors
.add(ApiParameterError.parameterError("error.msg.savings.charge.amount.rounded.to.zero", defaultUserMessage, "amount"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ public Integer getChargeTimeType() {
}

public BigDecimal deriveChargeAmount(BigDecimal transactionAmount, final MonetaryCurrency currency) {
BigDecimal toReturnAmount = amountOrPercentage;
BigDecimal toReturnAmount;
if (ChargeCalculationType.fromInt(this.chargeCalculation) == ChargeCalculationType.PERCENT_OF_AMOUNT) {
toReturnAmount = Money.of(currency, percentageOf(transactionAmount, this.percentage)).getAmount();
this.amountPercentageAppliedTo = transactionAmount;
Expand All @@ -384,7 +384,8 @@ public BigDecimal deriveChargeAmount(BigDecimal transactionAmount, final Monetar
this.amountWaived = null;
this.amountWrittenOff = null;
} else {
this.amount = this.amountOrPercentage;
this.amount = Money.of(currency, this.amountOrPercentage).getAmount();
toReturnAmount = this.amount;
this.amountOutstanding = calculateOutstanding();
this.amountWaived = null;
this.amountWrittenOff = null;
Expand All @@ -393,7 +394,7 @@ public BigDecimal deriveChargeAmount(BigDecimal transactionAmount, final Monetar
}

public BigDecimal updateChargeDetailsForAdditionalSharesRequest(final BigDecimal transactionAmount, final MonetaryCurrency currency) {
BigDecimal toReturnAmount = amountOrPercentage;
BigDecimal toReturnAmount;
if (ChargeCalculationType.fromInt(this.chargeCalculation) == ChargeCalculationType.PERCENT_OF_AMOUNT) {
toReturnAmount = Money.of(currency, percentageOf(transactionAmount, this.percentage)).getAmount();
this.amountPercentageAppliedTo = this.amountPercentageAppliedTo.add(transactionAmount);
Expand All @@ -402,7 +403,9 @@ public BigDecimal updateChargeDetailsForAdditionalSharesRequest(final BigDecimal
this.amountWaived = null;
this.amountWrittenOff = null;
} else {
this.amount = this.amount.add(this.amountOrPercentage);
BigDecimal roundedAmount = Money.of(currency, this.amountOrPercentage).getAmount();
toReturnAmount = roundedAmount;
this.amount = this.amount.add(roundedAmount);
this.amountOutstanding = calculateOutstanding();
this.amountWaived = null;
this.amountWrittenOff = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public static ShareAccountTransaction createChargeTransaction(final LocalDate tr
final BigDecimal unitPrice = null;
final Integer status = PurchasedSharesStatusType.APPROVED.getValue();
final Integer type = PurchasedSharesStatusType.CHARGE_PAYMENT.getValue();
BigDecimal amount = charge.percentageOrAmount();
BigDecimal amount = charge.amoutOutstanding();
BigDecimal chargeAmount = null;
BigDecimal amountPaid = null;
return new ShareAccountTransaction(transactionDate, totalShares, unitPrice, status, type, amount, chargeAmount, amountPaid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ private void createChargeTransaction(ShareAccount account) {
LocalDate currentDate = DateUtils.getBusinessLocalDate();
for (ShareAccountCharge charge : charges) {
if (charge.isActive() && charge.isShareAccountActivation()) {
charge.deriveChargeAmount(totalChargeAmount, account.getCurrency());
BigDecimal amount = charge.deriveChargeAmount(totalChargeAmount, account.getCurrency());
validateChargeAmountNotZero(amount);
ShareAccountTransaction chargeTransaction = ShareAccountTransaction.createChargeTransaction(currentDate, charge);
ShareAccountChargePaidBy paidBy = new ShareAccountChargePaidBy(chargeTransaction, charge, charge.percentageOrAmount());
chargeTransaction.addShareAccountChargePaidBy(paidBy);
Expand All @@ -255,6 +256,7 @@ private void createChargeTransaction(ShareAccount account) {
for (ShareAccountCharge charge : charges) {
if (charge.isActive() && charge.isSharesPurchaseCharge()) {
BigDecimal amount = charge.deriveChargeAmount(pending.amount(), account.getCurrency());
validateChargeAmountNotZero(amount);
ShareAccountChargePaidBy paidBy = new ShareAccountChargePaidBy(pending, charge, amount);
pending.addShareAccountChargePaidBy(paidBy);
totalChargeAmount = totalChargeAmount.add(amount);
Expand Down Expand Up @@ -1043,4 +1045,13 @@ public Map<String, Object> validateAndClose(JsonCommand jsonCommand, ShareAccoun
actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, transaction);
return actualChanges;
}

private void validateChargeAmountNotZero(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
List<ApiParameterError> errors = new ArrayList<>();
errors.add(ApiParameterError.parameterError("error.msg.share.charge.amount.rounded.to.zero",
"This charge cannot be added because the calculated amount becomes zero after rounding.", "amount"));
throw new PlatformApiDataValidationException(errors);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,12 @@ private void populateDerivedFields(final BigDecimal transactionAmount, final Big
this.amountWrittenOff = null;
break;
case FLAT:
Money money = Money.of(this.savingsAccount().getCurrency(), chargeAmount);
this.percentage = null;
this.amount = chargeAmount;
this.amount = money.getAmount();
this.amountPercentageAppliedTo = null;
this.amountPaid = null;
this.amountOutstanding = chargeAmount;
this.amountOutstanding = money.getAmount();
this.amountWaived = null;
this.amountWrittenOff = null;
break;
Expand Down
Loading
Loading