FINERACT-2042 Configurable CreditAllocations for Loan Product

This commit is contained in:
Peter Bagrij
2024-01-31 09:25:23 +01:00
committed by Peter Bagrij
parent 02cf6749af
commit cca520125b
19 changed files with 1206 additions and 39 deletions
@@ -18,9 +18,24 @@
*/ */
package org.apache.fineract.portfolio.loanproduct.domain; package org.apache.fineract.portfolio.loanproduct.domain;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
@RequiredArgsConstructor
@Getter
public enum AllocationType { public enum AllocationType {
PENALTY, //
FEE, // PENALTY("Penalty"), //
PRINCIPAL, // FEE("Fee"), //
INTEREST // PRINCIPAL("Principal"), //
INTEREST("Interest"); //
private final String humanReadableName;
public static List<EnumOptionData> getValuesAsEnumOptionDataList() {
return Arrays.stream(values()).map(v -> new EnumOptionData((long) (v.ordinal() + 1), v.name(), v.getHumanReadableName())).toList();
}
} }
@@ -0,0 +1,110 @@
/**
* 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 com.google.common.base.Enums;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class CreditAllocationsJsonParser {
public final CreditAllocationsValidator creditAllocationsValidator;
public List<LoanProductCreditAllocationRule> assembleLoanProductCreditAllocationRules(final JsonCommand command,
String loanTransactionProcessingStrategyCode) {
JsonArray creditAllocation = command.arrayOfParameterNamed("creditAllocation");
List<LoanProductCreditAllocationRule> productCreditAllocationRules = null;
if (creditAllocation != null) {
productCreditAllocationRules = creditAllocation.asList().stream().map(json -> {
Map<String, JsonElement> map = json.getAsJsonObject().asMap();
LoanProductCreditAllocationRule creditAllocationRule = new LoanProductCreditAllocationRule();
populateCreditAllocationRules(map, creditAllocationRule);
populateTransactionType(map, creditAllocationRule);
return creditAllocationRule;
}).toList();
}
creditAllocationsValidator.validate(productCreditAllocationRules, loanTransactionProcessingStrategyCode);
return productCreditAllocationRules;
}
private void populateCreditAllocationRules(Map<String, JsonElement> map, LoanProductCreditAllocationRule creditAllocationRule) {
JsonArray creditAllocationOrder = asJsonArrayOrNull(map.get("creditAllocationOrder"));
if (creditAllocationOrder != null) {
creditAllocationRule.setAllocationTypes(getAllocationTypes(creditAllocationOrder));
}
}
private void populateTransactionType(Map<String, JsonElement> map, LoanProductCreditAllocationRule creditAllocationRule) {
String transactionType = asStringOrNull(map.get("transactionType"));
if (transactionType != null) {
creditAllocationRule.setTransactionType(Enums.getIfPresent(CreditAllocationTransactionType.class, transactionType).orNull());
}
}
@NotNull
private List<AllocationType> getAllocationTypes(JsonArray allocationOrder) {
if (allocationOrder != null) {
List<Pair<Integer, AllocationType>> parsedListWithOrder = allocationOrder.asList().stream().map(json -> {
Map<String, JsonElement> map = json.getAsJsonObject().asMap();
AllocationType allocationType = null;
String creditAllocationRule = asStringOrNull(map.get("creditAllocationRule"));
if (creditAllocationRule != null) {
allocationType = Enums.getIfPresent(AllocationType.class, creditAllocationRule).orNull();
}
return Pair.of(asIntegerOrNull(map.get("order")), allocationType);
}).sorted(Comparator.comparing(Pair::getLeft)).toList();
creditAllocationsValidator.validatePairOfOrderAndCreditAllocationType(parsedListWithOrder);
return parsedListWithOrder.stream().map(Pair::getRight).toList();
} else {
return List.of();
}
}
private Integer asIntegerOrNull(JsonElement element) {
if (!element.isJsonNull()) {
return element.getAsInt();
}
return null;
}
private String asStringOrNull(JsonElement element) {
if (!element.isJsonNull()) {
return element.getAsString();
}
return null;
}
private JsonArray asJsonArrayOrNull(JsonElement element) {
if (!element.isJsonNull()) {
return element.getAsJsonArray();
}
return null;
}
}
@@ -0,0 +1,96 @@
/**
* 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.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.springframework.stereotype.Service;
@Service
public class CreditAllocationsValidator {
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
public void validate(List<LoanProductCreditAllocationRule> rules, String code) {
if (isAdvancedPaymentStrategy(code)) {
if (hasDuplicateTransactionTypes(rules)) {
raiseValidationError("advanced-payment-strategy-with-duplicate-credit-allocation",
"The same transaction type must be provided only once");
}
if (rules != null) {
for (LoanProductCreditAllocationRule rule : rules) {
validateAllocationRule(rule);
}
}
} else {
if (hasLoanProductCreditAllocationRule(rules)) {
raiseValidationError("credit_allocation.must.not.be.provided.when.allocation.strategy.is.not.advanced-payment-strategy",
"In case '" + code + "' payment strategy, creditAllocation must not be provided");
}
}
}
public void validatePairOfOrderAndCreditAllocationType(List<Pair<Integer, AllocationType>> rules) {
if (rules.size() != 4) {
raiseValidationError("advanced-payment-strategy.each_credit_allocation_order.must.contain.4.entries",
"Each provided credit allocation must contain exactly 4 allocation rules, but " + rules.size() + " were provided");
}
List<AllocationType> deduped = rules.stream().map(Pair::getRight).distinct().toList();
if (deduped.size() != 4) {
raiseValidationError("advanced-payment-strategy.must.not.have.duplicate.credit.allocation.rule",
"The list of provided credit allocation rules must not contain any duplicates");
}
if (!Arrays.equals(IntStream.rangeClosed(1, 4).boxed().toArray(), rules.stream().map(Pair::getLeft).toArray())) {
raiseValidationError("advanced-payment-strategy.invalid.order", "The provided orders must be between 1 and 4");
}
}
private boolean hasDuplicateTransactionTypes(List<LoanProductCreditAllocationRule> rules) {
return rules != null
&& rules.stream().map(LoanProductCreditAllocationRule::getTransactionType).distinct().toList().size() != rules.size();
}
private void validateAllocationRule(LoanProductCreditAllocationRule rule) {
if (rule.getTransactionType() == null) {
raiseValidationError("advanced-payment-strategy.with.not.valid.transaction.type",
"Credit allocation was provided with a not valid transaction type");
}
}
private boolean isAdvancedPaymentStrategy(String code) {
return ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(code);
}
private boolean hasLoanProductCreditAllocationRule(List<LoanProductCreditAllocationRule> rules) {
return rules != null && rules.size() > 0;
}
private void raiseValidationError(String globalisationMessageCode, String msg) {
throw new PlatformApiDataValidationException(List.of(ApiParameterError.generalError(globalisationMessageCode, msg)));
}
}
@@ -675,6 +675,13 @@ public class LoanProduct extends AbstractPersistableCustom {
} }
} }
this.creditAllocationRules = creditAllocationRules;
if (this.creditAllocationRules != null) {
for (LoanProductCreditAllocationRule loanProductCreditAllocationRule : this.creditAllocationRules) {
loanProductCreditAllocationRule.setLoanProduct(this);
}
}
this.name = name.trim(); this.name = name.trim();
this.shortName = shortName.trim(); this.shortName = shortName.trim();
if (StringUtils.isNotBlank(description)) { if (StringUtils.isNotBlank(description)) {
@@ -773,6 +780,13 @@ public class LoanProduct extends AbstractPersistableCustom {
"In case '" + transactionProcessingStrategyCode + "' payment strategy, payment_allocation must not be provided"); "In case '" + transactionProcessingStrategyCode + "' payment strategy, payment_allocation must not be provided");
} }
if (this.creditAllocationRules != null && creditAllocationRules.size() > 0
&& !transactionProcessingStrategyCode.equals("advanced-payment-allocation-strategy")) {
throw new LoanProductGeneralRuleException(
"creditAllocation.must.not.be.provided.when.allocation.strategy.is.not.advanced-payment-strategy",
"In case '" + transactionProcessingStrategyCode + "' payment strategy, creditAllocation must not be provided");
}
if (this.disallowExpectedDisbursements) { if (this.disallowExpectedDisbursements) {
if (!this.isMultiDisburseLoan()) { if (!this.isMultiDisburseLoan()) {
throw new LoanProductGeneralRuleException("allowMultipleDisbursals.not.set.disallowExpectedDisbursements.cant.be.set", throw new LoanProductGeneralRuleException("allowMultipleDisbursals.not.set.disallowExpectedDisbursements.cant.be.set",
@@ -906,6 +920,10 @@ public class LoanProduct extends AbstractPersistableCustom {
return this.paymentAllocationRules; return this.paymentAllocationRules;
} }
public List<LoanProductCreditAllocationRule> getCreditAllocationRules() {
return this.creditAllocationRules;
}
public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) { public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) {
this.loanConfigurableAttributes = loanConfigurableAttributes; this.loanConfigurableAttributes = loanConfigurableAttributes;
} }
@@ -999,6 +1017,14 @@ public class LoanProduct extends AbstractPersistableCustom {
} }
} }
final String creditAllocationParamName = "creditAllocation";
if (command.hasParameter(creditAllocationParamName)) {
final JsonArray jsonArray = command.arrayOfParameterNamed(creditAllocationParamName);
if (jsonArray != null) {
actualChanges.put(creditAllocationParamName, command.jsonFragment(creditAllocationParamName));
}
}
final String chargesParamName = "charges"; final String chargesParamName = "charges";
if (command.hasParameter(chargesParamName)) { if (command.hasParameter(chargesParamName)) {
final JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName); final JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
@@ -0,0 +1,125 @@
/**
* 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 static org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationsValidator.ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
import static org.mockito.Mockito.times;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonParser;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class CreditAllocationsJsonParserTest {
@InjectMocks
private CreditAllocationsJsonParser creditAllocationsJsonParser;
@Mock
private CreditAllocationsValidator creditAllocationsValidator;
private FromJsonHelper fromJsonHelper = new FromJsonHelper();
@Test
public void testEmptyJson() throws JsonProcessingException {
Map<String, Object> map = new HashMap<>();
JsonCommand command = createJsonCommand(map);
// when
List<LoanProductCreditAllocationRule> loanProductCreditAllocationRules = creditAllocationsJsonParser
.assembleLoanProductCreditAllocationRules(command, "other-strategy");
// then
Assertions.assertNull(loanProductCreditAllocationRules);
Mockito.verify(creditAllocationsValidator, times(1)).validate(null, "other-strategy");
}
@Test
public void testParseSingleChargebackAllocation() throws JsonProcessingException {
// given
Map<String, Object> map = new HashMap<>();
List<Map<String, Object>> creditAllocations = new ArrayList<>();
map.put("creditAllocation", creditAllocations);
List<String> allocationRule = EnumSet.allOf(AllocationType.class).stream().map(Enum::name).toList();
creditAllocations.add(createCreditAllocationEntry("CHARGEBACK", allocationRule));
JsonCommand command = createJsonCommand(map);
// when
List<LoanProductCreditAllocationRule> creditAllocationRules = creditAllocationsJsonParser
.assembleLoanProductCreditAllocationRules(command, ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
// then
Assertions.assertEquals(1, creditAllocationRules.size());
Assertions.assertEquals(CreditAllocationTransactionType.CHARGEBACK, creditAllocationRules.get(0).getTransactionType());
Assertions.assertEquals(4, creditAllocationRules.get(0).getAllocationTypes().size());
Assertions.assertEquals(EnumSet.allOf(AllocationType.class).stream().toList(), creditAllocationRules.get(0).getAllocationTypes());
Mockito.verify(creditAllocationsValidator, times(1)).validate(creditAllocationRules, ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
Mockito.verify(creditAllocationsValidator, times(1)).validatePairOfOrderAndCreditAllocationType(createAllocationTypeList());
Mockito.verifyNoMoreInteractions(creditAllocationsValidator);
}
private static List<Pair<Integer, AllocationType>> createAllocationTypeList() {
AtomicInteger i = new AtomicInteger(1);
List<Pair<Integer, AllocationType>> list = EnumSet.allOf(AllocationType.class).stream().map(p -> Pair.of(i.getAndIncrement(), p))
.toList();
return list;
}
public Map<String, Object> createCreditAllocationEntry(String transactionType, List<String> orderedRules) {
Map<String, Object> map = new HashMap<>();
map.put("transactionType", transactionType);
List<Map<String, Object>> creditAllocationOrder = new ArrayList<>();
map.put("creditAllocationOrder", creditAllocationOrder);
for (int i = 0; i < orderedRules.size(); i++) {
HashMap<String, Object> entry = new HashMap<>();
entry.put("creditAllocationRule", orderedRules.get(i));
entry.put("order", i + 1);
creditAllocationOrder.add(entry);
}
return map;
}
@NotNull
private JsonCommand createJsonCommand(Map<String, Object> jsonMap) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonMap);
JsonCommand command = JsonCommand.from(json, JsonParser.parseString(json), fromJsonHelper, null, 1L, 2L, 3L, 4L, null, null, null,
null, null, null, null, null);
return command;
}
}
@@ -0,0 +1,132 @@
/**
* 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 static org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsValidator.ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
import static org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType.CHARGEBACK;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
public class CreditAllocationsValidatorTest {
private CreditAllocationsValidator underTest = new CreditAllocationsValidator();
@Test
public void testCreditAllocationsHasNoError() {
underTest.validatePairOfOrderAndCreditAllocationType(createCreditAllocationTypeList());
}
@Test
public void testCreditAllocationsValidationThrowsErrorWhenLessElement() {
PlatformApiDataValidationException validationException = assertThrows(PlatformApiDataValidationException.class,
() -> underTest.validatePairOfOrderAndCreditAllocationType(createCreditAllocationTypeList().subList(0, 3)));
assertPlatformException("Each provided credit allocation must contain exactly 4 allocation rules, but 3 were provided",
"advanced-payment-strategy.each_credit_allocation_order.must.contain.4.entries", validationException);
}
@Test
public void testCreditAllocationsValidationThrowsErrorWhenWithDuplicate() {
ArrayList<Pair<Integer, AllocationType>> pairs = new ArrayList<>(createCreditAllocationTypeList().subList(0, 3));
pairs.add(pairs.get(2));
PlatformApiDataValidationException validationException = assertThrows(PlatformApiDataValidationException.class,
() -> underTest.validatePairOfOrderAndCreditAllocationType(pairs));
assertPlatformException("The list of provided credit allocation rules must not contain any duplicates",
"advanced-payment-strategy.must.not.have.duplicate.credit.allocation.rule", validationException);
}
@Test
public void testCreditAllocationsValidationThrowsErrorWhenOrderIsNotInRange() {
List<Pair<Integer, AllocationType>> pairs = createCreditAllocationTypeList().stream()
.map(p -> Pair.of(p.getLeft() + 1, p.getRight())).toList();
PlatformApiDataValidationException validationException = assertThrows(PlatformApiDataValidationException.class,
() -> underTest.validatePairOfOrderAndCreditAllocationType(pairs));
assertPlatformException("The provided orders must be between 1 and 4", "advanced-payment-strategy.invalid.order",
validationException);
}
@Test
public void testValidateThrowsErrorWhenPaymentAllocationProvidedWithOtherStrategy() {
assertPlatformValidationException("In case 'some-other-strategy' payment strategy, creditAllocation must not be provided",
"credit_allocation.must.not.be.provided.when.allocation.strategy.is.not.advanced-payment-strategy",
() -> underTest.validate(List.of(createLoanProductCreditAllocationRule1()), "some-other-strategy"));
}
@Test
public void testValidateThrowsErrorWhenTransactionTypeEmpty() {
LoanProductCreditAllocationRule lpcar = createLoanProductCreditAllocationRule1();
lpcar.setTransactionType(null);
assertPlatformValidationException("Credit allocation was provided with a not valid transaction type",
"advanced-payment-strategy.with.not.valid.transaction.type",
() -> underTest.validate(List.of(lpcar), ADVANCED_PAYMENT_ALLOCATION_STRATEGY));
}
@Test
public void testValidateNoError() {
underTest.validate(List.of(createLoanProductCreditAllocationRule1()), ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
}
@Test
public void testValidateCreditAllocationIsOptional() {
underTest.validate(List.of(), ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
}
@Test
public void testValidateThrowsErrorWhenDuplicate() {
assertPlatformValidationException("The same transaction type must be provided only once",
"advanced-payment-strategy-with-duplicate-credit-allocation",
() -> underTest.validate(List.of(createLoanProductCreditAllocationRule1(), createLoanProductCreditAllocationRule1()),
ADVANCED_PAYMENT_ALLOCATION_STRATEGY));
}
@NotNull
private static List<Pair<Integer, AllocationType>> createCreditAllocationTypeList() {
AtomicInteger i = new AtomicInteger(1);
return EnumSet.allOf(AllocationType.class).stream().map(p -> Pair.of(i.getAndIncrement(), p)).toList();
}
@NotNull
private static LoanProductCreditAllocationRule createLoanProductCreditAllocationRule1() {
LoanProductCreditAllocationRule lpcr1 = new LoanProductCreditAllocationRule();
lpcr1.setTransactionType(CHARGEBACK);
lpcr1.setAllocationTypes(EnumSet.allOf(AllocationType.class).stream().toList());
return lpcr1;
}
private void assertPlatformValidationException(String message, String code, Executable executable) {
PlatformApiDataValidationException validationException = assertThrows(PlatformApiDataValidationException.class, executable);
assertPlatformException(message, code, validationException);
}
private void assertPlatformException(String expectedMessage, String expectedCode,
PlatformApiDataValidationException platformApiDataValidationException) {
Assertions.assertEquals(expectedMessage, platformApiDataValidationException.getErrors().get(0).getDefaultUserMessage());
Assertions.assertEquals(expectedCode, platformApiDataValidationException.getErrors().get(0).getUserMessageGlobalisationCode());
}
}
@@ -81,6 +81,8 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule; import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
@@ -417,6 +419,8 @@ public class LoanProductsApiResource {
final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules = FutureInstallmentAllocationRule final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules = FutureInstallmentAllocationRule
.getValuesAsEnumOptionDataList(); .getValuesAsEnumOptionDataList();
final List<EnumOptionData> advancedPaymentAllocationTypes = PaymentAllocationType.getValuesAsEnumOptionDataList(); final List<EnumOptionData> advancedPaymentAllocationTypes = PaymentAllocationType.getValuesAsEnumOptionDataList();
final List<EnumOptionData> creditAllocationTransactionTypes = CreditAllocationTransactionType.getValuesAsEnumOptionDataList();
final List<EnumOptionData> creditAllocationAllocationTypes = AllocationType.getValuesAsEnumOptionDataList();
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions, return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -427,7 +431,8 @@ public class LoanProductsApiResource {
interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, delinquencyBucketOptions, repaymentStartDateTypeOptions, interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, delinquencyBucketOptions, repaymentStartDateTypeOptions,
advancedPaymentAllocationTransactionTypes, advancedPaymentAllocationFutureInstallmentAllocationRules, advancedPaymentAllocationTransactionTypes, advancedPaymentAllocationFutureInstallmentAllocationRules,
advancedPaymentAllocationTypes, LoanScheduleType.getValuesAsEnumOptionDataList(), advancedPaymentAllocationTypes, LoanScheduleType.getValuesAsEnumOptionDataList(),
LoanScheduleProcessingType.getValuesAsEnumOptionDataList()); LoanScheduleProcessingType.getValuesAsEnumOptionDataList(), creditAllocationTransactionTypes,
creditAllocationAllocationTypes);
} }
} }
@@ -114,6 +114,7 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "mifos-standard-strategy") @Schema(example = "mifos-standard-strategy")
public String transactionProcessingStrategyCode; public String transactionProcessingStrategyCode;
public List<AdvancedPaymentData> paymentAllocation; public List<AdvancedPaymentData> paymentAllocation;
public List<CreditAllocationData> creditAllocation;
@Schema(example = "false") @Schema(example = "false")
public Boolean isLinkedToFloatingInterestRates; public Boolean isLinkedToFloatingInterestRates;
@Schema(example = "false") @Schema(example = "false")
@@ -1042,6 +1043,10 @@ final class LoanProductsApiResourceSwagger {
public List<EnumOptionData> advancedPaymentAllocationTypes; public List<EnumOptionData> advancedPaymentAllocationTypes;
public List<EnumOptionData> loanScheduleTypeOptions; public List<EnumOptionData> loanScheduleTypeOptions;
public List<EnumOptionData> loanScheduleProcessingTypeOptions; public List<EnumOptionData> loanScheduleProcessingTypeOptions;
public List<EnumOptionData> creditAllocationAllocationTypes;
public List<EnumOptionData> creditAllocationTransactionTypes;
} }
@Schema(description = "GetLoanProductsProductIdResponse") @Schema(description = "GetLoanProductsProductIdResponse")
@@ -1222,6 +1227,8 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "[]") @Schema(example = "[]")
public List<AdvancedPaymentData> paymentAllocation; public List<AdvancedPaymentData> paymentAllocation;
@Schema(example = "[]") @Schema(example = "[]")
public List<CreditAllocationData> creditAllocation;
@Schema(example = "[]")
public List<Integer> charges; public List<Integer> charges;
public Set<GetLoanProductsPrincipalVariationsForBorrowerCycle> productsPrincipalVariationsForBorrowerCycle; public Set<GetLoanProductsPrincipalVariationsForBorrowerCycle> productsPrincipalVariationsForBorrowerCycle;
@Schema(example = "[]") @Schema(example = "[]")
@@ -1343,7 +1350,10 @@ final class LoanProductsApiResourceSwagger {
public Integer interestCalculationPeriodType; public Integer interestCalculationPeriodType;
@Schema(example = "mifos-standard-strategy") @Schema(example = "mifos-standard-strategy")
public String transactionProcessingStrategyCode; public String transactionProcessingStrategyCode;
@Schema(example = "[]")
public List<AdvancedPaymentData> paymentAllocation; public List<AdvancedPaymentData> paymentAllocation;
@Schema(example = "[]")
public List<CreditAllocationData> creditAllocation;
@Schema(example = "false") @Schema(example = "false")
public Boolean isLinkedToFloatingInterestRates; public Boolean isLinkedToFloatingInterestRates;
@Schema(example = "false") @Schema(example = "false")
@@ -1564,6 +1574,23 @@ final class LoanProductsApiResourceSwagger {
public Integer order; public Integer order;
} }
public static final class CreditAllocationData {
@Schema(example = "Chargeback")
public String transactionType;
@Schema(example = "[]")
public List<CreditAllocationOrder> creditAllocationOrder;
}
public static class CreditAllocationOrder {
@Schema(example = "PENALTY")
public String creditAllocationRule;
@Schema(example = "1")
public Integer order;
}
@Schema(description = "PutLoanProductsProductIdResponse") @Schema(description = "PutLoanProductsProductIdResponse")
public static final class PutLoanProductsProductIdResponse { public static final class PutLoanProductsProductIdResponse {
@@ -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.loanproduct.data;
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class CreditAllocationData implements Serializable {
private final String transactionType;
private final List<CreditAllocationOrder> creditAllocationOrder;
@Getter
@AllArgsConstructor
public static class CreditAllocationOrder implements Serializable {
private final String creditAllocationRule;
private final Integer order;
}
}
@@ -46,7 +46,9 @@ import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData; import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule; import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
@@ -116,6 +118,7 @@ public class LoanProductData implements Serializable {
private final String transactionProcessingStrategyCode; private final String transactionProcessingStrategyCode;
private final String transactionProcessingStrategyName; private final String transactionProcessingStrategyName;
private final Collection<AdvancedPaymentData> paymentAllocation; private final Collection<AdvancedPaymentData> paymentAllocation;
private final Collection<CreditAllocationData> creditAllocation;
private final Integer graceOnPrincipalPayment; private final Integer graceOnPrincipalPayment;
private final Integer recurringMoratoriumOnPrincipalPeriods; private final Integer recurringMoratoriumOnPrincipalPeriods;
private final Integer graceOnInterestPayment; private final Integer graceOnInterestPayment;
@@ -175,6 +178,10 @@ public class LoanProductData implements Serializable {
private final List<EnumOptionData> advancedPaymentAllocationTransactionTypes; private final List<EnumOptionData> advancedPaymentAllocationTransactionTypes;
private final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules; private final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules;
private final List<EnumOptionData> advancedPaymentAllocationTypes; private final List<EnumOptionData> advancedPaymentAllocationTypes;
private final List<EnumOptionData> creditAllocationTransactionTypes;
private final List<EnumOptionData> creditAllocationAllocationTypes;
private final List<EnumOptionData> loanScheduleTypeOptions; private final List<EnumOptionData> loanScheduleTypeOptions;
private final List<EnumOptionData> loanScheduleProcessingTypeOptions; private final List<EnumOptionData> loanScheduleProcessingTypeOptions;
@@ -306,6 +313,7 @@ public class LoanProductData implements Serializable {
final boolean enableDownPayment = false; final boolean enableDownPayment = false;
final BigDecimal disbursedAmountPercentageDownPayment = null; final BigDecimal disbursedAmountPercentageDownPayment = null;
final Collection<AdvancedPaymentData> paymentAllocation = null; final Collection<AdvancedPaymentData> paymentAllocation = null;
final Collection<CreditAllocationData> creditAllocation = null;
final boolean enableAutoRepaymentForDownPayment = false; final boolean enableAutoRepaymentForDownPayment = false;
final EnumOptionData repaymentStartDateType = null; final EnumOptionData repaymentStartDateType = null;
final boolean enableInstallmentLevelDelinquency = false; final boolean enableInstallmentLevelDelinquency = false;
@@ -332,7 +340,8 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType); paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType);
} }
@@ -426,6 +435,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null; final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false; final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null; final Collection<AdvancedPaymentData> paymentAllocation = null;
final Collection<CreditAllocationData> creditAllocation = null;
final EnumOptionData repaymentStartDateType = null; final EnumOptionData repaymentStartDateType = null;
final boolean enableInstallmentLevelDelinquency = false; final boolean enableInstallmentLevelDelinquency = false;
final EnumOptionData loanScheduleType = null; final EnumOptionData loanScheduleType = null;
@@ -449,7 +459,8 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType); paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType);
} }
@@ -550,6 +561,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null; final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false; final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null; final Collection<AdvancedPaymentData> paymentAllocation = null;
final Collection<CreditAllocationData> creditAllocation = null;
final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE); final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE);
final boolean enableInstallmentLevelDelinquency = false; final boolean enableInstallmentLevelDelinquency = false;
final EnumOptionData loanScheduleType = LoanScheduleType.CUMULATIVE.asEnumOptionData(); final EnumOptionData loanScheduleType = LoanScheduleType.CUMULATIVE.asEnumOptionData();
@@ -573,7 +585,8 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType); paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType);
} }
@@ -668,6 +681,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null; final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false; final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null; final Collection<AdvancedPaymentData> paymentAllocation = null;
final Collection<CreditAllocationData> creditAllocationData = null;
final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE); final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE);
final boolean enableInstallmentLevelDelinquency = false; final boolean enableInstallmentLevelDelinquency = false;
final EnumOptionData loanScheduleType = null; final EnumOptionData loanScheduleType = null;
@@ -691,7 +705,8 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType); paymentAllocation, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType);
} }
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings, public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -739,9 +754,9 @@ public class LoanProductData implements Serializable {
final DelinquencyBucketData delinquencyBucket, final Integer dueDaysForRepaymentEvent, final DelinquencyBucketData delinquencyBucket, final Integer dueDaysForRepaymentEvent,
final Integer overDueDaysForRepaymentEvent, final boolean enableDownPayment, final Integer overDueDaysForRepaymentEvent, final boolean enableDownPayment,
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment,
final Collection<AdvancedPaymentData> paymentAllocation, final EnumOptionData repaymentStartDateType, final Collection<AdvancedPaymentData> paymentAllocation, final Collection<CreditAllocationData> creditAllocation,
final boolean enableInstallmentLevelDelinquency, final EnumOptionData loanScheduleType, final EnumOptionData repaymentStartDateType, final boolean enableInstallmentLevelDelinquency,
final EnumOptionData loanScheduleProcessingType) { final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -862,12 +877,15 @@ public class LoanProductData implements Serializable {
this.enableDownPayment = enableDownPayment; this.enableDownPayment = enableDownPayment;
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
this.paymentAllocation = paymentAllocation; this.paymentAllocation = paymentAllocation;
this.creditAllocation = creditAllocation;
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment; this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
this.repaymentStartDateType = repaymentStartDateType; this.repaymentStartDateType = repaymentStartDateType;
this.repaymentStartDateTypeOptions = null; this.repaymentStartDateTypeOptions = null;
this.advancedPaymentAllocationTransactionTypes = PaymentAllocationTransactionType.getValuesAsEnumOptionDataList(); this.advancedPaymentAllocationTransactionTypes = PaymentAllocationTransactionType.getValuesAsEnumOptionDataList();
this.advancedPaymentAllocationFutureInstallmentAllocationRules = FutureInstallmentAllocationRule.getValuesAsEnumOptionDataList(); this.advancedPaymentAllocationFutureInstallmentAllocationRules = FutureInstallmentAllocationRule.getValuesAsEnumOptionDataList();
this.advancedPaymentAllocationTypes = PaymentAllocationType.getValuesAsEnumOptionDataList(); this.advancedPaymentAllocationTypes = PaymentAllocationType.getValuesAsEnumOptionDataList();
this.creditAllocationTransactionTypes = CreditAllocationTransactionType.getValuesAsEnumOptionDataList();
this.creditAllocationAllocationTypes = AllocationType.getValuesAsEnumOptionDataList();
this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency;
this.loanScheduleType = loanScheduleType; this.loanScheduleType = loanScheduleType;
this.loanScheduleProcessingType = loanScheduleProcessingType; this.loanScheduleProcessingType = loanScheduleProcessingType;
@@ -893,7 +911,9 @@ public class LoanProductData implements Serializable {
final List<EnumOptionData> advancedPaymentAllocationTransactionTypes, final List<EnumOptionData> advancedPaymentAllocationTransactionTypes,
final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules, final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules,
final List<EnumOptionData> advancedPaymentAllocationTypes, final List<EnumOptionData> loanScheduleTypeOptions, final List<EnumOptionData> advancedPaymentAllocationTypes, final List<EnumOptionData> loanScheduleTypeOptions,
final List<EnumOptionData> loanScheduleProcessingTypeOptions) { final List<EnumOptionData> loanScheduleProcessingTypeOptions, final List<EnumOptionData> creditAllocationTransactionTypes,
final List<EnumOptionData> creditAllocationAllocationTypes) {
this.id = productData.id; this.id = productData.id;
this.name = productData.name; this.name = productData.name;
this.shortName = productData.shortName; this.shortName = productData.shortName;
@@ -1032,11 +1052,14 @@ public class LoanProductData implements Serializable {
this.disbursedAmountPercentageForDownPayment = productData.disbursedAmountPercentageForDownPayment; this.disbursedAmountPercentageForDownPayment = productData.disbursedAmountPercentageForDownPayment;
this.enableAutoRepaymentForDownPayment = productData.enableAutoRepaymentForDownPayment; this.enableAutoRepaymentForDownPayment = productData.enableAutoRepaymentForDownPayment;
this.paymentAllocation = productData.paymentAllocation; this.paymentAllocation = productData.paymentAllocation;
this.creditAllocation = productData.creditAllocation;
this.repaymentStartDateType = productData.repaymentStartDateType; this.repaymentStartDateType = productData.repaymentStartDateType;
this.repaymentStartDateTypeOptions = repaymentStartDateTypeOptions; this.repaymentStartDateTypeOptions = repaymentStartDateTypeOptions;
this.advancedPaymentAllocationTransactionTypes = advancedPaymentAllocationTransactionTypes; this.advancedPaymentAllocationTransactionTypes = advancedPaymentAllocationTransactionTypes;
this.advancedPaymentAllocationFutureInstallmentAllocationRules = advancedPaymentAllocationFutureInstallmentAllocationRules; this.advancedPaymentAllocationFutureInstallmentAllocationRules = advancedPaymentAllocationFutureInstallmentAllocationRules;
this.advancedPaymentAllocationTypes = advancedPaymentAllocationTypes; this.advancedPaymentAllocationTypes = advancedPaymentAllocationTypes;
this.creditAllocationAllocationTypes = creditAllocationAllocationTypes;
this.creditAllocationTransactionTypes = creditAllocationTransactionTypes;
this.enableInstallmentLevelDelinquency = productData.enableInstallmentLevelDelinquency; this.enableInstallmentLevelDelinquency = productData.enableInstallmentLevelDelinquency;
this.loanScheduleType = productData.getLoanScheduleType(); this.loanScheduleType = productData.getLoanScheduleType();
this.loanScheduleProcessingType = productData.getLoanScheduleProcessingType(); this.loanScheduleProcessingType = productData.getLoanScheduleProcessingType();
@@ -91,6 +91,7 @@ public final class LoanProductDataValidator {
public static final String IN_ARREARS_TOLERANCE = "inArrearsTolerance"; public static final String IN_ARREARS_TOLERANCE = "inArrearsTolerance";
public static final String TRANSACTION_PROCESSING_STRATEGY_CODE = "transactionProcessingStrategyCode"; public static final String TRANSACTION_PROCESSING_STRATEGY_CODE = "transactionProcessingStrategyCode";
public static final String ADVANCED_PAYMENT_ALLOCATIONS = "paymentAllocation"; public static final String ADVANCED_PAYMENT_ALLOCATIONS = "paymentAllocation";
public static final String CREDIT_ALLOCATIONS = "creditAllocation";
public static final String GRACE_ON_PRINCIPAL_PAYMENT = "graceOnPrincipalPayment"; public static final String GRACE_ON_PRINCIPAL_PAYMENT = "graceOnPrincipalPayment";
public static final String GRACE_ON_INTEREST_PAYMENT = "graceOnInterestPayment"; public static final String GRACE_ON_INTEREST_PAYMENT = "graceOnInterestPayment";
public static final String GRACE_ON_INTEREST_CHARGED = "graceOnInterestCharged"; public static final String GRACE_ON_INTEREST_CHARGED = "graceOnInterestCharged";
@@ -114,17 +115,18 @@ public final class LoanProductDataValidator {
NUMBER_OF_REPAYMENTS, MIN_NUMBER_OF_REPAYMENTS, MAX_NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY_TYPE, INTEREST_RATE_PER_PERIOD, NUMBER_OF_REPAYMENTS, MIN_NUMBER_OF_REPAYMENTS, MAX_NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY_TYPE, INTEREST_RATE_PER_PERIOD,
MIN_INTEREST_RATE_PER_PERIOD, MAX_INTEREST_RATE_PER_PERIOD, INTEREST_RATE_FREQUENCY_TYPE, AMORTIZATION_TYPE, INTEREST_TYPE, MIN_INTEREST_RATE_PER_PERIOD, MAX_INTEREST_RATE_PER_PERIOD, INTEREST_RATE_FREQUENCY_TYPE, AMORTIZATION_TYPE, INTEREST_TYPE,
INTEREST_CALCULATION_PERIOD_TYPE, LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, INTEREST_CALCULATION_PERIOD_TYPE, LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
IN_ARREARS_TOLERANCE, TRANSACTION_PROCESSING_STRATEGY_CODE, ADVANCED_PAYMENT_ALLOCATIONS, GRACE_ON_PRINCIPAL_PAYMENT, IN_ARREARS_TOLERANCE, TRANSACTION_PROCESSING_STRATEGY_CODE, ADVANCED_PAYMENT_ALLOCATIONS, CREDIT_ALLOCATIONS,
"recurringMoratoriumOnPrincipalPeriods", GRACE_ON_INTEREST_PAYMENT, GRACE_ON_INTEREST_CHARGED, "charges", ACCOUNTING_RULE, GRACE_ON_PRINCIPAL_PAYMENT, "recurringMoratoriumOnPrincipalPeriods", GRACE_ON_INTEREST_PAYMENT, GRACE_ON_INTEREST_CHARGED,
INCLUDE_IN_BORROWER_CYCLE, "startDate", "closeDate", "externalId", IS_LINKED_TO_FLOATING_INTEREST_RATES, FLOATING_RATES_ID, "charges", ACCOUNTING_RULE, INCLUDE_IN_BORROWER_CYCLE, "startDate", "closeDate", "externalId",
INTEREST_RATE_DIFFERENTIAL, MIN_DIFFERENTIAL_LENDING_RATE, DEFAULT_DIFFERENTIAL_LENDING_RATE, MAX_DIFFERENTIAL_LENDING_RATE, IS_LINKED_TO_FLOATING_INTEREST_RATES, FLOATING_RATES_ID, INTEREST_RATE_DIFFERENTIAL, MIN_DIFFERENTIAL_LENDING_RATE,
IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED, "syncExpectedWithDisbursementDate", DEFAULT_DIFFERENTIAL_LENDING_RATE, MAX_DIFFERENTIAL_LENDING_RATE, IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED,
LoanProductAccountingParams.FEES_RECEIVABLE.getValue(), LoanProductAccountingParams.FUND_SOURCE.getValue(), "syncExpectedWithDisbursementDate", LoanProductAccountingParams.FEES_RECEIVABLE.getValue(),
LoanProductAccountingParams.INCOME_FROM_FEES.getValue(), LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), LoanProductAccountingParams.FUND_SOURCE.getValue(), LoanProductAccountingParams.INCOME_FROM_FEES.getValue(),
LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(),
LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), LoanProductAccountingParams.OVERPAYMENT.getValue(), LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(),
LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), LoanProductAccountingParams.OVERPAYMENT.getValue(), LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), LoanProductAccountingParams.GOODWILL_CREDIT.getValue(),
LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(),
LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(),
LoanProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
@@ -0,0 +1,92 @@
/**
* 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.service;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule;
public class LoanProductCreditAllocationRuleMerger {
public boolean updateCreditAllocationRules(LoanProduct loanProduct,
final List<LoanProductCreditAllocationRule> newLoanProductCreditAllocationRules) {
if (newLoanProductCreditAllocationRules == null) {
return false;
}
boolean updated = false;
Map<CreditAllocationTransactionType, LoanProductCreditAllocationRule> originalItems = loanProduct.getCreditAllocationRules()
.stream().collect(Collectors.toMap(LoanProductCreditAllocationRule::getTransactionType, Function.identity()));
Map<CreditAllocationTransactionType, LoanProductCreditAllocationRule> newItems = newLoanProductCreditAllocationRules.stream()
.collect(Collectors.toMap(LoanProductCreditAllocationRule::getTransactionType, Function.identity()));
// elements to be deleted
Set<CreditAllocationTransactionType> existing = new HashSet<>(originalItems.keySet());
Set<CreditAllocationTransactionType> newSet = new HashSet<>(newItems.keySet());
existing.removeAll(newSet);
if (existing.size() > 0) {
updated = true;
existing.forEach(type -> {
loanProduct.getCreditAllocationRules().remove(originalItems.get(type));
});
}
// elements to be added
existing = new HashSet<>(originalItems.keySet());
newSet = new HashSet<>(newItems.keySet());
newSet.removeAll(existing);
if (newSet.size() > 0) {
updated = true;
newSet.forEach(type -> {
loanProduct.getCreditAllocationRules().add(newItems.get(type));
});
}
// elements to be merged
existing = new HashSet<>(originalItems.keySet());
newSet = new HashSet<>(newItems.keySet());
existing.retainAll(newSet);
for (CreditAllocationTransactionType type : existing) {
boolean result = mergeLoanProductCreditAllocationRule(originalItems.get(type), newItems.get(type));
if (result) {
updated = true;
}
}
return updated;
}
private boolean mergeLoanProductCreditAllocationRule(LoanProductCreditAllocationRule into, LoanProductCreditAllocationRule newElement) {
boolean changed = false;
if (!Objects.equals(into.getAllocationTypes(), newElement.getAllocationTypes())) {
into.setAllocationTypes(newElement.getAllocationTypes());
changed = true;
}
return changed;
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanproduct.service;
import java.util.Collection; import java.util.Collection;
import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData; import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData;
import org.apache.fineract.portfolio.loanproduct.data.CreditAllocationData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -53,5 +54,7 @@ public interface LoanProductReadPlatformService {
Collection<AdvancedPaymentData> retrieveAdvancedPaymentData(Long loanProductId); Collection<AdvancedPaymentData> retrieveAdvancedPaymentData(Long loanProductId);
Collection<CreditAllocationData> retrieveCreditAllocationData(Long loanProductId);
LoanProductData retrieveLoanProductFloatingDetails(Long loanProductId); LoanProductData retrieveLoanProductFloatingDetails(Long loanProductId);
} }
@@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData; import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData;
import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData.PaymentAllocationOrder; import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData.PaymentAllocationOrder;
import org.apache.fineract.portfolio.loanproduct.data.CreditAllocationData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductGuaranteeData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductGuaranteeData;
@@ -84,10 +85,11 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas = retrieveLoanProductBorrowerCycleVariations( final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas = retrieveLoanProductBorrowerCycleVariations(
loanProductId); loanProductId);
final Collection<AdvancedPaymentData> advancedPaymentData = retrieveAdvancedPaymentData(loanProductId); final Collection<AdvancedPaymentData> advancedPaymentData = retrieveAdvancedPaymentData(loanProductId);
final Collection<CreditAllocationData> creditAllocationData = retrieveCreditAllocationData(loanProductId);
final Collection<DelinquencyBucketData> delinquencyBucketOptions = this.delinquencyReadPlatformService final Collection<DelinquencyBucketData> delinquencyBucketOptions = this.delinquencyReadPlatformService
.retrieveAllDelinquencyBuckets(); .retrieveAllDelinquencyBuckets();
final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates, delinquencyBucketOptions, final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates, delinquencyBucketOptions,
advancedPaymentData); advancedPaymentData, creditAllocationData);
final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?"; final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?";
return this.jdbcTemplate.queryForObject(sql, rm, loanProductId); // NOSONAR return this.jdbcTemplate.queryForObject(sql, rm, loanProductId); // NOSONAR
@@ -116,12 +118,19 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
return this.jdbcTemplate.query(sql, apdm, loanProductId); // NOSONAR return this.jdbcTemplate.query(sql, apdm, loanProductId); // NOSONAR
} }
@Override
public List<CreditAllocationData> retrieveCreditAllocationData(final Long loanProductId) {
final CreditAllocationDataMapper cadm = new CreditAllocationDataMapper();
final String sql = "select " + cadm.schema() + " where loan_product_id = ?";
return this.jdbcTemplate.query(sql, cadm, loanProductId); // NOSONAR
}
@Override @Override
public Collection<LoanProductData> retrieveAllLoanProducts() { public Collection<LoanProductData> retrieveAllLoanProducts() {
this.context.authenticatedUser(); this.context.authenticatedUser();
final LoanProductMapper rm = new LoanProductMapper(null, null, null, null, null); final LoanProductMapper rm = new LoanProductMapper(null, null, null, null, null, null);
String sql = "select " + rm.loanProductSchema(); String sql = "select " + rm.loanProductSchema();
@@ -200,18 +209,22 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
private final Collection<AdvancedPaymentData> advancedPaymentData; private final Collection<AdvancedPaymentData> advancedPaymentData;
private final Collection<CreditAllocationData> creditAllocationData;
private final Collection<RateData> rates; private final Collection<RateData> rates;
private final Collection<DelinquencyBucketData> delinquencyBucketOptions; private final Collection<DelinquencyBucketData> delinquencyBucketOptions;
LoanProductMapper(final Collection<ChargeData> charges, LoanProductMapper(final Collection<ChargeData> charges,
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> rates, final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> rates,
final Collection<DelinquencyBucketData> delinquencyBucketOptions, Collection<AdvancedPaymentData> advancedPaymentData) { final Collection<DelinquencyBucketData> delinquencyBucketOptions, Collection<AdvancedPaymentData> advancedPaymentData,
Collection<CreditAllocationData> creditAllocationData) {
this.charges = charges; this.charges = charges;
this.borrowerCycleVariationDatas = borrowerCycleVariationDatas; this.borrowerCycleVariationDatas = borrowerCycleVariationDatas;
this.rates = rates; this.rates = rates;
this.delinquencyBucketOptions = delinquencyBucketOptions; this.delinquencyBucketOptions = delinquencyBucketOptions;
this.advancedPaymentData = advancedPaymentData; this.advancedPaymentData = advancedPaymentData;
this.creditAllocationData = creditAllocationData;
} }
public String loanProductSchema() { public String loanProductSchema() {
@@ -530,8 +543,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates,
isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket,
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, advancedPaymentData, repaymentStartDateType, enableInstallmentLevelDelinquency, enableAutoRepaymentForDownPayment, advancedPaymentData, creditAllocationData, repaymentStartDateType,
loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData()); enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData());
} }
} }
@@ -589,8 +602,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
return new AdvancedPaymentData(transactionType, futureInstallmentAllocationRule, convert(allocationTypes)); return new AdvancedPaymentData(transactionType, futureInstallmentAllocationRule, convert(allocationTypes));
} }
private List<PaymentAllocationOrder> convert(String futureInstallmentAllocationRule) { private List<PaymentAllocationOrder> convert(String allocationOrders) {
String[] allocationRule = futureInstallmentAllocationRule.split(","); String[] allocationRule = allocationOrders.split(",");
AtomicInteger order = new AtomicInteger(1); AtomicInteger order = new AtomicInteger(1);
return Arrays.stream(allocationRule) // return Arrays.stream(allocationRule) //
.map(s -> new PaymentAllocationOrder(s, order.getAndIncrement())) // .map(s -> new PaymentAllocationOrder(s, order.getAndIncrement())) //
@@ -599,6 +612,29 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
} }
private static final class CreditAllocationDataMapper implements RowMapper<CreditAllocationData> {
public String schema() {
return "transaction_type, allocation_types from m_loan_product_credit_allocation_rule";
}
@Override
public CreditAllocationData mapRow(ResultSet rs, int rowNum) throws SQLException {
final String transactionType = rs.getString("transaction_type");
final String allocationTypes = rs.getString("allocation_types");
return new CreditAllocationData(transactionType, convert(allocationTypes));
}
private List<CreditAllocationData.CreditAllocationOrder> convert(String allocationOrders) {
String[] allocationRule = allocationOrders.split(",");
AtomicInteger order = new AtomicInteger(1);
return Arrays.stream(allocationRule) //
.map(s -> new CreditAllocationData.CreditAllocationOrder(s, order.getAndIncrement())) //
.toList();
}
}
private static final class LoanProductBorrowerCycleMapper implements RowMapper<LoanProductBorrowerCycleVariationData> { private static final class LoanProductBorrowerCycleMapper implements RowMapper<LoanProductBorrowerCycleVariationData> {
public String schema() { public String schema() {
@@ -630,7 +666,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
public Collection<LoanProductData> retrieveAllLoanProductsForCurrency(String currencyCode) { public Collection<LoanProductData> retrieveAllLoanProductsForCurrency(String currencyCode) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final LoanProductMapper rm = new LoanProductMapper(null, null, null, null, null); final LoanProductMapper rm = new LoanProductMapper(null, null, null, null, null, null);
String sql = "select " + rm.loanProductSchema() + " where lp.currency_code= ? "; String sql = "select " + rm.loanProductSchema() + " where lp.currency_code= ? ";
@@ -55,6 +55,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsJsonParser; import org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsJsonParser;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationsJsonParser;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
@@ -88,7 +89,9 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
private final DelinquencyBucketRepository delinquencyBucketRepository; private final DelinquencyBucketRepository delinquencyBucketRepository;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final AdvancedPaymentAllocationsJsonParser advancedPaymentJsonParser; private final AdvancedPaymentAllocationsJsonParser advancedPaymentJsonParser;
private final CreditAllocationsJsonParser creditAllocationsJsonParser;
private final LoanProductPaymentAllocationRuleMerger loanProductPaymentAllocationRuleMerger = new LoanProductPaymentAllocationRuleMerger(); private final LoanProductPaymentAllocationRuleMerger loanProductPaymentAllocationRuleMerger = new LoanProductPaymentAllocationRuleMerger();
private final LoanProductCreditAllocationRuleMerger loanProductCreditAllocationRuleMerger = new LoanProductCreditAllocationRuleMerger();
@Transactional @Transactional
@Override @Override
@@ -110,11 +113,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final List<Rate> rates = assembleListOfProductRates(command); final List<Rate> rates = assembleListOfProductRates(command);
final List<LoanProductPaymentAllocationRule> loanProductPaymentAllocationRules = advancedPaymentJsonParser final List<LoanProductPaymentAllocationRule> loanProductPaymentAllocationRules = advancedPaymentJsonParser
.assembleLoanProductPaymentAllocationRules(command, loanTransactionProcessingStrategyCode); .assembleLoanProductPaymentAllocationRules(command, loanTransactionProcessingStrategyCode);
List<LoanProductCreditAllocationRule> loanProductCreditAllocationRules = new ArrayList<>(); // TODO: this final List<LoanProductCreditAllocationRule> loanProductCreditAllocationRules = creditAllocationsJsonParser
// shall be .assembleLoanProductCreditAllocationRules(command, loanTransactionProcessingStrategyCode);
// parsed from
// the json
// command
FloatingRate floatingRate = null; FloatingRate floatingRate = null;
if (command.parameterExists("floatingRatesId")) { if (command.parameterExists("floatingRatesId")) {
floatingRate = this.floatingRateRepository floatingRate = this.floatingRateRepository
@@ -239,6 +239,17 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
} }
} }
if (changes.containsKey("creditAllocation")) {
final List<LoanProductCreditAllocationRule> loanProductCreditAllocationRules = creditAllocationsJsonParser
.assembleLoanProductCreditAllocationRules(command, product.getTransactionProcessingStrategyCode());
loanProductCreditAllocationRules.forEach(lpcar -> lpcar.setLoanProduct(product));
final boolean updated = loanProductCreditAllocationRuleMerger.updateCreditAllocationRules(product,
loanProductCreditAllocationRules);
if (!updated) {
changes.remove("creditAllocation");
}
}
// accounting related changes // accounting related changes
final boolean accountingTypeChanged = changes.containsKey("accountingRule"); final boolean accountingTypeChanged = changes.containsKey("accountingRule");
final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
@@ -33,6 +33,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTra
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsJsonParser; import org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsJsonParser;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationsJsonParser;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator; import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
@@ -78,10 +79,10 @@ public class LoanProductConfiguration {
LoanRepositoryWrapper loanRepositoryWrapper, BusinessEventNotifierService businessEventNotifierService, LoanRepositoryWrapper loanRepositoryWrapper, BusinessEventNotifierService businessEventNotifierService,
DelinquencyBucketRepository delinquencyBucketRepository, DelinquencyBucketRepository delinquencyBucketRepository,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
AdvancedPaymentAllocationsJsonParser advancedPaymentJsonParser) { AdvancedPaymentAllocationsJsonParser advancedPaymentJsonParser, CreditAllocationsJsonParser creditAllocationsJsonParser) {
return new LoanProductWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, loanProductRepository, aprCalculator, return new LoanProductWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, loanProductRepository, aprCalculator,
fundRepository, chargeRepository, rateRepository, accountMappingWritePlatformService, fineractEntityAccessUtil, fundRepository, chargeRepository, rateRepository, accountMappingWritePlatformService, fineractEntityAccessUtil,
floatingRateRepository, loanRepositoryWrapper, businessEventNotifierService, delinquencyBucketRepository, floatingRateRepository, loanRepositoryWrapper, businessEventNotifierService, delinquencyBucketRepository,
loanRepaymentScheduleTransactionProcessorFactory, advancedPaymentJsonParser); loanRepaymentScheduleTransactionProcessorFactory, advancedPaymentJsonParser, creditAllocationsJsonParser);
} }
} }
@@ -0,0 +1,106 @@
/**
* 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.service;
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
import java.util.List;
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class LoanProductCreditAllocationRuleMergerTest {
private LoanProductCreditAllocationRuleMerger underTest = new LoanProductCreditAllocationRuleMerger();
@Test
public void testMergerOneNewAdded() {
LoanProduct loanProduct = new LoanProduct();
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of(rule1));
Assertions.assertTrue(result);
Assertions.assertEquals(1, loanProduct.getCreditAllocationRules().size());
Assertions.assertEquals(rule1, loanProduct.getCreditAllocationRules().get(0));
}
@Test
public void testMergerExistingUpdated() {
LoanProduct loanProduct = new LoanProduct();
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
LoanProductCreditAllocationRule rule2 = createRule(CreditAllocationTransactionType.CHARGEBACK,
List.of(FEE, INTEREST, PRINCIPAL, PENALTY));
loanProduct.getCreditAllocationRules().add(rule1);
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of(rule2));
Assertions.assertTrue(result);
Assertions.assertEquals(1, loanProduct.getCreditAllocationRules().size());
Assertions.assertEquals(rule2.getTransactionType(), loanProduct.getCreditAllocationRules().get(0).getTransactionType());
Assertions.assertEquals(rule2.getAllocationTypes(), loanProduct.getCreditAllocationRules().get(0).getAllocationTypes());
}
@Test
public void testMergerNothingIsChanged() {
LoanProduct loanProduct = new LoanProduct();
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
LoanProductCreditAllocationRule rule2 = createRule(CreditAllocationTransactionType.CHARGEBACK,
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
loanProduct.getCreditAllocationRules().add(rule1);
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of(rule2));
Assertions.assertFalse(result);
Assertions.assertEquals(1, loanProduct.getCreditAllocationRules().size());
Assertions.assertEquals(rule1.getTransactionType(), loanProduct.getCreditAllocationRules().get(0).getTransactionType());
Assertions.assertEquals(rule1.getAllocationTypes(), loanProduct.getCreditAllocationRules().get(0).getAllocationTypes());
}
@Test
public void testMergerExistingDeleted() {
LoanProduct loanProduct = new LoanProduct();
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
loanProduct.getCreditAllocationRules().add(rule1);
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of());
Assertions.assertTrue(result);
Assertions.assertEquals(0, loanProduct.getCreditAllocationRules().size());
}
public LoanProductCreditAllocationRule createRule(CreditAllocationTransactionType transactionType,
List<AllocationType> allocationTypeList) {
return new LoanProductCreditAllocationRule(null, transactionType, allocationTypeList);
}
}
@@ -0,0 +1,309 @@
/**
* 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.integrationtests;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.accounting.common.AccountingConstants;
import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.CreditAllocationData;
import org.apache.fineract.client.models.CreditAllocationOrder;
import org.apache.fineract.client.models.GetFinancialActivityAccountsResponse;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostFinancialActivityAccountsRequest;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@Slf4j
@ExtendWith(LoanTestLifecycleExtension.class)
public class LoanProductWithCreditAllocationsIntegrationTests {
private static ResponseSpecification RESPONSE_SPEC;
private static RequestSpecification REQUEST_SPEC;
private static Account ASSET_ACCOUNT;
private static Account FEE_PENALTY_ACCOUNT;
private static Account TRANSFER_ACCOUNT;
private static Account EXPENSE_ACCOUNT;
private static Account INCOME_ACCOUNT;
private static Account OVERPAYMENT_ACCOUNT;
private static FinancialActivityAccountHelper FINANCIAL_ACTIVITY_ACCOUNT_HELPER;
private static LoanTransactionHelper LOAN_TRANSACTION_HELPER;
@BeforeAll
public static void setupTests() {
Utils.initializeRESTAssured();
REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build();
AccountHelper accountHelper = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC);
FINANCIAL_ACTIVITY_ACCOUNT_HELPER = new FinancialActivityAccountHelper(REQUEST_SPEC);
LOAN_TRANSACTION_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC);
ASSET_ACCOUNT = accountHelper.createAssetAccount();
FEE_PENALTY_ACCOUNT = accountHelper.createAssetAccount();
TRANSFER_ACCOUNT = accountHelper.createAssetAccount();
EXPENSE_ACCOUNT = accountHelper.createExpenseAccount();
INCOME_ACCOUNT = accountHelper.createIncomeAccount();
OVERPAYMENT_ACCOUNT = accountHelper.createLiabilityAccount();
setProperFinancialActivity(TRANSFER_ACCOUNT);
}
@Test
public void testCreateAndReadLoanProductWithAdvancedPaymentAndCreditAllocations() {
// given
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
// when
String loanProductJSON = baseLoanProduct().addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)
.addCreditAllocations(createChargebackAllocation()).build();
Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON);
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
// then
Assertions.assertNotNull(loanProduct.getCreditAllocation());
Assertions.assertEquals(1, loanProduct.getCreditAllocation().size());
Assertions.assertEquals(createChargebackAllocation(), loanProduct.getCreditAllocation().get(0));
}
@Test
public void testCreateLoanProductAndLaterAddCreditAllocation() {
// given
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
// create empty
String loanProductJSON = baseLoanProduct().addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation).build();
Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON);
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertEquals(0, loanProduct.getCreditAllocation().size());
// add credit allocation
PutLoanProductsProductIdRequest putLoanProductsProductIdRequest = updateLoanProductRequest(createChargebackAllocation());
LOAN_TRANSACTION_HELPER.updateLoanProduct(loanProductId.longValue(), putLoanProductsProductIdRequest);
loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getCreditAllocation());
Assertions.assertEquals(1, loanProduct.getCreditAllocation().size());
Assertions.assertEquals(createChargebackAllocation(), loanProduct.getCreditAllocation().get(0));
}
@Test
public void testCreateAndUpdateCreditAllocation() {
// given
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
// when
String loanProductJSON = baseLoanProduct().addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)
.addCreditAllocations(createChargebackAllocation()).build();
Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON);
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getCreditAllocation());
Assertions.assertEquals(1, loanProduct.getCreditAllocation().size());
Assertions.assertEquals(createChargebackAllocation(), loanProduct.getCreditAllocation().get(0));
CreditAllocationData updated = createChargebackAllocation();
List<CreditAllocationOrder> updatedOrder = createCreditAllocationOrders("FEE", "INTEREST", "PRINCIPAL", "PENALTY");
updated.setCreditAllocationOrder(updatedOrder);
PutLoanProductsProductIdRequest putLoanProductsProductIdRequest = updateLoanProductRequest(updated);
LOAN_TRANSACTION_HELPER.updateLoanProduct(loanProductId.longValue(), putLoanProductsProductIdRequest);
loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertEquals(1, loanProduct.getCreditAllocation().size());
Assertions.assertEquals(updatedOrder, loanProduct.getCreditAllocation().get(0).getCreditAllocationOrder());
}
@Test
public void testCreateAndDeleteCreditAllocation() {
// given
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
// when
String loanProductJSON = baseLoanProduct().addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)
.addCreditAllocations(createChargebackAllocation()).build();
Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON);
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getCreditAllocation());
Assertions.assertEquals(1, loanProduct.getCreditAllocation().size());
Assertions.assertEquals(createChargebackAllocation(), loanProduct.getCreditAllocation().get(0));
PutLoanProductsProductIdRequest putLoanProductsProductIdRequest = updateLoanProductRequest(new CreditAllocationData[] {});
LOAN_TRANSACTION_HELPER.updateLoanProduct(loanProductId.longValue(), putLoanProductsProductIdRequest);
loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertEquals(0, loanProduct.getCreditAllocation().size());
}
@Test
public void testCreditAllocationIsNotAllowedWhenPaymentStrategyIsNotAdvancedPaymentStrategy() {
// given
String loanProductJSON = baseLoanProduct().withRepaymentStrategy("mifos-standard-strategy")
.addCreditAllocations(createChargebackAllocation()).build();
ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build();
LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(REQUEST_SPEC, errorResponse);
// when
List<Map<String, String>> loanProductError = validationErrorHelper.getLoanProductError(loanProductJSON, "errors");
// then
Assertions.assertEquals("In case 'mifos-standard-strategy' payment strategy, creditAllocation must not be provided",
loanProductError.get(0).get("defaultUserMessage"));
}
@Test
public void testCreateLoanProductWithCreditAllocationThenUpdatePaymentStrategyShouldFail() {
// given
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
String loanProductJSON = baseLoanProduct().addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)
.addCreditAllocations(createChargebackAllocation()).build();
Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON);
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getCreditAllocation());
Assertions.assertEquals(1, loanProduct.getCreditAllocation().size());
Assertions.assertEquals(createChargebackAllocation(), loanProduct.getCreditAllocation().get(0));
PutLoanProductsProductIdRequest putLoanProductsProductIdRequest = updateLoanProductRequest("mifos-standard-strategy");
putLoanProductsProductIdRequest.setPaymentAllocation(List.of());
// when
CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class, () -> {
LOAN_TRANSACTION_HELPER.updateLoanProduct(loanProductId.longValue(), putLoanProductsProductIdRequest);
});
// then
Assertions.assertTrue(callFailedRuntimeException.getMessage()
.contains("In case 'mifos-standard-strategy' payment strategy, creditAllocation must not be provided"));
}
private CreditAllocationData createChargebackAllocation() {
CreditAllocationData creditAllocationData = new CreditAllocationData();
creditAllocationData.setTransactionType("CHARGEBACK");
creditAllocationData.setCreditAllocationOrder(createCreditAllocationOrders("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
return creditAllocationData;
}
public List<CreditAllocationOrder> createCreditAllocationOrders(String... allocationRule) {
AtomicInteger integer = new AtomicInteger(1);
return Arrays.stream(allocationRule).map(allocation -> {
CreditAllocationOrder creditAllocationOrder = new CreditAllocationOrder();
creditAllocationOrder.setCreditAllocationRule(allocation);
creditAllocationOrder.setOrder(integer.getAndIncrement());
return creditAllocationOrder;
}).toList();
}
private LoanProductTestBuilder baseLoanProduct() {
return new LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4").withRepaymentAfterEvery("1")
.withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
.withAccountingRulePeriodicAccrual(new Account[] { ASSET_ACCOUNT, EXPENSE_ACCOUNT, INCOME_ACCOUNT, OVERPAYMENT_ACCOUNT })
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
.withFeeAndPenaltyAssetAccount(FEE_PENALTY_ACCOUNT).withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
.withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL);
}
private PutLoanProductsProductIdRequest updateLoanProductRequest(CreditAllocationData... creditAllocationData) {
PutLoanProductsProductIdRequest putLoanProductsProductIdRequest = new PutLoanProductsProductIdRequest();
putLoanProductsProductIdRequest.creditAllocation(Arrays.stream(creditAllocationData).toList());
return putLoanProductsProductIdRequest;
}
private PutLoanProductsProductIdRequest updateLoanProductRequest(String transactionProcessingStrategyCode) {
PutLoanProductsProductIdRequest putLoanProductsProductIdRequest = new PutLoanProductsProductIdRequest();
putLoanProductsProductIdRequest.setTransactionProcessingStrategyCode(transactionProcessingStrategyCode);
return putLoanProductsProductIdRequest;
}
private AdvancedPaymentData createRepaymentPaymentAllocation() {
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
advancedPaymentData.setTransactionType("REPAYMENT");
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_INTEREST, PaymentAllocationType.PAST_DUE_PRINCIPAL,
PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_INTEREST,
PaymentAllocationType.DUE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST);
advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
return advancedPaymentData;
}
private AdvancedPaymentData createDefaultPaymentAllocation() {
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
advancedPaymentData.setTransactionType("DEFAULT");
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST,
PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST);
advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
return advancedPaymentData;
}
private List<PaymentAllocationOrder> getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
AtomicInteger integer = new AtomicInteger(1);
return Arrays.stream(paymentAllocationTypes).map(pat -> {
PaymentAllocationOrder paymentAllocationOrder = new PaymentAllocationOrder();
paymentAllocationOrder.setPaymentAllocationRule(pat.name());
paymentAllocationOrder.setOrder(integer.getAndIncrement());
return paymentAllocationOrder;
}).toList();
}
private static void setProperFinancialActivity(Account transferAccount) {
List<GetFinancialActivityAccountsResponse> financialMappings = FINANCIAL_ACTIVITY_ACCOUNT_HELPER.getAllFinancialActivityAccounts();
financialMappings.forEach(mapping -> FINANCIAL_ACTIVITY_ACCOUNT_HELPER.deleteFinancialActivityAccount(mapping.getId()));
FINANCIAL_ACTIVITY_ACCOUNT_HELPER.createFinancialActivityAccount(new PostFinancialActivityAccountsRequest()
.financialActivityId((long) AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue())
.glAccountId((long) transferAccount.getAccountID()));
}
}
@@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.fineract.client.models.AdvancedPaymentData; import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.CreditAllocationData;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
@@ -94,6 +95,7 @@ public class LoanProductTestBuilder {
private String inArrearsTolerance = "0"; private String inArrearsTolerance = "0";
private String transactionProcessingStrategyCode = DEFAULT_STRATEGY; private String transactionProcessingStrategyCode = DEFAULT_STRATEGY;
private List<AdvancedPaymentData> advancedPaymentAllocations = null; private List<AdvancedPaymentData> advancedPaymentAllocations = null;
private List<CreditAllocationData> creditAllocations = null;
private String accountingRule = NONE; private String accountingRule = NONE;
private final String currencyCode = USD; private final String currencyCode = USD;
private String amortizationType = EQUAL_INSTALLMENTS; private String amortizationType = EQUAL_INSTALLMENTS;
@@ -197,6 +199,7 @@ public class LoanProductTestBuilder {
map.put("inArrearsTolerance", this.inArrearsTolerance); map.put("inArrearsTolerance", this.inArrearsTolerance);
map.put("transactionProcessingStrategyCode", this.transactionProcessingStrategyCode); map.put("transactionProcessingStrategyCode", this.transactionProcessingStrategyCode);
map.put("paymentAllocation", this.advancedPaymentAllocations); map.put("paymentAllocation", this.advancedPaymentAllocations);
map.put("creditAllocation", this.creditAllocations);
map.put("accountingRule", this.accountingRule); map.put("accountingRule", this.accountingRule);
map.put("minPrincipal", this.minPrincipal); map.put("minPrincipal", this.minPrincipal);
map.put("maxPrincipal", this.maxPrincipal); map.put("maxPrincipal", this.maxPrincipal);
@@ -736,6 +739,11 @@ public class LoanProductTestBuilder {
return this; return this;
} }
public LoanProductTestBuilder addCreditAllocations(CreditAllocationData... creditAllocationData) {
this.creditAllocations = new ArrayList<>(Arrays.stream(creditAllocationData).toList());
return this;
}
public LoanProductTestBuilder withRepaymentStartDateType(final Integer repaymentStartDateType) { public LoanProductTestBuilder withRepaymentStartDateType(final Integer repaymentStartDateType) {
this.repaymentStartDateType = repaymentStartDateType; this.repaymentStartDateType = repaymentStartDateType;
return this; return this;