Skip to content

Commit

Permalink
Merge pull request apache#379 from FITER1/UC-23-loan-fees-capping
Browse files Browse the repository at this point in the history
UC-23 :- Apply min and max configs to loan charge
  • Loading branch information
hero78 committed Oct 20, 2022
2 parents 952f411 + 0082673 commit 928bb7b
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 58 deletions.
Expand Up @@ -191,23 +191,6 @@ public void validateForCreate(final String json) {
.isOneOfTheseValues(ChargeCalculationType.validValuesForSavings());
}

final ChargeCalculationType chargeCalculationTypeValue = ChargeCalculationType.fromInt(chargeCalculationType);

if((chargeCalculationTypeValue != null && chargeCalculationTypeValue.getValue().equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue())) &&
(ctt != null && ctt.isWithdrawalFee())){

final BigDecimal minAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minAmount", element.getAsJsonObject());
baseDataValidator.reset().parameter("minAmount").value(minAmount).notNull().positiveAmount();

final BigDecimal maxAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxAmount", element.getAsJsonObject());
baseDataValidator.reset().parameter("maxAmount").value(maxAmount).notNull().positiveAmount();
if (maxAmount.compareTo(minAmount) < 0) {
String message = "Minimum Amount [ %s ] can not be greater than Maximum Amount [ %s ] ";
throw new GeneralPlatformDomainRuleException(
String.format(message, minAmount, maxAmount),
String.format(message, minAmount, maxAmount));
}
}

} else if (appliesTo.isClientCharge()) {
// client applicable validation
Expand Down Expand Up @@ -283,10 +266,59 @@ public void validateForCreate(final String json) {
final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.taxGroupIdParamName, element);
baseDataValidator.reset().parameter(ChargesApiConstants.taxGroupIdParamName).value(taxGroupId).notNull().longGreaterThanZero();
}
validateMinMaxConfigurationOnLoanAndSavingsAccountCharges(baseDataValidator, element, chargeCalculationType, appliesTo);


throwExceptionIfValidationWarningsExist(dataValidationErrors);
}

private void validateMinMaxConfigurationOnLoanAndSavingsAccountCharges(DataValidatorBuilder baseDataValidator, JsonElement element, Integer chargeCalculationType, ChargeAppliesTo appliesTo) {
final BigDecimal minAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minAmount", element.getAsJsonObject());

final BigDecimal maxAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxAmount", element.getAsJsonObject());


if (appliesTo.isSavingsCharge() || appliesTo.isLoanCharge()) {

final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull();


final ChargeTimeType ctt = ChargeTimeType.fromInt(chargeTimeType);


final ChargeCalculationType chargeCalculationTypeValue = ChargeCalculationType.fromInt(chargeCalculationType);

if((appliesTo.isSavingsCharge() && chargeCalculationTypeValue.getValue().equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue()) && ctt.isWithdrawalFee()) ||
(appliesTo.isLoanCharge() && !ctt.isOnSpecifiedDueDate() && !chargeCalculationTypeValue.getValue().equals(ChargeCalculationType.FLAT.getValue()))){
validateMinMaxPolicyOnCharge(minAmount, maxAmount);
}else{
String message = "Minimum and Maximum Amount is not supported with given settings of [Applies To: " + appliesTo.name() +", charge time type :"+ ctt.name() + ", and Charge Calculation Type: "+ chargeCalculationTypeValue.name() +" ]";
minandmaxConfigurationAreNotSupported(minAmount, maxAmount, message);
}
}else{
String message = "Minimum and Maximum Amount is only supported on Loans and Savings Deposits charges";
minandmaxConfigurationAreNotSupported(minAmount, maxAmount, message);
}
}

private void minandmaxConfigurationAreNotSupported(BigDecimal minAmount, BigDecimal maxAmount, String message) {
if(minAmount != null || maxAmount != null){
throw new GeneralPlatformDomainRuleException(
String.format(message),
String.format(message));
}
}

private void validateMinMaxPolicyOnCharge(BigDecimal minAmount, BigDecimal maxAmount) {
if (maxAmount.compareTo(minAmount) < 0) {
String message = "Minimum Amount [ %s ] can not be greater than Maximum Amount [ %s ] ";
throw new GeneralPlatformDomainRuleException(
String.format(message, minAmount, maxAmount),
String.format(message, minAmount, maxAmount));
}
}

public void validateForUpdate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
Expand Down Expand Up @@ -432,29 +464,8 @@ public void validateForUpdate(final String json) {
final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.taxGroupIdParamName, element);
baseDataValidator.reset().parameter(ChargesApiConstants.taxGroupIdParamName).value(taxGroupId).notNull().longGreaterThanZero();
}
if (chargeCalculationType != null && chargeAppliesTo != null) {
final ChargeAppliesTo appliesTo = ChargeAppliesTo.fromInt(chargeAppliesTo);
final ChargeCalculationType chargeCalculationTypeValue = ChargeCalculationType.fromInt(chargeCalculationType);

if (chargeCalculationTypeValue.getValue().equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue()) && appliesTo.isSavingsCharge()) { // Apply this Validation to savings Account charge with Percentage Calculation Type
BigDecimal minAmount = null;
if (this.fromApiJsonHelper.parameterExists("minAmount", element)) {
minAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minAmount", element.getAsJsonObject());
baseDataValidator.reset().parameter("minAmount").value(minAmount).notNull().positiveAmount();
}
BigDecimal maxAmount = null;
if (this.fromApiJsonHelper.parameterExists("maxAmount", element)) {
maxAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxAmount", element.getAsJsonObject());
baseDataValidator.reset().parameter("maxAmount").value(maxAmount).notNull().positiveAmount();
}
if(maxAmount.compareTo(minAmount) < 0){
String message = "Minimum Amount [ %s ] can not be greater than Maximum Amount [ %s ] ";
throw new GeneralPlatformDomainRuleException(
String.format(message, minAmount,maxAmount),
String.format(message, minAmount,maxAmount));
}
}
}
final ChargeAppliesTo appliesTo = ChargeAppliesTo.fromInt(chargeAppliesTo);
validateMinMaxConfigurationOnLoanAndSavingsAccountCharges(baseDataValidator, element, chargeCalculationType, appliesTo);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}

Expand Down
Expand Up @@ -83,6 +83,10 @@ public class LoanChargeData {

private String externalId;

private BigDecimal minAmount;

private BigDecimal maxAmount;

public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null, null,
null, null, null);
Expand All @@ -105,7 +109,7 @@ public LoanChargeData(final Long id, final Long chargeId, final String name, fin
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 BigDecimal minCap, final BigDecimal maxCap, final BigDecimal amountOrPercentage,
Collection<LoanInstallmentChargeData> installmentChargeData, final String externalId) {
Collection<LoanInstallmentChargeData> installmentChargeData, final String externalId,final BigDecimal minAmount,final BigDecimal maxAmount) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
Expand Down Expand Up @@ -143,6 +147,8 @@ public LoanChargeData(final Long id, final Long chargeId, final String name, fin
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = externalId;
this.minAmount = minAmount;
this.maxAmount = maxAmount;
}

private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
Expand Down Expand Up @@ -310,6 +316,8 @@ public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentCharg
this.amountAccrued = chargeData.amountAccrued;
this.amountUnrecognized = chargeData.amountUnrecognized;
this.externalId = chargeData.externalId;
this.minAmount = chargeData.minAmount;
this.maxAmount = chargeData.maxAmount;
}

public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amount, final EnumOptionData chargeCalculationType,
Expand Down
Expand Up @@ -88,6 +88,12 @@ public class LoanCharge extends AbstractPersistableCustom {
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;

@Column(name = "min_amount", scale = 6, precision = 19)
private BigDecimal minAmount;

@Column(name = "max_amount", scale = 6, precision = 19)
private BigDecimal maxAmount;

@Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountPaid;

Expand Down Expand Up @@ -267,6 +273,8 @@ public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecim

populateDerivedFields(loanPrincipal, chargeAmount, numberOfRepayments, loanCharge);
this.paid = determineIfFullyPaid();
this.minAmount = chargeDefinition.getMinAmount();
this.maxAmount = chargeDefinition.getMaxAmount();
}

private void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount,
Expand Down Expand Up @@ -814,20 +822,6 @@ public Charge getCharge() {
return this.charge;
}

/*
* @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return
* true; } if (obj.getClass() != getClass()) { return false; } final LoanCharge rhs = (LoanCharge) obj; return new
* EqualsBuilder().appendSuper(super.equals(obj)) // .append(getId(), rhs.getId()) // .append(this.charge.getId(),
* rhs.charge.getId()) // .append(this.amount, rhs.amount) // .append(getDueLocalDate(), rhs.getDueLocalDate()) //
* .isEquals(); }
*
* @Override public int hashCode() { return 1;
*
* return new HashCodeBuilder(3, 5) // .append(getId()) // .append(this.charge.getId()) //
* .append(this.amount).append(getDueLocalDate()) // .toHashCode();
*
* }
*/

public ChargePaymentMode getChargePaymentMode() {
return ChargePaymentMode.fromInt(this.chargePaymentMode);
Expand Down
Expand Up @@ -69,7 +69,7 @@ public String schema() {
+ "c.currency_code as currencyCode, oc.name as currencyName, "
+ "date(coalesce(dd.disbursedon_date,dd.expected_disburse_date)) as disbursementDate, "
+ "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode from m_charge c "
+ "oc.internationalized_name_code as currencyNameCode,c.min_amount AS minAmount, c.max_amount AS maxAmount from m_charge c "
+ "join m_organisation_currency oc on c.currency_code = oc.code " + "join m_loan_charge lc on lc.charge_id = c.id "
+ "left join m_loan_tranche_disbursement_charge dc on dc.loan_charge_id=lc.id left join m_loan_disbursement_detail dd on dd.id=dc.disbursement_detail_id ";
}
Expand All @@ -80,6 +80,8 @@ public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") fin
final Long chargeId = rs.getLong("chargeId");
final String name = rs.getString("name");
final BigDecimal amount = rs.getBigDecimal("amountDue");
final BigDecimal minAmount = rs.getBigDecimal("minAmount");
final BigDecimal maxAmount = rs.getBigDecimal("maxAmount");
final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid");
final BigDecimal amountWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWaived");
final BigDecimal amountWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWrittenOff");
Expand Down Expand Up @@ -110,7 +112,7 @@ public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") fin
final int chargePaymentMode = rs.getInt("chargePaymentMode");
final EnumOptionData paymentMode = ChargeEnumerations.chargePaymentMode(chargePaymentMode);
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
final boolean waived = rs.getBoolean("waived");
final BigDecimal minCap = rs.getBigDecimal("minCap");
final BigDecimal maxCap = rs.getBigDecimal("maxCap");
final BigDecimal amountOrPercentage = rs.getBigDecimal("amountOrPercentage");
Expand All @@ -123,7 +125,7 @@ public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") fin

return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding,
chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, paymentMode, paid,
waived, null, minCap, maxCap, amountOrPercentage, null, externalId);
waived, null, minCap, maxCap, amountOrPercentage, null, externalId,minAmount,maxAmount);
}
}

Expand Down
Expand Up @@ -72,4 +72,30 @@
</column>
</addColumn>
</changeSet>
<changeSet author="bosco@fiter.io" id="add_min_amount_to_m_loan_charge_table">

<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="m_loan_charge" columnName="min_amount"/>
</not>
</preConditions>
<addColumn tableName="m_loan_charge">
<column name="min_amount" type="decimal(19,6)">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
<changeSet author="bosco@fiter.io" id="add_max_amount_to_m_loan_charge_table">

<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="m_loan_charge" columnName="max_amount"/>
</not>
</preConditions>
<addColumn tableName="m_loan_charge">
<column name="max_amount" type="decimal(19,6)">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>

0 comments on commit 928bb7b

Please sign in to comment.