Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/FBR-400: Implement tolerance limit on loan product #278

Merged
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 @@ -245,7 +245,7 @@ public void validateNewRepaymentTransaction(final String json) {

final Set<String> transactionParameters = new HashSet<>(
Arrays.asList("transactionDate", "transactionAmount", "externalId", "note", "locale", "dateFormat", "paymentTypeId",
"accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "loanId"));
"accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "loanId", "isBatchPayment"));

final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,8 @@ public CommandProcessingResult modifyApplication(final Long loanId, final JsonCo
/**
* Stores all charges which are passed in during modify loan application
**/
//TODO: FBR-369 we need the expected disbursement date and first repayment date. This is hack to get hose but should be considered for improvement
// TODO: FBR-369 we need the expected disbursement date and first repayment date. This is hack to get hose
// but should be considered for improvement
final LoanApplicationTerms loanApplicationTermsCharges = this.loanScheduleAssembler.assembleLoanTerms(command.parsedJson());
final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),
disbursementDetails, loanApplicationTermsCharges);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ private Loan assembleApplication(final JsonElement element, final Long clientId,
}
}

//TODO: FBR-369 get range rate and generate charge
// TODO: FBR-369 get range rate and generate charge
LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);

final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails, loanApplicationTerms);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import com.google.gson.JsonPrimitive;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.charge.domain.Charge;
Expand Down Expand Up @@ -68,7 +67,8 @@ public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeR
this.loanProductRepository = loanProductRepository;
}

public Set<LoanCharge> fromParsedJson(JsonElement element, List<LoanDisbursementDetails> disbursementDetails, final LoanApplicationTerms loanApplicationTermsCharges) {
public Set<LoanCharge> fromParsedJson(JsonElement element, List<LoanDisbursementDetails> disbursementDetails,
final LoanApplicationTerms loanApplicationTermsCharges) {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
List<Long> disbursementChargeIds = new ArrayList<>();

Expand Down Expand Up @@ -123,7 +123,7 @@ public Set<LoanCharge> fromParsedJson(JsonElement element, List<LoanDisbursement
locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
//TODO: FBR-369 Added to generate rate from range for installment fee and add on.
// TODO: FBR-369 Added to generate rate from range for installment fee and add on.
amount = generateInstallmentFeeRate(chargeDefinition, loanChargeElement, loanApplicationTermsCharges, amount);

if (chargeDefinition.isOverdueInstallment()) {
Expand Down Expand Up @@ -209,7 +209,7 @@ public Set<LoanCharge> fromParsedJson(JsonElement element, List<LoanDisbursement
} else {
final Long loanChargeId = id;
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId).orElse(null);
//TODO: FBR-369 Added to generate rate from range for installment fee and add on.
// TODO: FBR-369 Added to generate rate from range for installment fee and add on.
amount = generateInstallmentFeeRate(loanCharge.getCharge(), loanChargeElement, loanApplicationTermsCharges, amount);

if (loanCharge != null) {
Expand All @@ -226,15 +226,16 @@ public Set<LoanCharge> fromParsedJson(JsonElement element, List<LoanDisbursement
return loanCharges;
}

private BigDecimal generateInstallmentFeeRate(Charge charge, JsonObject loanChargeElement, LoanApplicationTerms loanApplicationTermsCharges, BigDecimal amount){
private BigDecimal generateInstallmentFeeRate(Charge charge, JsonObject loanChargeElement,
LoanApplicationTerms loanApplicationTermsCharges, BigDecimal amount) {
BigDecimal rate = amount;
//TODO: FBR-369 Added to generate rate from range for installment fee and add on. Necessary to make the check here before more loan processing happens
if(charge.isInstallmentFeeCharge() && charge.isAddOnInstallmentFeeType()
&& loanApplicationTermsCharges != null){
if(ChargeCalculationType.fromInt(charge.getChargeCalculation()).isPercentageBased()){
// TODO: FBR-369 Added to generate rate from range for installment fee and add on. Necessary to make the check
// here before more loan processing happens
if (charge.isInstallmentFeeCharge() && charge.isAddOnInstallmentFeeType() && loanApplicationTermsCharges != null) {
if (ChargeCalculationType.fromInt(charge.getChargeCalculation()).isPercentageBased()) {
// update rate
Pair<Integer, BigDecimal> addOnDaysAndRate = charge.getAddOnDisbursementChargeRate(loanApplicationTermsCharges.getExpectedDisbursementDate(),
loanApplicationTermsCharges.getRepaymentStartFromDate());
Pair<Integer, BigDecimal> addOnDaysAndRate = charge.getAddOnDisbursementChargeRate(
loanApplicationTermsCharges.getExpectedDisbursementDate(), loanApplicationTermsCharges.getRepaymentStartFromDate());
rate = addOnDaysAndRate.getRight();
loanChargeElement.add("amount", new JsonPrimitive(rate));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2403,7 +2403,7 @@ public String schema() {
sqlBuilder.append(
"l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, l.net_disbursal_amount as netDisbursalAmount, ");
sqlBuilder.append("rc." + sqlGenerator.escape("name")
+ " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode ");
+ " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, rc.int_code AS intCode ");
sqlBuilder.append("FROM m_loan l ");
sqlBuilder.append("JOIN m_currency rc on rc." + sqlGenerator.escape("code") + " = l.currency_code ");
sqlBuilder.append("JOIN m_loan_repayment_schedule ls ON ls.loan_id = l.id AND ls.completed_derived = false ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
Expand Down Expand Up @@ -159,6 +160,7 @@
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
Expand Down Expand Up @@ -945,6 +947,21 @@ public CommandProcessingResult makeLoanRepayment(final LoanTransactionType repay
changes.put("note", noteText);
}
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final boolean isBatchPayment = command.booleanPrimitiveValueOfParameterNamed("isBatchPayment");
final BigDecimal paymentToleranceLimit = loan.getLoanProduct().getPaymentToleranceLimit();
if (isBatchPayment && paymentToleranceLimit.compareTo(BigDecimal.ZERO) > 0) {
final LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(loanId);
final BigDecimal scheduledAmount = transactionData.getAmount();
final BigDecimal limitPortion = scheduledAmount.multiply(paymentToleranceLimit).divide(BigDecimal.valueOf(100L),
MoneyHelper.getRoundingMode());
final BigDecimal upperLimitAmount = scheduledAmount.add(limitPortion);
final BigDecimal lowerLimitAmount = scheduledAmount.subtract(limitPortion);
if (transactionAmount.compareTo(upperLimitAmount) > 0 || transactionAmount.compareTo(lowerLimitAmount) < 0) {
throw new PlatformServiceUnavailableException("error.msg.the.provided.transaction.amount.is.outside.tolerance.limits",
transactionAmount + " transaction amount is outside the set tolerance limit on product");
}
}

final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
final Boolean isHolidayValidationDone = false;
final HolidayDetailDTO holidayDetailDto = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,6 @@ public interface LoanProductConstants {

String ADD_NEW_CYCLES_ENABLED = "addNewCyclesEnabled";
String LOAN_PRODUCT_OWNER_TYPE = "ownerType";
String PAYMENT_TOLERANCE_LIMIT = "paymentToleranceLimit";

}
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ public class LoanProductData implements Serializable {
private final boolean addNewCyclesEnabled;
private final Integer daysLimitAddOn;
private final BigDecimal guaranteePercentage;
private final BigDecimal paymentToleranceLimit;

/**
* Used when returning lookup information about loan product for dropdowns.
Expand Down Expand Up @@ -295,6 +296,7 @@ public static LoanProductData lookup(final Long id, final String name, final Boo
final EnumOptionData ownerTypeOption = null;
final boolean addNewCyclesEnabled = true;
final Integer daysLimitAddOn = null;
final BigDecimal paymentToleranceLimit = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
Expand All @@ -312,7 +314,7 @@ public static LoanProductData lookup(final Long id, final String name, final Boo
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, ageLimitWarning, ageLimitBlock, ownerTypeOption, addNewCyclesEnabled,
daysLimitAddOn, null);
daysLimitAddOn, null, paymentToleranceLimit);

}

Expand Down Expand Up @@ -403,7 +405,7 @@ public static LoanProductData lookupWithCurrency(final Long id, final String nam
final EnumOptionData ownerTypeOption = null;
final boolean addNewCyclesEnabled = true;
final Integer daysLimitAddOn = null;

final BigDecimal paymentToleranceLimit = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
Expand All @@ -421,7 +423,7 @@ public static LoanProductData lookupWithCurrency(final Long id, final String nam
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, ageLimitWarning, ageLimitBlock, ownerTypeOption, addNewCyclesEnabled,
daysLimitAddOn, null);
daysLimitAddOn, null, paymentToleranceLimit);

}

Expand Down Expand Up @@ -519,7 +521,7 @@ public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
final EnumOptionData ownerTypeOption = null;
final boolean addNewCyclesEnabled = true;
final Integer daysLimitAddOn = LoanProductConstants.DEFAULT_LIMIT_OF_DAYS_FOR_ADDON;

final BigDecimal paymentToleranceLimit = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
Expand All @@ -537,7 +539,7 @@ public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, ageLimitWarning, ageLimitBlock, ownerTypeOption, addNewCyclesEnabled,
daysLimitAddOn, null);
daysLimitAddOn, null, paymentToleranceLimit);

}

Expand Down Expand Up @@ -629,7 +631,7 @@ public static LoanProductData loanProductWithFloatingRates(final Long id, final
final EnumOptionData ownerTypeOption = null;
final boolean addNewCyclesEnabled = true;
final Integer daysLimitAddOn = null;

final BigDecimal paymentToleranceLimit = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
Expand All @@ -647,7 +649,7 @@ public static LoanProductData loanProductWithFloatingRates(final Long id, final
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, ageLimitWarning, ageLimitBlock, ownerTypeOption, addNewCyclesEnabled,
daysLimitAddOn, null);
daysLimitAddOn, null, paymentToleranceLimit);

}

Expand Down Expand Up @@ -694,7 +696,7 @@ public LoanProductData(final Long id, final String name, final String shortName,
Collection<RateData> rateOptions, Collection<RateData> rates, final boolean isRatesEnabled,
final BigDecimal fixedPrincipalPercentagePerInstallmen, final Integer ageLimitWarning, final Integer ageLimitBlock,
final EnumOptionData ownerTypeOption, final boolean addNewCyclesEnabled, final Integer daysLimitAddOn,
BigDecimal guaranteePercentage) {
BigDecimal guaranteePercentage, final BigDecimal paymentToleranceLimit) {
this.id = id;
this.name = name;
this.shortName = shortName;
Expand Down Expand Up @@ -815,6 +817,7 @@ public LoanProductData(final Long id, final String name, final String shortName,
this.ageLimitWarning = ageLimitWarning;
this.daysLimitAddOn = daysLimitAddOn;
this.guaranteePercentage = guaranteePercentage;
this.paymentToleranceLimit = paymentToleranceLimit;

}

Expand Down Expand Up @@ -970,6 +973,7 @@ public LoanProductData(final LoanProductData productData, final Collection<Charg
this.ageLimitWarning = productData.ageLimitWarning;
this.daysLimitAddOn = productData.daysLimitAddOn;
this.guaranteePercentage = productData.guaranteePercentage;
this.paymentToleranceLimit = productData.paymentToleranceLimit;
}

private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
Expand Down
Loading
Loading