FINERACT-1932: Add loan module (WIP)

This commit is contained in:
Adam Saghy
2023-05-25 17:57:28 +02:00
parent 105b4ed154
commit 2e3b96df19
352 changed files with 938 additions and 413 deletions
@@ -0,0 +1,26 @@
/**
* 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.cob.loan;
import org.apache.fineract.cob.COBBusinessStep;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public interface LoanCOBBusinessStep extends COBBusinessStep<Loan> {
}
@@ -0,0 +1,427 @@
/**
* 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.interoperation.util;
import java.math.BigDecimal;
import java.math.MathContext;
import javax.validation.constraints.NotNull;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
public final class MathUtil {
private MathUtil() {
}
public static Long nullToZero(Long value) {
return nullToDefault(value, 0L);
}
public static Long nullToDefault(Long value, Long def) {
return value == null ? def : value;
}
public static Long zeroToNull(Long value) {
return isEmpty(value) ? null : value;
}
/** @return parameter value or ZERO if it is negative */
public static Long negativeToZero(Long value) {
return isGreaterThanZero(value) ? value : 0L;
}
public static boolean isEmpty(Long value) {
return value == null || value.equals(0L);
}
public static boolean isGreaterThanZero(Long value) {
return value != null && value > 0L;
}
public static boolean isLessThanZero(Long value) {
return value != null && value < 0L;
}
public static boolean isZero(Long value) {
return value != null && value.equals(0L);
}
public static boolean isEqualTo(Long first, Long second) {
return nullToZero(first).equals(nullToZero(second));
}
public static boolean isGreaterThan(Long first, Long second) {
return nullToZero(first) > nullToZero(second);
}
public static boolean isLessThan(Long first, Long second) {
return nullToZero(first) < nullToZero(second);
}
public static boolean isGreaterThanOrEqualTo(Long first, Long second) {
return nullToZero(first) >= nullToZero(second);
}
public static boolean isLessThanOrEqualZero(Long value) {
return nullToZero(value) <= 0L;
}
/** @return parameter value or negated value to positive */
public static Long abs(Long value) {
return value == null ? 0L : Math.abs(value);
}
/**
* @return calculates minimum of the two values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Long min(Long first, Long second, boolean notNull) {
if (first == null) {
return notNull ? second : null;
}
if (second == null) {
return notNull ? first : null;
}
return Math.min(first, second);
}
/**
* @return calculates minimum of the values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Long min(Long first, Long second, Long third, boolean notNull) {
return min(min(first, second, notNull), third, notNull);
}
/** @return sum the two values considering null values */
public static Long add(Long first, Long second) {
return first == null ? second : second == null ? first : Math.addExact(first, second);
}
/** @return sum the values considering null values */
public static Long add(Long first, Long second, Long third) {
return add(add(first, second), third);
}
/** @return sum the values considering null values */
public static Long add(Long first, Long second, Long third, Long fourth) {
return add(add(add(first, second), third), fourth);
}
/** @return sum the values considering null values */
public static Long add(Long first, Long second, Long third, Long fourth, Long fifth) {
return add(add(add(add(first, second), third), fourth), fifth);
}
/** @return first minus second considering null values, maybe negative */
public static Long subtract(Long first, Long second) {
return first == null ? null : second == null ? first : Math.subtractExact(first, second);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Long subtractToZero(Long first, Long second, Long third) {
return subtractToZero(subtract(first, second), third);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Long subtractToZero(Long first, Long second, Long third, Long fourth) {
return subtractToZero(subtract(subtract(first, second), third), fourth);
}
/** @return NONE negative first minus second considering null values */
public static Long subtractToZero(Long first, Long second) {
return negativeToZero(subtract(first, second));
}
/** @return BigDecimal null safe negate */
public static Long negate(Long amount) {
return isEmpty(amount) ? amount : Math.negateExact(amount);
}
// ----------------- BigDecimal -----------------
public static BigDecimal nullToZero(BigDecimal value) {
return nullToDefault(value, BigDecimal.ZERO);
}
public static BigDecimal nullToDefault(BigDecimal value, BigDecimal def) {
return value == null ? def : value;
}
public static BigDecimal zeroToNull(BigDecimal value) {
return isEmpty(value) ? null : value;
}
/** @return parameter value or ZERO if it is negative */
public static BigDecimal negativeToZero(BigDecimal value) {
return isGreaterThanZero(value) ? value : BigDecimal.ZERO;
}
public static boolean isEmpty(BigDecimal value) {
return value == null || BigDecimal.ZERO.compareTo(value) == 0;
}
public static boolean isGreaterThanZero(BigDecimal value) {
return value != null && value.compareTo(BigDecimal.ZERO) > 0;
}
public static boolean isLessThanZero(BigDecimal value) {
return value != null && value.compareTo(BigDecimal.ZERO) < 0;
}
public static boolean isZero(BigDecimal value) {
return value != null && value.compareTo(BigDecimal.ZERO) == 0;
}
public static boolean isEqualTo(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) == 0;
}
public static boolean isGreaterThan(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) > 0;
}
public static boolean isLessThan(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) < 0;
}
public static boolean isGreaterThanOrEqualTo(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) >= 0;
}
public static boolean isLessThanOrEqualZero(BigDecimal value) {
return nullToZero(value).compareTo(BigDecimal.ZERO) <= 0;
}
/** @return parameter value or negated value to positive */
public static BigDecimal abs(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value.abs();
}
/**
* @return calculates minimum of the two values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static BigDecimal min(BigDecimal first, BigDecimal second, boolean notNull) {
return notNull ? first == null ? second : second == null ? first : min(first, second, false)
: isLessThan(first, second) ? first : second;
}
/**
* @return calculates minimum of the values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static BigDecimal min(BigDecimal first, BigDecimal second, BigDecimal third, boolean notNull) {
return min(min(first, second, notNull), third, notNull);
}
/** @return sum the two values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second) {
return add(first, second, MoneyHelper.getMathContext());
}
/** @return sum the two values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, MathContext mc) {
return first == null ? second : second == null ? first : first.add(second, mc);
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third) {
return add(first, second, third, MoneyHelper.getMathContext());
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, MathContext mc) {
return add(add(first, second, mc), third, mc);
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) {
return add(first, second, third, fourth, MoneyHelper.getMathContext());
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, MathContext mc) {
return add(add(add(first, second, mc), third, mc), fourth, mc);
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth) {
return add(first, second, third, fourth, fifth, MoneyHelper.getMathContext());
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth,
MathContext mc) {
return add(add(add(add(first, second, mc), third, mc), fourth, mc), fifth, mc);
}
/** @return first minus second considering null values, maybe negative */
public static BigDecimal subtract(BigDecimal first, BigDecimal second) {
return subtract(first, second, MoneyHelper.getMathContext());
}
/** @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);
}
/** @return NONE negative first minus second considering null values */
public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second) {
return negativeToZero(subtract(first, second));
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third) {
return subtractToZero(subtract(first, second), third);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) {
return subtractToZero(subtract(subtract(first, second), third), fourth);
}
/**
* @return BigDecimal with scale set to the 'digitsAfterDecimal' of the parameter currency
*/
public static BigDecimal normalizeAmount(BigDecimal amount, @NotNull MonetaryCurrency currency) {
return amount == null ? null : amount.setScale(currency.getDigitsAfterDecimal(), MoneyHelper.getRoundingMode());
}
/** @return BigDecimal null safe negate */
public static BigDecimal negate(BigDecimal amount) {
return negate(amount, MoneyHelper.getMathContext());
}
/** @return BigDecimal null safe negate */
public static BigDecimal negate(BigDecimal amount, MathContext mc) {
return isEmpty(amount) ? amount : amount.negate(mc);
}
// ----------------- Money -----------------
public static Money nullToZero(Money value, @NotNull MonetaryCurrency currency) {
return nullToDefault(value, Money.zero(currency));
}
public static Money nullToDefault(Money value, Money def) {
return value == null ? def : value;
}
public static Money zeroToNull(Money value) {
return isEmpty(value) ? null : value;
}
/** @return parameter value or ZERO if it is negative */
public static Money negativeToZero(Money value) {
return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrency());
}
public static boolean isEmpty(Money value) {
return value == null || value.isZero();
}
public static boolean isGreaterThanZero(Money value) {
return value != null && value.isGreaterThanZero();
}
public static boolean isLessThanZero(Money value) {
return value != null && value.isLessThanZero();
}
public static boolean isEqualTo(Money first, Money second) {
return first == null ? second == null : (second != null && first.isEqualTo(second));
}
public static boolean isGreaterThan(Money first, Money second) {
return second == null || (first != null && first.isGreaterThan(second));
}
public static boolean isLessThan(Money first, Money second) {
return first == null || (second != null && first.isLessThan(second));
}
public static Money plus(Money first, Money second) {
return first == null ? second : second == null ? first : first.plus(second);
}
public static Money plus(Money first, Money second, Money third) {
return plus(plus(first, second), third);
}
public static Money plus(Money first, Money second, Money third, Money fourth) {
return plus(plus(plus(first, second), third), fourth);
}
public static Money minus(Money first, Money second) {
return first == null ? null : second == null ? first : first.minus(second);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Money minusToZero(Money first, Money second, Money third) {
return minusToZero(minus(first, second), third);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Money minusToZero(Money first, Money second, Money third, Money fourth) {
return minusToZero(minus(minus(first, second), third), fourth);
}
/** @return NONE negative first minus second considering null values */
public static Money minusToZero(Money first, Money second) {
return negativeToZero(minus(first, second));
}
/**
* @return calculates minimum of the two values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Money min(Money first, Money second, boolean notNull) {
return notNull ? first == null ? second : second == null ? first : min(first, second, false)
: isLessThan(first, second) ? first : second;
}
/**
* @return calculates minimum of the values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Money min(Money first, Money second, Money third, boolean notNull) {
return min(min(first, second, notNull), third, notNull);
}
}
@@ -0,0 +1,33 @@
/**
* 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.charge.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when currency mismatch occurs
*/
public class LoanChargeWithoutMandatoryFieldException extends AbstractPlatformDomainRuleException {
public LoanChargeWithoutMandatoryFieldException(final String entity, final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg." + entity + "." + postFix + ".cannot.be.blank", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,67 @@
/**
* 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.collateral.api;
import java.util.HashSet;
import java.util.Set;
public final class CollateralApiConstants {
private CollateralApiConstants() {
}
public static final String COLLATERAL_CODE_NAME = "LoanCollateral";
/***
* Enum of all parameters passed in while creating/updating a collateral
***/
public enum CollateralJSONinputParams {
LOAN_ID("loanId"), COLLATERAL_ID("collateralId"), COLLATERAL_TYPE_ID("collateralTypeId"), VALUE("value"), DESCRIPTION(
"description");
private final String value;
CollateralJSONinputParams(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final CollateralJSONinputParams type : CollateralJSONinputParams.values()) {
values.add(type.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
}
@@ -0,0 +1,72 @@
/**
* 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.collateral.data;
import java.math.BigDecimal;
import java.util.Collection;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
/**
* Immutable data object for Collateral data.
*/
public final class CollateralData {
private final Long id;
private final CodeValueData type;
private final BigDecimal value;
private final String description;
@SuppressWarnings("unused")
private final Collection<CodeValueData> allowedCollateralTypes;
private final CurrencyData currency;
public static CollateralData instance(final Long id, final CodeValueData type, final BigDecimal value, final String description,
final CurrencyData currencyData) {
return new CollateralData(id, type, value, description, currencyData);
}
public static CollateralData template(final Collection<CodeValueData> codeValues) {
return new CollateralData(null, null, null, null, null, codeValues);
}
private CollateralData(final Long id, final CodeValueData type, final BigDecimal value, final String description,
final CurrencyData currencyData) {
this.id = id;
this.type = type;
this.value = value;
this.description = description;
this.currency = currencyData;
this.allowedCollateralTypes = null;
}
private CollateralData(final Long id, final CodeValueData type, final BigDecimal value, final String description,
final CurrencyData currencyData, final Collection<CodeValueData> allowedCollateralTypes) {
this.id = id;
this.type = type;
this.value = value;
this.description = description;
this.currency = currencyData;
this.allowedCollateralTypes = allowedCollateralTypes;
}
public CollateralData template(final CollateralData collateralData, final Collection<CodeValueData> codeValues) {
return new CollateralData(collateralData.id, collateralData.type, collateralData.value, collateralData.description,
collateralData.currency, codeValues);
}
}
@@ -0,0 +1,133 @@
/**
* 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.collateral.domain;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.collateral.api.CollateralApiConstants.CollateralJSONinputParams;
import org.apache.fineract.portfolio.collateral.data.CollateralData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@Entity
@Table(name = "m_loan_collateral")
public class LoanCollateral extends AbstractPersistableCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@ManyToOne
@JoinColumn(name = "type_cv_id", nullable = false)
private CodeValue type;
@Column(name = "value", scale = 6, precision = 19)
private BigDecimal value;
@Column(name = "description", length = 500)
private String description;
public static LoanCollateral from(final CodeValue collateralType, final BigDecimal value, final String description) {
return new LoanCollateral(null, collateralType, value, description);
}
protected LoanCollateral() {
//
}
private LoanCollateral(final Loan loan, final CodeValue collateralType, final BigDecimal value, final String description) {
this.loan = loan;
this.type = collateralType;
this.value = value;
this.description = StringUtils.defaultIfEmpty(description, null);
}
public void assembleFrom(final CodeValue collateralType, final BigDecimal value, final String description) {
this.type = collateralType;
this.description = description;
this.value = value;
}
public void associateWith(final Loan loan) {
this.loan = loan;
}
public static LoanCollateral fromJson(final Loan loan, final CodeValue collateralType, final JsonCommand command) {
final String description = command.stringValueOfParameterNamed(CollateralJSONinputParams.DESCRIPTION.getValue());
final BigDecimal value = command.bigDecimalValueOfParameterNamed(CollateralJSONinputParams.VALUE.getValue());
return new LoanCollateral(loan, collateralType, value, description);
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
final String collateralTypeIdParamName = CollateralJSONinputParams.COLLATERAL_TYPE_ID.getValue();
if (command.isChangeInLongParameterNamed(collateralTypeIdParamName, this.type.getId())) {
final Long newValue = command.longValueOfParameterNamed(collateralTypeIdParamName);
actualChanges.put(collateralTypeIdParamName, newValue);
}
final String descriptionParamName = CollateralJSONinputParams.DESCRIPTION.getValue();
if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
actualChanges.put(descriptionParamName, newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null);
}
final String valueParamName = CollateralJSONinputParams.VALUE.getValue();
if (command.isChangeInBigDecimalParameterNamed(valueParamName, this.value)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(valueParamName);
actualChanges.put(valueParamName, newValue);
this.value = newValue;
}
return actualChanges;
}
public CollateralData toData() {
final CodeValueData typeData = this.type.toData();
return CollateralData.instance(getId(), typeData, this.value, this.description, null);
}
public void setCollateralType(final CodeValue type) {
this.type = type;
}
/*
* @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return
* true; } if (obj.getClass() != getClass()) { return false; } final LoanCollateral rhs = (LoanCollateral) obj;
* return new EqualsBuilder().appendSuper(super.equals(obj)) // .append(getId(), rhs.getId()) //
* .append(this.type.getId(), rhs.type.getId()) // .append(this.description, rhs.description) // .append(this.value,
* this.value)// .isEquals(); }
*
* @Override public int hashCode() { return new HashCodeBuilder(3, 5) // .append(getId()) //
* .append(this.type.getId()) // .append(this.description) // .append(this.value)// .toHashCode(); }
*/
}
@@ -0,0 +1,42 @@
/**
* 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.collateralmanagement.api;
public enum CollateralManagementJsonInputParams {
NAME("name"), QUALITY("quality"), BASE_PRICE("basePrice"), UNIT_TYPE("unitType"), PCT_TO_BASE("pctToBase"), CURRENCY(
"currency"), COLLATERAL_PRODUCT_READ_PERMISSION("COLLATERAL_PRODUCT"), CLIENT_COLLATERAL_PRODUCT_READ_PERMISSION(
"CLIENT_COLLATERAL_PRODUCT"), QUANTITY("quantity"), TOTAL_COLLATERAL_VALUE("totalCollateralValue");
private final String value;
CollateralManagementJsonInputParams(final String value) {
this.value = value;
}
@Override
public String toString() {
return name().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
@@ -0,0 +1,137 @@
/**
* 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.collateralmanagement.domain;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.collateralmanagement.api.CollateralManagementJsonInputParams;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
@Entity
@Table(name = "m_client_collateral_management")
public class ClientCollateralManagement extends AbstractPersistableCustom {
@Column(name = "quantity", nullable = false, scale = 5, precision = 20)
private BigDecimal quantity;
@ManyToOne(optional = false)
@JoinColumn(name = "client_id", nullable = false)
private Client client;
@ManyToOne(optional = false)
@JoinColumn(name = "collateral_id", nullable = false)
private CollateralManagementDomain collateral;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "clientCollateralManagement", fetch = FetchType.EAGER)
private Set<LoanCollateralManagement> loanCollateralManagementSet = new HashSet<>();
public ClientCollateralManagement() {
}
public ClientCollateralManagement(final BigDecimal quantity, final Client client) {
this.client = client;
this.quantity = quantity;
}
public ClientCollateralManagement(final BigDecimal quantity) {
this.quantity = quantity;
}
private ClientCollateralManagement(final BigDecimal quantity, final Client client,
final CollateralManagementDomain collateralManagementData) {
this.quantity = quantity;
this.client = client;
this.collateral = collateralManagementData;
}
public ClientCollateralManagement createNew(JsonCommand jsonCommand) {
BigDecimal quantity = jsonCommand.bigDecimalValueOfParameterNamed("quantity");
return new ClientCollateralManagement(quantity);
}
public static ClientCollateralManagement createNew(final BigDecimal quantity, final Client client,
final CollateralManagementDomain collateral) {
return new ClientCollateralManagement(quantity, client, collateral);
}
public Map<String, Object> update(JsonCommand command) {
final Map<String, Object> changes = new LinkedHashMap<>(3);
final String quantity = CollateralManagementJsonInputParams.QUANTITY.getValue();
if (command.isChangeInBigDecimalParameterNamed(quantity, this.quantity)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(quantity);
this.quantity = newValue;
changes.put(quantity, this.quantity);
}
return changes;
}
public BigDecimal getTotal() {
if (BigDecimal.ZERO.compareTo(this.quantity) == 0) {
return BigDecimal.ZERO;
}
return this.quantity.multiply(getCollaterals().getBasePrice());
}
public BigDecimal getTotalCollateral(BigDecimal total) {
if (BigDecimal.ZERO.compareTo(total) == 0) {
return BigDecimal.ZERO;
}
return total.multiply(getCollaterals().getPctToBase().divide(BigDecimal.valueOf(100)));
}
public void updateQuantity(BigDecimal quantity) {
this.quantity = quantity;
}
public void updateQuantityAfterLoanClosed(BigDecimal quantity) {
this.quantity = this.quantity.add(quantity);
}
public BigDecimal getQuantity() {
return this.quantity;
}
public Client getClient() {
return this.client;
}
public CollateralManagementDomain getCollaterals() {
return this.collateral;
}
public Set<LoanCollateralManagement> getLoanCollateralManagementSet() {
return this.loanCollateralManagementSet;
}
}
@@ -0,0 +1,160 @@
/**
* 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.collateralmanagement.domain;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.portfolio.collateralmanagement.api.CollateralManagementJsonInputParams;
@Entity
@Table(name = "m_collateral_management")
public class CollateralManagementDomain extends AbstractPersistableCustom {
@Column(name = "name", length = 20, columnDefinition = " ")
private String name;
@Column(name = "quality", nullable = false, length = 40)
private String quality;
@Column(name = "base_price", nullable = false, scale = 5, precision = 20)
private BigDecimal basePrice;
@Column(name = "unit_type", nullable = false, length = 10)
private String unitType;
@Column(name = "pct_to_base", nullable = false, scale = 5, precision = 20)
private BigDecimal pctToBase;
@ManyToOne
@JoinColumn(name = "currency")
private ApplicationCurrency currency;
@OneToMany(mappedBy = "collateral", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Set<ClientCollateralManagement> clientCollateralManagements = new HashSet<>();
public CollateralManagementDomain() {
}
private CollateralManagementDomain(final String quality, final BigDecimal basePrice, final String unitType, final BigDecimal pctToBase,
final ApplicationCurrency currency, final String name) {
this.basePrice = basePrice;
this.currency = currency;
this.pctToBase = pctToBase;
this.unitType = unitType;
this.quality = quality;
this.name = name;
}
public static CollateralManagementDomain createNew(JsonCommand jsonCommand, final ApplicationCurrency applicationCurrency) {
String quality = jsonCommand.stringValueOfParameterNamed("quality");
BigDecimal basePrice = jsonCommand.bigDecimalValueOfParameterNamed("basePrice");
BigDecimal pctToBase = jsonCommand.bigDecimalValueOfParameterNamedDefaultToNullIfZero("pctToBase");
String unitType = jsonCommand.stringValueOfParameterNamed("unitType");
String name = jsonCommand.stringValueOfParameterNamed("name");
return new CollateralManagementDomain(quality, basePrice, unitType, pctToBase, applicationCurrency, name);
}
public Map<String, Object> update(final JsonCommand command, final ApplicationCurrency applicationCurrency) {
final Map<String, Object> changes = new LinkedHashMap<>(5);
final String nameParamName = CollateralManagementJsonInputParams.NAME.getValue();
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(nameParamName);
this.name = StringUtils.defaultIfEmpty(newValue, null);
changes.put(nameParamName, this.name);
}
final String qualityParamName = CollateralManagementJsonInputParams.QUALITY.getValue();
if (command.isChangeInStringParameterNamed(qualityParamName, this.quality)) {
final String newValue = command.stringValueOfParameterNamed(qualityParamName);
this.quality = newValue;
changes.put(qualityParamName, this.quality);
}
final String unitTypeParamName = CollateralManagementJsonInputParams.UNIT_TYPE.getValue();
if (command.isChangeInStringParameterNamed(unitTypeParamName, this.unitType)) {
final String newValue = command.stringValueOfParameterNamed(unitTypeParamName);
this.unitType = newValue;
changes.put(unitTypeParamName, this.unitType);
}
this.currency = applicationCurrency;
final String basePriceParamName = CollateralManagementJsonInputParams.BASE_PRICE.getValue();
if (command.isChangeInBigDecimalParameterNamed(basePriceParamName, this.basePrice)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(basePriceParamName);
this.basePrice = newValue;
changes.put(basePriceParamName, this.basePrice);
}
final String pctToBaseParamName = CollateralManagementJsonInputParams.PCT_TO_BASE.getValue();
if (command.isChangeInBigDecimalParameterNamed(pctToBaseParamName, this.pctToBase)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(pctToBaseParamName);
this.pctToBase = newValue;
changes.put(pctToBaseParamName, this.pctToBase);
}
return changes;
}
public String getQuality() {
return this.quality;
}
public String getUnitType() {
return this.unitType;
}
public ApplicationCurrency getCurrency() {
return this.currency;
}
public BigDecimal getBasePrice() {
return this.basePrice;
}
public BigDecimal getPctToBase() {
return this.pctToBase;
}
public String getName() {
return this.name;
}
public Set<ClientCollateralManagement> getClientCollateralManagements() {
return this.clientCollateralManagements;
}
}
@@ -0,0 +1,175 @@
/**
* 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.api;
public interface LoanApiConstants {
String emiAmountParameterName = "fixedEmiAmount";
String maxOutstandingBalanceParameterName = "maxOutstandingLoanBalance";
String disbursementDataParameterName = "disbursementData";
String expectedDisbursementDateParameterName = "expectedDisbursementDate";
String disbursementPrincipalParameterName = "principal";
String principalParameterName = "principal";
String disbursementReversedParameterName = "reversed";
String disbursementNetDisbursalAmountParameterName = "netDisbursalAmount";
String updatedDisbursementDateParameterName = "updatedExpectedDisbursementDate";
String updatedDisbursementPrincipalParameterName = "updatedPrincipal";
String disbursementIdParameterName = "id";
String loanChargeIdParameterName = "loanChargeId";
String chargeIdParameterName = "chargeId";
String amountParameterName = "amount";
String chargeTimeTypeParameterName = "chargeTimeType";
String chargeCalculationTypeParameterName = "chargeCalculationType";
String principalDisbursedParameterName = "transactionAmount";
String chargesParameterName = "charges";
String loanIdTobeApproved = "loanId";
String postDatedChecks = "postDatedChecks";
String approvedLoanAmountParameterName = "approvedLoanAmount";
String approvedOnDateParameterName = "approvedOnDate";
String noteParameterName = "note";
String localeParameterName = "locale";
String dateFormatParameterName = "dateFormat";
String rejectedOnDateParameterName = "rejectedOnDate";
String withdrawnOnDateParameterName = "withdrawnOnDate";
String transactionProcessingStrategyCodeParameterName = "transactionProcessingStrategyCode";
String loanPurposeIdParameterName = "loanPurposeId";
String loanOfficerIdParameterName = "loanOfficerId";
String fundIdParameterName = "fundId";
String externalIdParameterName = "externalId";
String accountNoParameterName = "accountNo";
String productIdParameterName = "productId";
String calendarIdParameterName = "calendarId";
String loanTypeParameterName = "loanType";
String groupIdParameterName = "groupId";
String clientIdParameterName = "clientId";
String idParameterName = "id";
String graceOnInterestChargedParameterName = "graceOnInterestCharged";
String graceOnInterestPaymentParameterName = "graceOnInterestPayment";
String graceOnPrincipalPaymentParameterName = "graceOnPrincipalPayment";
String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
String interestRateFrequencyTypeParameterName = "interestRateFrequencyType";
String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType";
String interestTypeParameterName = "interestType";
String amortizationTypeParameterName = "amortizationType";
String amortizationTypeOptionsParameterName = "amortizationTypeOptions";
String repaymentFrequencyTypeParameterName = "repaymentFrequencyType";
String loanTermFrequencyTypeParameterName = "loanTermFrequencyType";
String loanTermFrequencyParameterName = "loanTermFrequency";
String numberOfRepaymentsParameterName = "numberOfRepayments";
String repaymentEveryParameterName = "repaymentEvery";
String interestRatePerPeriodParameterName = "interestRatePerPeriod";
String inArrearsToleranceParameterName = "inArrearsTolerance";
String interestChargedFromDateParameterName = "interestChargedFromDate";
String submittedOnDateParameterName = "submittedOnDate";
String submittedOnNoteParameterName = "submittedOnNote";
String collateralParameterName = "collateral";
String collateralsParameterName = "collaterals";
String clientCollateralIdParameterName = "clientCollateralId";
String quantityParameterName = "quantity";
String syncDisbursementWithMeetingParameterName = "syncDisbursementWithMeeting";
String linkAccountIdParameterName = "linkAccountId";
String createStandingInstructionAtDisbursementParameterName = "createStandingInstructionAtDisbursement";
String daysInYearTypeParameterName = "daysInYearType";
String daysInMonthTypeParameterName = "daysInMonthType";
String MULTIDISBURSE_DETAILS_PARAMNAME = "multiDisburseDetails";
String EMI_AMOUNT_VARIATIONS_PARAMNAME = "emiAmountVariations";
String COLLECTION_PARAMNAME = "collection";
// Interest recalculation related
String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
String repaymentFrequencyNthDayTypeParameterName = "repaymentFrequencyNthDayType";
String repaymentFrequencyDayOfWeekTypeParameterName = "repaymentFrequencyDayOfWeekType";
// Floating interest rate related
String interestRateDifferentialParameterName = "interestRateDifferential";
String isFloatingInterestRateParameterName = "isFloatingInterestRate";
// Error codes
String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE = "loancharge.with.calculation.type.interest.not.allowed";
String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE = "loancharge.with.calculation.type.principal.not.allowed";
String DISBURSEMENT_DATE_START_WITH_ERROR = "first.disbursement.date.must.start.with.expected.disbursement.date";
String PRINCIPAL_AMOUNT_SHOULD_BE_SAME = "sum.of.multi.disburse.amounts.must.equal.with.total.principal";
String DISBURSEMENT_DATE_UNIQUE_ERROR = "disbursement.date.must.be.unique.for.tranches";
String ALREADY_DISBURSED = "can.not.change.disbursement.date";
String APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES = "sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal";
String DISBURSEMENT_DATES_NOT_IN_ORDER = "disbursements.should.be.ordered.based.on.their.disbursement.dates";
String DISBURSEMENT_DATE_BEFORE_ERROR = "disbursement.date.of.tranche.cannot.be.before.expected.disbursement.date";
String LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT = "sum.of.multi.collateral.values.must.equal.or.greater.than.principal.amount";
String isFloatingInterestRate = "isFloatingInterestRate";
String interestRateDifferential = "interestRateDifferential";
String exceptionParamName = "exceptions";
String modifiedinstallmentsParamName = "modifiedinstallments";
String newinstallmentsParamName = "newinstallments";
String deletedinstallmentsParamName = "deletedinstallments";
String dueDateParamName = "dueDate";
String modifiedDueDateParamName = "modifiedDueDate";
String principalParamName = "principal";
String parentAccountParamName = "isParentAccount";
String totalLoanParamName = "totalLoan";
String installmentAmountParamName = "installmentAmount";
// loan write off
String WRITEOFFREASONS = "WriteOffReasons";
// loan charge-off
String CHARGE_OFF_REASONS = "ChargeOffReasons";
// fore closure constants
String transactionDateParamName = "transactionDate";
String noteParamName = "note";
String canUseForTopup = "canUseForTopup";
String clientActiveLoanOptions = "clientActiveLoanOptions";
String isTopup = "isTopup";
String loanIdToClose = "loanIdToClose";
String topupAmount = "topupAmount";
String datatables = "datatables";
String isEqualAmortizationParam = "isEqualAmortization";
String applicationId = "applicationId";
String lastApplication = "lastApplication";
String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment";
String LOAN_ASSOCIATIONS_ALL = "all";
String chargeOffReasonIdParamName = "chargeOffReasonId";
// Reversal Transation Data
String REVERSAL_EXTERNAL_ID_PARAMNAME = "reversalExternalId";
String REVERSED_ON_DATE_PARAMNAME = "reversedOnDate";
String DISALLOW_EXPECTED_DISBURSEMENTS = "disallowExpectedDisbursements";
String TRANSACTION_AMOUNT_PARAMNAME = "transactionAmount";
String PAYMENT_TYPE_PARAMNAME = "paymentTypeId";
String FRAUD_ATTRIBUTE_NAME = "fraud";
// Commands
String CHARGEBACK_TRANSACTION_COMMAND = "chargeback";
String MARK_AS_FRAUD_COMMAND = "markAsFraud";
// Data Validator names
String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud";
}
@@ -0,0 +1,59 @@
/**
* 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.command;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
/**
* Java object representation of {@link LoanCharge} API JSON.
*/
public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
@SuppressWarnings("unused")
private final Long id;
private final Long chargeId;
private final BigDecimal amount;
@SuppressWarnings("unused")
private final Integer chargeTimeType;
@SuppressWarnings("unused")
private final Integer chargeCalculationType;
@SuppressWarnings("unused")
private final LocalDate dueDate;
public LoanChargeCommand(final Long id, final Long chargeId, final BigDecimal amount, final Integer chargeTimeType,
final Integer chargeCalculationType, final LocalDate dueDate) {
this.id = id;
this.chargeId = chargeId;
this.amount = amount;
this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType;
this.dueDate = dueDate;
}
@Override
public int compareTo(final LoanChargeCommand o) {
int comparison = this.chargeId.compareTo(o.chargeId);
if (comparison == 0) {
comparison = this.amount.compareTo(o.amount);
}
return comparison;
}
}
@@ -0,0 +1,109 @@
/**
* 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.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Immutable data object representing disbursement information.
*/
@RequiredArgsConstructor
@Getter
public final class DisbursementData implements Comparable<DisbursementData> {
private final Long id;
private final LocalDate expectedDisbursementDate;
private final LocalDate actualDisbursementDate;
private final BigDecimal principal;
private final BigDecimal netDisbursalAmount;
private final String loanChargeId;
private final BigDecimal chargeAmount;
private final BigDecimal waivedChargeAmount;
// import fields
private transient Integer rowIndex;
private String dateFormat;
private String locale;
private String note;
private transient String linkAccountId;
public static DisbursementData importInstance(LocalDate actualDisbursementDate, String linkAccountId, Integer rowIndex, String locale,
String dateFormat) {
return new DisbursementData(actualDisbursementDate, linkAccountId, rowIndex, locale, dateFormat);
}
private DisbursementData(LocalDate actualDisbursementDate, String linkAccountId, Integer rowIndex, String locale, String dateFormat) {
this.dateFormat = dateFormat;
this.locale = locale;
this.actualDisbursementDate = actualDisbursementDate;
this.rowIndex = rowIndex;
this.note = "";
this.linkAccountId = linkAccountId;
this.id = null;
this.expectedDisbursementDate = null;
this.principal = null;
this.loanChargeId = null;
this.chargeAmount = null;
this.waivedChargeAmount = null;
this.netDisbursalAmount = null;
}
public LocalDate disbursementDate() {
LocalDate disbursementDate = this.expectedDisbursementDate;
if (this.actualDisbursementDate != null) {
disbursementDate = this.actualDisbursementDate;
}
return disbursementDate;
}
public boolean isDisbursed() {
return this.actualDisbursementDate != null;
}
@Override
public int compareTo(final DisbursementData obj) {
if (obj == null) {
return -1;
}
return obj.expectedDisbursementDate.compareTo(this.expectedDisbursementDate);
}
public boolean isDueForDisbursement(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) {
final LocalDate dueDate = disbursementDate();
return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate);
}
private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive,
final LocalDate target) {
return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive);
}
public BigDecimal getWaivedChargeAmount() {
if (this.waivedChargeAmount == null) {
return BigDecimal.ZERO;
}
return this.waivedChargeAmount;
}
}
@@ -0,0 +1,69 @@
/**
* 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.List;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
public class HolidayDetailDTO {
final boolean isHolidayEnabled;
final List<Holiday> holidays;
final WorkingDays workingDays;
final boolean allowTransactionsOnHoliday;
final boolean allowTransactionsOnNonWorkingDay;
public HolidayDetailDTO(final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
this.isHolidayEnabled = isHolidayEnabled;
this.holidays = holidays;
this.workingDays = workingDays;
this.allowTransactionsOnHoliday = false;
this.allowTransactionsOnNonWorkingDay = false;
}
public HolidayDetailDTO(final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay) {
this.isHolidayEnabled = isHolidayEnabled;
this.holidays = holidays;
this.workingDays = workingDays;
this.allowTransactionsOnHoliday = allowTransactionsOnHoliday;
this.allowTransactionsOnNonWorkingDay = allowTransactionsOnNonWorkingDay;
}
public boolean isHolidayEnabled() {
return this.isHolidayEnabled;
}
public List<Holiday> getHolidays() {
return this.holidays;
}
public WorkingDays getWorkingDays() {
return this.workingDays;
}
public boolean isAllowTransactionsOnHoliday() {
return this.allowTransactionsOnHoliday;
}
public boolean isAllowTransactionsOnNonWorkingDay() {
return this.allowTransactionsOnNonWorkingDay;
}
}
@@ -0,0 +1,430 @@
/**
* 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.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
/**
* Immutable data object for loan charge data.
*/
@Getter
@Builder
@AllArgsConstructor
public class LoanChargeData {
private final Long id;
private final Long chargeId;
private final String name;
private final EnumOptionData chargeTimeType;
private final LocalDate submittedOnDate;
private final LocalDate dueDate;
private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage;
private final BigDecimal amountPercentageAppliedTo;
private final CurrencyData currency;
private final BigDecimal amount;
private final BigDecimal amountPaid;
private final BigDecimal amountWaived;
private final BigDecimal amountWrittenOff;
private final BigDecimal amountOutstanding;
private final BigDecimal amountOrPercentage;
private final Collection<ChargeData> chargeOptions;
private final boolean penalty;
private final EnumOptionData chargePaymentMode;
private final boolean paid;
private final boolean waived;
private final boolean chargePayable;
private final Long loanId;
private final BigDecimal minCap;
private final BigDecimal maxCap;
private final Collection<LoanInstallmentChargeData> installmentChargeData;
private BigDecimal amountAccrued;
private BigDecimal amountUnrecognized;
private final ExternalId externalId;
private final ExternalId externalLoanId;
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,
ExternalId.empty(), null, null, null, null, ExternalId.empty());
}
/**
* used when populating with details from charge definition (for crud on charges)
*/
public static LoanChargeData newLoanChargeDetails(final Long chargeId, final String name, final CurrencyData currency,
final BigDecimal amount, final BigDecimal percentage, final EnumOptionData chargeTimeType,
final EnumOptionData chargeCalculationType, final boolean penalty, final EnumOptionData chargePaymentMode,
final BigDecimal minCap, final BigDecimal maxCap, final ExternalId externalId) {
return new LoanChargeData(null, chargeId, name, currency, amount, percentage, chargeTimeType, chargeCalculationType, null, penalty,
chargePaymentMode, false, false, null, ExternalId.empty(), minCap, maxCap, null, null, externalId);
}
public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate submittedOnDate,
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) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
this.currency = currency;
this.amount = amount;
this.amountPaid = amountPaid;
this.amountWaived = amountWaived;
this.amountWrittenOff = amountWrittenOff;
this.amountOutstanding = amountOutstanding;
this.chargeTimeType = chargeTimeType;
this.submittedOnDate = submittedOnDate;
this.dueDate = dueDate;
this.chargeCalculationType = chargeCalculationType;
this.percentage = percentage;
this.amountPercentageAppliedTo = amountPercentageAppliedTo;
this.penalty = penalty;
this.chargePaymentMode = chargePaymentMode;
this.paid = paid;
this.waived = waived;
this.minCap = minCap;
this.maxCap = maxCap;
if (amountOrPercentage == null) {
if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) {
this.amountOrPercentage = this.percentage;
} else {
this.amountOrPercentage = amount;
}
} else {
this.amountOrPercentage = amountOrPercentage;
}
this.chargeOptions = null;
this.chargePayable = isChargePayable();
this.loanId = loanId;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = externalId;
this.externalLoanId = externalLoanId;
}
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 boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
this.currency = currency;
this.amount = amount;
this.amountPaid = BigDecimal.ZERO;
this.amountWaived = BigDecimal.ZERO;
this.amountWrittenOff = BigDecimal.ZERO;
this.amountOutstanding = amount;
this.chargeTimeType = chargeTimeType;
this.submittedOnDate = null;
this.dueDate = null;
this.chargeCalculationType = chargeCalculationType;
this.percentage = percentage;
this.amountPercentageAppliedTo = null;
this.penalty = penalty;
this.chargePaymentMode = chargePaymentMode;
this.paid = paid;
this.waived = waived;
if (amountOrPercentage == null) {
if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) {
this.amountOrPercentage = this.percentage;
} else {
this.amountOrPercentage = amount;
}
} else {
this.amountOrPercentage = amountOrPercentage;
}
this.chargeOptions = chargeOptions;
this.chargePayable = isChargePayable();
this.loanId = loanId;
this.externalLoanId = externalLoanId;
this.minCap = minCap;
this.maxCap = maxCap;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = externalId;
}
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) {
this.id = id;
this.chargeId = null;
this.name = null;
this.currency = null;
this.amount = null;
this.amountPaid = null;
this.amountWaived = null;
this.amountWrittenOff = null;
this.amountOutstanding = amountOutstanding;
this.chargeTimeType = chargeTimeType;
this.submittedOnDate = submittedOnDate;
this.dueDate = dueAsOfDate;
this.chargeCalculationType = null;
this.percentage = null;
this.amountPercentageAppliedTo = null;
this.penalty = false;
this.chargePaymentMode = null;
this.paid = false;
this.waived = false;
this.amountOrPercentage = null;
this.chargeOptions = null;
this.chargePayable = false;
this.loanId = loanId;
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = externalId;
this.externalLoanId = externalLoanId;
}
public LoanChargeData(final Long id, final Long chargeId, final LocalDate dueAsOfDate, final LocalDate submittedOnDate,
EnumOptionData chargeTimeType, final BigDecimal amount, final BigDecimal amountAccrued, final BigDecimal amountWaived,
final boolean penalty, final ExternalId externalId) {
this.id = id;
this.chargeId = chargeId;
this.name = null;
this.currency = null;
this.amount = amount;
this.amountPaid = null;
this.amountWaived = amountWaived;
this.amountWrittenOff = null;
this.amountOutstanding = null;
this.chargeTimeType = chargeTimeType;
this.submittedOnDate = submittedOnDate;
this.dueDate = dueAsOfDate;
this.chargeCalculationType = null;
this.percentage = null;
this.amountPercentageAppliedTo = null;
this.penalty = penalty;
this.chargePaymentMode = null;
this.paid = false;
this.waived = false;
this.amountOrPercentage = null;
this.chargeOptions = null;
this.chargePayable = false;
this.loanId = null;
this.externalLoanId = ExternalId.empty();
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = null;
this.amountAccrued = amountAccrued;
this.amountUnrecognized = null;
this.externalId = externalId;
}
public LoanChargeData(final BigDecimal amountUnrecognized, final LoanChargeData chargeData) {
this.id = chargeData.id;
this.chargeId = chargeData.chargeId;
this.name = null;
this.currency = null;
this.amount = chargeData.amount;
this.amountPaid = null;
this.amountWaived = chargeData.amountWaived;
this.amountWrittenOff = null;
this.amountOutstanding = null;
this.chargeTimeType = chargeData.chargeTimeType;
this.submittedOnDate = chargeData.submittedOnDate;
this.dueDate = chargeData.dueDate;
this.chargeCalculationType = null;
this.percentage = null;
this.amountPercentageAppliedTo = null;
this.penalty = chargeData.penalty;
this.chargePaymentMode = null;
this.paid = false;
this.waived = false;
this.amountOrPercentage = null;
this.chargeOptions = null;
this.chargePayable = false;
this.loanId = null;
this.externalLoanId = ExternalId.empty();
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = null;
this.amountAccrued = chargeData.amountAccrued;
this.amountUnrecognized = amountUnrecognized;
this.externalId = chargeData.externalId;
}
public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
this.id = chargeData.id;
this.chargeId = chargeData.chargeId;
this.name = chargeData.name;
this.currency = chargeData.currency;
this.amount = chargeData.amount;
this.amountPaid = chargeData.amountPaid;
this.amountWaived = chargeData.amountWaived;
this.amountWrittenOff = chargeData.amountWrittenOff;
this.amountOutstanding = chargeData.amountOutstanding;
this.chargeTimeType = chargeData.chargeTimeType;
this.submittedOnDate = chargeData.submittedOnDate;
this.dueDate = chargeData.dueDate;
this.chargeCalculationType = chargeData.chargeCalculationType;
this.percentage = chargeData.percentage;
this.amountPercentageAppliedTo = chargeData.amountPercentageAppliedTo;
this.penalty = chargeData.penalty;
this.chargePaymentMode = chargeData.chargePaymentMode;
this.paid = chargeData.paid;
this.waived = chargeData.waived;
this.minCap = chargeData.minCap;
this.maxCap = chargeData.maxCap;
this.amountOrPercentage = chargeData.amountOrPercentage;
this.chargeOptions = chargeData.chargeOptions;
this.chargePayable = chargeData.chargePayable;
this.loanId = chargeData.loanId;
this.externalLoanId = chargeData.externalLoanId;
this.installmentChargeData = installmentChargeData;
this.amountAccrued = chargeData.amountAccrued;
this.amountUnrecognized = chargeData.amountUnrecognized;
this.externalId = chargeData.externalId;
}
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amount, final EnumOptionData chargeCalculationType,
final EnumOptionData chargeTimeType) {
this.id = null;
this.chargeId = id;
this.name = null;
this.currency = null;
this.amount = amount;
this.amountPaid = null;
this.amountWaived = null;
this.amountWrittenOff = null;
this.amountOutstanding = null;
this.chargeTimeType = chargeTimeType;
this.submittedOnDate = null;
this.dueDate = dueAsOfDate;
this.chargeCalculationType = chargeCalculationType;
this.percentage = null;
this.amountPercentageAppliedTo = null;
this.penalty = false;
this.chargePaymentMode = null;
this.paid = false;
this.waived = false;
this.amountOrPercentage = null;
this.chargeOptions = null;
this.chargePayable = false;
this.loanId = null;
this.externalLoanId = ExternalId.empty();
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = null;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = ExternalId.empty();
}
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOrPercentage) {
this.id = id;
this.chargeId = null;
this.name = null;
this.currency = null;
this.amount = null;
this.amountPaid = null;
this.amountWaived = null;
this.amountWrittenOff = null;
this.amountOutstanding = null;
this.chargeTimeType = null;
this.submittedOnDate = null;
this.dueDate = dueAsOfDate;
this.chargeCalculationType = null;
this.percentage = null;
this.amountPercentageAppliedTo = null;
this.penalty = false;
this.chargePaymentMode = null;
this.paid = false;
this.waived = false;
this.amountOrPercentage = amountOrPercentage;
this.chargeOptions = null;
this.chargePayable = false;
this.loanId = null;
this.externalLoanId = ExternalId.empty();
this.minCap = null;
this.maxCap = null;
this.installmentChargeData = null;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = ExternalId.empty();
}
public boolean isChargePayable() {
boolean isAccountTransfer = false;
if (this.chargePaymentMode != null) {
isAccountTransfer = ChargePaymentMode.fromInt(this.chargePaymentMode.getId().intValue()).isPaymentModeAccountTransfer();
}
return isAccountTransfer && !this.paid && !this.waived;
}
public boolean isInstallmentFee() {
boolean isInstalmentFee = false;
if (this.chargeTimeType != null) {
isInstalmentFee = ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isInstalmentFee();
}
return isInstalmentFee;
}
public void updateAmountAccrued(BigDecimal amountAccrued) {
this.amountAccrued = amountAccrued;
}
}
@@ -0,0 +1,46 @@
/**
* 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.math.BigDecimal;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class LoanChargePaidByData {
private final Long id;
private final BigDecimal amount;
private final Integer installmentNumber;
private final Long chargeId;
private final Long transactionId;
private final String name;
public LoanChargePaidByData(final Long id, final BigDecimal amount, final Integer installmentNumber, final Long chargeId,
final Long transactionId) {
this.id = id;
this.amount = amount;
this.installmentNumber = installmentNumber;
this.chargeId = chargeId;
this.transactionId = transactionId;
this.name = null;
}
}
@@ -0,0 +1,47 @@
/**
* 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 org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
public class LoanChargePaidDetail {
private final Money amount;
private final LoanRepaymentScheduleInstallment installment;
private final boolean isFeeCharge;
public LoanChargePaidDetail(Money amount, LoanRepaymentScheduleInstallment installment, boolean isFeeCharge) {
this.amount = amount;
this.installment = installment;
this.isFeeCharge = isFeeCharge;
}
public Money getAmount() {
return this.amount;
}
public LoanRepaymentScheduleInstallment getInstallment() {
return this.installment;
}
public boolean isFeeCharge() {
return this.isFeeCharge;
}
}
@@ -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 java.math.BigDecimal;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class LoanCollateralManagementData {
private final Long clientCollateralId;
private final BigDecimal quantity;
private final BigDecimal total;
private final BigDecimal totalCollateral;
private final Long id;
}
@@ -0,0 +1,39 @@
/**
* 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.math.BigDecimal;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class LoanInstallmentChargeData {
private final Integer installmentNumber;
private final LocalDate dueDate;
private final BigDecimal amount;
private final BigDecimal amountOutstanding;
private final BigDecimal amountWaived;
private final boolean paid;
private final boolean waived;
private BigDecimal amountAccrued;
private BigDecimal amountUnrecognized;
}
@@ -0,0 +1,43 @@
/**
* 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.math.BigDecimal;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public final class LoanRepaymentScheduleInstallmentData {
private Long id;
private Integer installmentId;
private LocalDate date;
private BigDecimal amount;
public static LoanRepaymentScheduleInstallmentData instanceOf(final Long id, final Integer installmentId, final LocalDate date,
final BigDecimal amount) {
return new LoanRepaymentScheduleInstallmentData(id, installmentId, date, amount);
}
}
@@ -0,0 +1,54 @@
/**
* 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.Getter;
/**
* Immutable data object represent loan status enumerations.
*/
@Getter
public class LoanStatusEnumData {
private final Long id;
private final String code;
private final String value;
private final boolean pendingApproval;
private final boolean waitingForDisbursal;
private final boolean active;
private final boolean closedObligationsMet;
private final boolean closedWrittenOff;
private final boolean closedRescheduled;
private final boolean closed;
private final boolean overpaid;
public LoanStatusEnumData(final Long id, final String code, final String value) {
this.id = id;
this.code = code;
this.value = value;
this.pendingApproval = Long.valueOf(100).equals(this.id);
this.waitingForDisbursal = Long.valueOf(200).equals(this.id);
this.active = Long.valueOf(300).equals(this.id);
this.closedObligationsMet = Long.valueOf(600).equals(this.id);
this.closedWrittenOff = Long.valueOf(601).equals(this.id);
this.closedRescheduled = Long.valueOf(602).equals(this.id);
this.closed = this.closedObligationsMet || this.closedWrittenOff || this.closedRescheduled;
this.overpaid = Long.valueOf(700).equals(this.id);
}
}
@@ -0,0 +1,109 @@
/**
* 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.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
@Getter
public class LoanTermVariationsData implements Comparable<LoanTermVariationsData> {
private final Long id;
private final EnumOptionData termType;
private LocalDate termVariationApplicableFrom;
private final BigDecimal decimalValue;
private final LocalDate dateValue;
private final boolean isSpecificToInstallment;
private Boolean isProcessed;
public LoanTermVariationsData(final Long id, final EnumOptionData termType, final LocalDate termVariationApplicableFrom,
final BigDecimal decimalValue, final LocalDate dateValue, final boolean isSpecificToInstallment) {
this.id = id;
this.termType = termType;
this.termVariationApplicableFrom = termVariationApplicableFrom;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
}
public LoanTermVariationsData(final EnumOptionData termType, final LocalDate termVariationApplicableFrom, final BigDecimal decimalValue,
LocalDate dateValue, final boolean isSpecificToInstallment) {
this.id = null;
this.termType = termType;
this.termVariationApplicableFrom = termVariationApplicableFrom;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
}
public LoanTermVariationType getTermVariationType() {
return LoanTermVariationType.fromInt(this.termType.getId().intValue());
}
public boolean isApplicable(final LocalDate fromDate, final LocalDate dueDate) {
return occursOnDayFromAndUpTo(fromDate, dueDate, this.termVariationApplicableFrom);
}
private boolean occursOnDayFromAndUpTo(final LocalDate fromNotInclusive, final LocalDate upToInclusive, final LocalDate target) {
return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToInclusive);
}
public boolean isApplicable(final LocalDate fromDate) {
return occursBefore(fromDate, this.termVariationApplicableFrom);
}
private boolean occursBefore(final LocalDate date, final LocalDate target) {
return target != null && !target.isAfter(date);
}
public boolean isSpecificToInstallment() {
return this.isSpecificToInstallment;
}
public boolean isIsSpecificToInstallment() {
return this.isSpecificToInstallment;
}
public Boolean isProcessed() {
return this.isProcessed == null ? false : this.isProcessed;
}
public void setProcessed(Boolean isProcessed) {
this.isProcessed = isProcessed;
}
@Override
public int compareTo(LoanTermVariationsData o) {
int comparsion = getTermVariationApplicableFrom().compareTo(o.getTermVariationApplicableFrom());
if (comparsion == 0) {
if (o.getTermVariationType().isDueDateVariation() || o.getTermVariationType().isInsertInstallment()) {
comparsion = 1;
}
}
return comparsion;
}
public void setApplicableFromDate(final LocalDate applicableFromDate) {
this.termVariationApplicableFrom = applicableFromDate;
}
}
@@ -0,0 +1,174 @@
/**
* 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.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
public class LoanTermVariationsDataWrapper {
private final List<LoanTermVariationsData> exceptionData;
private ListIterator<LoanTermVariationsData> iterator;
private final List<LoanTermVariationsData> interestRateChanges;
private final List<LoanTermVariationsData> interestRateFromInstallment;
private final List<LoanTermVariationsData> dueDateVariation;
private ListIterator<LoanTermVariationsData> dueDateIterator;
public LoanTermVariationsDataWrapper(final List<LoanTermVariationsData> exceptionData) {
if (exceptionData == null) {
this.exceptionData = new ArrayList<>(1);
} else {
this.exceptionData = exceptionData;
}
this.interestRateChanges = new ArrayList<>();
this.dueDateVariation = new ArrayList<>();
this.interestRateFromInstallment = new ArrayList<>();
deriveLoanTermVariations();
}
public boolean hasVariation(final LocalDate date) {
ListIterator<LoanTermVariationsData> iterator = this.iterator;
return hasNext(date, iterator);
}
private boolean hasNext(final LocalDate date, ListIterator<LoanTermVariationsData> iterator) {
boolean hasVariation = false;
if (iterator.hasNext()) {
LoanTermVariationsData loanTermVariationsData = iterator.next();
if (!loanTermVariationsData.getTermVariationApplicableFrom().isAfter(date)) {
hasVariation = true;
}
iterator.previous();
}
return hasVariation;
}
public boolean hasDueDateVariation(final LocalDate date) {
ListIterator<LoanTermVariationsData> iterator = this.dueDateIterator;
return hasNext(date, iterator);
}
public LoanTermVariationsData nextVariation() {
return this.iterator.next();
}
public LoanTermVariationsData nextDueDateVariation() {
return this.dueDateIterator.next();
}
public LoanTermVariationsData previousDueDateVariation() {
return this.dueDateIterator.previous();
}
public List<LoanTermVariationsData> getInterestRateChanges() {
return this.interestRateChanges;
}
public List<LoanTermVariationsData> getDueDateVariation() {
return this.dueDateVariation;
}
public List<LoanTermVariationsData> getExceptionData() {
return this.exceptionData;
}
public void setExceptionData(final List<LoanTermVariationsData> exceptionData) {
clearTerms();
this.exceptionData.addAll(exceptionData);
deriveLoanTermVariations();
}
public void clearTerms() {
this.exceptionData.clear();
this.interestRateChanges.clear();
this.dueDateVariation.clear();
this.interestRateFromInstallment.clear();
}
public List<LoanTermVariationsData> getInterestRateFromInstallment() {
return this.interestRateFromInstallment;
}
public int adjustNumberOfRepayments() {
int repaymetsForAdjust = 0;
for (LoanTermVariationsData loanTermVariations : this.exceptionData) {
if (loanTermVariations.getTermVariationType().isInsertInstallment()) {
repaymetsForAdjust++;
} else if (loanTermVariations.getTermVariationType().isDeleteInstallment()) {
repaymetsForAdjust--;
}
}
return repaymetsForAdjust;
}
public LoanTermVariationsData fetchLoanTermDueDateVariationsData(final LocalDate onDate) {
LoanTermVariationsData data = null;
for (LoanTermVariationsData termVariationsData : this.dueDateVariation) {
if (onDate.isEqual(termVariationsData.getTermVariationApplicableFrom())) {
data = termVariationsData;
break;
}
}
return data;
}
public boolean hasExceptionVariation(final LocalDate date, ListIterator<LoanTermVariationsData> exceptionDataListIterator) {
ListIterator<LoanTermVariationsData> iterator = exceptionDataListIterator;
return hasNext(date, iterator);
}
public void updateLoanTermVariationsData(final List<LoanTermVariationsData> exceptionData) {
if (this.exceptionData != null && exceptionData != null && exceptionData.size() > 0) {
this.exceptionData.addAll(exceptionData);
deriveLoanTermVariations();
}
}
private void deriveLoanTermVariations() {
Collections.sort(this.exceptionData);
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) {
this.interestRateChanges.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isDueDateVariation()) {
this.dueDateVariation.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isInterestRateFromInstallment()) {
this.interestRateFromInstallment.add(loanTermVariationsData);
}
}
Collections.sort(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateChanges);
this.exceptionData.removeAll(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateFromInstallment);
this.iterator = this.exceptionData.listIterator();
this.dueDateIterator = this.dueDateVariation.listIterator();
}
public void resetVariations() {
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
loanTermVariationsData.setProcessed(false);
}
this.iterator = this.exceptionData.listIterator();
this.dueDateIterator = this.dueDateVariation.listIterator();
}
}
@@ -0,0 +1,385 @@
/**
* 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.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import lombok.Getter;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
/**
* Immutable data object representing a loan transaction.
*/
@Getter
public class LoanTransactionData {
private final Long id;
private final Long loanId;
private final ExternalId externalLoanId;
private final Long officeId;
private final String officeName;
private final LoanTransactionEnumData type;
private final LocalDate date;
private final CurrencyData currency;
private final PaymentDetailData paymentDetailData;
private final BigDecimal amount;
private final BigDecimal netDisbursalAmount;
private final BigDecimal principalPortion;
private final BigDecimal interestPortion;
private final BigDecimal feeChargesPortion;
private final BigDecimal penaltyChargesPortion;
private final BigDecimal overpaymentPortion;
private final BigDecimal unrecognizedIncomePortion;
private final ExternalId externalId;
private final AccountTransferData transfer;
private final BigDecimal fixedEmiAmount;
private final BigDecimal outstandingLoanBalance;
private final LocalDate submittedOnDate;
private final boolean manuallyReversed;
private final LocalDate possibleNextRepaymentDate;
private Collection<LoanChargePaidByData> loanChargePaidByList;
// templates
final Collection<PaymentTypeData> paymentTypeOptions;
private Collection<CodeValueData> writeOffReasonOptions = null;
private Integer numberOfRepayments = 0;
// import fields
private transient Integer rowIndex;
private String dateFormat;
private String locale;
private BigDecimal transactionAmount;
private LocalDate transactionDate;
private Long paymentTypeId;
private String accountNumber;
private Integer checkNumber;
private Integer routingCode;
private Integer receiptNumber;
private Integer bankNumber;
private transient Long accountId;
private transient String transactionType;
private List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments;
// Reverse Data
private final ExternalId reversalExternalId;
private LocalDate reversedOnDate;
private List<LoanTransactionRelationData> transactionRelations;
private Collection<CodeValueData> chargeOffReasonOptions = null;
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
Integer rowIndex, String locale, String dateFormat) {
return new LoanTransactionData(repaymentAmount, lastRepaymentDate, repaymentTypeId, rowIndex, locale, dateFormat);
}
private LoanTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, Integer rowIndex,
String locale, String dateFormat) {
this.transactionAmount = transactionAmount;
this.transactionDate = transactionDate;
this.paymentTypeId = paymentTypeId;
this.rowIndex = rowIndex;
this.dateFormat = dateFormat;
this.locale = locale;
this.amount = null;
this.netDisbursalAmount = null;
this.date = null;
this.type = null;
this.id = null;
this.loanId = null;
this.externalLoanId = ExternalId.empty();
this.officeId = null;
this.officeName = null;
this.currency = null;
this.paymentDetailData = null;
this.principalPortion = null;
this.interestPortion = null;
this.feeChargesPortion = null;
this.penaltyChargesPortion = null;
this.overpaymentPortion = null;
this.unrecognizedIncomePortion = null;
this.externalId = ExternalId.empty();
this.transfer = null;
this.fixedEmiAmount = null;
this.outstandingLoanBalance = null;
this.submittedOnDate = null;
this.manuallyReversed = false;
this.possibleNextRepaymentDate = null;
this.paymentTypeOptions = null;
this.writeOffReasonOptions = null;
this.reversalExternalId = ExternalId.empty();
}
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate repaymentDate, Long repaymentTypeId,
String accountNumber, Integer checkNumber, Integer routingCode, Integer receiptNumber, Integer bankNumber, Long loanAccountId,
String transactionType, Integer rowIndex, String locale, String dateFormat) {
return new LoanTransactionData(repaymentAmount, repaymentDate, repaymentTypeId, accountNumber, checkNumber, routingCode,
receiptNumber, bankNumber, loanAccountId, "", rowIndex, locale, dateFormat);
}
private LoanTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, String accountNumber,
Integer checkNumber, Integer routingCode, Integer receiptNumber, Integer bankNumber, Long accountId, String transactionType,
Integer rowIndex, String locale, String dateFormat) {
this.transactionAmount = transactionAmount;
this.transactionDate = transactionDate;
this.paymentTypeId = paymentTypeId;
this.accountNumber = accountNumber;
this.checkNumber = checkNumber;
this.routingCode = routingCode;
this.receiptNumber = receiptNumber;
this.bankNumber = bankNumber;
this.accountId = accountId;
this.transactionType = transactionType;
this.rowIndex = rowIndex;
this.dateFormat = dateFormat;
this.locale = locale;
this.id = null;
this.loanId = null;
this.externalLoanId = ExternalId.empty();
this.officeId = null;
this.officeName = null;
this.type = null;
this.date = null;
this.currency = null;
this.paymentDetailData = null;
this.amount = null;
this.netDisbursalAmount = null;
this.principalPortion = null;
this.interestPortion = null;
this.feeChargesPortion = null;
this.penaltyChargesPortion = null;
this.overpaymentPortion = null;
this.unrecognizedIncomePortion = null;
this.externalId = ExternalId.empty();
this.transfer = null;
this.fixedEmiAmount = null;
this.outstandingLoanBalance = null;
this.submittedOnDate = null;
this.manuallyReversed = false;
this.possibleNextRepaymentDate = null;
this.paymentTypeOptions = null;
this.writeOffReasonOptions = null;
this.reversalExternalId = ExternalId.empty();
}
public void setNumberOfRepayments(Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
}
public void setLoanRepaymentScheduleInstallments(final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallments) {
this.loanRepaymentScheduleInstallments = loanRepaymentScheduleInstallments;
}
public static LoanTransactionData templateOnTop(final LoanTransactionData loanTransactionData,
final Collection<PaymentTypeData> paymentTypeOptions) {
return new LoanTransactionData(loanTransactionData.id, loanTransactionData.officeId, loanTransactionData.officeName,
loanTransactionData.type, loanTransactionData.paymentDetailData, loanTransactionData.currency, loanTransactionData.date,
loanTransactionData.amount, loanTransactionData.netDisbursalAmount, loanTransactionData.principalPortion,
loanTransactionData.interestPortion, loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion,
loanTransactionData.overpaymentPortion, loanTransactionData.unrecognizedIncomePortion, paymentTypeOptions,
loanTransactionData.externalId, loanTransactionData.transfer, loanTransactionData.fixedEmiAmount,
loanTransactionData.outstandingLoanBalance, loanTransactionData.manuallyReversed, loanTransactionData.loanId,
loanTransactionData.externalLoanId);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
final ExternalId externalId, final AccountTransferData transfer, BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
final BigDecimal unrecognizedIncomePortion, final boolean manuallyReversed, Long loanId, ExternalId externalLoanId) {
this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount, principalPortion,
interestPortion, feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, null, externalId,
transfer, fixedEmiAmount, outstandingLoanBalance, manuallyReversed, loanId, externalLoanId);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
BigDecimal unrecognizedIncomePortion, final Collection<PaymentTypeData> paymentTypeOptions, final ExternalId externalId,
final AccountTransferData transfer, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
boolean manuallyReversed, Long loanId, ExternalId externalLoanId) {
this(id, externalLoanId, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion,
paymentTypeOptions, externalId, transfer, fixedEmiAmount, outstandingLoanBalance, null, manuallyReversed,
ExternalId.empty(), null, loanId);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
final BigDecimal unrecognizedIncomePortion, final ExternalId externalId, final AccountTransferData transfer,
BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, LocalDate submittedOnDate, final boolean manuallyReversed,
final ExternalId reversalExternalId, final LocalDate reversedOnDate, Long loanId, ExternalId externalLoanId) {
this(id, externalLoanId, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion,
null, externalId, transfer, fixedEmiAmount, outstandingLoanBalance, submittedOnDate, manuallyReversed, reversalExternalId,
reversedOnDate, loanId);
}
public LoanTransactionData(final Long id, final ExternalId externalLoanId, final Long officeId, final String officeName,
final LoanTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final CurrencyData currency,
final LocalDate date, final BigDecimal amount, final BigDecimal netDisbursalAmount, final BigDecimal principalPortion,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
final BigDecimal overpaymentPortion, final BigDecimal unrecognizedIncomePortion,
final Collection<PaymentTypeData> paymentTypeOptions, final ExternalId externalId, final AccountTransferData transfer,
final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, final LocalDate submittedOnDate,
final boolean manuallyReversed, final ExternalId reversalExternalId, final LocalDate reversedOnDate, Long loanId) {
this.id = id;
this.loanId = loanId;
this.externalLoanId = externalLoanId;
this.officeId = officeId;
this.officeName = officeName;
this.type = transactionType;
this.paymentDetailData = paymentDetailData;
this.currency = currency;
this.date = date;
this.amount = amount;
this.netDisbursalAmount = netDisbursalAmount;
this.principalPortion = principalPortion;
this.interestPortion = interestPortion;
this.feeChargesPortion = feeChargesPortion;
this.penaltyChargesPortion = penaltyChargesPortion;
this.unrecognizedIncomePortion = unrecognizedIncomePortion;
this.paymentTypeOptions = paymentTypeOptions;
this.externalId = externalId;
this.transfer = transfer;
this.overpaymentPortion = overpaymentPortion;
this.fixedEmiAmount = fixedEmiAmount;
this.outstandingLoanBalance = outstandingLoanBalance;
this.submittedOnDate = submittedOnDate;
this.manuallyReversed = manuallyReversed;
this.possibleNextRepaymentDate = null;
this.reversalExternalId = reversalExternalId;
this.reversedOnDate = reversedOnDate;
}
public LoanTransactionData(Long id, LoanTransactionEnumData transactionType, LocalDate date, BigDecimal totalAmount,
BigDecimal netDisbursalAmount, BigDecimal principalPortion, BigDecimal interestPortion, BigDecimal feeChargesPortion,
BigDecimal penaltyChargesPortion, BigDecimal overpaymentPortion, BigDecimal unrecognizedIncomePortion,
BigDecimal outstandingLoanBalance, final boolean manuallyReversed, ExternalId externalId, Long loanId,
ExternalId externalLoanId) {
this(id, externalLoanId, null, null, transactionType, null, null, date, totalAmount, netDisbursalAmount, principalPortion,
interestPortion, feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, null, externalId,
null, null, outstandingLoanBalance, null, manuallyReversed, ExternalId.empty(), null, loanId);
}
public static LoanTransactionData loanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData transactionType,
final LocalDate expectedDisbursedOnLocalDateForTemplate, final BigDecimal disburseAmountForTemplate,
final BigDecimal netDisbursalAmount, final Collection<PaymentTypeData> paymentOptions, final BigDecimal retriveLastEmiAmount,
final LocalDate possibleNextRepaymentDate) {
final Long id = null;
final Long loanId = null;
final ExternalId externalLoanId = ExternalId.empty();
final Long officeId = null;
final String officeName = null;
final PaymentDetailData paymentDetailData = null;
final CurrencyData currency = null;
final BigDecimal unrecognizedIncomePortion = null;
final BigDecimal principalPortion = null;
final BigDecimal interestPortion = null;
final BigDecimal feeChargesPortion = null;
final BigDecimal penaltyChargesPortion = null;
final BigDecimal overpaymentPortion = null;
final ExternalId externalId = ExternalId.empty();
final BigDecimal outstandingLoanBalance = null;
final AccountTransferData transfer = null;
final LocalDate submittedOnDate = null;
final boolean manuallyReversed = false;
return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currency,
expectedDisbursedOnLocalDateForTemplate, disburseAmountForTemplate, netDisbursalAmount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, paymentOptions, transfer,
externalId, retriveLastEmiAmount, outstandingLoanBalance, submittedOnDate, manuallyReversed, possibleNextRepaymentDate,
loanId, externalLoanId);
}
private LoanTransactionData(Long id, final Long officeId, final String officeName, LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, BigDecimal amount,
BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
BigDecimal unrecognizedIncomePortion, Collection<PaymentTypeData> paymentOptions, final AccountTransferData transfer,
final ExternalId externalId, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
final LocalDate submittedOnDate, final boolean manuallyReversed, final LocalDate possibleNextRepaymentDate, Long loanId,
ExternalId externalLoanId) {
this.id = id;
this.loanId = loanId;
this.externalLoanId = externalLoanId;
this.officeId = officeId;
this.officeName = officeName;
this.type = transactionType;
this.paymentDetailData = paymentDetailData;
this.currency = currency;
this.date = date;
this.amount = amount;
this.netDisbursalAmount = netDisbursalAmount;
this.principalPortion = principalPortion;
this.interestPortion = interestPortion;
this.feeChargesPortion = feeChargesPortion;
this.penaltyChargesPortion = penaltyChargesPortion;
this.unrecognizedIncomePortion = unrecognizedIncomePortion;
this.paymentTypeOptions = paymentOptions;
this.externalId = externalId;
this.transfer = transfer;
this.overpaymentPortion = overpaymentPortion;
this.fixedEmiAmount = fixedEmiAmount;
this.outstandingLoanBalance = outstandingLoanBalance;
this.submittedOnDate = submittedOnDate;
this.manuallyReversed = manuallyReversed;
this.possibleNextRepaymentDate = possibleNextRepaymentDate;
this.reversalExternalId = ExternalId.empty();
}
public boolean isNotDisbursement() {
return type.getId() == 1;
}
public void setWriteOffReasonOptions(Collection<CodeValueData> writeOffReasonOptions) {
this.writeOffReasonOptions = writeOffReasonOptions;
}
public void setChargeOffReasonOptions(Collection<CodeValueData> chargeOffReasonOptions) {
this.chargeOffReasonOptions = chargeOffReasonOptions;
}
public void setLoanChargePaidByList(Collection<LoanChargePaidByData> loanChargePaidByList) {
this.loanChargePaidByList = loanChargePaidByList;
}
public void setLoanTransactionRelations(List<LoanTransactionRelationData> transactionRelations) {
this.transactionRelations = transactionRelations;
}
}
@@ -0,0 +1,95 @@
/**
* 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.Getter;
/**
* Immutable data object represent loan status enumerations.
*/
@Getter
public class LoanTransactionEnumData {
private final Long id;
private final String code;
private final String value;
private final boolean disbursement;
private final boolean repaymentAtDisbursement;
private final boolean repayment;
private final boolean merchantIssuedRefund;
private final boolean payoutRefund;
private final boolean goodwillCredit;
private final boolean chargeRefund;
private final boolean contra;
private final boolean waiveInterest;
private final boolean waiveCharges;
private final boolean accrual;
private final boolean writeOff;
private final boolean recoveryRepayment;
private final boolean initiateTransfer;
private final boolean approveTransfer;
private final boolean withdrawTransfer;
private final boolean rejectTransfer;
private final boolean chargePayment;
private final boolean refund;
private final boolean refundForActiveLoans;
private final boolean creditBalanceRefund;
private final boolean chargeAdjustment;
private final boolean chargeback;
private final boolean chargeoff;
public LoanTransactionEnumData(final Long id, final String code, final String value) {
this.id = id;
this.code = code;
this.value = value;
this.disbursement = Long.valueOf(1).equals(this.id);
this.repaymentAtDisbursement = Long.valueOf(5).equals(this.id);
this.repayment = Long.valueOf(2).equals(this.id);
this.merchantIssuedRefund = Long.valueOf(21).equals(this.id);
this.payoutRefund = Long.valueOf(22).equals(this.id);
this.goodwillCredit = Long.valueOf(23).equals(this.id);
this.chargeRefund = Long.valueOf(24).equals(this.id);
this.contra = Long.valueOf(3).equals(this.id);
this.waiveInterest = Long.valueOf(4).equals(this.id);
this.waiveCharges = Long.valueOf(9).equals(this.id);
this.accrual = Long.valueOf(10).equals(this.id);
this.writeOff = Long.valueOf(6).equals(this.id);
this.recoveryRepayment = Long.valueOf(8).equals(this.id);
this.initiateTransfer = Long.valueOf(12).equals(this.id);
this.approveTransfer = Long.valueOf(13).equals(this.id);
this.withdrawTransfer = Long.valueOf(14).equals(this.id);
this.rejectTransfer = Long.valueOf(15).equals(this.id);
this.refund = Long.valueOf(16).equals(this.id);
this.chargePayment = Long.valueOf(17).equals(this.id);
this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
this.creditBalanceRefund = Long.valueOf(20).equals(this.id);
this.chargeback = Long.valueOf(25).equals(this.id);
this.chargeAdjustment = Long.valueOf(26).equals(this.id);
this.chargeoff = Long.valueOf(27).equals(this.id);
}
public boolean isRepaymentType() {
if (isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|| isChargeAdjustment()) {
return true;
}
return false;
}
}
@@ -0,0 +1,40 @@
/**
* 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.io.Serializable;
import java.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
@Getter
@Setter
@AllArgsConstructor
public class LoanTransactionRelationData implements Serializable {
private Long fromLoanTransaction;
private Long toLoanTransaction;
private Long toLoanCharge;
private LoanTransactionRelationTypeEnum relationType;
private BigDecimal amount;
private String paymentType;
}
@@ -0,0 +1,163 @@
/**
* 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.time.LocalDate;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
public class ScheduleGeneratorDTO {
final LoanScheduleGeneratorFactory loanScheduleFactory;
final ApplicationCurrency applicationCurrency;
final LocalDate calculatedRepaymentsStartingFromDate;
final HolidayDetailDTO holidayDetailDTO;
final CalendarInstance calendarInstanceForInterestRecalculation;
final CalendarInstance compoundingCalendarInstance;
LocalDate recalculateFrom;
final Long overdurPenaltyWaitPeriod;
final FloatingRateDTO floatingRateDTO;
final Calendar calendar;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper;
final Boolean isInterestChargedFromDateAsDisbursementDateEnabled;
final Integer numberOfdays;
final boolean isSkipRepaymentOnFirstDayofMonth;
final Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled;
final boolean isFirstRepaymentDateAllowedOnHoliday;
final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI;
final boolean isPrincipalCompoundingDisabledForOverdueLoans;
public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO,
final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance,
final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO,
final Calendar calendar, final CalendarHistoryDataWrapper calendarHistoryDataWrapper,
final Boolean isInterestChargedFromDateAsDisbursementDateEnabled, final Integer numberOfdays,
final boolean isSkipRepaymentOnFirstDayofMonth, final Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled,
final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI,
final boolean isPrincipalCompoundingDisabledForOverdueLoans) {
this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency;
this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation;
this.compoundingCalendarInstance = compoundingCalendarInstance;
this.recalculateFrom = recalculateFrom;
this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod;
this.holidayDetailDTO = holidayDetailDTO;
this.floatingRateDTO = floatingRateDTO;
this.calendar = calendar;
this.calendarHistoryDataWrapper = calendarHistoryDataWrapper;
this.isInterestChargedFromDateAsDisbursementDateEnabled = isInterestChargedFromDateAsDisbursementDateEnabled;
this.numberOfdays = numberOfdays;
this.isSkipRepaymentOnFirstDayofMonth = isSkipRepaymentOnFirstDayofMonth;
this.isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled = isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled;
this.isFirstRepaymentDateAllowedOnHoliday = isFirstRepaymentDateAllowedOnHoliday;
this.isInterestToBeRecoveredFirstWhenGreaterThanEMI = isInterestToBeRecoveredFirstWhenGreaterThanEMI;
this.isPrincipalCompoundingDisabledForOverdueLoans = isPrincipalCompoundingDisabledForOverdueLoans;
}
public LoanScheduleGeneratorFactory getLoanScheduleFactory() {
return this.loanScheduleFactory;
}
public ApplicationCurrency getApplicationCurrency() {
return this.applicationCurrency;
}
public LocalDate getCalculatedRepaymentsStartingFromDate() {
return this.calculatedRepaymentsStartingFromDate;
}
public CalendarInstance getCalendarInstanceForInterestRecalculation() {
return this.calendarInstanceForInterestRecalculation;
}
public LocalDate getRecalculateFrom() {
return this.recalculateFrom;
}
public Long getOverdurPenaltyWaitPeriod() {
return this.overdurPenaltyWaitPeriod;
}
public int getPenaltyWaitPeriod() {
int penaltyWaitPeriod = 0;
if (this.overdurPenaltyWaitPeriod != null) {
penaltyWaitPeriod = this.overdurPenaltyWaitPeriod.intValue();
}
return penaltyWaitPeriod;
}
public HolidayDetailDTO getHolidayDetailDTO() {
return this.holidayDetailDTO;
}
public void setRecalculateFrom(LocalDate recalculateFrom) {
this.recalculateFrom = recalculateFrom;
}
public CalendarInstance getCompoundingCalendarInstance() {
return this.compoundingCalendarInstance;
}
public FloatingRateDTO getFloatingRateDTO() {
return this.floatingRateDTO;
}
public Calendar getCalendar() {
return this.calendar;
}
public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper() {
return this.calendarHistoryDataWrapper;
}
public Boolean isInterestChargedFromDateAsDisbursementDateEnabled() {
return this.isInterestChargedFromDateAsDisbursementDateEnabled;
}
public Integer getNumberOfdays() {
return numberOfdays;
}
public boolean isSkipRepaymentOnFirstDayofMonth() {
return isSkipRepaymentOnFirstDayofMonth;
}
public Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled() {
return this.isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled;
}
public boolean isFirstRepaymentDateAllowedOnHoliday() {
return isFirstRepaymentDateAllowedOnHoliday;
}
public boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI() {
return isInterestToBeRecoveredFirstWhenGreaterThanEMI;
}
public boolean isPrincipalCompoundingDisabledForOverdueLoans() {
return isPrincipalCompoundingDisabledForOverdueLoans;
}
}
@@ -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.portfolio.loanaccount.domain;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Stores details of {@link LoanTransaction}'s that were reversed or newly created
*/
public class ChangedTransactionDetail {
private final Map<Long, LoanTransaction> newTransactionMappings = new LinkedHashMap<>();
public Map<Long, LoanTransaction> getNewTransactionMappings() {
return this.newTransactionMappings;
}
}
@@ -0,0 +1,145 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.group.domain.Group;
@Entity
@Table(name = "glim_accounts", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_number" }, name = "FK_glim_id") })
public class GroupLoanIndividualMonitoringAccount extends AbstractPersistableCustom {
@ManyToOne
@JoinColumn(name = "group_id", nullable = false)
private Group group;
@Column(name = "account_number", nullable = false)
private String accountNumber;
@Column(name = "principal_amount")
private BigDecimal principalAmount;
@Column(name = "child_accounts_count")
private Long childAccountsCount;
@Column(name = "accepting_child")
private Boolean isAcceptingChild;
@OneToMany
private Set<Loan> childLoan;
@Column(name = "loan_status_id", nullable = false)
private Integer loanStatus;
@Column(name = "application_id", nullable = true)
private BigDecimal applicationId;
protected GroupLoanIndividualMonitoringAccount() {}
private GroupLoanIndividualMonitoringAccount(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount,
Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) {
this.accountNumber = accountNumber;
this.group = group;
this.principalAmount = principalAmount;
this.childAccountsCount = childAccountsCount;
this.isAcceptingChild = isAcceptingChild;
this.loanStatus = loanStatus;
this.applicationId = applicationId;
}
public static GroupLoanIndividualMonitoringAccount getInstance(String accountNumber, Group group, BigDecimal principalAmount,
Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) {
return new GroupLoanIndividualMonitoringAccount(accountNumber, group, principalAmount, childAccountsCount, isAcceptingChild,
loanStatus, applicationId);
}
public String getAccountNumber() {
return accountNumber;
}
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public Set<Loan> getChildLoan() {
return childLoan;
}
public void setChildLoan(Set<Loan> childLoan) {
this.childLoan = childLoan;
}
public BigDecimal getPrincipalAmount() {
return principalAmount;
}
public void setPrincipalAmount(BigDecimal principalAmount) {
this.principalAmount = principalAmount;
}
public Long getChildAccountsCount() {
return childAccountsCount;
}
public void setChildAccountsCount(Long childAccountsCount) {
this.childAccountsCount = childAccountsCount;
}
public Boolean getIsAcceptingChild() {
return isAcceptingChild;
}
public void setIsAcceptingChild(Boolean isAcceptingChild) {
this.isAcceptingChild = isAcceptingChild;
}
public Integer getLoanStatus() {
return loanStatus;
}
public void setLoanStatus(Integer loanStatus) {
this.loanStatus = loanStatus;
}
public BigDecimal getApplicationId() {
return applicationId;
}
public void setApplicationId(BigDecimal applicationId) {
this.applicationId = applicationId;
}
}
@@ -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.domain;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_loan_charge_paid_by")
public class LoanChargePaidBy extends AbstractPersistableCustom {
@ManyToOne
@JoinColumn(name = "loan_transaction_id", nullable = false)
private LoanTransaction loanTransaction;
@ManyToOne(optional = false)
@JoinColumn(name = "loan_charge_id", nullable = false)
private LoanCharge loanCharge;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@Column(name = "installment_number", nullable = true)
private Integer installmentNumber;
protected LoanChargePaidBy() {
}
public LoanChargePaidBy(final LoanTransaction loanTransaction, final LoanCharge loanCharge, final BigDecimal amount,
Integer installmentNumber) {
this.loanTransaction = loanTransaction;
this.loanCharge = loanCharge;
this.amount = amount;
this.installmentNumber = installmentNumber;
}
public LoanTransaction getLoanTransaction() {
return this.loanTransaction;
}
public void setLoanTransaction(final LoanTransaction loanTransaction) {
this.loanTransaction = loanTransaction;
}
public LoanCharge getLoanCharge() {
return this.loanCharge;
}
public void setLoanCharge(final LoanCharge loanCharge) {
this.loanCharge = loanCharge;
}
public BigDecimal getAmount() {
return this.amount;
}
public void setAmount(final BigDecimal amount) {
this.amount = amount;
}
public Integer getInstallmentNumber() {
return this.installmentNumber;
}
}
@@ -0,0 +1,124 @@
/**
* 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.domain;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementData;
@Entity
@Table(name = "m_loan_collateral_management")
public class LoanCollateralManagement extends AbstractPersistableCustom {
@Column(name = "quantity", nullable = false, scale = 5, precision = 20)
private BigDecimal quantity;
@ManyToOne()
@JoinColumn(name = "transaction_id")
private LoanTransaction loanTransaction = null;
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", referencedColumnName = "id", nullable = false)
private Loan loan;
@Column(name = "is_released", nullable = false)
private boolean isReleased = false;
@ManyToOne(optional = false)
@JoinColumn(name = "client_collateral_id", nullable = false)
private ClientCollateralManagement clientCollateralManagement;
public LoanCollateralManagement() {
}
public LoanCollateralManagement(final BigDecimal quantity, final boolean isReleased) {
this.quantity = quantity;
this.isReleased = isReleased;
}
private LoanCollateralManagement(final BigDecimal quantity, final ClientCollateralManagement clientCollateralManagement) {
this.clientCollateralManagement = clientCollateralManagement;
this.quantity = quantity;
}
public void setLoan(Loan loan) {
this.loan = loan;
}
public static LoanCollateralManagement from(final ClientCollateralManagement clientCollateralManagement, final BigDecimal quantity) {
return new LoanCollateralManagement(quantity, clientCollateralManagement);
}
public static LoanCollateralManagement fromExisting(final ClientCollateralManagement clientCollateralManagement,
final BigDecimal quantity, final Loan loan, final LoanTransaction transaction, final Long id) {
LoanCollateralManagement loanCollateralManagementInstance = new LoanCollateralManagement(quantity, clientCollateralManagement);
loanCollateralManagementInstance.setLoan(loan);
loanCollateralManagementInstance.setLoanTransactionData(transaction);
loanCollateralManagementInstance.setId(id);
return loanCollateralManagementInstance;
}
public void setClientCollateralManagement(final ClientCollateralManagement clientCollateralManagement) {
this.clientCollateralManagement = clientCollateralManagement;
}
public void setLoanTransactionData(final LoanTransaction loanTransaction) {
this.loanTransaction = loanTransaction;
}
public void setIsReleased(final boolean isReleased) {
this.isReleased = isReleased;
}
public void setQuantity(final BigDecimal quantity) {
this.quantity = quantity;
}
public BigDecimal getQuantity() {
return this.quantity;
}
public Loan getLoanData() {
return this.loan;
}
public LoanTransaction getLoanTransaction() {
return this.loanTransaction;
}
public ClientCollateralManagement getClientCollateralManagement() {
return this.clientCollateralManagement;
}
public boolean isReleased() {
return this.isReleased;
}
public LoanCollateralManagementData toCommand() {
return new LoanCollateralManagementData(this.clientCollateralManagement.getId(), this.getQuantity(), null, null, getId());
}
}
@@ -0,0 +1,157 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
@Entity
@Table(name = "m_loan_disbursement_detail")
public class LoanDisbursementDetails extends AbstractPersistableCustom {
@ManyToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Column(name = "expected_disburse_date")
private LocalDate expectedDisbursementDate;
@Column(name = "disbursedon_date")
private LocalDate actualDisbursementDate;
@Column(name = "principal", scale = 6, precision = 19, nullable = false)
private BigDecimal principal;
@Column(name = "net_disbursal_amount", scale = 6, precision = 19)
private BigDecimal netDisbursalAmount;
@Column(name = "is_reversed", nullable = false)
private boolean reversed;
protected LoanDisbursementDetails() {
}
public LoanDisbursementDetails(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
final BigDecimal principal, final BigDecimal netDisbursalAmount, final boolean reversed) {
this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate;
this.principal = principal;
this.netDisbursalAmount = netDisbursalAmount;
this.reversed = reversed;
}
public void updateLoan(final Loan loan) {
this.loan = loan;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof LoanDisbursementDetails)) {
return false;
}
final LoanDisbursementDetails loanDisbursementDetails = (LoanDisbursementDetails) obj;
if (loanDisbursementDetails.principal.equals(this.principal)
&& loanDisbursementDetails.expectedDisbursementDate.compareTo(this.expectedDisbursementDate) == 0 ? Boolean.TRUE
: Boolean.FALSE) {
return true;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(expectedDisbursementDate, principal);
}
public void copy(final LoanDisbursementDetails disbursementDetails) {
this.principal = disbursementDetails.principal;
this.expectedDisbursementDate = disbursementDetails.expectedDisbursementDate;
this.actualDisbursementDate = disbursementDetails.actualDisbursementDate;
this.reversed = disbursementDetails.reversed;
}
public LocalDate expectedDisbursementDate() {
return this.expectedDisbursementDate;
}
public LocalDate expectedDisbursementDateAsLocalDate() {
LocalDate expectedDisburseDate = null;
if (this.expectedDisbursementDate != null) {
expectedDisburseDate = this.expectedDisbursementDate;
}
return expectedDisburseDate;
}
public LocalDate actualDisbursementDate() {
return this.actualDisbursementDate;
}
public BigDecimal principal() {
return this.principal;
}
public void updatePrincipal(BigDecimal principal) {
this.principal = principal;
}
public LocalDate getDisbursementDate() {
return this.actualDisbursementDate;
}
public DisbursementData toData() {
LocalDate expectedDisburseDate = expectedDisbursementDateAsLocalDate();
BigDecimal waivedChargeAmount = null;
return new DisbursementData(getId(), expectedDisburseDate, this.actualDisbursementDate, this.principal, this.netDisbursalAmount,
null, null, waivedChargeAmount);
}
public void updateActualDisbursementDate(LocalDate actualDisbursementDate) {
this.actualDisbursementDate = actualDisbursementDate;
}
public BigDecimal getNetDisbursalAmount() {
return this.netDisbursalAmount;
}
public void setNetDisbursalAmount(BigDecimal netDisbursalAmount) {
this.netDisbursalAmount = netDisbursalAmount;
}
public void updateExpectedDisbursementDateAndAmount(LocalDate expectedDisbursementDate, BigDecimal principal) {
this.expectedDisbursementDate = expectedDisbursementDate;
this.principal = principal;
}
public void reverse() {
this.reversed = true;
}
public boolean isReversed() {
return reversed;
}
}
@@ -0,0 +1,55 @@
/**
* 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.domain;
/**
*
*/
public enum LoanEvent {
LOAN_CREATED, //
LOAN_REJECTED, //
LOAN_WITHDRAWN, //
LOAN_APPROVED, //
LOAN_APPROVAL_UNDO, //
LOAN_RECOVERY_PAYMENT, //
LOAN_DISBURSED, //
LOAN_DISBURSAL_UNDO, //
LOAN_DISBURSAL_UNDO_LAST, //
LOAN_REPAYMENT_OR_WAIVER, //
REPAID_IN_FULL, //
WRITE_OFF_OUTSTANDING, //
WRITE_OFF_OUTSTANDING_UNDO, //
LOAN_RESCHEDULE, //
INTERST_REBATE_OWED, //
LOAN_OVERPAYMENT, //
LOAN_CHARGE_PAYMENT, //
LOAN_CLOSED, //
LOAN_EDIT_MULTI_DISBURSE_DATE, //
LOAN_REFUND, //
LOAN_FORECLOSURE, //
LOAN_ADJUST_TRANSACTION, //
LOAN_INITIATE_TRANSFER, //
LOAN_REJECT_TRANSFER, //
LOAN_WITHDRAW_TRANSFER, //
LOAN_CREDIT_BALANCE_REFUND, //
LOAN_CHARGEBACK, //
LOAN_CHARGE_ADDED, //
LOAN_CHARGE_ADJUSTMENT;
}
@@ -0,0 +1,331 @@
/**
* 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.domain;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
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.portfolio.loanaccount.data.LoanInstallmentChargeData;
@Entity
@Table(name = "m_loan_installment_charge")
public class LoanInstallmentCharge extends AbstractPersistableCustom implements Comparable<LoanInstallmentCharge> {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
private LoanCharge loancharge;
@ManyToOne
@JoinColumn(name = "loan_schedule_id", nullable = false)
private LoanRepaymentScheduleInstallment installment;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountPaid;
@Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountWaived;
@Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountWrittenOff;
@Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
private BigDecimal amountOutstanding;
@Column(name = "amount_through_charge_payment", scale = 6, precision = 19, nullable = true)
private BigDecimal amountThroughChargePayment;
@Column(name = "is_paid_derived", nullable = false)
private boolean paid = false;
@Column(name = "waived", nullable = false)
private boolean waived = false;
public LoanInstallmentCharge() {
// TODO Auto-generated constructor stub
}
@Override
public int compareTo(LoanInstallmentCharge o) {
return this.installment.getInstallmentNumber().compareTo(o.installment.getInstallmentNumber());
}
public LoanInstallmentCharge(final BigDecimal amount, final LoanCharge loancharge, final LoanRepaymentScheduleInstallment installment) {
this.loancharge = loancharge;
this.installment = installment;
this.amount = amount;
this.amountOutstanding = amount;
this.amountPaid = null;
this.amountWaived = null;
this.amountWrittenOff = null;
}
public void copyFrom(final LoanInstallmentCharge loanChargePerInstallment) {
this.amount = loanChargePerInstallment.amount;
this.installment = loanChargePerInstallment.installment;
this.amountOutstanding = calculateOutstanding();
this.paid = determineIfFullyPaid();
}
public Money waive(final MonetaryCurrency currency) {
this.amountWaived = this.amountOutstanding;
this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true;
return getAmountWaived(currency);
}
public Money getAmountWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.amountWaived);
}
private boolean determineIfFullyPaid() {
if (this.amount == null) {
return true;
}
return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
}
public void undoWaive(final BigDecimal amountOutstanding, final BigDecimal amountWaived) {
this.amountOutstanding = amountOutstanding;
this.amountWaived = amountWaived;
}
private BigDecimal calculateOutstanding() {
if (this.amount == null) {
return null;
}
BigDecimal amountPaidLocal = BigDecimal.ZERO;
if (this.amountPaid != null) {
amountPaidLocal = this.amountPaid;
}
BigDecimal amountWaivedLocal = BigDecimal.ZERO;
if (this.amountWaived != null) {
amountWaivedLocal = this.amountWaived;
}
BigDecimal amountWrittenOffLocal = BigDecimal.ZERO;
if (this.amountWrittenOff != null) {
amountWrittenOffLocal = this.amountWrittenOff;
}
final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal);
return this.amount.subtract(totalAccountedFor);
}
public BigDecimal getAmount() {
return this.amount;
}
public Money getAmount(final MonetaryCurrency currency) {
return Money.of(currency, this.amount);
}
public Money getAmountPaid(final MonetaryCurrency currency) {
return Money.of(currency, this.amountPaid);
}
public BigDecimal getAmountOutstanding() {
return this.amountOutstanding;
}
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
}
public boolean isPaid() {
return this.paid;
}
public boolean isWaived() {
return this.waived;
}
public boolean isPending() {
return !(isPaid() || isWaived());
}
public boolean isChargeAmountpaid(MonetaryCurrency currency) {
Money amounPaidThroughChargePayment = Money.of(currency, this.amountThroughChargePayment);
Money paid = Money.of(currency, this.amountPaid);
return amounPaidThroughChargePayment.isEqualTo(paid);
}
public LoanRepaymentScheduleInstallment getRepaymentInstallment() {
return this.installment;
}
public Money updatePaidAmountBy(final Money incrementBy, final Money feeAmount) {
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
Money amountPaidPreviously = amountPaidToDate;
Money amountPaidOnThisCharge;
if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
amountPaidOnThisCharge = amountOutstanding;
amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = BigDecimal.ZERO;
} else {
amountPaidOnThisCharge = incrementBy;
amountPaidToDate = amountPaidToDate.plus(incrementBy);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
}
Money amountFromChargePayment = Money.of(incrementBy.getCurrency(), this.amountThroughChargePayment);
if (amountPaidPreviously.isGreaterThanZero()) {
amountFromChargePayment = amountFromChargePayment.plus(feeAmount);
} else {
amountFromChargePayment = feeAmount;
}
this.amountThroughChargePayment = amountFromChargePayment.getAmount();
if (determineIfFullyPaid()) {
Money waivedAmount = getAmountWaived(incrementBy.getCurrency());
if (waivedAmount.isGreaterThanZero()) {
this.waived = true;
} else {
this.paid = true;
}
}
return amountPaidOnThisCharge;
}
public Money getAmountWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.amountWrittenOff);
}
public void resetPaidAmount(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
}
public void setAmountWaived(final BigDecimal amountWaived) {
this.amountWaived = amountWaived;
}
public void undoWaiveFlag() {
this.waived = false;
}
public void setOutstandingAmount(final BigDecimal amountOutstanding) {
this.amountOutstanding = amountOutstanding;
}
public void resetToOriginal(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountWaived = BigDecimal.ZERO;
this.amountWrittenOff = BigDecimal.ZERO;
this.amountThroughChargePayment = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
this.waived = false;
}
public Money getAmountThroughChargePayment(final MonetaryCurrency currency) {
return Money.of(currency, this.amountThroughChargePayment);
}
public Money getUnpaidAmountThroughChargePayment(final MonetaryCurrency currency) {
return Money.of(currency, this.amountThroughChargePayment).minus(this.amountPaid);
}
private void updateAmountThroughChargePayment(final MonetaryCurrency currency) {
Money amountThroughChargePayment = getAmountThroughChargePayment(currency);
if (amountThroughChargePayment.isGreaterThanZero() && amountThroughChargePayment.isGreaterThan(this.getAmount(currency))) {
this.amountThroughChargePayment = this.getAmount();
}
}
public Money updateWaivedAndAmountPaidThroughChargePaymentAmount(final MonetaryCurrency currency) {
updateWaivedAmount(currency);
updateAmountThroughChargePayment(currency);
return getAmountWaived(currency);
}
private void updateWaivedAmount(final MonetaryCurrency currency) {
Money waivedAmount = getAmountWaived(currency);
if (waivedAmount.isGreaterThanZero()) {
if (waivedAmount.isGreaterThan(this.getAmount(currency))) {
this.amountWaived = this.getAmount();
this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true;
} else if (waivedAmount.isLessThan(this.getAmount(currency))) {
this.paid = false;
this.waived = false;
}
}
}
public void updateInstallment(LoanRepaymentScheduleInstallment installment) {
this.installment = installment;
}
public Money undoPaidAmountBy(final Money incrementBy, final Money feeAmount) {
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
Money amountToDeductOnThisCharge;
if (incrementBy.isGreaterThanOrEqualTo(amountPaidToDate)) {
amountToDeductOnThisCharge = amountPaidToDate;
amountPaidToDate = Money.zero(incrementBy.getCurrency());
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = this.amount;
} else {
amountToDeductOnThisCharge = incrementBy;
amountPaidToDate = amountPaidToDate.minus(incrementBy);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
}
this.amountThroughChargePayment = feeAmount.getAmount();
this.paid = determineIfFullyPaid();
return amountToDeductOnThisCharge;
}
public LoanCharge getLoanCharge() {
return this.loancharge;
}
public LoanRepaymentScheduleInstallment getInstallment() {
return this.installment;
}
public void setInstallment(LoanRepaymentScheduleInstallment installment) {
this.installment = installment;
}
public LoanInstallmentChargeData toData() {
return LoanInstallmentChargeData.builder().installmentNumber(installment.getInstallmentNumber()).dueDate(installment.getDueDate())
.amount(amount).amountOutstanding(amountOutstanding).amountWaived(amountWaived).paid(paid).waived(waived).build();
}
}
@@ -0,0 +1,70 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_loan_interest_recalculation_additional_details")
public class LoanInterestRecalcualtionAdditionalDetails extends AbstractPersistableCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_repayment_schedule_id", nullable = false)
private LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment;
@Column(name = "effective_date")
private LocalDate effectiveDate;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
protected LoanInterestRecalcualtionAdditionalDetails() {
}
public LoanInterestRecalcualtionAdditionalDetails(final LocalDate effectiveDate, final BigDecimal amount) {
if (effectiveDate != null) {
this.effectiveDate = effectiveDate;
}
this.amount = amount;
}
public LocalDate getEffectiveDate() {
return this.effectiveDate;
}
public BigDecimal getAmount() {
return this.amount;
}
public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() {
return loanRepaymentScheduleInstallment;
}
public void setLoanRepaymentScheduleInstallment(LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
this.loanRepaymentScheduleInstallment = loanRepaymentScheduleInstallment;
}
}
@@ -0,0 +1,192 @@
/**
* 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.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductInterestRecalculationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
/**
* Entity for holding interest recalculation setting, details will be copied from product directly
*
* @author conflux
*/
@Entity
@Table(name = "m_loan_recalculation_details")
public class LoanInterestRecalculationDetails extends AbstractPersistableCustom {
@OneToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
/**
* {@link InterestRecalculationCompoundingMethod}
*/
@Column(name = "compound_type_enum", nullable = false)
private Integer interestRecalculationCompoundingMethod;
/**
* {@link LoanRescheduleStrategyMethod}
*/
@Column(name = "reschedule_strategy_enum", nullable = false)
private Integer rescheduleStrategyMethod;
@Column(name = "rest_frequency_type_enum", nullable = false)
private Integer restFrequencyType;
@Column(name = "rest_frequency_interval", nullable = false)
private Integer restInterval;
@Column(name = "rest_frequency_nth_day_enum", nullable = true)
private Integer restFrequencyNthDay;
@Column(name = "rest_frequency_weekday_enum", nullable = true)
private Integer restFrequencyWeekday;
@Column(name = "rest_frequency_on_day", nullable = true)
private Integer restFrequencyOnDay;
@Column(name = "compounding_frequency_type_enum", nullable = true)
private Integer compoundingFrequencyType;
@Column(name = "compounding_frequency_interval", nullable = true)
private Integer compoundingInterval;
@Column(name = "compounding_frequency_nth_day_enum", nullable = true)
private Integer compoundingFrequencyNthDay;
@Column(name = "compounding_frequency_weekday_enum", nullable = true)
private Integer compoundingFrequencyWeekday;
@Column(name = "compounding_frequency_on_day", nullable = true)
private Integer compoundingFrequencyOnDay;
@Column(name = "is_compounding_to_be_posted_as_transaction")
private Boolean isCompoundingToBePostedAsTransaction;
@Column(name = "allow_compounding_on_eod")
private Boolean allowCompoundingOnEod;
protected LoanInterestRecalculationDetails() {
// Default constructor for jpa repository
}
private LoanInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod,
final Integer restFrequencyType, final Integer restInterval, final Integer restFrequencyNthDay, Integer restFrequencyWeekday,
Integer restFrequencyOnDay, Integer compoundingFrequencyType, Integer compoundingInterval, Integer compoundingFrequencyNthDay,
Integer compoundingFrequencyWeekday, Integer compoundingFrequencyOnDay, final boolean isCompoundingToBePostedAsTransaction,
final boolean allowCompoundingOnEod) {
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.restFrequencyNthDay = restFrequencyNthDay;
this.restFrequencyWeekday = restFrequencyWeekday;
this.restFrequencyOnDay = restFrequencyOnDay;
this.restFrequencyType = restFrequencyType;
this.restInterval = restInterval;
this.compoundingFrequencyNthDay = compoundingFrequencyNthDay;
this.compoundingFrequencyWeekday = compoundingFrequencyWeekday;
this.compoundingFrequencyOnDay = compoundingFrequencyOnDay;
this.compoundingFrequencyType = compoundingFrequencyType;
this.compoundingInterval = compoundingInterval;
this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod;
}
public static LoanInterestRecalculationDetails createFrom(
final LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails) {
return new LoanInterestRecalculationDetails(loanProductInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(),
loanProductInterestRecalculationDetails.getRescheduleStrategyMethod(),
loanProductInterestRecalculationDetails.getRestFrequencyType().getValue(),
loanProductInterestRecalculationDetails.getRestInterval(), loanProductInterestRecalculationDetails.getRestFrequencyNthDay(),
loanProductInterestRecalculationDetails.getRestFrequencyWeekday(),
loanProductInterestRecalculationDetails.getRestFrequencyOnDay(),
loanProductInterestRecalculationDetails.getCompoundingFrequencyType().getValue(),
loanProductInterestRecalculationDetails.getCompoundingInterval(),
loanProductInterestRecalculationDetails.getCompoundingFrequencyNthDay(),
loanProductInterestRecalculationDetails.getCompoundingFrequencyWeekday(),
loanProductInterestRecalculationDetails.getCompoundingFrequencyOnDay(),
loanProductInterestRecalculationDetails.getIsCompoundingToBePostedAsTransaction(),
loanProductInterestRecalculationDetails.allowCompoundingOnEod());
}
public void updateLoan(final Loan loan) {
this.loan = loan;
}
public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() {
return InterestRecalculationCompoundingMethod.fromInt(this.interestRecalculationCompoundingMethod);
}
public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() {
return LoanRescheduleStrategyMethod.fromInt(this.rescheduleStrategyMethod);
}
public RecalculationFrequencyType getRestFrequencyType() {
return RecalculationFrequencyType.fromInt(this.restFrequencyType);
}
public Integer getRestInterval() {
return this.restInterval;
}
public RecalculationFrequencyType getCompoundingFrequencyType() {
return RecalculationFrequencyType.fromInt(this.compoundingFrequencyType);
}
public Integer getCompoundingInterval() {
return this.compoundingInterval;
}
public Integer getRestFrequencyNthDay() {
return this.restFrequencyNthDay;
}
public Integer getRestFrequencyWeekday() {
return this.restFrequencyWeekday;
}
public Integer getRestFrequencyOnDay() {
return this.restFrequencyOnDay;
}
public Integer getCompoundingFrequencyNthDay() {
return this.compoundingFrequencyNthDay;
}
public Integer getCompoundingFrequencyWeekday() {
return this.compoundingFrequencyWeekday;
}
public Integer getCompoundingFrequencyOnDay() {
return this.compoundingFrequencyOnDay;
}
public boolean isCompoundingToBePostedAsTransaction() {
return null == this.isCompoundingToBePostedAsTransaction ? false : this.isCompoundingToBePostedAsTransaction;
}
public boolean allowCompoundingOnEod() {
return this.allowCompoundingOnEod;
}
}
@@ -0,0 +1,26 @@
/**
* 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.domain;
public interface LoanLifecycleStateMachine {
LoanStatus dryTransition(LoanEvent loanEvent, Loan loan);
void transition(LoanEvent loanEvent, Loan loan);
}
@@ -0,0 +1,108 @@
/**
* 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.domain;
import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.organisation.staff.domain.Staff;
@Entity
@Table(name = "m_loan_officer_assignment_history")
public class LoanOfficerAssignmentHistory extends AbstractAuditableCustom {
@ManyToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@ManyToOne
@JoinColumn(name = "loan_officer_id", nullable = true)
private Staff loanOfficer;
@Column(name = "start_date")
private LocalDate startDate;
@Column(name = "end_date")
private LocalDate endDate;
public static LoanOfficerAssignmentHistory createNew(final Loan loan, final Staff loanOfficer, final LocalDate startDate) {
return new LoanOfficerAssignmentHistory(loan, loanOfficer, startDate, null);
}
protected LoanOfficerAssignmentHistory() {
//
}
private LoanOfficerAssignmentHistory(final Loan loan, final Staff loanOfficer, final LocalDate startDate, final LocalDate endDate) {
this.loan = loan;
this.loanOfficer = loanOfficer;
this.startDate = startDate;
this.endDate = endDate;
}
public void updateLoanOfficer(final Staff loanOfficer) {
this.loanOfficer = loanOfficer;
}
public void updateStartDate(final LocalDate startDate) {
this.startDate = startDate;
}
public void updateEndDate(final LocalDate endDate) {
this.endDate = endDate;
}
public boolean matchesStartDateOf(final LocalDate matchingDate) {
return getStartDate().isEqual(matchingDate);
}
public LocalDate getStartDate() {
return this.startDate;
}
public boolean hasStartDateBefore(final LocalDate matchingDate) {
return matchingDate.isBefore(getStartDate());
}
public boolean isCurrentRecord() {
return this.endDate == null;
}
/**
* If endDate is null then return false.
*
* @param compareDate
* @return
*/
public boolean isEndDateAfter(final LocalDate compareDate) {
return this.endDate.isAfter(compareDate);
}
public LocalDate getEndDate() {
return this.endDate;
}
public boolean isSameLoanOfficer(final Staff staff) {
return this.loanOfficer.identifiedBy(staff);
}
}
@@ -0,0 +1,59 @@
/**
* 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.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Getter
@Table(name = "m_loan_overdue_installment_charge")
public class LoanOverdueInstallmentCharge extends AbstractPersistableCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
private LoanCharge loancharge;
@ManyToOne(optional = false)
@JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false)
private LoanRepaymentScheduleInstallment installment;
@Column(name = "frequency_number")
private Integer frequencyNumber;
public LoanOverdueInstallmentCharge() {
}
public LoanOverdueInstallmentCharge(final LoanCharge loancharge, final LoanRepaymentScheduleInstallment installment,
final Integer frequencyNumber) {
this.loancharge = loancharge;
this.installment = installment;
this.frequencyNumber = frequencyNumber;
}
public void updateLoanRepaymentScheduleInstallment(LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
this.installment = loanRepaymentScheduleInstallment;
}
}
@@ -0,0 +1,907 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
@Entity
@Table(name = "m_loan_repayment_schedule")
public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDateTimeCustom
implements Comparable<LoanRepaymentScheduleInstallment> {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", referencedColumnName = "id")
private Loan loan;
@Column(name = "installment", nullable = false)
private Integer installmentNumber;
@Column(name = "fromdate", nullable = true)
private LocalDate fromDate;
@Column(name = "duedate", nullable = false)
private LocalDate dueDate;
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal principal;
@Column(name = "principal_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalCompleted;
@Column(name = "principal_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalWrittenOff;
@Column(name = "interest_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal interestCharged;
@Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestPaid;
@Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWaived;
@Column(name = "interest_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWrittenOff;
@Column(name = "accrual_interest_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestAccrued;
@Column(name = "reschedule_interest_portion", scale = 6, precision = 19, nullable = true)
private BigDecimal rescheduleInterestPortion;
@Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesCharged;
@Column(name = "fee_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesPaid;
@Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWrittenOff;
@Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWaived;
@Column(name = "accrual_fee_charges_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeAccrued;
@Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyCharges;
@Column(name = "penalty_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPaid;
@Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWrittenOff;
@Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWaived;
@Column(name = "accrual_penalty_charges_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyAccrued;
@Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal totalPaidInAdvance;
@Column(name = "total_paid_late_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal totalPaidLate;
@Column(name = "completed_derived", nullable = false)
private boolean obligationsMet;
@Column(name = "obligations_met_on_date")
private LocalDate obligationsMetOnDate;
@Column(name = "recalculated_interest_component", nullable = false)
private boolean recalculatedInterestComponent;
@Column(name = "is_additional", nullable = false)
private boolean additional;
@Column(name = "credits_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal credits;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
private Set<PostDatedChecks> postDatedChecks;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "installment")
private Set<LoanInstallmentCharge> installmentCharges = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "installment")
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
public LoanRepaymentScheduleInstallment() {
this.installmentNumber = null;
this.fromDate = null;
this.dueDate = null;
this.obligationsMet = false;
}
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails, final BigDecimal rescheduleInterestPortion) {
this.loan = loan;
this.installmentNumber = installmentNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.principal = defaultToNullIfZero(principal);
this.interestCharged = defaultToNullIfZero(interest);
this.feeChargesCharged = defaultToNullIfZero(feeCharges);
this.penaltyCharges = defaultToNullIfZero(penaltyCharges);
this.obligationsMet = false;
this.recalculatedInterestComponent = recalculatedInterestComponent;
if (compoundingDetails != null) {
compoundingDetails.forEach(cd -> cd.setLoanRepaymentScheduleInstallment(this));
}
this.loanCompoundingDetails = compoundingDetails;
this.rescheduleInterestPortion = rescheduleInterestPortion;
}
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails) {
this.loan = loan;
this.installmentNumber = installmentNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.principal = defaultToNullIfZero(principal);
this.interestCharged = defaultToNullIfZero(interest);
this.feeChargesCharged = defaultToNullIfZero(feeCharges);
this.penaltyCharges = defaultToNullIfZero(penaltyCharges);
this.obligationsMet = false;
this.recalculatedInterestComponent = recalculatedInterestComponent;
if (compoundingDetails != null) {
compoundingDetails.forEach(cd -> cd.setLoanRepaymentScheduleInstallment(this));
}
this.loanCompoundingDetails = compoundingDetails;
}
public LoanRepaymentScheduleInstallment(final Loan loan) {
this.loan = loan;
this.installmentNumber = null;
this.fromDate = null;
this.dueDate = null;
this.obligationsMet = false;
}
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
BigDecimal result = value;
if (BigDecimal.ZERO.compareTo(value) == 0) {
result = null;
}
return result;
}
public Loan getLoan() {
return this.loan;
}
public Integer getInstallmentNumber() {
return this.installmentNumber;
}
public LocalDate getFromDate() {
return this.fromDate;
}
public void setPostDatedChecksToNull() {
this.postDatedChecks = null;
}
public Set<PostDatedChecks> getPostDatedCheck() {
return this.postDatedChecks;
}
public LocalDate getDueDate() {
return this.dueDate;
}
public Money getCredits(final MonetaryCurrency currency) {
return Money.of(currency, this.credits);
}
public Money getPrincipal(final MonetaryCurrency currency) {
return Money.of(currency, this.principal);
}
public Money getPrincipalCompleted(final MonetaryCurrency currency) {
return Money.of(currency, this.principalCompleted);
}
public void updateLoanRepaymentSchedule(final BigDecimal amountWaived) {
this.feeChargesWaived = this.feeChargesWaived.subtract(amountWaived);
}
public Money getPrincipalWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.principalWrittenOff);
}
public Money getPrincipalOutstanding(final MonetaryCurrency currency) {
final Money principalAccountedFor = getPrincipalCompleted(currency).plus(getPrincipalWrittenOff(currency));
return getPrincipal(currency).minus(principalAccountedFor);
}
public Money getInterestCharged(final MonetaryCurrency currency) {
return Money.of(currency, this.interestCharged);
}
public Money getInterestPaid(final MonetaryCurrency currency) {
return Money.of(currency, this.interestPaid);
}
public Money getInterestWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.interestWaived);
}
public Money getInterestWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.interestWrittenOff);
}
public Money getInterestOutstanding(final MonetaryCurrency currency) {
final Money interestAccountedFor = getInterestPaid(currency).plus(getInterestWaived(currency))
.plus(getInterestWrittenOff(currency));
return getInterestCharged(currency).minus(interestAccountedFor);
}
public Money getInterestAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.interestAccrued);
}
public Money getFeeChargesCharged(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesCharged);
}
public Money getFeeChargesPaid(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesPaid);
}
public Money getFeeChargesWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesWaived);
}
public Money getFeeChargesWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesWrittenOff);
}
public Money getFeeChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getFeeChargesPaid(currency).plus(getFeeChargesWaived(currency))
.plus(getFeeChargesWrittenOff(currency));
return getFeeChargesCharged(currency).minus(feeChargesAccountedFor);
}
public Money getFeeAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.feeAccrued);
}
public Money getPenaltyChargesCharged(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyCharges);
}
public Money getPenaltyChargesPaid(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesPaid);
}
public Money getPenaltyChargesWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesWaived);
}
public Money getPenaltyChargesWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesWrittenOff);
}
public Money getPenaltyChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getPenaltyChargesPaid(currency).plus(getPenaltyChargesWaived(currency))
.plus(getPenaltyChargesWrittenOff(currency));
return getPenaltyChargesCharged(currency).minus(feeChargesAccountedFor);
}
public Money getPenaltyAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyAccrued);
}
public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterestOutstanding(currency).isGreaterThanZero();
}
public Money getTotalPrincipalAndInterest(final MonetaryCurrency currency) {
return getPrincipal(currency).plus(getInterestCharged(currency));
}
public Money getTotalOutstanding(final MonetaryCurrency currency) {
return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)).plus(getFeeChargesOutstanding(currency))
.plus(getPenaltyChargesOutstanding(currency));
}
public void updateLoan(final Loan loan) {
this.loan = loan;
}
public boolean isPartlyPaid() {
return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null);
}
public boolean isObligationsMet() {
return this.obligationsMet;
}
public boolean isNotFullyPaidOff() {
return !this.obligationsMet;
}
@Override
public int compareTo(LoanRepaymentScheduleInstallment o) {
return this.installmentNumber.compareTo(o.installmentNumber);
}
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
return !isPrincipalCompleted(currency);
}
public boolean isPrincipalCompleted(final MonetaryCurrency currency) {
return getPrincipalOutstanding(currency).isZero();
}
public void resetDerivedComponents() {
this.principalCompleted = null;
this.principalWrittenOff = null;
this.interestPaid = null;
this.interestWaived = null;
this.interestWrittenOff = null;
this.feeChargesPaid = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.penaltyChargesPaid = null;
this.penaltyChargesWaived = null;
this.penaltyChargesWrittenOff = null;
this.totalPaidInAdvance = null;
this.totalPaidLate = null;
this.obligationsMet = false;
this.obligationsMetOnDate = null;
if (this.credits != null) {
this.principal = this.principal.subtract(this.credits);
this.credits = null;
}
}
public void resetAccrualComponents() {
this.interestAccrued = null;
this.feeAccrued = null;
this.penaltyAccrued = null;
}
public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money penaltyPortionOfTransaction = Money.zero(currency);
final Money penaltyChargesDue = getPenaltyChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesDue)) {
this.penaltyChargesPaid = getPenaltyChargesPaid(currency).plus(penaltyChargesDue).getAmount();
penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(penaltyChargesDue);
} else {
this.penaltyChargesPaid = getPenaltyChargesPaid(currency).plus(transactionAmountRemaining).getAmount();
penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(transactionAmountRemaining);
}
this.penaltyChargesPaid = defaultToNullIfZero(this.penaltyChargesPaid);
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
trackAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, penaltyPortionOfTransaction);
return penaltyPortionOfTransaction;
}
public Money payFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money feePortionOfTransaction = Money.zero(currency);
final Money feeChargesDue = getFeeChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesDue)) {
this.feeChargesPaid = getFeeChargesPaid(currency).plus(feeChargesDue).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(feeChargesDue);
} else {
this.feeChargesPaid = getFeeChargesPaid(currency).plus(transactionAmountRemaining).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(transactionAmountRemaining);
}
this.feeChargesPaid = defaultToNullIfZero(this.feeChargesPaid);
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
trackAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, feePortionOfTransaction);
return feePortionOfTransaction;
}
public Money payInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money interestPortionOfTransaction = Money.zero(currency);
final Money interestDue = getInterestOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestPaid = getInterestPaid(currency).plus(interestDue).getAmount();
interestPortionOfTransaction = interestPortionOfTransaction.plus(interestDue);
} else {
this.interestPaid = getInterestPaid(currency).plus(transactionAmountRemaining).getAmount();
interestPortionOfTransaction = interestPortionOfTransaction.plus(transactionAmountRemaining);
}
this.interestPaid = defaultToNullIfZero(this.interestPaid);
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
trackAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, interestPortionOfTransaction);
return interestPortionOfTransaction;
}
public Money payPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money principalPortionOfTransaction = Money.zero(currency);
final Money principalDue = getPrincipalOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) {
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
} else {
this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmountRemaining).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining);
}
this.principalCompleted = defaultToNullIfZero(this.principalCompleted);
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
trackAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, principalPortionOfTransaction);
return principalPortionOfTransaction;
}
public Money waiveInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedInterestPortionOfTransaction = Money.zero(currency);
final Money interestDue = getInterestOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount();
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(interestDue);
} else {
this.interestWaived = getInterestWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(transactionAmountRemaining);
}
this.interestWaived = defaultToNullIfZero(this.interestWaived);
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return waivedInterestPortionOfTransaction;
}
public Money waivePenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedPenaltyChargesPortionOfTransaction = Money.zero(currency);
final Money penanltiesDue = getPenaltyChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penanltiesDue)) {
this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(penanltiesDue).getAmount();
waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(penanltiesDue);
} else {
this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(transactionAmountRemaining);
}
this.penaltyChargesWaived = defaultToNullIfZero(this.penaltyChargesWaived);
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return waivedPenaltyChargesPortionOfTransaction;
}
public Money waiveFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedFeeChargesPortionOfTransaction = Money.zero(currency);
final Money feesDue = getFeeChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) {
this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount();
waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(feesDue);
} else {
this.feeChargesWaived = getFeeChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(transactionAmountRemaining);
}
this.feeChargesWaived = defaultToNullIfZero(this.feeChargesWaived);
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return waivedFeeChargesPortionOfTransaction;
}
public Money writeOffOutstandingPrincipal(final LocalDate transactionDate, final MonetaryCurrency currency) {
final Money principalDue = getPrincipalOutstanding(currency);
this.principalWrittenOff = defaultToNullIfZero(principalDue.getAmount());
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return principalDue;
}
public Money writeOffOutstandingInterest(final LocalDate transactionDate, final MonetaryCurrency currency) {
final Money interestDue = getInterestOutstanding(currency);
this.interestWrittenOff = defaultToNullIfZero(interestDue.getAmount());
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return interestDue;
}
public Money writeOffOutstandingFeeCharges(final LocalDate transactionDate, final MonetaryCurrency currency) {
final Money feeChargesOutstanding = getFeeChargesOutstanding(currency);
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesOutstanding.getAmount());
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return feeChargesOutstanding;
}
public Money writeOffOutstandingPenaltyCharges(final LocalDate transactionDate, final MonetaryCurrency currency) {
final Money penaltyChargesOutstanding = getPenaltyChargesOutstanding(currency);
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesOutstanding.getAmount());
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return penaltyChargesOutstanding;
}
public boolean isOverdueOn(final LocalDate date) {
return getDueDate().isBefore(date);
}
public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
this.feeChargesCharged = defaultToNullIfZero(feeChargesDue.getAmount());
this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount());
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount());
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.getAmount());
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.getAmount());
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount());
}
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
this.interestAccrued = defaultToNullIfZero(interest.getAmount());
this.feeAccrued = defaultToNullIfZero(feeCharges.getAmount());
this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount());
}
public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) {
if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) {
this.obligationsMet = true;
this.obligationsMetOnDate = actualDisbursementDate;
}
}
private void trackAdvanceAndLateTotalsForRepaymentPeriod(final LocalDate transactionDate, final MonetaryCurrency currency,
final Money amountPaidInRepaymentPeriod) {
if (isInAdvance(transactionDate)) {
this.totalPaidInAdvance = asMoney(this.totalPaidInAdvance, currency).plus(amountPaidInRepaymentPeriod).getAmount();
} else if (isLatePayment(transactionDate)) {
this.totalPaidLate = asMoney(this.totalPaidLate, currency).plus(amountPaidInRepaymentPeriod).getAmount();
}
}
private Money asMoney(final BigDecimal decimal, final MonetaryCurrency currency) {
return Money.of(currency, decimal);
}
private boolean isInAdvance(final LocalDate transactionDate) {
return transactionDate.isBefore(getDueDate());
}
private boolean isLatePayment(final LocalDate transactionDate) {
return transactionDate.isAfter(getDueDate());
}
private void checkIfRepaymentPeriodObligationsAreMet(final LocalDate transactionDate, final MonetaryCurrency currency) {
this.obligationsMet = getTotalOutstanding(currency).isZero();
if (this.obligationsMet) {
this.obligationsMetOnDate = transactionDate;
} else {
this.obligationsMetOnDate = null;
}
}
public void updateDueDate(final LocalDate newDueDate) {
if (newDueDate != null) {
this.dueDate = newDueDate;
}
}
public void updateFromDate(final LocalDate newFromDate) {
if (newFromDate != null) {
this.fromDate = newFromDate;
}
}
public Money getTotalPaidInAdvance(final MonetaryCurrency currency) {
return Money.of(currency, this.totalPaidInAdvance);
}
public Money getTotalPaidLate(final MonetaryCurrency currency) {
return Money.of(currency, this.totalPaidLate);
}
public boolean isRecalculatedInterestComponent() {
return this.recalculatedInterestComponent;
}
public void setRecalculatedInterestComponent(boolean recalculatedInterestComponent) {
this.recalculatedInterestComponent = recalculatedInterestComponent;
}
public void updateInstallmentNumber(final Integer installmentNumber) {
if (installmentNumber != null) {
this.installmentNumber = installmentNumber;
}
}
public void updateInterestCharged(final BigDecimal interestCharged) {
this.interestCharged = interestCharged;
}
public void updateObligationMet(final Boolean obligationMet) {
this.obligationsMet = obligationMet;
}
public void updateObligationMetOnDate(final LocalDate obligationsMetOnDate) {
this.obligationsMetOnDate = obligationsMetOnDate;
}
public void updateInterestWrittenOff(final BigDecimal interestWrittenOff) {
this.interestWrittenOff = interestWrittenOff;
}
public void updatePrincipal(final BigDecimal principal) {
this.principal = principal;
}
public void addToPrincipal(final LocalDate transactionDate, final Money transactionAmount) {
if (this.principal == null) {
this.principal = transactionAmount.getAmount();
} else {
this.principal = this.principal.add(transactionAmount.getAmount());
}
checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
}
public void addToCredits(final BigDecimal amount) {
if (this.credits == null) {
this.credits = amount;
} else {
this.credits = this.credits.add(amount);
}
}
public BigDecimal getTotalPaidInAdvance() {
return this.totalPaidInAdvance;
}
public BigDecimal getTotalPaidLate() {
return this.totalPaidLate;
}
public LocalDate getObligationsMetOnDate() {
return this.obligationsMetOnDate;
}
/********** UNPAY COMPONENTS ****/
public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money penaltyPortionOfTransactionDeducted;
final Money penaltyChargesCompleted = getPenaltyChargesPaid(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesCompleted)) {
this.penaltyChargesPaid = Money.zero(currency).getAmount();
penaltyPortionOfTransactionDeducted = penaltyChargesCompleted;
} else {
this.penaltyChargesPaid = penaltyChargesCompleted.minus(transactionAmountRemaining).getAmount();
penaltyPortionOfTransactionDeducted = transactionAmountRemaining;
}
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
return penaltyPortionOfTransactionDeducted;
}
public Money unpayFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money feePortionOfTransactionDeducted;
final Money feeChargesCompleted = getFeeChargesPaid(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesCompleted)) {
this.feeChargesPaid = Money.zero(currency).getAmount();
feePortionOfTransactionDeducted = feeChargesCompleted;
} else {
this.feeChargesPaid = feeChargesCompleted.minus(transactionAmountRemaining).getAmount();
feePortionOfTransactionDeducted = transactionAmountRemaining;
}
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, feePortionOfTransactionDeducted);
return feePortionOfTransactionDeducted;
}
public Money unpayInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money interestPortionOfTransactionDeducted;
final Money interestCompleted = getInterestPaid(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestCompleted)) {
this.interestPaid = Money.zero(currency).getAmount();
interestPortionOfTransactionDeducted = interestCompleted;
} else {
this.interestPaid = interestCompleted.minus(transactionAmountRemaining).getAmount();
interestPortionOfTransactionDeducted = transactionAmountRemaining;
}
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, interestPortionOfTransactionDeducted);
return interestPortionOfTransactionDeducted;
}
public Money unpayPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money principalPortionOfTransactionDeducted;
final Money principalCompleted = getPrincipalCompleted(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalCompleted)) {
this.principalCompleted = Money.zero(currency).getAmount();
principalPortionOfTransactionDeducted = principalCompleted;
} else {
this.principalCompleted = principalCompleted.minus(transactionAmountRemaining).getAmount();
principalPortionOfTransactionDeducted = transactionAmountRemaining;
}
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, principalPortionOfTransactionDeducted);
return principalPortionOfTransactionDeducted;
}
private void reduceAdvanceAndLateTotalsForRepaymentPeriod(final LocalDate transactionDate, final MonetaryCurrency currency,
final Money amountDeductedInRepaymentPeriod) {
if (isInAdvance(transactionDate)) {
Money mTotalPaidInAdvance = Money.of(currency, this.totalPaidInAdvance);
if (mTotalPaidInAdvance.isLessThan(amountDeductedInRepaymentPeriod)
|| mTotalPaidInAdvance.isEqualTo(amountDeductedInRepaymentPeriod)) {
this.totalPaidInAdvance = Money.zero(currency).getAmount();
} else {
this.totalPaidInAdvance = mTotalPaidInAdvance.minus(amountDeductedInRepaymentPeriod).getAmount();
}
} else if (isLatePayment(transactionDate)) {
Money mTotalPaidLate = Money.of(currency, this.totalPaidLate);
if (mTotalPaidLate.isLessThan(amountDeductedInRepaymentPeriod) || mTotalPaidLate.isEqualTo(amountDeductedInRepaymentPeriod)) {
this.totalPaidLate = Money.zero(currency).getAmount();
} else {
this.totalPaidLate = mTotalPaidLate.minus(amountDeductedInRepaymentPeriod).getAmount();
}
}
}
public void updateDueAndCredits(final LocalDate transactionDate, final Money transactionAmount) {
updateDueDate(transactionDate);
addToCredits(transactionAmount.getAmount());
addToPrincipal(transactionDate, transactionAmount);
}
public Money getDue(MonetaryCurrency currency) {
return getPrincipal(currency).plus(getInterestCharged(currency)).plus(getFeeChargesCharged(currency))
.plus(getPenaltyChargesCharged(currency));
}
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return this.loanCompoundingDetails;
}
public Money getAccruedInterestOutstanding(final MonetaryCurrency currency) {
final Money interestAccountedFor = getInterestPaid(currency).plus(getInterestWaived(currency))
.plus(getInterestWrittenOff(currency));
return getInterestAccrued(currency).minus(interestAccountedFor);
}
public Money getTotalPaid(final MonetaryCurrency currency) {
return getPenaltyChargesPaid(currency).plus(getFeeChargesPaid(currency)).plus(getInterestPaid(currency))
.plus(getPrincipalCompleted(currency));
}
public BigDecimal getRescheduleInterestPortion() {
return rescheduleInterestPortion;
}
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
this.rescheduleInterestPortion = rescheduleInterestPortion;
}
public void setFeeChargesWaived(final BigDecimal newFeeChargesCharged) {
this.feeChargesWaived = newFeeChargesCharged;
}
public void setPenaltyChargesWaived(final BigDecimal newPenaltyChargesCharged) {
this.penaltyChargesWaived = newPenaltyChargesCharged;
}
public Set<LoanInstallmentCharge> getInstallmentCharges() {
return installmentCharges;
}
public boolean isAdditional() {
return additional;
}
public void markAsAdditional() {
this.additional = true;
}
public boolean isFirstPeriod() {
return (this.installmentNumber == 1);
}
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
return this.loanTransactionToRepaymentScheduleMappings;
}
}
@@ -0,0 +1,272 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
/**
* A wrapper around loan schedule related data exposing needed behaviour by loan.
*/
public class LoanRepaymentScheduleProcessingWrapper {
public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate,
final List<LoanRepaymentScheduleInstallment> repaymentPeriods, final Set<LoanCharge> loanCharges) {
Money totalInterest = Money.zero(currency);
Money totalPrincipal = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) {
totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
}
LocalDate startDate = disbursementDate;
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent());
final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges,
currency, !period.isRecalculatedInterestComponent(), period.isFirstPeriod());
final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
loanCharges, currency, !period.isRecalculatedInterestComponent(), period.isFirstPeriod());
final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), loanCharges,
currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent());
final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(),
loanCharges, currency, !period.isRecalculatedInterestComponent(), period.isFirstPeriod());
final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate,
period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent(), period.isFirstPeriod());
period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod,
feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod,
penaltyChargesWrittenOffForRepaymentPeriod);
startDate = period.getDueDate();
}
}
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal,
final Money totalInterest, boolean isInstallmentChargeApplicable) {
Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
boolean isDue = period.isFirstPeriod()
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount())
.add(period.getInterestCharged(monetaryCurrency).getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(period.getInterestCharged(monetaryCurrency).getAmount());
} else {
amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amountOrPercentage());
}
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(totalInterest.getAmount());
} else {
// If charge type is specified due date and loan is
// multi disburment loan.
// Then we need to get as of this loan charge due date
// how much amount disbursed.
if (loanCharge.getLoan() != null && loanCharge.isSpecifiedDueDate()
&& loanCharge.getLoan().isMultiDisburmentLoan()) {
for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) {
if (!loanDisbursementDetails.expectedDisbursementDate().isAfter(loanCharge.getDueDate())) {
amount = amount.add(loanDisbursementDetails.principal());
}
}
} else {
amount = amount.add(totalPrincipal.getAmount());
}
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else if (isDue) {
cumulative = cumulative.plus(loanCharge.amount());
}
}
}
return cumulative;
}
private Money cumulativeFeeChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable,
boolean isFirstPeriod) {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
}
} else if (isDue) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
}
}
}
return cumulative;
}
private Money cumulativeFeeChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable,
boolean isFirstPeriod) {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
}
} else if (isDue) {
cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
}
}
}
return cumulative;
}
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period,
final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable) {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) {
boolean isDue = period.isFirstPeriod()
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getPrincipal(currency).getAmount())
.add(period.getInterestCharged(currency).getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(period.getInterestCharged(currency).getAmount());
} else {
amount = amount.add(period.getPrincipal(currency).getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amountOrPercentage());
}
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(totalInterest.getAmount());
} else {
amount = amount.add(totalPrincipal.getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else if (isDue) {
cumulative = cumulative.plus(loanCharge.amount());
}
}
}
return cumulative;
}
private Money cumulativePenaltyChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable,
boolean isFirstPeriod) {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) {
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
}
} else if (isDue) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
}
}
}
return cumulative;
}
private Money cumulativePenaltyChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable,
final boolean isFirstPeriod) {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) {
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
}
} else if (isDue) {
cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
}
}
}
return cumulative;
}
}
@@ -0,0 +1,54 @@
/**
* 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.domain;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.springframework.beans.factory.annotation.Value;
@RequiredArgsConstructor
public class LoanRepaymentScheduleTransactionProcessorFactory {
private final LoanRepaymentScheduleTransactionProcessor defaultLoanRepaymentScheduleTransactionProcessor;
private final List<LoanRepaymentScheduleTransactionProcessor> processors;
@Value("${fineract.loan.transactionprocessor.error-not-found-fail}")
private Boolean errorNotFoundFail;
public LoanRepaymentScheduleTransactionProcessor determineProcessor(final String transactionProcessingStrategy) {
Optional<LoanRepaymentScheduleTransactionProcessor> processor = processors.stream()
.filter(p -> p.accept(transactionProcessingStrategy)).findFirst();
if (processor.isEmpty() && Boolean.TRUE.equals(errorNotFoundFail)) {
throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategy);
} else {
return processor.orElse(defaultLoanRepaymentScheduleTransactionProcessor);
}
}
public List<TransactionProcessingStrategyData> getStrategies() {
return processors.stream().map(p -> new TransactionProcessingStrategyData(null, p.getCode(), p.getName())).toList();
}
}
@@ -0,0 +1,155 @@
/**
* 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.domain;
/**
* Enum representation of loan status states.
*/
public enum LoanStatus {
INVALID(0, "loanStatusType.invalid"), //
SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
APPROVED(200, "loanStatusType.approved"), //
ACTIVE(300, "loanStatusType.active"), //
TRANSFER_IN_PROGRESS(303, "loanStatusType.transfer.in.progress"), //
TRANSFER_ON_HOLD(304, "loanStatusType.transfer.on.hold"), //
WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
REJECTED(500, "loanStatusType.rejected"), //
CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), //
CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), //
CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), //
OVERPAID(700, "loanStatusType.overpaid");
private final Integer value;
private final String code;
public static LoanStatus fromInt(final Integer statusValue) {
LoanStatus enumeration = LoanStatus.INVALID;
switch (statusValue) {
case 100:
enumeration = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
break;
case 200:
enumeration = LoanStatus.APPROVED;
break;
case 300:
enumeration = LoanStatus.ACTIVE;
break;
case 303:
enumeration = LoanStatus.TRANSFER_IN_PROGRESS;
break;
case 304:
enumeration = LoanStatus.TRANSFER_ON_HOLD;
break;
case 400:
enumeration = LoanStatus.WITHDRAWN_BY_CLIENT;
break;
case 500:
enumeration = LoanStatus.REJECTED;
break;
case 600:
enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET;
break;
case 601:
enumeration = LoanStatus.CLOSED_WRITTEN_OFF;
break;
case 602:
enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT;
break;
case 700:
enumeration = LoanStatus.OVERPAID;
break;
}
return enumeration;
}
LoanStatus(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final LoanStatus state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isSubmittedAndPendingApproval() {
return this.value.equals(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue());
}
public boolean isApproved() {
return this.value.equals(LoanStatus.APPROVED.getValue());
}
public boolean isActive() {
return this.value.equals(LoanStatus.ACTIVE.getValue());
}
public boolean isClosed() {
return isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule();
}
public boolean isClosedObligationsMet() {
return this.value.equals(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue());
}
public boolean isClosedWrittenOff() {
return this.value.equals(LoanStatus.CLOSED_WRITTEN_OFF.getValue());
}
public boolean isClosedWithOutsandingAmountMarkedForReschedule() {
return this.value.equals(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue());
}
public boolean isWithdrawnByClient() {
return this.value.equals(LoanStatus.WITHDRAWN_BY_CLIENT.getValue());
}
public boolean isRejected() {
return this.value.equals(LoanStatus.REJECTED.getValue());
}
public boolean isActiveOrAwaitingApprovalOrDisbursal() {
return isApproved() || isSubmittedAndPendingApproval() || isActive();
}
public boolean isTransferInProgress() {
return this.value.equals(LoanStatus.TRANSFER_IN_PROGRESS.getValue());
}
public boolean isTransferOnHold() {
return this.value.equals(LoanStatus.TRANSFER_ON_HOLD.getValue());
}
public boolean isUnderTransfer() {
return isTransferInProgress() || isTransferOnHold();
}
public boolean isOverpaid() {
return this.value.equals(LoanStatus.OVERPAID.getValue());
}
}
@@ -0,0 +1,81 @@
/**
* 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.domain;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public enum LoanSubStatus {
INVALID(0, "loanSubStatusType.invalid"), //
FORECLOSED(100, "loanSubStatusType.foreclosed");
private final Integer value;
private final String code;
public static LoanSubStatus fromInt(final Integer statusValue) {
LoanSubStatus enumeration = LoanSubStatus.INVALID;
switch (statusValue) {
case 100:
enumeration = LoanSubStatus.FORECLOSED;
break;
}
return enumeration;
}
LoanSubStatus(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final LoanSubStatus state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isForeclosed() {
return this.value.equals(LoanSubStatus.FORECLOSED.getValue());
}
public static EnumOptionData loanSubStatus(final int id) {
return loanSubStatusEnum(LoanSubStatus.fromInt(id));
}
public static EnumOptionData loanSubStatusEnum(final LoanSubStatus type) {
final String codePrefix = "loanSubStatus.";
EnumOptionData optionData = null;
switch (type) {
case FORECLOSED:
optionData = new EnumOptionData(LoanSubStatus.FORECLOSED.getValue().longValue(),
codePrefix + LoanSubStatus.FORECLOSED.getCode(), "Foreclosed");
break;
default:
optionData = new EnumOptionData(LoanSubStatus.INVALID.getValue().longValue(), LoanSubStatus.INVALID.getCode(), "Invalid");
break;
}
return optionData;
}
}
@@ -0,0 +1,372 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.Getter;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
/**
* Encapsulates all the summary details of a {@link Loan}.
*
* {@link LoanSummary} fields are updated through a scheduled job. see -
*
*/
@Embeddable
@Getter
public class LoanSummary {
// derived totals fields
@Column(name = "principal_disbursed_derived", scale = 6, precision = 19)
private BigDecimal totalPrincipalDisbursed;
@Column(name = "principal_adjustments_derived", scale = 6, precision = 19)
private BigDecimal totalPrincipalAdjustments;
@Column(name = "principal_repaid_derived", scale = 6, precision = 19)
private BigDecimal totalPrincipalRepaid;
@Column(name = "principal_writtenoff_derived", scale = 6, precision = 19)
private BigDecimal totalPrincipalWrittenOff;
@Column(name = "principal_outstanding_derived", scale = 6, precision = 19)
private BigDecimal totalPrincipalOutstanding;
@Column(name = "interest_charged_derived", scale = 6, precision = 19)
private BigDecimal totalInterestCharged;
@Column(name = "interest_repaid_derived", scale = 6, precision = 19)
private BigDecimal totalInterestRepaid;
@Column(name = "interest_waived_derived", scale = 6, precision = 19)
private BigDecimal totalInterestWaived;
@Column(name = "interest_writtenoff_derived", scale = 6, precision = 19)
private BigDecimal totalInterestWrittenOff;
@Column(name = "interest_outstanding_derived", scale = 6, precision = 19)
private BigDecimal totalInterestOutstanding;
@Column(name = "fee_charges_charged_derived", scale = 6, precision = 19)
private BigDecimal totalFeeChargesCharged;
@Column(name = "total_charges_due_at_disbursement_derived", scale = 6, precision = 19)
private BigDecimal totalFeeChargesDueAtDisbursement;
@Column(name = "fee_charges_repaid_derived", scale = 6, precision = 19)
private BigDecimal totalFeeChargesRepaid;
@Column(name = "fee_charges_waived_derived", scale = 6, precision = 19)
private BigDecimal totalFeeChargesWaived;
@Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19)
private BigDecimal totalFeeChargesWrittenOff;
@Column(name = "fee_charges_outstanding_derived", scale = 6, precision = 19)
private BigDecimal totalFeeChargesOutstanding;
@Column(name = "penalty_charges_charged_derived", scale = 6, precision = 19)
private BigDecimal totalPenaltyChargesCharged;
@Column(name = "penalty_charges_repaid_derived", scale = 6, precision = 19)
private BigDecimal totalPenaltyChargesRepaid;
@Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19)
private BigDecimal totalPenaltyChargesWaived;
@Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19)
private BigDecimal totalPenaltyChargesWrittenOff;
@Column(name = "penalty_charges_outstanding_derived", scale = 6, precision = 19)
private BigDecimal totalPenaltyChargesOutstanding;
@Column(name = "total_expected_repayment_derived", scale = 6, precision = 19)
private BigDecimal totalExpectedRepayment;
@Column(name = "total_repayment_derived", scale = 6, precision = 19)
private BigDecimal totalRepayment;
@Column(name = "total_expected_costofloan_derived", scale = 6, precision = 19)
private BigDecimal totalExpectedCostOfLoan;
@Column(name = "total_costofloan_derived", scale = 6, precision = 19)
private BigDecimal totalCostOfLoan;
@Column(name = "total_waived_derived", scale = 6, precision = 19)
private BigDecimal totalWaived;
@Column(name = "total_writtenoff_derived", scale = 6, precision = 19)
private BigDecimal totalWrittenOff;
@Column(name = "total_outstanding_derived", scale = 6, precision = 19)
private BigDecimal totalOutstanding;
public static LoanSummary create(final BigDecimal totalFeeChargesDueAtDisbursement) {
return new LoanSummary(totalFeeChargesDueAtDisbursement);
}
protected LoanSummary() {
//
}
private LoanSummary(final BigDecimal totalFeeChargesDueAtDisbursement) {
this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement;
}
public void updateTotalFeeChargesDueAtDisbursement(final BigDecimal totalFeeChargesDueAtDisbursement) {
this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement;
}
public Money getTotalFeeChargesDueAtDisbursement(final MonetaryCurrency currency) {
return Money.of(currency, this.totalFeeChargesDueAtDisbursement);
}
public Money getTotalOutstanding(final MonetaryCurrency currency) {
return Money.of(currency, this.totalOutstanding);
}
public void updateFeeChargeOutstanding(final BigDecimal totalFeeChargesOutstanding) {
this.totalFeeChargesOutstanding = totalFeeChargesOutstanding;
}
public void updatePenaltyChargeOutstanding(final BigDecimal totalPenaltyChargesOutstanding) {
this.totalPenaltyChargesOutstanding = totalPenaltyChargesOutstanding;
}
public void updateFeeChargesWaived(final BigDecimal totalFeeChargesWaived) {
this.totalFeeChargesWaived = totalFeeChargesWaived;
}
public void updatePenaltyChargesWaived(final BigDecimal totalPenaltyChargesWaived) {
this.totalPenaltyChargesWaived = totalPenaltyChargesWaived;
}
public boolean isRepaidInFull(final MonetaryCurrency currency) {
return getTotalOutstanding(currency).isZero();
}
public BigDecimal getTotalInterestCharged() {
return this.totalInterestCharged;
}
public BigDecimal getTotalPrincipalOutstanding() {
return this.totalPrincipalOutstanding;
}
public BigDecimal getTotalInterestOutstanding() {
return this.totalInterestOutstanding;
}
public BigDecimal getTotalFeeChargesOutstanding() {
return this.totalFeeChargesOutstanding;
}
public BigDecimal getTotalPenaltyChargesOutstanding() {
return this.totalPenaltyChargesOutstanding;
}
public BigDecimal getTotalOutstanding() {
return this.totalOutstanding;
}
public void updateTotalOutstanding(final BigDecimal newTotalOutstanding) {
this.totalOutstanding = newTotalOutstanding;
}
public void updateTotalWaived(final BigDecimal totalWaived) {
this.totalWaived = totalWaived;
}
/**
* All fields but <code>totalFeeChargesDueAtDisbursement</code> should be reset.
*/
public void zeroFields() {
this.totalPrincipalDisbursed = BigDecimal.ZERO;
this.totalPrincipalAdjustments = BigDecimal.ZERO;
this.totalPrincipalRepaid = BigDecimal.ZERO;
this.totalPrincipalWrittenOff = BigDecimal.ZERO;
this.totalPrincipalOutstanding = BigDecimal.ZERO;
this.totalInterestCharged = BigDecimal.ZERO;
this.totalInterestRepaid = BigDecimal.ZERO;
this.totalInterestWaived = BigDecimal.ZERO;
this.totalInterestWrittenOff = BigDecimal.ZERO;
this.totalInterestOutstanding = BigDecimal.ZERO;
this.totalFeeChargesCharged = BigDecimal.ZERO;
this.totalFeeChargesRepaid = BigDecimal.ZERO;
this.totalFeeChargesWaived = BigDecimal.ZERO;
this.totalFeeChargesWrittenOff = BigDecimal.ZERO;
this.totalFeeChargesOutstanding = BigDecimal.ZERO;
this.totalPenaltyChargesCharged = BigDecimal.ZERO;
this.totalPenaltyChargesRepaid = BigDecimal.ZERO;
this.totalPenaltyChargesWaived = BigDecimal.ZERO;
this.totalPenaltyChargesWrittenOff = BigDecimal.ZERO;
this.totalPenaltyChargesOutstanding = BigDecimal.ZERO;
this.totalExpectedRepayment = BigDecimal.ZERO;
this.totalRepayment = BigDecimal.ZERO;
this.totalExpectedCostOfLoan = BigDecimal.ZERO;
this.totalCostOfLoan = BigDecimal.ZERO;
this.totalWaived = BigDecimal.ZERO;
this.totalWrittenOff = BigDecimal.ZERO;
this.totalOutstanding = BigDecimal.ZERO;
}
public void updateSummary(final MonetaryCurrency currency, final Money principal,
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LoanSummaryWrapper summaryWrapper,
Set<LoanCharge> charges) {
this.totalPrincipalDisbursed = principal.getAmount();
this.totalPrincipalAdjustments = summaryWrapper.calculateTotalPrincipalAdjusted(repaymentScheduleInstallments, currency)
.getAmount();
this.totalPrincipalRepaid = summaryWrapper.calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount();
this.totalPrincipalWrittenOff = summaryWrapper.calculateTotalPrincipalWrittenOff(repaymentScheduleInstallments, currency)
.getAmount();
this.totalPrincipalOutstanding = principal.plus(this.totalPrincipalAdjustments).minus(this.totalPrincipalRepaid)
.minus(this.totalPrincipalWrittenOff).getAmount();
final Money totalInterestCharged = summaryWrapper.calculateTotalInterestCharged(repaymentScheduleInstallments, currency);
this.totalInterestCharged = totalInterestCharged.getAmount();
this.totalInterestRepaid = summaryWrapper.calculateTotalInterestRepaid(repaymentScheduleInstallments, currency).getAmount();
this.totalInterestWaived = summaryWrapper.calculateTotalInterestWaived(repaymentScheduleInstallments, currency).getAmount();
this.totalInterestWrittenOff = summaryWrapper.calculateTotalInterestWrittenOff(repaymentScheduleInstallments, currency).getAmount();
this.totalInterestOutstanding = totalInterestCharged.minus(this.totalInterestRepaid).minus(this.totalInterestWaived)
.minus(this.totalInterestWrittenOff).getAmount();
final Money totalFeeChargesCharged = summaryWrapper.calculateTotalFeeChargesCharged(repaymentScheduleInstallments, currency)
.plus(this.totalFeeChargesDueAtDisbursement);
this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount();
Money totalFeeChargesRepaidAtDisbursement = summaryWrapper.calculateTotalChargesRepaidAtDisbursement(charges, currency);
this.totalFeeChargesRepaid = totalFeeChargesRepaidAtDisbursement.getAmount();
if (charges != null) {
this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(charges, currency).getAmount();
} else {
this.totalFeeChargesWaived = BigDecimal.ZERO;
}
this.totalFeeChargesWrittenOff = summaryWrapper.calculateTotalFeeChargesWrittenOff(repaymentScheduleInstallments, currency)
.getAmount();
this.totalFeeChargesOutstanding = totalFeeChargesCharged.minus(this.totalFeeChargesRepaid).minus(this.totalFeeChargesWaived)
.minus(this.totalFeeChargesWrittenOff).getAmount();
final Money totalPenaltyChargesCharged = summaryWrapper.calculateTotalPenaltyChargesCharged(repaymentScheduleInstallments,
currency);
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged.getAmount();
this.totalPenaltyChargesRepaid = summaryWrapper.calculateTotalPenaltyChargesRepaid(repaymentScheduleInstallments, currency)
.getAmount();
this.totalPenaltyChargesWaived = summaryWrapper.calculateTotalPenaltyChargesWaived(repaymentScheduleInstallments, currency)
.getAmount();
this.totalPenaltyChargesWrittenOff = summaryWrapper.calculateTotalPenaltyChargesWrittenOff(repaymentScheduleInstallments, currency)
.getAmount();
this.totalPenaltyChargesOutstanding = totalPenaltyChargesCharged.minus(this.totalPenaltyChargesRepaid)
.minus(this.totalPenaltyChargesWaived).minus(this.totalPenaltyChargesWrittenOff).getAmount();
final Money totalExpectedRepayment = Money.of(currency, this.totalPrincipalDisbursed).plus(this.totalInterestCharged)
.plus(this.totalFeeChargesCharged).plus(this.totalPenaltyChargesCharged);
this.totalExpectedRepayment = totalExpectedRepayment.getAmount();
final Money totalRepayment = Money.of(currency, this.totalPrincipalRepaid).plus(this.totalInterestRepaid)
.plus(this.totalFeeChargesRepaid).plus(this.totalPenaltyChargesRepaid);
this.totalRepayment = totalRepayment.getAmount();
final Money totalExpectedCostOfLoan = Money.of(currency, this.totalInterestCharged).plus(this.totalFeeChargesCharged)
.plus(this.totalPenaltyChargesCharged);
this.totalExpectedCostOfLoan = totalExpectedCostOfLoan.getAmount();
final Money totalCostOfLoan = Money.of(currency, this.totalInterestRepaid).plus(this.totalFeeChargesRepaid)
.plus(this.totalPenaltyChargesRepaid);
this.totalCostOfLoan = totalCostOfLoan.getAmount();
final Money totalWaived = Money.of(currency, this.totalInterestWaived).plus(this.totalFeeChargesWaived)
.plus(this.totalPenaltyChargesWaived);
this.totalWaived = totalWaived.getAmount();
final Money totalWrittenOff = Money.of(currency, this.totalPrincipalWrittenOff).plus(this.totalInterestWrittenOff)
.plus(this.totalFeeChargesWrittenOff).plus(this.totalPenaltyChargesWrittenOff);
this.totalWrittenOff = totalWrittenOff.getAmount();
final Money totalOutstanding = Money.of(currency, this.totalPrincipalOutstanding).plus(this.totalInterestOutstanding)
.plus(this.totalFeeChargesOutstanding).plus(this.totalPenaltyChargesOutstanding);
this.totalOutstanding = totalOutstanding.getAmount();
}
public BigDecimal getTotalPrincipalDisbursed() {
return this.totalPrincipalDisbursed;
}
public BigDecimal getTotalPrincipalRepaid() {
return this.totalPrincipalRepaid;
}
public BigDecimal getTotalWrittenOff() {
return this.totalWrittenOff;
}
/**
* @return total interest repaid
**/
public BigDecimal getTotalInterestRepaid() {
return this.totalInterestRepaid;
}
public BigDecimal getTotalFeeChargesCharged() {
return this.totalFeeChargesCharged;
}
public BigDecimal getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
public BigDecimal getTotalPrincipalWrittenOff() {
return this.totalPrincipalWrittenOff;
}
public BigDecimal getTotalInterestWaived() {
return this.totalInterestWaived;
}
public BigDecimal getTotalFeeChargesRepaid() {
return this.totalFeeChargesRepaid;
}
public BigDecimal getTotalFeeChargesWaived() {
return this.totalFeeChargesWaived;
}
public BigDecimal getTotalPenaltyChargesRepaid() {
return this.totalPenaltyChargesRepaid;
}
public BigDecimal getTotalPenaltyChargesWaived() {
return this.totalPenaltyChargesWaived;
}
public BigDecimal getTotalExpectedRepayment() {
return this.totalExpectedRepayment;
}
}
@@ -0,0 +1,256 @@
/**
* 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.domain;
import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.springframework.stereotype.Component;
/**
* A wrapper for dealing with side-effect free functionality related to a loans transactions and repayment schedule.
*/
@Component
public final class LoanSummaryWrapper {
public Money calculateTotalPrincipalRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getPrincipalCompleted(currency));
}
return total;
}
public Money calculateTotalPrincipalAdjusted(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getCredits(currency));
}
return total;
}
public Money calculateTotalPrincipalWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getPrincipalWrittenOff(currency));
}
return total;
}
public Money calculateTotalPrincipalOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency, final LocalDate overdueAsOf) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
if (installment.isOverdueOn(overdueAsOf)) {
total = total.plus(installment.getPrincipalOutstanding(currency));
}
}
return total;
}
public Money calculateTotalInterestCharged(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getInterestCharged(currency));
}
return total;
}
public Money calculateTotalInterestRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getInterestPaid(currency));
}
return total;
}
public Money calculateTotalInterestWaived(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getInterestWaived(currency));
}
return total;
}
public Money calculateTotalInterestWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getInterestWrittenOff(currency));
}
return total;
}
public Money calculateTotalInterestOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency, final LocalDate overdueAsOf) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
if (installment.isOverdueOn(overdueAsOf)) {
total = total.plus(installment.getInterestOutstanding(currency));
}
}
return total;
}
public Money calculateTotalFeeChargesCharged(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getFeeChargesCharged(currency));
}
return total;
}
public Money calculateTotalFeeChargesRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getFeeChargesPaid(currency));
}
return total;
}
public Money calculateTotalFeeChargesWaived(Set<LoanCharge> charges, final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanCharge charge : charges) {
if (charge.isActive() && !charge.isPenaltyCharge()) {
total = total.plus(charge.getAmountWaived(currency));
}
}
return total;
}
public Money calculateTotalFeeChargesWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getFeeChargesWrittenOff(currency));
}
return total;
}
public Money calculateTotalFeeChargesOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency, final LocalDate overdueAsOf) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
if (installment.isOverdueOn(overdueAsOf)) {
total = total.plus(installment.getFeeChargesOutstanding(currency));
}
}
return total;
}
public Money calculateTotalPenaltyChargesCharged(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getPenaltyChargesCharged(currency));
}
return total;
}
public Money calculateTotalPenaltyChargesRepaid(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getPenaltyChargesPaid(currency));
}
return total;
}
public Money calculateTotalPenaltyChargesWaived(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getPenaltyChargesWaived(currency));
}
return total;
}
public Money calculateTotalPenaltyChargesWrittenOff(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getPenaltyChargesWrittenOff(currency));
}
return total;
}
public Money calculateTotalPenaltyChargesOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency, final LocalDate overdueAsOf) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
if (installment.isOverdueOn(overdueAsOf)) {
total = total.plus(installment.getPenaltyChargesOutstanding(currency));
}
}
return total;
}
public Money calculateTotalOverdueOn(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency, final LocalDate overdueAsOf) {
final Money principalOverdue = calculateTotalPrincipalOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
final Money interestOverdue = calculateTotalInterestOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
final Money feeChargesOverdue = calculateTotalFeeChargesOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
final Money penaltyChargesOverdue = calculateTotalPenaltyChargesOverdueOn(repaymentScheduleInstallments, currency, overdueAsOf);
return principalOverdue.plus(interestOverdue).plus(feeChargesOverdue).plus(penaltyChargesOverdue);
}
public LocalDate determineOverdueSinceDateFrom(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
final MonetaryCurrency currency, final LocalDate from) {
LocalDate overdueSince = null;
final Money totalOverdue = calculateTotalOverdueOn(repaymentScheduleInstallments, currency, from);
if (totalOverdue.isGreaterThanZero()) {
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
if (installment.isOverdueOn(from)) {
if (overdueSince == null || overdueSince.isAfter(installment.getDueDate())) {
overdueSince = installment.getDueDate();
}
}
}
}
return overdueSince;
}
public Money calculateTotalChargesRepaidAtDisbursement(Set<LoanCharge> charges, MonetaryCurrency currency) {
Money total = Money.zero(currency);
if (charges == null) {
return total;
}
for (final LoanCharge loanCharge : charges) {
if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()) {
total = total.plus(loanCharge.getAmountPaid(currency));
}
}
return total;
}
}
@@ -0,0 +1,126 @@
/**
* 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.domain;
public enum LoanTermVariationType {
INVALID(0, "loanTermType.invalid"), //
EMI_AMOUNT(1, "loanTermType.emiAmount"), //
INTEREST_RATE(2, "loanTermType.interestRate"), //
PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), //
DUE_DATE(4, "loanTermType.dueDate"), //
INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), //
DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment"), GRACE_ON_INTEREST(7, "loanTermType.graceOnInterest"), GRACE_ON_PRINCIPAL(8,
"loanTermType.graceOnPrincipal"), EXTEND_REPAYMENT_PERIOD(9,
"loanTermType.extendRepaymentPeriod"), INTEREST_RATE_FROM_INSTALLMENT(10, "loanTermType.interestRateFromInstallment");
private final Integer value;
private final String code;
LoanTermVariationType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static LoanTermVariationType fromInt(final Integer value) {
LoanTermVariationType enumeration = LoanTermVariationType.INVALID;
switch (value) {
case 1:
enumeration = LoanTermVariationType.EMI_AMOUNT;
break;
case 2:
enumeration = LoanTermVariationType.INTEREST_RATE;
break;
case 3:
enumeration = LoanTermVariationType.PRINCIPAL_AMOUNT;
break;
case 4:
enumeration = LoanTermVariationType.DUE_DATE;
break;
case 5:
enumeration = LoanTermVariationType.INSERT_INSTALLMENT;
break;
case 6:
enumeration = LoanTermVariationType.DELETE_INSTALLMENT;
break;
case 7:
enumeration = LoanTermVariationType.GRACE_ON_INTEREST;
break;
case 8:
enumeration = LoanTermVariationType.GRACE_ON_PRINCIPAL;
break;
case 9:
enumeration = LoanTermVariationType.EXTEND_REPAYMENT_PERIOD;
break;
case 10:
enumeration = LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT;
break;
}
return enumeration;
}
public boolean isEMIAmountVariation() {
return this.value.equals(LoanTermVariationType.EMI_AMOUNT.getValue());
}
public boolean isInterestRateVariation() {
return this.value.equals(LoanTermVariationType.INTEREST_RATE.getValue());
}
public boolean isPrincipalAmountVariation() {
return this.value.equals(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue());
}
public boolean isDueDateVariation() {
return this.value.equals(LoanTermVariationType.DUE_DATE.getValue());
}
public boolean isInsertInstallment() {
return this.value.equals(LoanTermVariationType.INSERT_INSTALLMENT.getValue());
}
public boolean isDeleteInstallment() {
return this.value.equals(LoanTermVariationType.DELETE_INSTALLMENT.getValue());
}
public boolean isGraceOnInterest() {
return this.value.equals(LoanTermVariationType.GRACE_ON_INTEREST.getValue());
}
public boolean isGraceOnPrincipal() {
return this.value.equals(LoanTermVariationType.GRACE_ON_PRINCIPAL.getValue());
}
public boolean isExtendRepaymentPeriod() {
return this.value.equals(LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue());
}
public boolean isInterestRateFromInstallment() {
return this.value.equals(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getValue());
}
}
@@ -0,0 +1,170 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
@Entity
@Table(name = "m_loan_term_variations")
public class LoanTermVariations extends AbstractPersistableCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Column(name = "term_type", nullable = false)
private Integer termType;
@Column(name = "applicable_date", nullable = false)
private LocalDate termApplicableFrom;
@Column(name = "decimal_value", scale = 6, precision = 19)
private BigDecimal decimalValue;
@Column(name = "date_value")
private LocalDate dateValue;
@Column(name = "is_specific_to_installment", nullable = false)
private boolean isSpecificToInstallment;
@Column(name = "applied_on_loan_status", nullable = false)
private Integer onLoanStatus;
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private LoanTermVariations parent;
public LoanTermVariations(final Integer termType, final LocalDate termApplicableFrom, final BigDecimal decimalValue,
final LocalDate dateValue, final boolean isSpecificToInstallment, final Loan loan) {
this.loan = loan;
this.termApplicableFrom = termApplicableFrom;
this.termType = termType;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loan.getStatus().getValue();
this.isActive = true;
this.parent = null;
}
public LoanTermVariations(final Integer termType, final LocalDate termApplicableFrom, final BigDecimal decimalValue,
final LocalDate dateValue, final boolean isSpecificToInstallment, final Loan loan, final Integer loanStatus) {
this.loan = loan;
this.termApplicableFrom = termApplicableFrom;
this.termType = termType;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loanStatus;
this.isActive = true;
this.parent = null;
}
public LoanTermVariations(final Integer termType, final LocalDate termApplicableFrom, final BigDecimal decimalValue,
final LocalDate dateValue, final boolean isSpecificToInstallment, final Loan loan, final Integer loanStatus,
final Boolean isActive, final LoanTermVariations parent) {
this.loan = loan;
this.termApplicableFrom = termApplicableFrom;
this.termType = termType;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loanStatus;
this.isActive = isActive;
this.parent = parent;
}
protected LoanTermVariations() {
}
public LoanTermVariationType getTermType() {
return LoanTermVariationType.fromInt(this.termType);
}
public LoanTermVariationsData toData() {
EnumOptionData type = LoanEnumerations.loanVariationType(this.termType);
return new LoanTermVariationsData(getId(), type, this.termApplicableFrom, this.decimalValue, this.dateValue,
this.isSpecificToInstallment);
}
public LocalDate getTermApplicableFrom() {
return this.termApplicableFrom;
}
public LocalDate fetchTermApplicaDate() {
return this.termApplicableFrom;
}
public BigDecimal getTermValue() {
return this.decimalValue;
}
public LocalDate getDateValue() {
return this.dateValue;
}
public LocalDate fetchDateValue() {
return this.dateValue;
}
public void setTermApplicableFrom(LocalDate termApplicableFrom) {
this.termApplicableFrom = termApplicableFrom;
}
public void setDecimalValue(BigDecimal decimalValue) {
this.decimalValue = decimalValue;
}
public Integer getOnLoanStatus() {
return this.onLoanStatus;
}
public Boolean isActive() {
return this.isActive;
}
public LoanTermVariations parent() {
return this.parent;
}
public void updateIsActive(final Boolean isActive) {
this.isActive = isActive;
}
public void markAsInactive() {
this.isActive = false;
}
}
@@ -0,0 +1,69 @@
/**
* 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.domain;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_loan_topup")
public class LoanTopupDetails extends AbstractPersistableCustom {
@OneToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Column(name = "closure_loan_id", nullable = false)
private Long closureLoanId;
@Column(name = "account_transfer_details_id", nullable = true)
private Long accountTransferDetailsId;
@Column(name = "topup_amount", nullable = true)
private BigDecimal topupAmount;
protected LoanTopupDetails() {}
public LoanTopupDetails(final Loan loan, final Long loanIdToClose) {
this.loan = loan;
this.closureLoanId = loanIdToClose;
}
public Long getLoanIdToClose() {
return this.closureLoanId;
}
public BigDecimal getTopupAmount() {
return this.topupAmount;
}
public void setTopupAmount(BigDecimal topupAmount) {
this.topupAmount = topupAmount;
}
public void setAccountTransferDetails(Long accountTransferDetailsId) {
this.accountTransferDetailsId = accountTransferDetailsId;
}
}
@@ -0,0 +1,65 @@
/**
* 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.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.charge.domain.Charge;
@Entity
@Table(name = "m_loan_tranche_charges")
public class LoanTrancheCharge extends AbstractPersistableCustom {
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "charge_id", nullable = false)
private Charge charge;
LoanTrancheCharge() {
}
LoanTrancheCharge(Charge chargeDefinition) {
this.charge = chargeDefinition;
}
public LoanTrancheCharge(Charge charge, Loan loan) {
this.charge = charge;
this.loan = loan;
}
public static LoanTrancheCharge createLoanTrancheCharge(Charge chargeDefinition) {
return new LoanTrancheCharge(chargeDefinition);
}
public static LoanTrancheCharge createLoanTrancheChargeWithLoan(Charge chargeDefinition, Loan loan) {
return new LoanTrancheCharge(chargeDefinition, loan);
}
public Charge getCharge() {
return charge;
}
}
@@ -0,0 +1,53 @@
/**
* 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.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_loan_tranche_disbursement_charge")
public class LoanTrancheDisbursementCharge extends AbstractPersistableCustom {
@ManyToOne
@JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
private LoanCharge loancharge;
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "disbursement_detail_id", referencedColumnName = "id", nullable = false)
private LoanDisbursementDetails loanDisbursementDetails;
public LoanTrancheDisbursementCharge() {
}
public LoanTrancheDisbursementCharge(final LoanCharge loancharge, final LoanDisbursementDetails loanDisbursementDetails) {
this.loancharge = loancharge;
this.loanDisbursementDetails = loanDisbursementDetails;
}
public LoanDisbursementDetails getloanDisbursementDetails() {
return this.loanDisbursementDetails;
}
}
@@ -0,0 +1,961 @@
/**
* 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.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
/**
* All monetary transactions against a loan are modelled through this entity. Disbursements, Repayments, Waivers,
* Write-off etc
*/
@Entity
@Table(name = "m_loan_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") })
public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@ManyToOne(optional = true)
@JoinColumn(name = "payment_detail_id", nullable = true)
private PaymentDetail paymentDetail;
@Column(name = "transaction_type_enum", nullable = false)
private Integer typeOf;
@Column(name = "transaction_date", nullable = false)
private LocalDate dateOf;
@Column(name = "submitted_on_date", nullable = false)
private LocalDate submittedOnDate;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalPortion;
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestPortion;
@Column(name = "fee_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesPortion;
@Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPortion;
@Column(name = "overpayment_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal overPaymentPortion;
@Column(name = "unrecognized_income_portion", scale = 6, precision = 19, nullable = true)
private BigDecimal unrecognizedIncomePortion;
@Column(name = "is_reversed", nullable = false)
private boolean reversed;
@Column(name = "external_id", length = 100, nullable = true, unique = true)
private ExternalId externalId;
@Column(name = "reversal_external_id", length = 100, nullable = true, unique = true)
private ExternalId reversalExternalId;
@Column(name = "reversed_on_date", nullable = true)
private LocalDate reversedOnDate;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true, fetch = FetchType.EAGER)
private Set<LoanChargePaidBy> loanChargesPaid = new HashSet<>();
@Column(name = "outstanding_loan_balance_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal outstandingLoanBalance;
@Column(name = "manually_adjusted_or_reversed", nullable = false)
private boolean manuallyAdjustedOrReversed;
@Column(name = "charge_refund_charge_type", length = 1, nullable = true, unique = true)
private String chargeRefundChargeType;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "loanTransaction")
private Set<LoanCollateralManagement> loanCollateralManagementSet = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanTransaction")
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "fromTransaction")
private Set<LoanTransactionRelation> loanTransactionRelations = new HashSet<>();
protected LoanTransaction() {}
public static LoanTransaction incomePosting(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
final ExternalId externalId) {
final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue();
final BigDecimal principalPortion = BigDecimal.ZERO;
final BigDecimal overPaymentPortion = BigDecimal.ZERO;
final boolean reversed = false;
final PaymentDetail paymentDetail = null;
return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion,
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
}
public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate disbursementDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate,
externalId);
}
public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId);
}
public static LoanTransaction chargeback(final Loan loan, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final ExternalId externalId) {
LoanTransaction loanTransaction = new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGEBACK, paymentDetail,
amount.getAmount(), paymentDate, externalId);
loanTransaction.principalPortion = amount.getAmount();
return loanTransaction;
}
public static LoanTransaction repaymentType(final LoanTransactionType repaymentType, final Office office, final Money amount,
final PaymentDetail paymentDetail, final LocalDate paymentDate, final ExternalId externalId,
final String chargeRefundChargeType) {
return new LoanTransaction(null, office, repaymentType, paymentDetail, amount.getAmount(), paymentDate, externalId,
chargeRefundChargeType);
}
public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate,
final ExternalId externalId, PaymentDetail paymentDetail) {
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, paymentDetail, amount, transactionDate,
externalId);
}
public void setLoanTransactionToRepaymentScheduleMappings(final Integer installmentId, final BigDecimal chargePerInstallment) {
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) {
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
.getLoanRepaymentScheduleInstallment();
if (loanRepaymentScheduleInstallment.getInstallmentNumber().equals(installmentId)) {
loanRepaymentScheduleInstallment.updateLoanRepaymentSchedule(chargePerInstallment);
break;
}
}
}
public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate,
externalId);
}
public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final ExternalId externalId, final LoanTransactionType transactionType) {
return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId);
}
public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(),
paymentDate, externalId);
}
public static LoanTransaction waiver(final Office office, final Loan loan, final Money amount, final LocalDate waiveDate,
final Money waived, final Money unrecognizedPortion, final ExternalId externalId) {
LoanTransaction loanTransaction = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_INTEREST, amount.getAmount(),
waiveDate, externalId);
loanTransaction.updateInterestComponent(waived, unrecognizedPortion);
return loanTransaction;
}
public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount,
final LocalDate interestAppliedDate, final ExternalId externalId) {
BigDecimal principalPortion = null;
BigDecimal feesPortion = null;
BigDecimal penaltiesPortion = null;
BigDecimal interestPortion = amount.getAmount();
BigDecimal overPaymentPortion = null;
boolean reversed = false;
PaymentDetail paymentDetail = null;
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate, interestPortion,
principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
}
public static LoanTransaction accrual(final Loan loan, final Office office, final Money amount, final Money interest,
final Money feeCharges, final Money penaltyCharges, final LocalDate transactionDate, final ExternalId externalId) {
return accrueTransaction(loan, office, transactionDate, amount.getAmount(), interest.getAmount(), feeCharges.getAmount(),
penaltyCharges.getAmount(), externalId);
}
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
final ExternalId externalId) {
BigDecimal principalPortion = null;
BigDecimal overPaymentPortion = null;
boolean reversed = false;
PaymentDetail paymentDetail = null;
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
}
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate,
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
}
public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate,
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
}
public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate,
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
}
public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId);
}
public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion,
loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId);
}
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
final Money feeCharges, final Money penaltyCharges, final ExternalId externalId) {
final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
externalId);
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
return applyCharge;
}
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
final ExternalId externalId, PaymentDetail paymentDetail) {
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), paymentDate, amount.getAmount(),
null, null, null, null, amount.getAmount(), false, paymentDetail, externalId);
}
public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(), paymentDate,
externalId);
}
public static boolean transactionAmountsMatch(final MonetaryCurrency currency, final LoanTransaction loanTransaction,
final LoanTransaction newLoanTransaction) {
return loanTransaction.getAmount(currency).isEqualTo(newLoanTransaction.getAmount(currency))
&& loanTransaction.getPrincipalPortion(currency).isEqualTo(newLoanTransaction.getPrincipalPortion(currency))
&& loanTransaction.getInterestPortion(currency).isEqualTo(newLoanTransaction.getInterestPortion(currency))
&& loanTransaction.getFeeChargesPortion(currency).isEqualTo(newLoanTransaction.getFeeChargesPortion(currency))
&& loanTransaction.getPenaltyChargesPortion(currency).isEqualTo(newLoanTransaction.getPenaltyChargesPortion(currency))
&& loanTransaction.getOverPaymentPortion(currency).isEqualTo(newLoanTransaction.getOverPaymentPortion(currency));
}
private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed,
final PaymentDetail paymentDetail, final ExternalId externalId) {
this.loan = loan;
this.typeOf = typeOf;
this.dateOf = dateOf;
this.amount = amount;
this.principalPortion = principalPortion;
this.interestPortion = interestPortion;
this.feeChargesPortion = feeChargesPortion;
this.penaltyChargesPortion = penaltyChargesPortion;
this.overPaymentPortion = overPaymentPortion;
this.reversed = reversed;
this.paymentDetail = paymentDetail;
this.office = office;
this.externalId = externalId;
this.submittedOnDate = DateUtils.getBusinessLocalDate();
}
public static LoanTransaction waiveLoanCharge(final Loan loan, final Office office, final Money waived, final LocalDate waiveDate,
final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge, final ExternalId externalId) {
final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate,
externalId);
waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge);
return waiver;
}
public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId);
}
public static LoanTransaction chargeOff(final Loan loan, final LocalDate chargeOffDate, final ExternalId externalId) {
BigDecimal principalPortion = loan.getLoanSummary().getTotalPrincipalOutstanding().compareTo(BigDecimal.ZERO) != 0
? loan.getLoanSummary().getTotalPrincipalOutstanding()
: null;
BigDecimal interestPortion = loan.getLoanSummary().getTotalInterestOutstanding().compareTo(BigDecimal.ZERO) != 0
? loan.getLoanSummary().getTotalInterestOutstanding()
: null;
BigDecimal feePortion = loan.getLoanSummary().getTotalFeeChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
? loan.getLoanSummary().getTotalFeeChargesOutstanding()
: null;
BigDecimal penaltyPortion = loan.getLoanSummary().getTotalPenaltyChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
? loan.getLoanSummary().getTotalPenaltyChargesOutstanding()
: null;
BigDecimal totalOutstanding = loan.getLoanSummary().getTotalOutstanding();
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_OFF.getValue(), chargeOffDate, totalOutstanding,
principalPortion, interestPortion, feePortion, penaltyPortion, null, false, null, externalId);
}
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount,
final LocalDate date, final ExternalId externalId) {
this.loan = loan;
this.typeOf = type.getValue();
this.amount = amount;
this.dateOf = date;
this.externalId = externalId;
this.office = office;
this.submittedOnDate = DateUtils.getBusinessLocalDate();
}
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail,
final BigDecimal amount, final LocalDate date, final ExternalId externalId) {
this.loan = loan;
this.typeOf = type.getValue();
this.paymentDetail = paymentDetail;
this.amount = amount;
this.dateOf = date;
this.externalId = externalId;
this.office = office;
this.submittedOnDate = DateUtils.getBusinessLocalDate();
}
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail,
final BigDecimal amount, final LocalDate date, final ExternalId externalId, final String chargeRefundChargeType) {
this.loan = loan;
this.typeOf = type.getValue();
this.paymentDetail = paymentDetail;
this.amount = amount;
this.dateOf = date;
this.externalId = externalId;
this.office = office;
this.submittedOnDate = DateUtils.getBusinessLocalDate();
this.chargeRefundChargeType = chargeRefundChargeType;
}
public void reverse() {
this.loan.validateRepaymentTypeTransactionNotBeforeAChargeRefund(this, "reversed");
this.reversed = true;
this.reversedOnDate = DateUtils.getBusinessLocalDate();
this.loanTransactionToRepaymentScheduleMappings.clear();
}
public void reverse(final ExternalId reversalExternalId) {
this.reverse();
this.reversalExternalId = reversalExternalId;
}
public void resetDerivedComponents() {
this.principalPortion = null;
this.interestPortion = null;
this.feeChargesPortion = null;
this.penaltyChargesPortion = null;
this.overPaymentPortion = null;
this.outstandingLoanBalance = null;
}
public void updateLoan(final Loan loan) {
this.loan = loan;
}
/**
* This updates the derived fields of a loan transaction for the principal, interest and interest waived portions.
*
* This accumulates the values passed to the already existent values for each of the portions.
*
* @param principal
* principal
* @param interest
* interest
* @param feeCharges
* feeCharges
* @param penaltyCharges
* penaltyCharges
*/
public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
final MonetaryCurrency currency = principal.getCurrency();
this.principalPortion = defaultToNullIfZero(getPrincipalPortion(currency).plus(principal).getAmount());
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
updateChargesComponents(feeCharges, penaltyCharges);
}
public void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
final MonetaryCurrency currency = feeCharges.getCurrency();
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
}
private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges, final Money unrecognizedCharges) {
final MonetaryCurrency currency = feeCharges.getCurrency();
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedCharges).getAmount());
}
private void updateInterestComponent(final Money interest, final Money unrecognizedInterest) {
final MonetaryCurrency currency = interest.getCurrency();
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount());
}
public void adjustInterestComponent(final MonetaryCurrency currency) {
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).minus(getUnrecognizedIncomePortion(currency)).getAmount());
}
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
updateComponents(principal, interest, feeCharges, penaltyCharges);
final MonetaryCurrency currency = principal.getCurrency();
this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(getFeeChargesPortion(currency))
.plus(getPenaltyChargesPortion(currency)).getAmount();
}
public void updateOverPayments(final Money overPayment) {
final MonetaryCurrency currency = overPayment.getCurrency();
this.overPaymentPortion = defaultToNullIfZero(getOverPaymentPortion(currency).plus(overPayment).getAmount());
}
public void setOverPayments(final Money overPayment) {
if (overPayment != null) {
this.overPaymentPortion = defaultToNullIfZero(overPayment.getAmount());
}
}
public Money getPrincipalPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.principalPortion);
}
public BigDecimal getPrincipalPortion() {
return this.principalPortion;
}
public BigDecimal getInterestPortion() {
return this.interestPortion;
}
public BigDecimal getPenaltyChargesPortion() {
return this.penaltyChargesPortion;
}
public BigDecimal getFeeChargesPortion() {
return this.feeChargesPortion;
}
public Money getInterestPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.interestPortion);
}
public Money getUnrecognizedIncomePortion(final MonetaryCurrency currency) {
return Money.of(currency, this.unrecognizedIncomePortion);
}
public Money getFeeChargesPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesPortion);
}
public Money getPenaltyChargesPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesPortion);
}
public Money getOverPaymentPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.overPaymentPortion);
}
public Money getAmount(final MonetaryCurrency currency) {
return Money.of(currency, this.amount);
}
public LocalDate getTransactionDate() {
return this.dateOf;
}
public LocalDate getDateOf() {
return this.dateOf;
}
public LoanTransactionType getTypeOf() {
return LoanTransactionType.fromInt(this.typeOf);
}
public boolean isReversed() {
return this.reversed;
}
public boolean isNotReversed() {
return !isReversed();
}
public void setReversed() {
this.reversed = true;
}
public void setManuallyAdjustedOrReversed() {
this.manuallyAdjustedOrReversed = true;
}
public boolean isRepaymentLikeType() {
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|| isChargeAdjustment();
}
public boolean isTypeAllowedForChargeback() {
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit();
}
public boolean isRepayment() {
return LoanTransactionType.REPAYMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isMerchantIssuedRefund() {
return LoanTransactionType.MERCHANT_ISSUED_REFUND.equals(getTypeOf()) && isNotReversed();
}
public boolean isPayoutRefund() {
return LoanTransactionType.PAYOUT_REFUND.equals(getTypeOf()) && isNotReversed();
}
public boolean isGoodwillCredit() {
return LoanTransactionType.GOODWILL_CREDIT.equals(getTypeOf()) && isNotReversed();
}
public boolean isChargeRefund() {
return LoanTransactionType.CHARGE_REFUND.equals(getTypeOf()) && isNotReversed();
}
public boolean isChargeAdjustment() {
return LoanTransactionType.CHARGE_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotRepaymentLikeType() {
return !isRepaymentLikeType();
}
public boolean isIncomePosting() {
return LoanTransactionType.INCOME_POSTING.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotIncomePosting() {
return !isIncomePosting();
}
public boolean isDisbursement() {
return LoanTransactionType.DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isRepaymentAtDisbursement() {
return LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotRecoveryRepayment() {
return !isRecoveryRepayment();
}
public boolean isRecoveryRepayment() {
return LoanTransactionType.RECOVERY_REPAYMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isInterestWaiver() {
return LoanTransactionType.WAIVE_INTEREST.equals(getTypeOf()) && isNotReversed();
}
public boolean isChargesWaiver() {
return LoanTransactionType.WAIVE_CHARGES.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotInterestWaiver() {
return !isInterestWaiver();
}
public boolean isWaiver() {
return isInterestWaiver() || isChargesWaiver();
}
public boolean isNotWaiver() {
return !isInterestWaiver() && !isChargesWaiver();
}
public boolean isNotCreditBalanceRefund() {
return !isCreditBalanceRefund();
}
public boolean isChargePayment() {
return getTypeOf().isChargePayment() && isNotReversed();
}
public boolean isChargeback() {
return getTypeOf().isChargeback() && isNotReversed();
}
public boolean isEditable() {
return !(isChargeback() || isGoodwillCredit() || isPayoutRefund() || isMerchantIssuedRefund());
}
public boolean isPenaltyPayment() {
boolean isPenalty = false;
if (isChargePayment()) {
for (final LoanChargePaidBy chargePaidBy : this.loanChargesPaid) {
isPenalty = chargePaidBy.getLoanCharge().isPenaltyCharge();
break;
}
}
return isPenalty;
}
public boolean isWriteOff() {
return getTypeOf().isWriteOff() && isNotReversed();
}
public boolean isChargeOff() {
return getTypeOf().isChargeOff() && isNotReversed();
}
public boolean isIdentifiedBy(final Long identifier) {
return getId().equals(identifier);
}
public boolean isBelongingToLoanOf(final Loan check) {
return this.loan.getId().equals(check.getId());
}
public boolean isNotBelongingToLoanOf(final Loan check) {
return !isBelongingToLoanOf(check);
}
public boolean isNonZero() {
return this.amount.subtract(BigDecimal.ZERO).doubleValue() > 0;
}
public boolean isGreaterThan(final Money monetaryAmount) {
return getAmount(monetaryAmount.getCurrency()).isGreaterThan(monetaryAmount);
}
public boolean isGreaterThanZero(final MonetaryCurrency currency) {
return getAmount(currency).isGreaterThanZero();
}
public boolean isGreaterThanZeroAndLessThanOrEqualTo(BigDecimal totalOverpaid) {
return isNonZero() && this.amount.compareTo(totalOverpaid) <= 0;
}
public boolean isNotZero(final MonetaryCurrency currency) {
return !getAmount(currency).isZero();
}
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
BigDecimal result = value;
if (BigDecimal.ZERO.compareTo(value) == 0) {
result = null;
}
return result;
}
public LoanTransactionData toData(final CurrencyData currencyData, final AccountTransferData transfer) {
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(this.typeOf);
PaymentDetailData paymentDetailData = null;
if (this.paymentDetail != null) {
paymentDetailData = this.paymentDetail.toData();
}
return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData,
currencyData, getTransactionDate(), this.amount, this.loan.getNetDisbursalAmount(), this.principalPortion,
this.interestPortion, this.feeChargesPortion, this.penaltyChargesPortion, this.overPaymentPortion, this.externalId,
transfer, null, outstandingLoanBalance, this.unrecognizedIncomePortion, this.manuallyAdjustedOrReversed, this.loan.getId(),
this.loan.getExternalId());
}
public Map<String, Object> toMapData(final String currencyCode) {
final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(this.typeOf);
thisTransactionData.put("id", getId());
thisTransactionData.put("officeId", this.office.getId());
thisTransactionData.put("type", transactionType);
thisTransactionData.put("reversed", Boolean.valueOf(isReversed()));
thisTransactionData.put("date", getTransactionDate());
thisTransactionData.put("currencyCode", currencyCode);
thisTransactionData.put("amount", this.amount);
thisTransactionData.put("netDisbursalAmount", this.loan.getNetDisbursalAmount());
thisTransactionData.put("principalPortion", this.principalPortion);
thisTransactionData.put("interestPortion", this.interestPortion);
thisTransactionData.put("feeChargesPortion", this.feeChargesPortion);
thisTransactionData.put("penaltyChargesPortion", this.penaltyChargesPortion);
thisTransactionData.put("overPaymentPortion", this.overPaymentPortion);
if (transactionType.isChargeRefund()) {
thisTransactionData.put("chargeRefundChargeType", this.chargeRefundChargeType);
}
if (this.paymentDetail != null) {
thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId());
}
if (!this.loanChargesPaid.isEmpty()) {
final List<Map<String, Object>> loanChargesPaidData = new ArrayList<>();
for (final LoanChargePaidBy chargePaidBy : this.loanChargesPaid) {
final Map<String, Object> loanChargePaidData = new LinkedHashMap<>();
loanChargePaidData.put("chargeId", chargePaidBy.getLoanCharge().getCharge().getId());
loanChargePaidData.put("isPenalty", chargePaidBy.getLoanCharge().isPenaltyCharge());
loanChargePaidData.put("loanChargeId", chargePaidBy.getLoanCharge().getId());
loanChargePaidData.put("amount", chargePaidBy.getAmount());
loanChargesPaidData.add(loanChargePaidData);
}
thisTransactionData.put("loanChargesPaid", loanChargesPaidData);
}
LoanTransactionRelation loanTransactionRelation = loanTransactionRelations.stream()
.filter(e -> LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT.equals(e.getRelationType())).findAny().orElse(null);
if (loanTransactionRelation != null) {
LoanCharge loanCharge = loanTransactionRelation.getToCharge();
thisTransactionData.put("loanChargeData", loanCharge.toData());
}
return thisTransactionData;
}
public Loan getLoan() {
return this.loan;
}
public Set<LoanChargePaidBy> getLoanChargesPaid() {
return this.loanChargesPaid;
}
public void setLoanChargesPaid(final Set<LoanChargePaidBy> loanChargesPaid) {
this.loanChargesPaid = loanChargesPaid;
}
public ExternalId getExternalId() {
return this.externalId;
}
public boolean isRefund() {
return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed();
}
public boolean isCreditBalanceRefund() {
return LoanTransactionType.CREDIT_BALANCE_REFUND.equals(getTypeOf()) && isNotReversed();
}
public void updateExternalId(final ExternalId externalId) {
this.externalId = externalId;
}
public boolean isAccrual() {
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
}
public boolean isNonMonetaryTransaction() {
return isNotReversed() && (LoanTransactionType.CONTRA.equals(getTypeOf())
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
|| LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf()) || LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf())
|| LoanTransactionType.CHARGE_OFF.equals(getTypeOf()));
}
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
this.outstandingLoanBalance = outstandingLoanBalance;
}
public boolean isNotRefundForActiveLoan() {
// TODO Auto-generated method stub
return !isRefundForActiveLoan();
}
public boolean isRefundForActiveLoan() {
return LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.equals(getTypeOf()) && isNotReversed();
}
public boolean isManuallyAdjustedOrReversed() {
return this.manuallyAdjustedOrReversed;
}
public boolean isNotManuallyAdjustedOrReversed() {
return !this.manuallyAdjustedOrReversed;
}
public void manuallyAdjustedOrReversed() {
this.manuallyAdjustedOrReversed = true;
}
public OffsetDateTime getCreatedDateTime() {
return (this.getCreatedDate().isPresent() ? this.getCreatedDate().get() : DateUtils.getOffsetDateTimeOfTenant());
}
public boolean isLastTransaction(final LoanTransaction loanTransaction) {
boolean isLatest = false;
if (loanTransaction != null) {
isLatest = this.getTransactionDate().isBefore(loanTransaction.getTransactionDate())
|| (this.getTransactionDate().isEqual(loanTransaction.getTransactionDate())
&& this.getCreatedDateTime().isBefore(loanTransaction.getCreatedDateTime()));
}
return isLatest;
}
public boolean isLatestTransaction(final LoanTransaction loanTransaction) {
boolean isLatest = false;
if (loanTransaction != null) {
isLatest = this.getCreatedDateTime().isBefore(loanTransaction.getCreatedDateTime());
}
return isLatest;
}
public void updateLoanTransactionToRepaymentScheduleMappings(final Collection<LoanTransactionToRepaymentScheduleMapping> mappings) {
Collection<LoanTransactionToRepaymentScheduleMapping> retainMappings = new ArrayList<>();
for (LoanTransactionToRepaymentScheduleMapping updatedrepaymentScheduleMapping : mappings) {
updateMapingDetail(retainMappings, updatedrepaymentScheduleMapping);
}
this.loanTransactionToRepaymentScheduleMappings.retainAll(retainMappings);
}
private boolean updateMapingDetail(final Collection<LoanTransactionToRepaymentScheduleMapping> retainMappings,
final LoanTransactionToRepaymentScheduleMapping updatedrepaymentScheduleMapping) {
boolean isMappingUpdated = false;
for (LoanTransactionToRepaymentScheduleMapping repaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) {
if (updatedrepaymentScheduleMapping.getLoanRepaymentScheduleInstallment().getId() != null
&& repaymentScheduleMapping.getLoanRepaymentScheduleInstallment().getDueDate()
.equals(updatedrepaymentScheduleMapping.getLoanRepaymentScheduleInstallment().getDueDate())) {
repaymentScheduleMapping.setComponents(updatedrepaymentScheduleMapping.getPrincipalPortion(),
updatedrepaymentScheduleMapping.getInterestPortion(), updatedrepaymentScheduleMapping.getFeeChargesPortion(),
updatedrepaymentScheduleMapping.getPenaltyChargesPortion());
isMappingUpdated = true;
retainMappings.add(repaymentScheduleMapping);
break;
}
}
if (!isMappingUpdated) {
updatedrepaymentScheduleMapping.setLoanTransaction(this);
this.loanTransactionToRepaymentScheduleMappings.add(updatedrepaymentScheduleMapping);
retainMappings.add(updatedrepaymentScheduleMapping);
}
return isMappingUpdated;
}
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
return this.loanTransactionToRepaymentScheduleMappings;
}
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
}
public boolean isAccrualTransaction() {
return isAccrual();
}
public BigDecimal getOutstandingLoanBalance() {
return outstandingLoanBalance;
}
public PaymentDetail getPaymentDetail() {
return this.paymentDetail;
}
public boolean isPaymentTransaction() {
return this.isNotReversed() && !(this.isDisbursement() || this.isAccrual() || this.isRepaymentAtDisbursement()
|| this.isNonMonetaryTransaction() || this.isIncomePosting());
}
public Set<LoanCollateralManagement> getLoanCollateralManagementSet() {
return this.loanCollateralManagementSet;
}
public LocalDate getSubmittedOnDate() {
return submittedOnDate;
}
public boolean hasLoanTransactionRelations() {
return (loanTransactionRelations != null && loanTransactionRelations.size() > 0);
}
public boolean hasChargebackLoanTransactionRelations() {
return (loanTransactionRelations != null
&& loanTransactionRelations.stream().anyMatch(e -> LoanTransactionRelationTypeEnum.CHARGEBACK.equals(e.getRelationType())));
}
public Set<LoanTransactionRelation> getLoanTransactionRelations() {
return loanTransactionRelations;
}
public void copyLoanTransactionRelations(Set<LoanTransactionRelation> sourceLoanTransactionRelations) {
for (LoanTransactionRelation existingLoanTransactionRelation : sourceLoanTransactionRelations) {
loanTransactionRelations.add(new LoanTransactionRelation(this, existingLoanTransactionRelation.getToTransaction(),
existingLoanTransactionRelation.getToCharge(), existingLoanTransactionRelation.getRelationType()));
}
}
public BigDecimal getAmount() {
return amount;
}
// TODO missing hashCode(), equals(Object obj), but probably OK as long as
// this is never stored in a Collection.
}
@@ -0,0 +1,65 @@
/**
* 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.domain;
import java.util.Comparator;
/**
* Sort loan transactions by transaction date, created date and transaction type placing
*/
public class LoanTransactionComparator implements Comparator<LoanTransaction> {
@Override
public int compare(final LoanTransaction o1, final LoanTransaction o2) {
int compareResult = 0;
final int comparsion = o1.getTransactionDate().compareTo(o2.getTransactionDate());
/**
* For transactions bearing the same transaction date, we sort transactions based on created date (when
* available) after which sorting for waivers takes place
**/
if (comparsion == 0) {
int comparisonBasedOnCreatedDate = 0;
if (o1.isIncomePosting() && o2.isNotIncomePosting()) {
compareResult = -1;
} else if (o1.isNotIncomePosting() && o2.isIncomePosting()) {
compareResult = 1;
} else {
compareResult = 0;
}
if (o1.getCreatedDateTime() != null && o2.getCreatedDateTime() != null) {
comparisonBasedOnCreatedDate = o1.getCreatedDateTime().compareTo(o2.getCreatedDateTime());
}
// equal transaction dates
if (comparisonBasedOnCreatedDate == 0) {
if (o1.isWaiver() && o2.isNotWaiver()) {
compareResult = -1;
} else if (o1.isNotWaiver() && o2.isWaiver()) {
compareResult = 1;
} else {
compareResult = 0;
}
}
} else {
compareResult = comparsion;
}
return compareResult;
}
}
@@ -0,0 +1,72 @@
/**
* 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.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
@Getter
@Entity
@Table(name = "m_loan_transaction_relation")
public class LoanTransactionRelation extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne
@JoinColumn(name = "from_loan_transaction_id", nullable = false)
private LoanTransaction fromTransaction;
@ManyToOne
@JoinColumn(name = "to_loan_transaction_id")
private LoanTransaction toTransaction;
@ManyToOne
@JoinColumn(name = "to_loan_charge_id")
private LoanCharge toCharge;
@Enumerated(EnumType.ORDINAL)
@Column(name = "relation_type_enum", nullable = false)
private LoanTransactionRelationTypeEnum relationType;
protected LoanTransactionRelation() {}
protected LoanTransactionRelation(@NotNull LoanTransaction fromTransaction, LoanTransaction toTransaction, LoanCharge toCharge,
LoanTransactionRelationTypeEnum relationType) {
this.fromTransaction = fromTransaction;
this.toTransaction = toTransaction;
this.toCharge = toCharge;
this.relationType = relationType;
}
public static LoanTransactionRelation linkToTransaction(@NotNull LoanTransaction fromTransaction,
@NotNull LoanTransaction toTransaction, LoanTransactionRelationTypeEnum relation) {
return new LoanTransactionRelation(fromTransaction, toTransaction, null, relation);
}
public static LoanTransactionRelation linkToCharge(@NotNull LoanTransaction fromTransaction, @NotNull LoanCharge loanCharge,
LoanTransactionRelationTypeEnum relation) {
return new LoanTransactionRelation(fromTransaction, null, loanCharge, relation);
}
}
@@ -0,0 +1,58 @@
/**
* 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.domain;
public enum LoanTransactionRelationTypeEnum {
INVALID(0, "loanTransactionType.invalid"), //
CHARGEBACK(1, "loanTransactionRelationType.chargeback"), //
CHARGE_ADJUSTMENT(2, "loanTransactionRelationType.chargeAdjustment"), //
REPLAYED(3, "loanTransactionRelationType.replayed");
private final Integer value;
private final String code;
LoanTransactionRelationTypeEnum(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static LoanTransactionRelationTypeEnum fromInt(final Integer transactionType) {
if (transactionType == null) {
return LoanTransactionRelationTypeEnum.INVALID;
}
return switch (transactionType) {
case 1 -> LoanTransactionRelationTypeEnum.CHARGEBACK;
case 2 -> LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT;
case 3 -> LoanTransactionRelationTypeEnum.REPLAYED;
default -> LoanTransactionRelationTypeEnum.INVALID;
};
}
}
@@ -0,0 +1,170 @@
/**
* 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.domain;
import java.math.BigDecimal;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@Entity
@Table(name = "m_loan_transaction_repayment_schedule_mapping")
public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistableCustom {
@ManyToOne(optional = false, cascade = CascadeType.PERSIST)
@JoinColumn(name = "loan_transaction_id", nullable = false)
private LoanTransaction loanTransaction;
@ManyToOne(optional = false, cascade = CascadeType.PERSIST)
@JoinColumn(name = "loan_repayment_schedule_id", nullable = false)
private LoanRepaymentScheduleInstallment installment;
@Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalPortion;
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestPortion;
@Column(name = "fee_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesPortion;
@Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPortion;
@Column(name = "amount", scale = 6, precision = 19)
private BigDecimal amount;
protected LoanTransactionToRepaymentScheduleMapping() {
}
private LoanTransactionToRepaymentScheduleMapping(final LoanTransaction loanTransaction,
final LoanRepaymentScheduleInstallment installment, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal amount) {
this.loanTransaction = loanTransaction;
this.installment = installment;
this.principalPortion = principalPortion;
this.interestPortion = interestPortion;
this.feeChargesPortion = feeChargesPortion;
this.penaltyChargesPortion = penaltyChargesPortion;
this.amount = amount;
}
public static LoanTransactionToRepaymentScheduleMapping createFrom(final LoanTransaction loanTransaction,
final LoanRepaymentScheduleInstallment installment, final Money principalPortion, final Money interestPortion,
final Money feeChargesPortion, final Money penaltyChargesPortion) {
return new LoanTransactionToRepaymentScheduleMapping(loanTransaction, installment, defaultToNullIfZero(principalPortion),
defaultToNullIfZero(interestPortion), defaultToNullIfZero(feeChargesPortion), defaultToNullIfZero(penaltyChargesPortion),
defaultToNullIfZero(principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion)));
}
private static BigDecimal defaultToNullIfZero(final Money value) {
BigDecimal result = value.getAmount();
if (value.isZero()) {
result = null;
}
return result;
}
private BigDecimal defaultToZeroIfNull(final BigDecimal value) {
BigDecimal result = value;
if (value == null) {
result = BigDecimal.ZERO;
}
return result;
}
public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() {
return this.installment;
}
public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
final MonetaryCurrency currency = principal.getCurrency();
this.principalPortion = defaultToNullIfZero(getPrincipalPortion(currency).plus(principal));
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest));
updateChargesComponents(feeCharges, penaltyCharges);
updateAmount();
}
private void updateAmount() {
this.amount = defaultToZeroIfNull(getPrincipalPortion()).add(defaultToZeroIfNull(getInterestPortion()))
.add(defaultToZeroIfNull(getFeeChargesPortion())).add(defaultToZeroIfNull(getPenaltyChargesPortion()));
}
public void setComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
final BigDecimal penaltyCharges) {
this.principalPortion = principal;
this.interestPortion = interest;
this.feeChargesPortion = feeCharges;
this.penaltyChargesPortion = penaltyCharges;
updateAmount();
}
private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
final MonetaryCurrency currency = feeCharges.getCurrency();
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges));
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges));
}
public Money getPrincipalPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.principalPortion);
}
public Money getInterestPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.interestPortion);
}
public Money getFeeChargesPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesPortion);
}
public Money getPenaltyChargesPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesPortion);
}
public LoanTransaction getLoanTransaction() {
return loanTransaction;
}
public BigDecimal getPrincipalPortion() {
return this.principalPortion;
}
public BigDecimal getInterestPortion() {
return this.interestPortion;
}
public BigDecimal getFeeChargesPortion() {
return this.feeChargesPortion;
}
public BigDecimal getPenaltyChargesPortion() {
return this.penaltyChargesPortion;
}
public void setLoanTransaction(LoanTransaction loanTransaction) {
this.loanTransaction = loanTransaction;
}
}
@@ -0,0 +1,192 @@
/**
* 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.domain;
public enum LoanTransactionType {
INVALID(0, "loanTransactionType.invalid"), //
DISBURSEMENT(1, "loanTransactionType.disbursement"), //
REPAYMENT(2, "loanTransactionType.repayment"), //
CONTRA(3, "loanTransactionType.contra"), //
WAIVE_INTEREST(4, "loanTransactionType.waiver"), //
REPAYMENT_AT_DISBURSEMENT(5, "loanTransactionType.repaymentAtDisbursement"), //
WRITEOFF(6, "loanTransactionType.writeOff"), //
MARKED_FOR_RESCHEDULING(7, "loanTransactionType.marked.for.rescheduling"), //
/**
* This type of transactions is allowed on written-off loans where mfi still attempts to recover payments from
* applicant after writing-off.
*/
RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), //
WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), //
// UNDO_WAIVE_CHARGE(20, "loanTransactionType.undoWaiveCharge"),
/**
* Transaction represents an Accrual (For either interest, charge or a penalty
**/
ACCRUAL(10, "loanTransactionType.accrual"), //
/***
* A Loan Transfer involves two steps, first a "initiate" Loan transfer transaction done by the Source branch
* followed by a "complete" loan transaction initiated by the destination branch
**/
INITIATE_TRANSFER(12, "loanTransactionType.initiateTransfer"), //
APPROVE_TRANSFER(13, "loanTransactionType.approveTransfer"), //
WITHDRAW_TRANSFER(14, "loanTransactionType.withdrawTransfer"), //
REJECT_TRANSFER(15, "loanTransactionType.rejectTransfer"), //
REFUND(16, "loanTransactionType.refund"), //
CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"), //
REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund"), //
INCOME_POSTING(19, "loanTransactionType.incomePosting"), //
CREDIT_BALANCE_REFUND(20, "loanTransactionType.creditBalanceRefund"), //
MERCHANT_ISSUED_REFUND(21, "loanTransactionType.merchantIssuedRefund"), //
PAYOUT_REFUND(22, "loanTransactionType.payoutRefund"), //
GOODWILL_CREDIT(23, "loanTransactionType.goodwillCredit"), //
CHARGE_REFUND(24, "loanTransactionType.chargeRefund"), //
CHARGEBACK(25, "loanTransactionType.chargeback"), //
CHARGE_ADJUSTMENT(26, "loanTransactionType.chargeAdjustment"), //
CHARGE_OFF(27, "loanTransactionType.chargeOff");
private final Integer value;
private final String code;
LoanTransactionType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public static LoanTransactionType fromInt(final Integer transactionType) {
if (transactionType == null) {
return LoanTransactionType.INVALID;
}
return switch (transactionType) {
case 1 -> LoanTransactionType.DISBURSEMENT;
case 2 -> LoanTransactionType.REPAYMENT;
case 3 -> LoanTransactionType.CONTRA;
case 4 -> LoanTransactionType.WAIVE_INTEREST;
case 5 -> LoanTransactionType.REPAYMENT_AT_DISBURSEMENT;
case 6 -> LoanTransactionType.WRITEOFF;
case 7 -> LoanTransactionType.MARKED_FOR_RESCHEDULING;
case 8 -> LoanTransactionType.RECOVERY_REPAYMENT;
case 9 -> LoanTransactionType.WAIVE_CHARGES;
case 10 -> LoanTransactionType.ACCRUAL;
case 12 -> LoanTransactionType.INITIATE_TRANSFER;
case 13 -> LoanTransactionType.APPROVE_TRANSFER;
case 14 -> LoanTransactionType.WITHDRAW_TRANSFER;
case 15 -> LoanTransactionType.REJECT_TRANSFER;
case 16 -> LoanTransactionType.REFUND;
case 17 -> LoanTransactionType.CHARGE_PAYMENT;
case 18 -> LoanTransactionType.REFUND_FOR_ACTIVE_LOAN;
case 19 -> LoanTransactionType.INCOME_POSTING;
case 20 -> LoanTransactionType.CREDIT_BALANCE_REFUND;
case 21 -> LoanTransactionType.MERCHANT_ISSUED_REFUND;
case 22 -> LoanTransactionType.PAYOUT_REFUND;
case 23 -> LoanTransactionType.GOODWILL_CREDIT;
case 24 -> LoanTransactionType.CHARGE_REFUND;
case 25 -> LoanTransactionType.CHARGEBACK;
case 26 -> LoanTransactionType.CHARGE_ADJUSTMENT;
case 27 -> LoanTransactionType.CHARGE_OFF;
default -> LoanTransactionType.INVALID;
};
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isDisbursement() {
return this.equals(LoanTransactionType.DISBURSEMENT);
}
public boolean isRepaymentAtDisbursement() {
return this.equals(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT);
}
public boolean isRepayment() {
return this.equals(LoanTransactionType.REPAYMENT);
}
public boolean isMerchantIssuedRefund() {
return this.equals(LoanTransactionType.MERCHANT_ISSUED_REFUND);
}
public boolean isPayoutRefund() {
return this.equals(LoanTransactionType.PAYOUT_REFUND);
}
public boolean isGoodwillCredit() {
return this.equals(LoanTransactionType.GOODWILL_CREDIT);
}
public boolean isChargeRefund() {
return this.equals(LoanTransactionType.CHARGE_REFUND);
}
public boolean isRepaymentType() {
return (isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund());
}
public boolean isRecoveryRepayment() {
return this.equals(LoanTransactionType.RECOVERY_REPAYMENT);
}
public boolean isWaiveInterest() {
return this.equals(LoanTransactionType.WAIVE_INTEREST);
}
public boolean isWaiveCharges() {
return this.equals(LoanTransactionType.WAIVE_CHARGES);
}
public boolean isAccrual() {
return this.equals(LoanTransactionType.ACCRUAL);
}
public boolean isWriteOff() {
return this.equals(LoanTransactionType.WRITEOFF);
}
public boolean isChargePayment() {
return this.equals(LoanTransactionType.CHARGE_PAYMENT);
}
public boolean isRefundForActiveLoan() {
return this.equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN);
}
public boolean isIncomePosting() {
return this.equals(LoanTransactionType.INCOME_POSTING);
}
public boolean isChargeback() {
return this.equals(LoanTransactionType.CHARGEBACK);
}
public boolean isChargeAdjustment() {
return this.equals(LoanTransactionType.CHARGE_ADJUSTMENT);
}
public boolean isChargeOff() {
return this.equals(LoanTransactionType.CHARGE_OFF);
}
}
@@ -0,0 +1,65 @@
/**
* 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.domain.transactionprocessor;
import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
public interface LoanRepaymentScheduleTransactionProcessor {
String getCode();
String getName();
boolean accept(String s);
void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges);
ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges);
/**
* Used in interest recalculation to introduce new interest only installment.
*/
boolean isInterestFirstRepaymentScheduleTransactionProcessor();
void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges);
void handleChargeback(LoanTransaction loanTransaction, MonetaryCurrency currency, Money overpaidAmount,
List<LoanRepaymentScheduleInstallment> installments);
void processTransactionsFromDerivedFields(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges);
}
@@ -0,0 +1,29 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class ExceedingTrancheCountException extends AbstractPlatformDomainRuleException {
public ExceedingTrancheCountException(final String entity, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".exceeding.max.tranche.count", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,33 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown an action to transition a loan from one state to another violates
* a domain rule.
*/
public class InvalidLoanStateTransitionException extends AbstractPlatformDomainRuleException {
public InvalidLoanStateTransitionException(final String action, final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg.loan." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,33 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown an action to transition a loan from one state to another violates
* a domain rule.
*/
public class InvalidLoanTransactionTypeException extends AbstractPlatformDomainRuleException {
public InvalidLoanTransactionTypeException(final String action, final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg.loan." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,34 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class InvalidRefundDateException extends AbstractPlatformDomainRuleException {
public InvalidRefundDateException(final String refundDateAsString) {
super("error.msg.loan.refund.failed",
"The refund date`" + refundDateAsString + "`" + "` cannot be before the smallest repayment transaction date",
new Object[] { refundDateAsString });
}
public InvalidRefundDateException(final String defaultUserMessage, final String entity, final Object... defaultUserMessageArgs) {
super("error.msg.loan." + entity, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,29 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanApplicationDateException extends AbstractPlatformDomainRuleException {
public LoanApplicationDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loan.application." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,29 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanChargeRefundException extends AbstractPlatformDomainRuleException {
public LoanChargeRefundException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg." + defaultUserMessage, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,34 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanDisbursalException extends AbstractPlatformDomainRuleException {
public LoanDisbursalException(final String currentProduct, final String restrictedProduct) {
super("error.msg.loan.disbursal.failed",
"This loan could not be disbursed as `" + currentProduct + "` and `" + restrictedProduct + "` are not allowed to co-exist",
new Object[] { currentProduct, restrictedProduct });
}
public LoanDisbursalException(final String defaultUserMessage, final String entity, final Object... defaultUserMessageArgs) {
super("error.msg.loan." + entity, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,28 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanForeclosureException extends AbstractPlatformDomainRuleException {
public LoanForeclosureException(final String errorCode, final String errorMessage, final Object... defaultUserMessageArgs) {
super(errorCode, errorMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,30 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanOfficerAssignmentDateException extends AbstractPlatformDomainRuleException {
public LoanOfficerAssignmentDateException(final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg.loan.assignment.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -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.portfolio.loanaccount.exception;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanOfficerAssignmentException extends AbstractPlatformDomainRuleException {
public LoanOfficerAssignmentException(final Long loanId, final Long fromLoanOfficerId) {
super("error.msg.loan.not.assigned.to.loan.officer",
"Loan with identifier " + loanId + " is not assigned to Loan Officer with identifier " + fromLoanOfficerId + ".", loanId);
}
public LoanOfficerAssignmentException(final Long loanId, final LocalDate date) {
super("error.msg.loan.assignment.date.is.before.last.assignment.date",
"Loan with identifier " + loanId + " was already assigned before date " + date.toString());
}
}
@@ -0,0 +1,29 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanOfficerUnassignmentDateException extends AbstractPlatformDomainRuleException {
public LoanOfficerUnassignmentDateException(final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg.loan.loanofficer.unassign.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,32 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when loan transactions processing strategy resources are not found.
*/
public class LoanTransactionProcessingStrategyNotFoundException extends AbstractPlatformResourceNotFoundException {
public LoanTransactionProcessingStrategyNotFoundException(final String code) {
super("error.msg.transactions.processing.strategy.code.invalid",
"Loan transaction processing strategy with code " + code + " does not exist", code);
}
}
@@ -0,0 +1,30 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class MultiDisbursementDataNotAllowedException extends AbstractPlatformDomainRuleException {
public MultiDisbursementDataNotAllowedException(final String entity, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".not.allowed", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,30 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class MultiDisbursementDataRequiredException extends AbstractPlatformDomainRuleException {
public MultiDisbursementDataRequiredException(final String entity, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".required", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,31 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class UndoLastTrancheDisbursementException extends AbstractPlatformDomainRuleException {
public UndoLastTrancheDisbursementException(final Object... defaultUserMessageArgs) {
super("error.msg.cannot.undo.last.disbursal.after.repayments or waivers",
" Cannot undo last disbursement after repayments or waivers.", defaultUserMessageArgs);
}
}
@@ -0,0 +1,51 @@
/**
* 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.loanschedule.data;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
/**
* Transfer object to return the schedule after generation of schedule
*/
public final class LoanScheduleDTO {
private final List<LoanRepaymentScheduleInstallment> installments;
private final LoanScheduleModel loanScheduleModel;
private LoanScheduleDTO(final List<LoanRepaymentScheduleInstallment> installments, final LoanScheduleModel loanScheduleModel) {
this.installments = installments;
this.loanScheduleModel = loanScheduleModel;
}
public static LoanScheduleDTO from(final List<LoanRepaymentScheduleInstallment> installments,
final LoanScheduleModel loanScheduleModel) {
return new LoanScheduleDTO(installments, loanScheduleModel);
}
public List<LoanRepaymentScheduleInstallment> getInstallments() {
return this.installments;
}
public LoanScheduleModel getLoanScheduleModel() {
return this.loanScheduleModel;
}
}
@@ -0,0 +1,116 @@
/**
* 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.loanschedule.data;
import java.math.BigDecimal;
import java.util.Collection;
import lombok.Getter;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
/**
* Immutable data object to represent aspects of a loan schedule such as:
*
* <ul>
* <li>Totals information - the totals for each part of repayment schedule monitored.</li>
* <li>Repayment schedule - the principal due, outstanding balance and cost of loan items such as interest and charges
* (both fees and penalties)</li>
* </ul>
*/
@Getter
public class LoanScheduleData {
/**
* The currency associated with all monetary values in loan schedule.
*/
private final CurrencyData currency;
private final Integer loanTermInDays;
private final BigDecimal totalPrincipalDisbursed;
private final BigDecimal totalPrincipalExpected;
private final BigDecimal totalPrincipalPaid;
private final BigDecimal totalInterestCharged;
private final BigDecimal totalFeeChargesCharged;
private final BigDecimal totalPenaltyChargesCharged;
private final BigDecimal totalWaived;
private final BigDecimal totalWrittenOff;
private final BigDecimal totalRepaymentExpected;
private final BigDecimal totalRepayment;
private final BigDecimal totalPaidInAdvance;
private final BigDecimal totalPaidLate;
private final BigDecimal totalOutstanding;
private final BigDecimal totalCredits;
/**
* <code>periods</code> is collection of data objects containing specific information to each period of the loan
* schedule including disbursement and repayment information.
*/
private final Collection<LoanSchedulePeriodData> periods;
private Collection<LoanSchedulePeriodData> futurePeriods;
public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final Integer loanTermInDays,
final BigDecimal totalPrincipalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid,
final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged,
final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalRepaymentExpected,
final BigDecimal totalRepayment, final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate,
final BigDecimal totalOutstanding, final BigDecimal totalCredits) {
this.currency = currency;
this.periods = periods;
this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = totalPrincipalDisbursed;
this.totalPrincipalExpected = totalPrincipalExpected;
this.totalPrincipalPaid = totalPrincipalPaid;
this.totalInterestCharged = totalInterestCharged;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalWaived = totalWaived;
this.totalWrittenOff = totalWrittenOff;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalRepayment = totalRepayment;
this.totalPaidInAdvance = totalPaidInAdvance;
this.totalPaidLate = totalPaidLate;
this.totalOutstanding = totalOutstanding;
this.totalCredits = totalCredits;
}
public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final Integer loanTermInDays,
final BigDecimal totalPrincipalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalInterestCharged,
final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected) {
this.currency = currency;
this.periods = periods;
this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = totalPrincipalDisbursed;
this.totalPrincipalExpected = totalPrincipalExpected;
this.totalPrincipalPaid = null;
this.totalInterestCharged = totalInterestCharged;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalWaived = null;
this.totalWrittenOff = null;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalRepayment = null;
this.totalPaidInAdvance = null;
this.totalPaidLate = null;
this.totalOutstanding = null;
this.totalCredits = BigDecimal.ZERO;
}
public void updateFuturePeriods(Collection<LoanSchedulePeriodData> futurePeriods) {
this.futurePeriods = futurePeriods;
}
}
@@ -0,0 +1,436 @@
/**
* 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.loanschedule.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.service.DateUtils;
/**
* Immutable data object that represents a period of a loan schedule.
*
*/
@Getter
public final class LoanSchedulePeriodData {
private final Integer period;
private final LocalDate fromDate;
private final LocalDate dueDate;
private final LocalDate obligationsMetOnDate;
private final Boolean complete;
private final Integer daysInPeriod;
private final BigDecimal principalDisbursed;
private final BigDecimal principalOriginalDue;
private final BigDecimal principalDue;
private final BigDecimal principalPaid;
private final BigDecimal principalWrittenOff;
private final BigDecimal principalOutstanding;
private final BigDecimal principalLoanBalanceOutstanding;
private final BigDecimal interestOriginalDue;
private final BigDecimal interestDue;
private final BigDecimal interestPaid;
private final BigDecimal interestWaived;
private final BigDecimal interestWrittenOff;
private final BigDecimal interestOutstanding;
private final BigDecimal feeChargesDue;
private final BigDecimal feeChargesPaid;
private final BigDecimal feeChargesWaived;
private final BigDecimal feeChargesWrittenOff;
private final BigDecimal feeChargesOutstanding;
private final BigDecimal penaltyChargesDue;
private final BigDecimal penaltyChargesPaid;
private final BigDecimal penaltyChargesWaived;
private final BigDecimal penaltyChargesWrittenOff;
private final BigDecimal penaltyChargesOutstanding;
private final BigDecimal totalOriginalDueForPeriod;
private final BigDecimal totalDueForPeriod;
private final BigDecimal totalPaidForPeriod;
private final BigDecimal totalPaidInAdvanceForPeriod;
private final BigDecimal totalPaidLateForPeriod;
private final BigDecimal totalWaivedForPeriod;
private final BigDecimal totalWrittenOffForPeriod;
private final BigDecimal totalOutstandingForPeriod;
private final BigDecimal totalOverdue;
private final BigDecimal totalActualCostOfLoanForPeriod;
private final BigDecimal totalInstallmentAmountForPeriod;
private final BigDecimal totalCredits;
public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed,
final BigDecimal feeChargesDueAtTimeOfDisbursement, final boolean isDisbursed) {
final Integer periodNumber = null;
final LocalDate from = null;
return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, feeChargesDueAtTimeOfDisbursement,
isDisbursed);
}
public static LoanSchedulePeriodData repaymentOnlyPeriod(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
final BigDecimal principalDue, final BigDecimal principalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding,
final BigDecimal feeChargesDueForPeriod, final BigDecimal penaltyChargesDueForPeriod, final BigDecimal totalDueForPeriod,
final BigDecimal totalInstallmentAmountForPeriod) {
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalDue, principalOutstanding,
interestDueOnPrincipalOutstanding, feeChargesDueForPeriod, penaltyChargesDueForPeriod, totalDueForPeriod,
totalInstallmentAmountForPeriod);
}
public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnings("unused") final Long loanId,
final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate,
final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid,
final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding, final BigDecimal outstandingPrincipalBalanceOfLoan,
final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal interestPaid, final BigDecimal interestWaived,
final BigDecimal interestWrittenOff, final BigDecimal interestOutstanding, final BigDecimal feeChargesDue,
final BigDecimal feeChargesPaid, final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesOutstanding, final BigDecimal penaltyChargesDue, final BigDecimal penaltyChargesPaid,
final BigDecimal penaltyChargesWaived, final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesOutstanding,
final BigDecimal totalDueForPeriod, final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod,
final BigDecimal totalPaidLateForPeriod, final BigDecimal totalWaived, final BigDecimal totalWrittenOff,
final BigDecimal totalOutstanding, final BigDecimal totalActualCostOfLoanForPeriod,
final BigDecimal totalInstallmentAmountForPeriod, final BigDecimal totalCredits) {
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue,
principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesDue,
feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid,
penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid,
totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding,
totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod, totalCredits);
}
public static LoanSchedulePeriodData withPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData, final boolean complete,
final BigDecimal principalPaid, final BigDecimal interestPaid, final BigDecimal feeChargesPaid,
final BigDecimal penaltyChargesPaid) {
return new LoanSchedulePeriodData(loanSchedulePeriodData.period, loanSchedulePeriodData.fromDate, loanSchedulePeriodData.dueDate,
loanSchedulePeriodData.obligationsMetOnDate, complete, loanSchedulePeriodData.principalOriginalDue, principalPaid,
loanSchedulePeriodData.principalWrittenOff, loanSchedulePeriodData.principalOutstanding,
loanSchedulePeriodData.principalLoanBalanceOutstanding, loanSchedulePeriodData.interestDue, interestPaid,
loanSchedulePeriodData.interestWaived, loanSchedulePeriodData.interestWrittenOff,
loanSchedulePeriodData.interestOutstanding, loanSchedulePeriodData.feeChargesDue, feeChargesPaid,
loanSchedulePeriodData.feeChargesWaived, loanSchedulePeriodData.feeChargesWrittenOff,
loanSchedulePeriodData.feeChargesOutstanding, loanSchedulePeriodData.penaltyChargesDue, penaltyChargesPaid,
loanSchedulePeriodData.penaltyChargesWaived, loanSchedulePeriodData.penaltyChargesWrittenOff,
loanSchedulePeriodData.penaltyChargesOutstanding, loanSchedulePeriodData.totalDueForPeriod,
loanSchedulePeriodData.totalPaidForPeriod, loanSchedulePeriodData.totalPaidInAdvanceForPeriod,
loanSchedulePeriodData.totalPaidLateForPeriod, loanSchedulePeriodData.totalWaivedForPeriod,
loanSchedulePeriodData.totalWrittenOffForPeriod, loanSchedulePeriodData.totalOutstandingForPeriod,
loanSchedulePeriodData.totalActualCostOfLoanForPeriod, loanSchedulePeriodData.totalInstallmentAmountForPeriod,
loanSchedulePeriodData.totalCredits);
}
/*
* constructor used for creating period on loan schedule that is only a disbursement (typically first period)
*/
private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
final BigDecimal principalDisbursed, final BigDecimal chargesDueAtTimeOfDisbursement, final boolean isDisbursed) {
this.period = periodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.obligationsMetOnDate = null;
this.complete = null;
if (fromDate != null) {
this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate));
} else {
this.daysInPeriod = null;
}
this.principalDisbursed = principalDisbursed;
this.principalOriginalDue = null;
this.principalDue = null;
this.principalPaid = null;
this.principalWrittenOff = null;
this.principalOutstanding = null;
this.principalLoanBalanceOutstanding = principalDisbursed;
this.interestOriginalDue = null;
this.interestDue = null;
this.interestPaid = null;
this.interestWaived = null;
this.interestWrittenOff = null;
this.interestOutstanding = null;
this.feeChargesDue = chargesDueAtTimeOfDisbursement;
if (isDisbursed) {
this.feeChargesPaid = chargesDueAtTimeOfDisbursement;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.feeChargesOutstanding = null;
} else {
this.feeChargesPaid = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.feeChargesOutstanding = chargesDueAtTimeOfDisbursement;
}
this.penaltyChargesDue = null;
this.penaltyChargesPaid = null;
this.penaltyChargesWaived = null;
this.penaltyChargesWrittenOff = null;
this.penaltyChargesOutstanding = null;
this.totalOriginalDueForPeriod = chargesDueAtTimeOfDisbursement;
this.totalDueForPeriod = chargesDueAtTimeOfDisbursement;
this.totalPaidForPeriod = this.feeChargesPaid;
this.totalPaidInAdvanceForPeriod = null;
this.totalPaidLateForPeriod = null;
this.totalWaivedForPeriod = null;
this.totalWrittenOffForPeriod = null;
this.totalOutstandingForPeriod = this.feeChargesOutstanding;
this.totalActualCostOfLoanForPeriod = this.feeChargesDue;
this.totalInstallmentAmountForPeriod = null;
if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod;
} else {
this.totalOverdue = null;
}
this.totalCredits = BigDecimal.ZERO;
}
/*
* used for repayment only period when creating an empty loan schedule for preview etc
*/
private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
final BigDecimal principalOriginalDue, final BigDecimal principalOutstanding,
final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal feeChargesDueForPeriod,
final BigDecimal penaltyChargesDueForPeriod, final BigDecimal totalDueForPeriod, BigDecimal totalInstallmentAmountForPeriod) {
this.period = periodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.obligationsMetOnDate = null;
this.complete = null;
if (fromDate != null) {
this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate));
} else {
this.daysInPeriod = null;
}
this.principalDisbursed = null;
this.principalOriginalDue = principalOriginalDue;
this.principalDue = principalOriginalDue;
this.principalPaid = null;
this.principalWrittenOff = null;
this.principalOutstanding = principalOriginalDue;
this.principalLoanBalanceOutstanding = principalOutstanding;
this.interestOriginalDue = interestDueOnPrincipalOutstanding;
this.interestDue = interestDueOnPrincipalOutstanding;
this.interestPaid = null;
this.interestWaived = null;
this.interestWrittenOff = null;
this.interestOutstanding = interestDueOnPrincipalOutstanding;
this.feeChargesDue = feeChargesDueForPeriod;
this.feeChargesPaid = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.feeChargesOutstanding = null;
this.penaltyChargesDue = penaltyChargesDueForPeriod;
this.penaltyChargesPaid = null;
this.penaltyChargesWaived = null;
this.penaltyChargesWrittenOff = null;
this.penaltyChargesOutstanding = null;
this.totalOriginalDueForPeriod = totalDueForPeriod;
this.totalDueForPeriod = totalDueForPeriod;
this.totalPaidForPeriod = BigDecimal.ZERO;
this.totalPaidInAdvanceForPeriod = null;
this.totalPaidLateForPeriod = null;
this.totalWaivedForPeriod = null;
this.totalWrittenOffForPeriod = null;
this.totalOutstandingForPeriod = totalDueForPeriod;
this.totalActualCostOfLoanForPeriod = interestDueOnPrincipalOutstanding.add(feeChargesDueForPeriod);
this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod;
if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod;
} else {
this.totalOverdue = null;
}
this.totalCredits = BigDecimal.ZERO;
}
/*
* Used for creating loan schedule periods with full information on expected principal, interest & charges along
* with what portion of each is paid.
*/
private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
final LocalDate obligationsMetOnDate, final boolean complete, final BigDecimal principalOriginalDue,
final BigDecimal principalPaid, final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
final BigDecimal principalLoanBalanceOutstanding, final BigDecimal interestDueOnPrincipalOutstanding,
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
final BigDecimal interestOutstanding, final BigDecimal feeChargesDue, final BigDecimal feeChargesPaid,
final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, final BigDecimal feeChargesOutstanding,
final BigDecimal penaltyChargesDue, final BigDecimal penaltyChargesPaid, final BigDecimal penaltyChargesWaived,
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesOutstanding, final BigDecimal totalDueForPeriod,
final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod,
final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
final BigDecimal totalActualCostOfLoanForPeriod, final BigDecimal totalInstallmentAmountForPeriod,
final BigDecimal totalCredits) {
this.period = periodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.obligationsMetOnDate = obligationsMetOnDate;
this.complete = complete;
if (fromDate != null) {
this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate));
} else {
this.daysInPeriod = null;
}
this.principalDisbursed = null;
this.principalOriginalDue = principalOriginalDue;
this.principalDue = principalOriginalDue;
this.principalPaid = principalPaid;
this.principalWrittenOff = principalWrittenOff;
this.principalOutstanding = principalOutstanding;
this.principalLoanBalanceOutstanding = principalLoanBalanceOutstanding;
this.interestOriginalDue = interestDueOnPrincipalOutstanding;
this.interestDue = interestDueOnPrincipalOutstanding;
this.interestPaid = interestPaid;
this.interestWaived = interestWaived;
this.interestWrittenOff = interestWrittenOff;
this.interestOutstanding = interestOutstanding;
this.feeChargesDue = feeChargesDue;
this.feeChargesPaid = feeChargesPaid;
this.feeChargesWaived = feeChargesWaived;
this.feeChargesWrittenOff = feeChargesWrittenOff;
this.feeChargesOutstanding = feeChargesOutstanding;
this.penaltyChargesDue = penaltyChargesDue;
this.penaltyChargesPaid = penaltyChargesPaid;
this.penaltyChargesWaived = penaltyChargesWaived;
this.penaltyChargesWrittenOff = penaltyChargesWrittenOff;
this.penaltyChargesOutstanding = penaltyChargesOutstanding;
this.totalOriginalDueForPeriod = totalDueForPeriod;
this.totalDueForPeriod = totalDueForPeriod;
this.totalPaidForPeriod = totalPaid;
this.totalPaidInAdvanceForPeriod = totalPaidInAdvanceForPeriod;
this.totalPaidLateForPeriod = totalPaidLateForPeriod;
this.totalWaivedForPeriod = totalWaived;
this.totalWrittenOffForPeriod = totalWrittenOff;
this.totalOutstandingForPeriod = totalOutstanding;
this.totalActualCostOfLoanForPeriod = totalActualCostOfLoanForPeriod;
this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod;
if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod;
} else {
this.totalOverdue = null;
}
this.totalCredits = totalCredits;
}
private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) {
BigDecimal value = BigDecimal.ZERO;
if (possibleNullValue != null) {
value = possibleNullValue;
}
return value;
}
public BigDecimal getPrincipalDisbursed() {
return defaultToZeroIfNull(this.principalDisbursed);
}
public BigDecimal getPrincipalDue() {
return defaultToZeroIfNull(this.principalDue);
}
public BigDecimal getPrincipalPaid() {
return defaultToZeroIfNull(this.principalPaid);
}
public BigDecimal getPrincipalWrittenOff() {
return defaultToZeroIfNull(this.principalWrittenOff);
}
public BigDecimal getPrincipalOutstanding() {
return defaultToZeroIfNull(this.principalOutstanding);
}
public BigDecimal getInterestDue() {
return defaultToZeroIfNull(this.interestDue);
}
public BigDecimal getInterestPaid() {
return defaultToZeroIfNull(this.interestPaid);
}
public BigDecimal getInterestWaived() {
return defaultToZeroIfNull(this.interestWaived);
}
public BigDecimal getInterestWrittenOff() {
return defaultToZeroIfNull(this.interestWrittenOff);
}
public BigDecimal getInterestOutstanding() {
return defaultToZeroIfNull(this.interestOutstanding);
}
public BigDecimal getFeeChargesDue() {
return defaultToZeroIfNull(this.feeChargesDue);
}
public BigDecimal getFeeChargesWaived() {
return defaultToZeroIfNull(this.feeChargesWaived);
}
public BigDecimal getFeeChargesWrittenOff() {
return defaultToZeroIfNull(this.feeChargesWrittenOff);
}
public BigDecimal getFeeChargesPaid() {
return defaultToZeroIfNull(this.feeChargesPaid);
}
public BigDecimal getFeeChargesOutstanding() {
return defaultToZeroIfNull(this.feeChargesOutstanding);
}
public BigDecimal getPenaltyChargesDue() {
return defaultToZeroIfNull(this.penaltyChargesDue);
}
public BigDecimal getPenaltyChargesWaived() {
return defaultToZeroIfNull(this.penaltyChargesWaived);
}
public BigDecimal getPenaltyChargesWrittenOff() {
return defaultToZeroIfNull(this.penaltyChargesWrittenOff);
}
public BigDecimal getPenaltyChargesPaid() {
return defaultToZeroIfNull(this.penaltyChargesPaid);
}
public BigDecimal getPenaltyChargesOutstanding() {
return defaultToZeroIfNull(this.penaltyChargesOutstanding);
}
public BigDecimal getTotalOverdue() {
return defaultToZeroIfNull(this.totalOverdue);
}
public BigDecimal totalOutstandingForPeriod() {
return defaultToZeroIfNull(this.totalOutstandingForPeriod);
}
}
@@ -0,0 +1,75 @@
/**
* 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.loanschedule.domain;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.springframework.stereotype.Component;
@Component
public class AprCalculator {
public BigDecimal calculateFrom(final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal interestRatePerPeriod,
final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType) {
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO;
switch (interestPeriodFrequencyType) {
case DAYS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(365));
break;
case WEEKS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52));
break;
case MONTHS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(12));
break;
case YEARS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(1));
break;
case WHOLE_TERM:
final BigDecimal ratePerPeriod = interestRatePerPeriod.divide(BigDecimal.valueOf(numberOfRepayments * repaymentEvery), 8,
RoundingMode.HALF_UP);
switch (repaymentPeriodFrequencyType) {
case DAYS:
defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(365));
break;
case WEEKS:
defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(52));
break;
case MONTHS:
defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(12));
break;
case YEARS:
defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(1));
break;
case WHOLE_TERM:
break;
case INVALID:
break;
}
break;
case INVALID:
break;
}
return defaultAnnualNominalInterestRate;
}
}
@@ -0,0 +1,69 @@
/**
* 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.loanschedule.domain;
public final class FinanicalFunctions {
private FinanicalFunctions() {
}
/**
* PMT calculates a fixed monthly payment to be paid by borrower every 'period' to ensure loan is paid off in full
* (with interest).
*
* This monthly payment c depends upon the monthly interest rate r (expressed as a fraction, not a percentage, i.e.,
* divide the quoted yearly percentage rate by 100 and by 12 to obtain the monthly interest rate), the number of
* monthly payments N called the loan's term, and the amount borrowed P known as the loan's principal; c is given by
* the formula:
*
* c = (r / (1 - (1 + r)^-N))P
*
* @param interestRateFraction
* @param numberOfPayments
* @param principal
* @param futureValue
* @param type
*/
public static double pmt(final double interestRateFraction, final double numberOfPayments, final double principal,
final double futureValue, final boolean type) {
double payment = 0;
if (interestRateFraction == 0) {
payment = -1 * (futureValue + principal) / numberOfPayments;
} else {
final double r1 = interestRateFraction + 1;
payment = (futureValue + principal * Math.pow(r1, numberOfPayments)) * interestRateFraction
/ ((type ? r1 : 1) * (1 - Math.pow(r1, numberOfPayments)));
}
return payment;
}
public static int nop(final double interestRateFraction, final double emiAmount, final double principal, final double futureValue,
final boolean type) {
double numberOfPayments = 0;
if (interestRateFraction == 0) {
numberOfPayments = ((Double) (-1 * (futureValue + principal) / emiAmount)).intValue();
} else {
final double r1 = interestRateFraction + 1;
numberOfPayments = (futureValue + principal * Math.pow(r1, emiAmount)) * interestRateFraction
/ ((type ? r1 : 1) * (1 - Math.pow(r1, emiAmount)));
}
return Double.valueOf(numberOfPayments).intValue();
}
}
@@ -0,0 +1,45 @@
/**
* 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.loanschedule.domain;
import java.math.MathContext;
import java.time.LocalDate;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
public interface LoanScheduleGenerator {
LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
HolidayDetailDTO holidayDetailDTO);
LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
LocalDate rescheduleFrom);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
}
@@ -0,0 +1,26 @@
/**
* 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.loanschedule.domain;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
public interface LoanScheduleGeneratorFactory {
LoanScheduleGenerator create(InterestMethod interestMethod);
}
@@ -0,0 +1,129 @@
/**
* 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.loanschedule.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
/**
* Domain representation of a Loan Schedule (not used for persistence)
*/
public final class LoanScheduleModel {
private final Collection<LoanScheduleModelPeriod> periods;
private final ApplicationCurrency applicationCurrency;
private final int loanTermInDays;
private final Money totalPrincipalDisbursed;
private final BigDecimal totalPrincipalExpected;
private final BigDecimal totalPrincipalPaid;
private final BigDecimal totalInterestCharged;
private final BigDecimal totalFeeChargesCharged;
private final BigDecimal totalPenaltyChargesCharged;
private final BigDecimal totalRepaymentExpected;
private final BigDecimal totalOutstanding;
public static LoanScheduleModel from(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
return new LoanScheduleModel(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstanding);
}
public static LoanScheduleModel withOverdueChargeUpdation(final Collection<LoanScheduleModelPeriod> periods,
final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) {
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, totalPenaltyChargesCharged,
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
}
public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection<LoanScheduleModelPeriod> periods,
final LoanScheduleModel loanScheduleModel) {
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged,
loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
}
private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
this.periods = periods;
this.applicationCurrency = applicationCurrency;
this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = principalDisbursed;
this.totalPrincipalExpected = totalPrincipalExpected;
this.totalPrincipalPaid = totalPrincipalPaid;
this.totalInterestCharged = totalInterestCharged;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalOutstanding = totalOutstanding;
}
public LoanScheduleData toData() {
final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal();
final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf();
final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf);
final BigDecimal totalCredits = BigDecimal.ZERO;
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
for (final LoanScheduleModelPeriod modelPeriod : this.periods) {
periodsData.add(modelPeriod.toData());
}
final BigDecimal totalWaived = null;
final BigDecimal totalWrittenOff = null;
final BigDecimal totalRepayment = null;
final BigDecimal totalPaidInAdvance = null;
final BigDecimal totalPaidLate = null;
return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(),
this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged,
this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment,
totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits);
}
public Collection<LoanScheduleModelPeriod> getPeriods() {
return this.periods;
}
public BigDecimal getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
public BigDecimal getTotalInterestCharged() {
return this.totalInterestCharged;
}
}
@@ -0,0 +1,65 @@
/**
* 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.loanschedule.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
public interface LoanScheduleModelPeriod {
LoanSchedulePeriodData toData();
boolean isRepaymentPeriod();
Integer periodNumber();
LocalDate periodFromDate();
LocalDate periodDueDate();
BigDecimal principalDue();
BigDecimal interestDue();
BigDecimal feeChargesDue();
BigDecimal penaltyChargesDue();
void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge);
boolean isRecalculatedInterestComponent();
void addPrincipalAmount(Money principalDue);
void addInterestAmount(Money interestDue);
Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails();
void setEMIFixedSpecificToInstallmentTrue();
boolean isEMIFixedSpecificToInstallment();
BigDecimal rescheduleInterestPortion();
void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion);
}
@@ -0,0 +1,31 @@
/**
* 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.loanschedule.domain;
import java.time.LocalDate;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
public interface PaymentPeriodsInOneYearCalculator {
Integer calculate(PeriodFrequencyType repaymentFrequencyType);
double calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaidEvery);
}
@@ -0,0 +1,55 @@
/**
* 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.loanschedule.domain;
import org.apache.fineract.organisation.monetary.domain.Money;
public class PrincipalInterest {
private final Money principal;
private final Money interest;
private final Money interestPaymentDueToGrace;
private Money rescheduleInterestPortion;
public PrincipalInterest(final Money principal, final Money interest, final Money interestPaymentDueToGrace) {
this.principal = principal;
this.interest = interest;
this.interestPaymentDueToGrace = interestPaymentDueToGrace;
}
public Money principal() {
return this.principal;
}
public Money interest() {
return this.interest;
}
public Money interestPaymentDueToGrace() {
return this.interestPaymentDueToGrace;
}
public Money getRescheduleInterestPortion() {
return rescheduleInterestPortion;
}
public void setRescheduleInterestPortion(Money rescheduleInterestPortion) {
this.rescheduleInterestPortion = rescheduleInterestPortion;
}
}
@@ -0,0 +1,150 @@
/**
* 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.loanproduct;
import java.math.BigDecimal;
public interface LoanProductConstants {
String USE_BORROWER_CYCLE_PARAMETER_NAME = "useBorrowerCycle";
String PRINCIPAL_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME = "principalVariationsForBorrowerCycle";
String INTEREST_RATE_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME = "interestRateVariationsForBorrowerCycle";
String NUMBER_OF_REPAYMENT_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME = "numberOfRepaymentVariationsForBorrowerCycle";
String DEFAULT_VALUE_PARAMETER_NAME = "defaultValue";
String MIN_VALUE_PARAMETER_NAME = "minValue";
String MAX_VALUE_PARAMETER_NAME = "maxValue";
String VALUE_CONDITION_TYPE_PARAM_NAME = "valueConditionType";
String BORROWER_CYCLE_NUMBER_PARAM_NAME = "borrowerCycleNumber";
String BORROWER_CYCLE_ID_PARAMETER_NAME = "id";
String PRINCIPAL_PER_CYCLE_PARAMETER_NAME = "principalPerCycle";
String MIN_PRINCIPAL_PER_CYCLE_PARAMETER_NAME = "minPrincipalPerCycle";
String MAX_PRINCIPAL_PER_CYCLE_PARAMETER_NAME = "maxPrincipalPerCycle";
String PRINCIPAL_VALUE_USAGE_CONDITION_PARAM_NAME = "principalValueUsageCondition";
String PRINCIPAL_CYCLE_NUMBERS_PARAM_NAME = "principalCycleNumbers";
String NUMBER_OF_REPAYMENTS_PER_CYCLE_PARAMETER_NAME = "numberOfRepaymentsPerCycle";
String MIN_NUMBER_OF_REPAYMENTS_PER_CYCLE_PARAMETER_NAME = "minNumberOfRepaymentsPerCycle";
String MAX_NUMBER_OF_REPAYMENTS_PER_CYCLE_PARAMETER_NAME = "maxNumberOfRepaymentsPerCycle";
String REPAYMENT_VALUE_USAGE_CONDITION_PARAM_NAME = "repaymentValueUsageCondition";
String REPAYMENT_CYCLE_NUMBER_PARAM_NAME = "repaymentCycleNumber";
String INTEREST_RATE_PER_PERIOD_PER_CYCLE_PARAMETER_NAME = "interestRatePerPeriodPerCycle";
String MIN_INTEREST_RATE_PER_PERIOD_PER_CYCLE_PARAMETER_NAME = "minInterestRatePerPeriodPerCycle";
String MAX_INTEREST_RATE_PER_PERIOD_PER_CYCLE_PARAMETER_NAME = "maxInterestRatePerPeriodPerCycle";
String INTEREST_RATE_VALUE_USAGE_CONDITION_PARAM_NAME = "interestRateValueUsageCondition";
String INTEREST_RATE_CYCLE_NUMBER_PARAM_NAME = "interestRateCycleNumber";
String PRINCIPAL = "principal";
String MIN_PRINCIPAL = "minPrincipal";
String MAX_PRINCIPAL = "maxPrincipalValue";
String INTEREST_RATE_PER_PERIOD = "interestRatePerPeriod";
String MIN_INTEREST_RATE_PER_PERIOD = "minInterestRatePerPeriod";
String MAX_INTEREST_RATE_PER_PERIOD = "maxInterestRatePerPeriod";
String NUMBER_OF_REPAYMENTS = "numberOfRepayments";
String MIN_NUMBER_OF_REPAYMENTS = "minNumberOfRepayments";
String MAX_NUMBER_OF_REPAYMENTS = "maxNumberOfRepayments";
String VALUE_CONDITION_END_WITH_ERROR = "condition.type.must.end.with.greater.than";
String VALUE_CONDITION_START_WITH_ERROR = "condition.type.must.start.with.equal";
String SHORT_NAME = "shortName";
String MULTI_DISBURSE_LOAN_PARAMETER_NAME = "multiDisburseLoan";
String MAX_TRANCHE_COUNT_PARAMETER_NAME = "maxTrancheCount";
String OUTSTANDING_LOAN_BALANCE_PARAMETER_NAME = "outstandingLoanBalance";
String GRACE_ON_ARREARS_AGEING_PARAMETER_NAME = "graceOnArrearsAgeing";
String OVERDUE_DAYS_FOR_NPA_PARAMETER_NAME = "overdueDaysForNPA";
String MINIMUM_DAYS_BETWEEN_DISBURSAL_AND_FIRST_REPAYMENT = "minimumDaysBetweenDisbursalAndFirstRepayment";
String ACCOUNT_MOVES_OUT_OF_NPA_ONLY_ON_ARREARS_COMPLETION_PARAM_NAME = "accountMovesOutOfNPAOnlyOnArrearsCompletion";
// Interest recalculation related
String IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME = "isInterestRecalculationEnabled";
String DAYS_IN_YEAR_TYPE_PARAMETER_NAME = "daysInYearType";
String DAYS_IN_MONTH_TYPE_PARAMETER_NAME = "daysInMonthType";
String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
String recalculationRestFrequencyTypeParameterName = "recalculationRestFrequencyType";
String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval";
String recalculationRestFrequencyWeekdayParamName = "recalculationRestFrequencyDayOfWeekType";
String recalculationRestFrequencyNthDayParamName = "recalculationRestFrequencyNthDayType";
String recalculationRestFrequencyOnDayParamName = "recalculationRestFrequencyOnDayType";
String isArrearsBasedOnOriginalScheduleParamName = "isArrearsBasedOnOriginalSchedule";
String preClosureInterestCalculationStrategyParamName = "preClosureInterestCalculationStrategy";
String recalculationCompoundingFrequencyTypeParameterName = "recalculationCompoundingFrequencyType";
String recalculationCompoundingFrequencyIntervalParameterName = "recalculationCompoundingFrequencyInterval";
String recalculationCompoundingFrequencyWeekdayParamName = "recalculationCompoundingFrequencyDayOfWeekType";
String recalculationCompoundingFrequencyNthDayParamName = "recalculationCompoundingFrequencyNthDayType";
String recalculationCompoundingFrequencyOnDayParamName = "recalculationCompoundingFrequencyOnDayType";
String isCompoundingToBePostedAsTransactionParamName = "isCompoundingToBePostedAsTransaction";
// Guarantee related
String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
String mandatoryGuaranteeParamName = "mandatoryGuarantee";
String minimumGuaranteeFromOwnFundsParamName = "minimumGuaranteeFromOwnFunds";
String minimumGuaranteeFromGuarantorParamName = "minimumGuaranteeFromGuarantor";
String principalThresholdForLastInstallmentParamName = "principalThresholdForLastInstallment";
BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_MULTI_DISBURSE_LOAN = BigDecimal.valueOf(50);
BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN = BigDecimal.valueOf(0);
// Fixed installment configuration related
String canDefineEmiAmountParamName = "canDefineInstallmentAmount";
String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment";
// Loan Configurable Attributes
String allowAttributeOverridesParamName = "allowAttributeOverrides";
String amortizationTypeParamName = "amortizationType";
String interestTypeParamName = "interestType";
String transactionProcessingStrategyCodeParamName = "transactionProcessingStrategyCode";
String interestCalculationPeriodTypeParamName = "interestCalculationPeriodType";
String inArrearsToleranceParamName = "inArrearsTolerance";
String repaymentEveryParamName = "repaymentEvery";
String graceOnPrincipalAndInterestPaymentParamName = "graceOnPrincipalAndInterestPayment";
String allowCompoundingOnEodParamName = "allowCompoundingOnEod";
// Variable Installments Settings
String allowVariableInstallmentsParamName = "allowVariableInstallments";
String minimumGapBetweenInstallments = "minimumGap";
String maximumGapBetweenInstallments = "maximumGap";
String ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME = "allowPartialPeriodInterestCalcualtion";
String CAN_USE_FOR_TOPUP = "canUseForTopup";
String IS_EQUAL_AMORTIZATION_PARAM = "isEqualAmortization";
String RATES_PARAM_NAME = "rates";
// Multiple disbursement related
String installmentAmountInMultiplesOfParamName = "installmentAmountInMultiplesOf";
String DISALLOW_EXPECTED_DISBURSEMENTS = "disallowExpectedDisbursements";
String ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED = "allowApprovedDisbursedAmountsOverApplied";
String OVER_APPLIED_CALCULATION_TYPE = "overAppliedCalculationType";
String OVER_APPLIED_NUMBER = "overAppliedNumber";
String DELINQUENCY_BUCKET_PARAM_NAME = "delinquencyBucketId";
// repayment events related
String DUE_DAYS_FOR_REPAYMENT_EVENT = "dueDaysForRepaymentEvent";
String OVER_DUE_DAYS_FOR_REPAYMENT_EVENT = "overDueDaysForRepaymentEvent";
}
@@ -0,0 +1,36 @@
/**
* 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.loanproduct.data;
import java.io.Serializable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Immutable data object representing a transaction strategy option for a loan.
*/
@RequiredArgsConstructor
@Getter
public final class TransactionProcessingStrategyData implements Serializable {
private final Long id;
@SuppressWarnings("unused")
private final String code;
private final String name;
}
@@ -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.loanproduct.domain;
public enum AmortizationMethod {
EQUAL_PRINCIPAL(0, "amortizationType.equal.principal"), //
EQUAL_INSTALLMENTS(1, "amortizationType.equal.installments"), //
INVALID(2, "amortizationType.invalid");
private final Integer value;
private final String code;
AmortizationMethod(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static AmortizationMethod fromInt(final Integer selectedMethod) {
if (selectedMethod == null) {
return null;
}
return switch (selectedMethod) {
case 0 -> AmortizationMethod.EQUAL_PRINCIPAL;
case 1 -> AmortizationMethod.EQUAL_INSTALLMENTS;
default -> AmortizationMethod.INVALID;
};
}
public boolean isEqualInstallment() {
return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS.getValue());
}
public boolean isEqualPrincipal() {
return this.value.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue());
}
}
@@ -0,0 +1,56 @@
/**
* 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.loanproduct.domain;
public enum InterestCalculationPeriodMethod {
DAILY(0, "interestCalculationPeriodType.daily"), //
SAME_AS_REPAYMENT_PERIOD(1, "interestCalculationPeriodType.same.as.repayment.period"), //
INVALID(2, "interestCalculationPeriodType.invalid");
private final Integer value;
private final String code;
InterestCalculationPeriodMethod(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static InterestCalculationPeriodMethod fromInt(final Integer selectedMethod) {
return switch (selectedMethod) {
case 0 -> InterestCalculationPeriodMethod.DAILY;
case 1 -> InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
default -> InterestCalculationPeriodMethod.INVALID;
};
}
public boolean isDaily() {
return this.value.equals(InterestCalculationPeriodMethod.DAILY.getValue());
}
}
@@ -0,0 +1,53 @@
/**
* 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.loanproduct.domain;
public enum InterestMethod {
DECLINING_BALANCE(0, "interestType.declining.balance"), FLAT(1, "interestType.flat"), INVALID(2, "interestType.invalid");
private final Integer value;
private final String code;
InterestMethod(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static InterestMethod fromInt(final Integer selectedMethod) {
return switch (selectedMethod) {
case 0 -> InterestMethod.DECLINING_BALANCE;
case 1 -> InterestMethod.FLAT;
default -> InterestMethod.INVALID;
};
}
public boolean isDecliningBalance() {
return this.value.equals(InterestMethod.DECLINING_BALANCE.getValue());
}
}
@@ -0,0 +1,82 @@
/**
* 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.loanproduct.domain;
import java.util.HashMap;
import java.util.Map;
/***
*
* People typically use either of the following settings when defining interest recalculation method:
* <ul>
* <li>NONE</li>
* <li>INTEREST</li>
* <li>FEE</li>
* <li>INTEREST_AND_FEE</li>
* </ul>
*/
public enum InterestRecalculationCompoundingMethod {
NONE(0, "interestRecalculationCompoundingMethod.none"), //
INTEREST(1, "interestRecalculationCompoundingMethod.interest"), //
FEE(2, "interestRecalculationCompoundingMethod.fee"), //
INTEREST_AND_FEE(3, "interestRecalculationCompoundingMethod.interest.and.fee");
private final Integer value;
private final String code;
private static final Map<Integer, InterestRecalculationCompoundingMethod> intToEnumMap = new HashMap<>();
static {
for (final InterestRecalculationCompoundingMethod type : InterestRecalculationCompoundingMethod.values()) {
intToEnumMap.put(type.value, type);
}
}
public static InterestRecalculationCompoundingMethod fromInt(final Integer ruleTypeValue) {
return intToEnumMap.get(ruleTypeValue);
}
InterestRecalculationCompoundingMethod(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isFeeCompoundingEnabled() {
return this.getValue().equals(InterestRecalculationCompoundingMethod.FEE.getValue())
|| this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue());
}
public boolean isCompoundingEnabled() {
return !this.getValue().equals(InterestRecalculationCompoundingMethod.NONE.getValue());
}
public boolean isInterestCompoundingEnabled() {
return this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST.getValue())
|| this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue());
}
}
@@ -0,0 +1,89 @@
/**
* 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.loanproduct.domain;
import java.util.HashMap;
import java.util.Map;
public enum LoanPreClosureInterestCalculationStrategy {
NONE(0, "loanPreClosureInterestCalculationStrategy.none"), //
TILL_PRE_CLOSURE_DATE(1, "loanPreClosureInterestCalculationStrategy.tillPreClosureDate"), //
TILL_REST_FREQUENCY_DATE(2, "loanPreClosureInterestCalculationStrategy.tillRestFrequencyDate");
// REPAYMENT_PERIOD_DATE(3,
// "loanPreClosureInterestCalculationStrategy.repaymentPeriodDate")
private final Integer value;
private final String code;
private static final Map<Integer, LoanPreClosureInterestCalculationStrategy> intToEnumMap = new HashMap<>();
private static int minValue;
private static int maxValue;
static {
int i = 0;
for (final LoanPreClosureInterestCalculationStrategy type : LoanPreClosureInterestCalculationStrategy.values()) {
if (i == 0) {
minValue = type.value;
}
intToEnumMap.put(type.value, type);
if (minValue >= type.value) {
minValue = type.value;
}
if (maxValue < type.value) {
maxValue = type.value;
}
i = i + 1;
}
}
public static LoanPreClosureInterestCalculationStrategy fromInt(final Integer ruleTypeValue) {
return intToEnumMap.get(ruleTypeValue);
}
LoanPreClosureInterestCalculationStrategy(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
public boolean calculateTillRestFrequencyEnabled() {
return this.getValue().equals(LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE.getValue());
}
public boolean calculateTillPreClosureDateEnabled() {
return this.getValue().equals(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue());
}
}
@@ -0,0 +1,120 @@
/**
* 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.loanproduct.domain;
import java.math.BigDecimal;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_product_loan_variations_borrower_cycle")
public class LoanProductBorrowerCycleVariations extends AbstractPersistableCustom {
@ManyToOne
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@Column(name = "borrower_cycle_number", nullable = false)
private Integer borrowerCycleNumber;
@Column(name = "param_type", nullable = false)
private Integer paramType;
@Column(name = "value_condition", nullable = false)
private Integer valueConditionType;
@Column(name = "min_value", scale = 6, precision = 19)
private BigDecimal minValue;
@Column(name = "max_value", scale = 6, precision = 19)
private BigDecimal maxValue;
@Column(name = "default_value", scale = 6, precision = 19, nullable = false)
private BigDecimal defaultValue;
protected LoanProductBorrowerCycleVariations() {}
public LoanProductBorrowerCycleVariations(final Integer borrowerCycleNumber, final Integer paramType, final Integer valueConditionType,
final BigDecimal minValue, final BigDecimal maxValue, final BigDecimal defaultValue) {
this.borrowerCycleNumber = borrowerCycleNumber;
this.paramType = paramType;
this.valueConditionType = valueConditionType;
this.minValue = minValue;
this.maxValue = maxValue;
this.defaultValue = defaultValue;
}
public void updateLoanProduct(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public LoanProductParamType getParamType() {
return LoanProductParamType.fromInt(this.paramType);
}
public LoanProductValueConditionType getValueConditionType() {
return LoanProductValueConditionType.fromInt(this.valueConditionType);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof LoanProductBorrowerCycleVariations)) {
return false;
}
final LoanProductBorrowerCycleVariations other = (LoanProductBorrowerCycleVariations) obj;
return Objects.equals(loanProduct, other.loanProduct) && Objects.equals(borrowerCycleNumber, other.borrowerCycleNumber)
&& Objects.equals(paramType, other.paramType) && Objects.equals(valueConditionType, other.valueConditionType)
&& Objects.equals(minValue, other.minValue) && Objects.equals(maxValue, other.maxValue)
&& Objects.equals(defaultValue, other.defaultValue);
}
@Override
public int hashCode() {
return Objects.hash(loanProduct, borrowerCycleNumber, paramType, valueConditionType, minValue, maxValue, defaultValue);
}
public void copy(final LoanProductBorrowerCycleVariations borrowerCycleVariations) {
this.defaultValue = borrowerCycleVariations.defaultValue;
this.minValue = borrowerCycleVariations.minValue;
this.maxValue = borrowerCycleVariations.maxValue;
this.valueConditionType = borrowerCycleVariations.valueConditionType;
this.borrowerCycleNumber = borrowerCycleVariations.borrowerCycleNumber;
}
public Integer getBorrowerCycleNumber() {
return this.borrowerCycleNumber;
}
public BigDecimal getMinValue() {
return this.minValue;
}
public BigDecimal getMaxValue() {
return this.maxValue;
}
public BigDecimal getDefaultValue() {
return this.defaultValue;
}
}
@@ -0,0 +1,238 @@
/**
* 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.loanproduct.domain;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
@Entity
@Table(name = "m_product_loan_configurable_attributes")
public class LoanProductConfigurableAttributes extends AbstractPersistableCustom implements Serializable {
@OneToOne
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@Column(name = "amortization_method_enum")
private Boolean amortizationType;
@Column(name = "interest_method_enum")
private Boolean interestType;
@Column(name = "loan_transaction_strategy_code")
private Boolean transactionProcessingStrategyCode;
@Column(name = "interest_calculated_in_period_enum")
private Boolean interestCalculationPeriodType;
@Column(name = "arrearstolerance_amount")
private Boolean inArrearsTolerance;
@Column(name = "repay_every")
private Boolean repaymentEvery;
@Column(name = "moratorium")
private Boolean graceOnPrincipalAndInterestPayment;
@Column(name = "grace_on_arrears_ageing")
private Boolean graceOnArrearsAgeing;
private static final String[] supportedLoanConfigurableAttributes = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
LoanProductConstants.interestCalculationPeriodTypeParamName, LoanProductConstants.inArrearsToleranceParamName,
LoanProductConstants.repaymentEveryParamName, LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName,
LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME };
public static LoanProductConfigurableAttributes createFrom(JsonCommand command) {
final Boolean amortization = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.amortizationTypeParamName).getAsBoolean();
final Boolean interestMethod = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.interestTypeParamName).getAsBoolean();
final Boolean transactionProcessingStrategy = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.transactionProcessingStrategyCodeParamName).getAsBoolean();
final Boolean interestCalcPeriod = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.interestCalculationPeriodTypeParamName).getAsBoolean();
final Boolean arrearsTolerance = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.inArrearsToleranceParamName).getAsBoolean();
final Boolean repaymentEvery = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.repaymentEveryParamName).getAsBoolean();
final Boolean graceOnPrincipalAndInterestPayment = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName).getAsBoolean();
final Boolean graceOnArrearsAging = command.parsedJson().getAsJsonObject()
.getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName)
.getAsJsonPrimitive(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME).getAsBoolean();
return new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy, interestCalcPeriod,
arrearsTolerance, repaymentEvery, graceOnPrincipalAndInterestPayment, graceOnArrearsAging);
}
public void updateLoanProduct(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public static LoanProductConfigurableAttributes populateDefaultsForConfigurableAttributes() {
final Boolean amortization = true;
final Boolean interestMethod = true;
final Boolean transactionProcessingStrategy = true;
final Boolean interestCalcPeriod = true;
final Boolean arrearsTolerance = true;
final Boolean repaymentEvery = true;
final Boolean graceOnPrincipalAndInterestPayment = true;
final Boolean graceOnArrearsAging = true;
return new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy, interestCalcPeriod,
arrearsTolerance, repaymentEvery, graceOnPrincipalAndInterestPayment, graceOnArrearsAging);
}
public LoanProductConfigurableAttributes(Boolean amortization, Boolean interestMethod, Boolean transactionProcessingStrategyCode,
Boolean interestCalcPeriod, Boolean arrearsTolerance, Boolean repaymentEvery, Boolean graceOnPrincipalAndInterestPayment,
Boolean graceOnArrearsAging) {
this.amortizationType = amortization;
this.interestType = interestMethod;
this.inArrearsTolerance = arrearsTolerance;
this.graceOnArrearsAgeing = graceOnArrearsAging;
this.interestCalculationPeriodType = interestCalcPeriod;
this.graceOnPrincipalAndInterestPayment = graceOnPrincipalAndInterestPayment;
this.repaymentEvery = repaymentEvery;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
}
protected LoanProductConfigurableAttributes() {
}
public static String[] getAllowedLoanConfigurableAttributes() {
return supportedLoanConfigurableAttributes;
}
public LoanProduct getLoanProduct() {
return loanProduct;
}
public Boolean getAmortizationBoolean() {
return amortizationType;
}
public Boolean getInterestMethodBoolean() {
return interestType;
}
public Boolean getTransactionProcessingStrategyBoolean() {
return transactionProcessingStrategyCode;
}
public Boolean getInterestCalcPeriodBoolean() {
return interestCalculationPeriodType;
}
public Boolean getArrearsToleranceBoolean() {
return inArrearsTolerance;
}
public Boolean getRepaymentEveryBoolean() {
return repaymentEvery;
}
public Boolean getGraceOnPrincipalAndInterestPaymentBoolean() {
return graceOnPrincipalAndInterestPayment;
}
public Boolean getGraceOnArrearsAgingBoolean() {
return graceOnArrearsAgeing;
}
public void setLoanProduct(LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public void setAmortizationType(Boolean amortizationType) {
this.amortizationType = amortizationType;
}
public void setInterestType(Boolean interestType) {
this.interestType = interestType;
}
public void setTransactionProcessingStrategyCode(Boolean transactionProcessingStrategyCode) {
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
}
public void setInterestCalculationPeriodType(Boolean interestCalculationPeriodType) {
this.interestCalculationPeriodType = interestCalculationPeriodType;
}
public void setInArrearsTolerance(Boolean inArrearsTolerance) {
this.inArrearsTolerance = inArrearsTolerance;
}
public void setRepaymentEvery(Boolean repaymentEvery) {
this.repaymentEvery = repaymentEvery;
}
public void setGraceOnPrincipalAndInterestPayment(Boolean graceOnPrincipalAndInterestPayment) {
this.graceOnPrincipalAndInterestPayment = graceOnPrincipalAndInterestPayment;
}
public void setGraceOnArrearsAgeing(Boolean graceOnArrearsAgeing) {
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof LoanProductConfigurableAttributes)) {
return false;
}
LoanProductConfigurableAttributes that = (LoanProductConfigurableAttributes) o;
return Objects.equals(loanProduct, that.loanProduct) && Objects.equals(amortizationType, that.amortizationType)
&& Objects.equals(interestType, that.interestType)
&& Objects.equals(transactionProcessingStrategyCode, that.transactionProcessingStrategyCode)
&& Objects.equals(interestCalculationPeriodType, that.interestCalculationPeriodType)
&& Objects.equals(inArrearsTolerance, that.inArrearsTolerance) && Objects.equals(repaymentEvery, that.repaymentEvery)
&& Objects.equals(graceOnPrincipalAndInterestPayment, that.graceOnPrincipalAndInterestPayment)
&& Objects.equals(graceOnArrearsAgeing, that.graceOnArrearsAgeing);
}
@Override
public int hashCode() {
return Objects.hash(loanProduct, amortizationType, interestType, transactionProcessingStrategyCode, interestCalculationPeriodType,
inArrearsTolerance, repaymentEvery, graceOnPrincipalAndInterestPayment, graceOnArrearsAgeing);
}
}
@@ -0,0 +1,160 @@
/**
* 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.loanproduct.domain;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
@Entity
@Table(name = "m_product_loan_floating_rates")
public class LoanProductFloatingRates extends AbstractPersistableCustom {
@OneToOne
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@ManyToOne
@JoinColumn(name = "floating_rates_id", nullable = false)
private FloatingRate floatingRate;
@Column(name = "interest_rate_differential", nullable = false)
private BigDecimal interestRateDifferential;
@Column(name = "min_differential_lending_rate", nullable = false)
private BigDecimal minDifferentialLendingRate;
@Column(name = "default_differential_lending_rate", nullable = false)
private BigDecimal defaultDifferentialLendingRate;
@Column(name = "max_differential_lending_rate", nullable = false)
private BigDecimal maxDifferentialLendingRate;
@Column(name = "is_floating_interest_rate_calculation_allowed", nullable = false)
private boolean isFloatingInterestRateCalculationAllowed;
public LoanProductFloatingRates() {
}
public LoanProductFloatingRates(FloatingRate floatingRate, LoanProduct loanProduct, BigDecimal interestRateDifferential,
BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate,
boolean isFloatingInterestRateCalculationAllowed) {
this.floatingRate = floatingRate;
this.loanProduct = loanProduct;
this.interestRateDifferential = interestRateDifferential;
this.minDifferentialLendingRate = minDifferentialLendingRate;
this.maxDifferentialLendingRate = maxDifferentialLendingRate;
this.defaultDifferentialLendingRate = defaultDifferentialLendingRate;
this.isFloatingInterestRateCalculationAllowed = isFloatingInterestRateCalculationAllowed;
}
public LoanProduct getLoanProduct() {
return this.loanProduct;
}
public FloatingRate getFloatingRate() {
return this.floatingRate;
}
public BigDecimal getInterestRateDifferential() {
return this.interestRateDifferential;
}
public BigDecimal getMinDifferentialLendingRate() {
return this.minDifferentialLendingRate;
}
public BigDecimal getDefaultDifferentialLendingRate() {
return this.defaultDifferentialLendingRate;
}
public BigDecimal getMaxDifferentialLendingRate() {
return this.maxDifferentialLendingRate;
}
public boolean isFloatingInterestRateCalculationAllowed() {
return this.isFloatingInterestRateCalculationAllowed;
}
public Map<? extends String, ?> update(JsonCommand command, FloatingRate floatingRate) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
if (floatingRate != null) {
final String floatingRatesId = "floatingRatesId";
if (this.floatingRate == null || command.isChangeInLongParameterNamed(floatingRatesId, this.floatingRate.getId())) {
final long newValue = command.longValueOfParameterNamed(floatingRatesId);
actualChanges.put(floatingRatesId, newValue);
this.floatingRate = floatingRate;
}
}
final String interestRateDifferential = "interestRateDifferential";
if (command.isChangeInBigDecimalParameterNamed(interestRateDifferential, this.interestRateDifferential)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRateDifferential);
actualChanges.put(interestRateDifferential, newValue);
this.interestRateDifferential = newValue;
}
final String minDifferentialLendingRate = "minDifferentialLendingRate";
if (command.isChangeInBigDecimalParameterNamed(minDifferentialLendingRate, this.minDifferentialLendingRate)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minDifferentialLendingRate);
actualChanges.put(minDifferentialLendingRate, newValue);
this.minDifferentialLendingRate = newValue;
}
final String defaultDifferentialLendingRate = "defaultDifferentialLendingRate";
if (command.isChangeInBigDecimalParameterNamed(defaultDifferentialLendingRate, this.defaultDifferentialLendingRate)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(defaultDifferentialLendingRate);
actualChanges.put(defaultDifferentialLendingRate, newValue);
this.defaultDifferentialLendingRate = newValue;
}
final String maxDifferentialLendingRate = "maxDifferentialLendingRate";
if (command.isChangeInBigDecimalParameterNamed(maxDifferentialLendingRate, this.maxDifferentialLendingRate)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxDifferentialLendingRate);
actualChanges.put(maxDifferentialLendingRate, newValue);
this.maxDifferentialLendingRate = newValue;
}
final String isFloatingInterestRateCalculationAllowed = "isFloatingInterestRateCalculationAllowed";
if (command.isChangeInBooleanParameterNamed(isFloatingInterestRateCalculationAllowed,
this.isFloatingInterestRateCalculationAllowed)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isFloatingInterestRateCalculationAllowed);
actualChanges.put(isFloatingInterestRateCalculationAllowed, newValue);
this.isFloatingInterestRateCalculationAllowed = newValue;
}
return actualChanges;
}
public Collection<FloatingRatePeriodData> fetchInterestRates(final FloatingRateDTO floatingRateDTO) {
floatingRateDTO.addInterestRateDiff(this.interestRateDifferential);
return floatingRate.fetchInterestRates(floatingRateDTO);
}
}
@@ -0,0 +1,118 @@
/**
* 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.loanproduct.domain;
import java.math.BigDecimal;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
/**
* Entity for capturing interest recalculation settings
*
* @author conflux
*/
@Entity
@Table(name = "m_product_loan_guarantee_details")
public class LoanProductGuaranteeDetails extends AbstractPersistableCustom {
@OneToOne
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@Column(name = "mandatory_guarantee", scale = 6, precision = 19, nullable = false)
private BigDecimal mandatoryGuarantee;
@Column(name = "minimum_guarantee_from_own_funds", scale = 6, precision = 19)
private BigDecimal minimumGuaranteeFromOwnFunds;
@Column(name = "minimum_guarantee_from_guarantor_funds", scale = 6, precision = 19)
private BigDecimal minimumGuaranteeFromGuarantor;
protected LoanProductGuaranteeDetails() {
//
}
public static LoanProductGuaranteeDetails createFrom(final JsonCommand command) {
final BigDecimal mandatoryGuarantee = command.bigDecimalValueOfParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName);
final BigDecimal minimumGuaranteeFromGuarantor = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName);
final BigDecimal minimumGuaranteeFromOwnFunds = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName);
return new LoanProductGuaranteeDetails(mandatoryGuarantee, minimumGuaranteeFromOwnFunds, minimumGuaranteeFromGuarantor);
}
private LoanProductGuaranteeDetails(final BigDecimal mandatoryGuarantee, final BigDecimal minimumGuaranteeFromOwnFunds,
final BigDecimal minimumGuaranteeFromGuarantor) {
this.mandatoryGuarantee = mandatoryGuarantee;
this.minimumGuaranteeFromGuarantor = minimumGuaranteeFromGuarantor;
this.minimumGuaranteeFromOwnFunds = minimumGuaranteeFromOwnFunds;
}
public void updateProduct(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public void update(final JsonCommand command, final Map<String, Object> actualChanges) {
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName, this.mandatoryGuarantee)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName);
actualChanges.put(LoanProductConstants.mandatoryGuaranteeParamName, newValue);
this.mandatoryGuarantee = newValue;
}
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
this.minimumGuaranteeFromGuarantor)) {
final BigDecimal newValue = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName);
actualChanges.put(LoanProductConstants.minimumGuaranteeFromGuarantorParamName, newValue);
this.minimumGuaranteeFromGuarantor = newValue;
}
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName,
this.minimumGuaranteeFromOwnFunds)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName);
actualChanges.put(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, newValue);
this.minimumGuaranteeFromOwnFunds = newValue;
}
}
public BigDecimal getMandatoryGuarantee() {
return this.mandatoryGuarantee;
}
public BigDecimal getMinimumGuaranteeFromOwnFunds() {
return this.minimumGuaranteeFromOwnFunds;
}
public BigDecimal getMinimumGuaranteeFromGuarantor() {
return this.minimumGuaranteeFromGuarantor;
}
}
@@ -0,0 +1,466 @@
/**
* 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.loanproduct.domain;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
/**
* Entity for capturing interest recalculation settings
*
* @author conflux
*/
@Entity
@Table(name = "m_product_loan_recalculation_details")
public class LoanProductInterestRecalculationDetails extends AbstractPersistableCustom {
@OneToOne
@JoinColumn(name = "product_id", nullable = false)
private LoanProduct loanProduct;
/**
* {@link InterestRecalculationCompoundingMethod}
*/
@Column(name = "compound_type_enum", nullable = false)
private Integer interestRecalculationCompoundingMethod;
/**
* {@link LoanRescheduleStrategyMethod}
*/
@Column(name = "reschedule_strategy_enum", nullable = false)
private Integer rescheduleStrategyMethod;
@Column(name = "rest_frequency_type_enum", nullable = false)
private Integer restFrequencyType;
@Column(name = "rest_frequency_interval", nullable = false)
private Integer restInterval;
@Column(name = "rest_frequency_nth_day_enum")
private Integer restFrequencyNthDay;
@Column(name = "rest_frequency_weekday_enum")
private Integer restFrequencyWeekday;
@Column(name = "rest_frequency_on_day")
private Integer restFrequencyOnDay;
@Column(name = "compounding_frequency_type_enum")
private Integer compoundingFrequencyType;
@Column(name = "compounding_frequency_interval")
private Integer compoundingInterval;
@Column(name = "compounding_frequency_nth_day_enum")
private Integer compoundingFrequencyNthDay;
@Column(name = "compounding_frequency_weekday_enum")
private Integer compoundingFrequencyWeekday;
@Column(name = "compounding_frequency_on_day")
private Integer compoundingFrequencyOnDay;
@Column(name = "arrears_based_on_original_schedule")
private boolean isArrearsBasedOnOriginalSchedule;
@Column(name = "pre_close_interest_calculation_strategy")
private Integer preClosureInterestCalculationStrategy;
@Column(name = "is_compounding_to_be_posted_as_transaction")
private Boolean isCompoundingToBePostedAsTransaction;
@Column(name = "allow_compounding_on_eod")
private Boolean allowCompoundingOnEod;
protected LoanProductInterestRecalculationDetails() {
//
}
public static LoanProductInterestRecalculationDetails createFrom(final JsonCommand command) {
final Integer interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod
.fromInt(command.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName))
.getValue();
final Integer loanRescheduleStrategyMethod = LoanRescheduleStrategyMethod
.fromInt(command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName)).getValue();
final Integer recurrenceFrequency = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyTypeParameterName);
final Integer recurrenceOnNthDay = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyNthDayParamName);
final Integer recurrenceOnDay = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName);
final Integer recurrenceOnWeekday = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyWeekdayParamName);
Integer recurrenceInterval = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName);
final boolean isArrearsBasedOnOriginalSchedule = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName);
RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency);
if (frequencyType.isSameAsRepayment()) {
recurrenceInterval = 0;
}
InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod
.fromInt(interestRecalculationCompoundingMethod);
Integer compoundingRecurrenceFrequency = null;
Integer compoundingInterval = null;
Integer compoundingRecurrenceOnNthDay = null;
Integer compoundingRecurrenceOnDay = null;
Integer compoundingRecurrenceOnWeekday = null;
boolean allowCompoundingOnEod = false;
if (compoundingMethod.isCompoundingEnabled()) {
compoundingRecurrenceFrequency = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName);
compoundingInterval = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName);
RecalculationFrequencyType compoundingFrequencyType = RecalculationFrequencyType.fromInt(compoundingRecurrenceFrequency);
if (compoundingFrequencyType.isSameAsRepayment()) {
recurrenceInterval = 0;
}
compoundingRecurrenceOnNthDay = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName);
compoundingRecurrenceOnDay = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName);
compoundingRecurrenceOnWeekday = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName);
if (!compoundingFrequencyType.isDaily()) {
allowCompoundingOnEod = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowCompoundingOnEodParamName);
}
}
Integer preCloseInterestCalculationStrategy = command
.integerValueOfParameterNamed(LoanProductConstants.preClosureInterestCalculationStrategyParamName);
if (preCloseInterestCalculationStrategy == null) {
preCloseInterestCalculationStrategy = LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue();
}
final boolean isCompoundingToBePostedAsTransaction = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName);
return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod,
recurrenceFrequency, recurrenceInterval, recurrenceOnNthDay, recurrenceOnDay, recurrenceOnWeekday,
compoundingRecurrenceFrequency, compoundingInterval, compoundingRecurrenceOnNthDay, compoundingRecurrenceOnDay,
compoundingRecurrenceOnWeekday, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy,
isCompoundingToBePostedAsTransaction, allowCompoundingOnEod);
}
private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod,
final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval,
final Integer restFrequencyNthDay, final Integer restFrequencyOnDay, final Integer restFrequencyWeekday,
Integer compoundingFrequencyType, Integer compoundingInterval, final Integer compoundingFrequencyNthDay,
final Integer compoundingFrequencyOnDay, final Integer compoundingFrequencyWeekday,
final boolean isArrearsBasedOnOriginalSchedule, final Integer preCloseInterestCalculationStrategy,
final boolean isCompoundingToBePostedAsTransaction, final boolean allowCompoundingOnEod) {
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.restFrequencyType = restFrequencyType;
this.restInterval = restInterval;
this.restFrequencyNthDay = restFrequencyNthDay;
this.restFrequencyOnDay = restFrequencyOnDay;
this.restFrequencyWeekday = restFrequencyWeekday;
this.compoundingFrequencyType = compoundingFrequencyType;
this.compoundingInterval = compoundingInterval;
this.compoundingFrequencyNthDay = compoundingFrequencyNthDay;
this.compoundingFrequencyOnDay = compoundingFrequencyOnDay;
this.compoundingFrequencyWeekday = compoundingFrequencyWeekday;
this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule;
this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy;
this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod;
}
public void updateProduct(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public Integer getInterestRecalculationCompoundingMethod() {
return this.interestRecalculationCompoundingMethod;
}
public Integer getRescheduleStrategyMethod() {
return this.rescheduleStrategyMethod;
}
public void update(final JsonCommand command, final Map<String, Object> actualChanges, final String localeAsInput) {
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName,
this.interestRecalculationCompoundingMethod)) {
final Integer newValue = command
.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName);
actualChanges.put(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, newValue);
actualChanges.put("locale", localeAsInput);
this.interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod.fromInt(newValue).getValue();
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName,
this.rescheduleStrategyMethod)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName);
actualChanges.put(LoanProductConstants.rescheduleStrategyMethodParameterName, newValue);
actualChanges.put("locale", localeAsInput);
this.rescheduleStrategyMethod = LoanRescheduleStrategyMethod.fromInt(newValue).getValue();
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyTypeParameterName,
this.restFrequencyType)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyTypeParameterName);
actualChanges.put(LoanProductConstants.recalculationRestFrequencyTypeParameterName, newValue);
actualChanges.put("locale", localeAsInput);
this.restFrequencyType = RecalculationFrequencyType.fromInt(newValue).getValue();
}
RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(this.restFrequencyType);
if (frequencyType.isSameAsRepayment()) {
this.restInterval = 0;
this.restFrequencyNthDay = null;
this.restFrequencyWeekday = null;
this.restFrequencyOnDay = null;
} else {
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
this.restInterval)) {
Integer newValue = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName);
actualChanges.put(LoanProductConstants.recalculationRestFrequencyIntervalParameterName, newValue);
actualChanges.put("locale", localeAsInput);
this.restInterval = newValue;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyNthDayParamName,
getRestFrequencyNthDay())) {
Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyNthDayParamName);
actualChanges.put(LoanProductConstants.recalculationRestFrequencyNthDayParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.restFrequencyNthDay = newValue;
this.restFrequencyOnDay = null;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyWeekdayParamName,
getRestFrequencyWeekday())) {
Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyWeekdayParamName);
actualChanges.put(LoanProductConstants.recalculationRestFrequencyWeekdayParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.restFrequencyWeekday = newValue;
this.restFrequencyOnDay = null;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName,
getRestFrequencyOnDay())) {
Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName);
actualChanges.put(LoanProductConstants.recalculationRestFrequencyOnDayParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.restFrequencyOnDay = newValue;
this.restFrequencyNthDay = null;
this.restFrequencyWeekday = null;
}
if (frequencyType.isWeekly()) {
this.restFrequencyNthDay = null;
this.restFrequencyOnDay = null;
} else if (frequencyType.isMonthly()) {
if (command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName) != null) {
this.restFrequencyNthDay = null;
this.restFrequencyWeekday = null;
} else {
this.restFrequencyOnDay = null;
}
} else if (frequencyType.isDaily()) {
this.restFrequencyNthDay = null;
this.restFrequencyWeekday = null;
this.restFrequencyOnDay = null;
}
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName,
this.compoundingFrequencyType)) {
final Integer newValue = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName);
actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, newValue);
this.compoundingFrequencyType = RecalculationFrequencyType.fromInt(newValue).getValue();
}
InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod
.fromInt(this.interestRecalculationCompoundingMethod);
if (compoundingMethod.isCompoundingEnabled()) {
RecalculationFrequencyType compoundingFrequencyType = RecalculationFrequencyType.fromInt(this.compoundingFrequencyType);
if (compoundingFrequencyType.isSameAsRepayment()) {
this.compoundingInterval = null;
this.compoundingFrequencyNthDay = null;
this.compoundingFrequencyWeekday = null;
this.compoundingFrequencyOnDay = null;
} else {
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName,
this.compoundingInterval)) {
Integer newValue = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName);
actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, newValue);
this.compoundingInterval = newValue;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName,
getCompoundingFrequencyNthDay())) {
Integer newValue = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName);
actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.compoundingFrequencyNthDay = newValue;
this.compoundingFrequencyOnDay = null;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName,
getCompoundingFrequencyWeekday())) {
Integer newValue = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName);
actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.compoundingFrequencyWeekday = newValue;
this.compoundingFrequencyOnDay = null;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName,
getCompoundingFrequencyOnDay())) {
Integer newValue = command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName);
actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.compoundingFrequencyOnDay = newValue;
this.compoundingFrequencyNthDay = null;
this.compoundingFrequencyWeekday = null;
}
if (compoundingFrequencyType.isWeekly()) {
this.compoundingFrequencyNthDay = null;
this.compoundingFrequencyOnDay = null;
} else if (compoundingFrequencyType.isMonthly()) {
if (command
.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName) != null) {
this.compoundingFrequencyNthDay = null;
this.compoundingFrequencyWeekday = null;
} else {
this.compoundingFrequencyOnDay = null;
}
} else if (compoundingFrequencyType.isDaily()) {
this.compoundingFrequencyNthDay = null;
this.compoundingFrequencyWeekday = null;
this.compoundingFrequencyOnDay = null;
}
}
if (!compoundingFrequencyType.isDaily()) {
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.allowCompoundingOnEodParamName, allowCompoundingOnEod())) {
boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowCompoundingOnEodParamName);
actualChanges.put(LoanProductConstants.allowCompoundingOnEodParamName, newValue);
this.allowCompoundingOnEod = newValue;
}
} else {
this.allowCompoundingOnEod = false;
}
} else {
this.compoundingFrequencyType = null;
this.compoundingInterval = null;
this.compoundingFrequencyNthDay = null;
this.compoundingFrequencyWeekday = null;
this.compoundingFrequencyOnDay = null;
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName,
this.isArrearsBasedOnOriginalSchedule)) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName);
actualChanges.put(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, newValue);
this.isArrearsBasedOnOriginalSchedule = newValue;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.preClosureInterestCalculationStrategyParamName,
this.preClosureInterestCalculationStrategy)) {
Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.preClosureInterestCalculationStrategyParamName);
if (newValue == null) {
newValue = LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue();
}
actualChanges.put(LoanProductConstants.preClosureInterestCalculationStrategyParamName, newValue);
this.preClosureInterestCalculationStrategy = newValue;
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName,
this.isCompoundingToBePostedAsTransaction)) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName);
actualChanges.put(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, newValue);
this.isCompoundingToBePostedAsTransaction = newValue;
}
}
public RecalculationFrequencyType getRestFrequencyType() {
return RecalculationFrequencyType.fromInt(this.restFrequencyType);
}
public Integer getRestInterval() {
return this.restInterval;
}
public RecalculationFrequencyType getCompoundingFrequencyType() {
return RecalculationFrequencyType.fromInt(this.compoundingFrequencyType);
}
public Integer getCompoundingInterval() {
return this.compoundingInterval;
}
public boolean isArrearsBasedOnOriginalSchedule() {
return this.isArrearsBasedOnOriginalSchedule;
}
public LoanPreClosureInterestCalculationStrategy preCloseInterestCalculationStrategy() {
return LoanPreClosureInterestCalculationStrategy.fromInt(this.preClosureInterestCalculationStrategy);
}
public Integer getRestFrequencyNthDay() {
return this.restFrequencyNthDay;
}
public Integer getRestFrequencyWeekday() {
return this.restFrequencyWeekday;
}
public Integer getRestFrequencyOnDay() {
return this.restFrequencyOnDay;
}
public Integer getCompoundingFrequencyNthDay() {
return this.compoundingFrequencyNthDay;
}
public Integer getCompoundingFrequencyWeekday() {
return this.compoundingFrequencyWeekday;
}
public Integer getCompoundingFrequencyOnDay() {
return this.compoundingFrequencyOnDay;
}
public Boolean getIsCompoundingToBePostedAsTransaction() {
return this.isCompoundingToBePostedAsTransaction;
}
public Boolean allowCompoundingOnEod() {
return this.allowCompoundingOnEod;
}
}
@@ -0,0 +1,172 @@
/**
* 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.loanproduct.domain;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
/**
* LoanProductMinMaxConstraints encapsulates all the Min and Max details of a {@link LoanProduct}.
*/
@Embeddable
public class LoanProductMinMaxConstraints {
@Column(name = "min_principal_amount", scale = 6, precision = 19)
private BigDecimal minPrincipal;
@Column(name = "max_principal_amount", scale = 6, precision = 19)
private BigDecimal maxPrincipal;
@Column(name = "min_nominal_interest_rate_per_period", scale = 6, precision = 19)
private BigDecimal minNominalInterestRatePerPeriod;
@Column(name = "max_nominal_interest_rate_per_period", scale = 6, precision = 19)
private BigDecimal maxNominalInterestRatePerPeriod;
@Column(name = "min_number_of_repayments")
private Integer minNumberOfRepayments;
@Column(name = "max_number_of_repayments")
private Integer maxNumberOfRepayments;
public static LoanProductMinMaxConstraints createFrom(final BigDecimal minPrincipal, final BigDecimal maxPrincipal,
final BigDecimal minNominalInterestRatePerPeriod, final BigDecimal maxNominalInterestRatePerPeriod,
final Integer minNumberOfRepayments, final Integer maxNumberOfRepayments) {
return new LoanProductMinMaxConstraints(minPrincipal, maxPrincipal, minNominalInterestRatePerPeriod,
maxNominalInterestRatePerPeriod, minNumberOfRepayments, maxNumberOfRepayments);
}
protected LoanProductMinMaxConstraints() {
//
}
public LoanProductMinMaxConstraints(final BigDecimal defaultMinPrincipal, final BigDecimal defaultMaxPrincipal,
final BigDecimal defaultMinNominalInterestRatePerPeriod, final BigDecimal defaultMaxNominalInterestRatePerPeriod,
final Integer defaultMinNumberOfRepayments, final Integer defaultMaxNumberOfRepayments) {
this.minPrincipal = defaultMinPrincipal;
this.maxPrincipal = defaultMaxPrincipal;
this.minNominalInterestRatePerPeriod = defaultMinNominalInterestRatePerPeriod;
this.maxNominalInterestRatePerPeriod = defaultMaxNominalInterestRatePerPeriod;
this.minNumberOfRepayments = defaultMinNumberOfRepayments;
this.maxNumberOfRepayments = defaultMaxNumberOfRepayments;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
final String localeAsInput = command.locale();
final String minPrincipalParamName = "minPrincipal";
if (command.isChangeInBigDecimalParameterNamedWithNullCheck(minPrincipalParamName, this.minPrincipal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minPrincipalParamName);
actualChanges.put(minPrincipalParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.minPrincipal = newValue;
}
final String maxPrincipalParamName = "maxPrincipal";
if (command.isChangeInBigDecimalParameterNamedWithNullCheck(maxPrincipalParamName, this.maxPrincipal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxPrincipalParamName);
actualChanges.put(maxPrincipalParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.maxPrincipal = newValue;
}
final String minNumberOfRepaymentsParamName = "minNumberOfRepayments";
if (command.isChangeInIntegerParameterNamed(minNumberOfRepaymentsParamName, this.minNumberOfRepayments)) {
final Integer newValue = command.integerValueOfParameterNamed(minNumberOfRepaymentsParamName);
actualChanges.put(minNumberOfRepaymentsParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.minNumberOfRepayments = newValue;
}
final String maxNumberOfRepaymentsParamName = "maxNumberOfRepayments";
if (command.isChangeInIntegerParameterNamed(maxNumberOfRepaymentsParamName, this.maxNumberOfRepayments)) {
final Integer newValue = command.integerValueOfParameterNamed(maxNumberOfRepaymentsParamName);
actualChanges.put(maxNumberOfRepaymentsParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.maxNumberOfRepayments = newValue;
}
final String minInterestRatePerPeriodParamName = "minInterestRatePerPeriod";
if (command.isChangeInBigDecimalParameterNamedWithNullCheck(minInterestRatePerPeriodParamName,
this.minNominalInterestRatePerPeriod)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minInterestRatePerPeriodParamName);
actualChanges.put(minInterestRatePerPeriodParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.minNominalInterestRatePerPeriod = newValue;
}
final String maxInterestRatePerPeriodParamName = "maxInterestRatePerPeriod";
if (command.isChangeInBigDecimalParameterNamedWithNullCheck(maxInterestRatePerPeriodParamName,
this.maxNominalInterestRatePerPeriod)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxInterestRatePerPeriodParamName);
actualChanges.put(maxInterestRatePerPeriodParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.maxNominalInterestRatePerPeriod = newValue;
}
return actualChanges;
}
public BigDecimal getMinPrincipal() {
return this.minPrincipal;
}
public BigDecimal getMaxPrincipal() {
return this.maxPrincipal;
}
public BigDecimal getMinNominalInterestRatePerPeriod() {
return this.minNominalInterestRatePerPeriod == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.minNominalInterestRatePerPeriod.stripTrailingZeros().toString()));
}
public BigDecimal getMaxNominalInterestRatePerPeriod() {
return this.maxNominalInterestRatePerPeriod == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.maxNominalInterestRatePerPeriod.stripTrailingZeros().toString()));
}
public Integer getMinNumberOfRepayments() {
return this.minNumberOfRepayments;
}
public Integer getMaxNumberOfRepayments() {
return this.maxNumberOfRepayments;
}
public void updateForFloatingInterestRates() {
this.minNominalInterestRatePerPeriod = null;
this.maxNominalInterestRatePerPeriod = null;
}
public void setMinNominalInterestRatePerPeriod(BigDecimal minNominalInterestRatePerPeriod) {
this.minNominalInterestRatePerPeriod = minNominalInterestRatePerPeriod;
}
public void setMaxNominalInterestRatePerPeriod(BigDecimal maxNominalInterestRatePerPeriod) {
this.maxNominalInterestRatePerPeriod = maxNominalInterestRatePerPeriod;
}
}

Some files were not shown because too many files have changed in this diff Show More