FINERACT-2358: Allow to configure advanced accounting rules based on write-off reason
This commit is contained in:
+4
@@ -68,6 +68,10 @@ public class ProductToGLAccountMapping extends AbstractPersistableCustom<Long> {
|
|||||||
@JoinColumn(name = "charge_off_reason_id", nullable = true)
|
@JoinColumn(name = "charge_off_reason_id", nullable = true)
|
||||||
private CodeValue chargeOffReason;
|
private CodeValue chargeOffReason;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "write_off_reason_id", nullable = true)
|
||||||
|
private CodeValue writeOffReason;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "capitalized_income_classification_id", nullable = true)
|
@JoinColumn(name = "capitalized_income_classification_id", nullable = true)
|
||||||
private CodeValue capitalizedIncomeClassification;
|
private CodeValue capitalizedIncomeClassification;
|
||||||
|
|||||||
+9
-2
@@ -35,7 +35,7 @@ public interface ProductToGLAccountMappingRepository
|
|||||||
@Param("productType") int productType, @Param("financialAccountType") int financialAccountType,
|
@Param("productType") int productType, @Param("financialAccountType") int financialAccountType,
|
||||||
@Param("chargeId") Long ChargeId);
|
@Param("chargeId") Long ChargeId);
|
||||||
|
|
||||||
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL and mapping.chargeOffReason is NULL and mapping.capitalizedIncomeClassification is NULL and mapping.buydownFeeClassification is NULL")
|
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL and mapping.chargeOffReason is NULL and mapping.writeOffReason is NULL and mapping.capitalizedIncomeClassification is NULL and mapping.buydownFeeClassification is NULL")
|
||||||
ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType,
|
ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType,
|
||||||
@Param("financialAccountType") int financialAccountType);
|
@Param("financialAccountType") int financialAccountType);
|
||||||
|
|
||||||
@@ -66,11 +66,18 @@ public interface ProductToGLAccountMappingRepository
|
|||||||
List<ProductToGLAccountMapping> findAllChargeOffReasonsMappings(@Param("productId") Long productId,
|
List<ProductToGLAccountMapping> findAllChargeOffReasonsMappings(@Param("productId") Long productId,
|
||||||
@Param("productType") int productType);
|
@Param("productType") int productType);
|
||||||
|
|
||||||
|
List<ProductToGLAccountMapping> findAllProductToGLAccountMappingsByProductIdAndProductTypeAndFinancialAccountType(Long productId,
|
||||||
|
int productType, int financialAccountType);
|
||||||
|
|
||||||
|
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.writeOffReason is not NULL")
|
||||||
|
List<ProductToGLAccountMapping> findAllWriteOffReasonsMappings(@Param("productId") Long productId,
|
||||||
|
@Param("productType") int productType);
|
||||||
|
|
||||||
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.chargeOffReason.id =:chargeOffReasonId AND mapping.productId =:productId AND mapping.productType =:productType")
|
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.chargeOffReason.id =:chargeOffReasonId AND mapping.productId =:productId AND mapping.productType =:productType")
|
||||||
ProductToGLAccountMapping findChargeOffReasonMapping(@Param("productId") Long productId, @Param("productType") Integer productType,
|
ProductToGLAccountMapping findChargeOffReasonMapping(@Param("productId") Long productId, @Param("productType") Integer productType,
|
||||||
@Param("chargeOffReasonId") Long chargeOffReasonId);
|
@Param("chargeOffReasonId") Long chargeOffReasonId);
|
||||||
|
|
||||||
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId AND mapping.productType =:productType AND mapping.charge IS NULL AND mapping.paymentType IS NULL AND mapping.chargeOffReason IS NULL AND mapping.capitalizedIncomeClassification is NULL AND mapping.buydownFeeClassification is NULL")
|
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId AND mapping.productType =:productType AND mapping.charge IS NULL AND mapping.paymentType IS NULL AND mapping.chargeOffReason IS NULL AND mapping.writeOffReason IS NULL AND mapping.capitalizedIncomeClassification is NULL AND mapping.buydownFeeClassification is NULL")
|
||||||
List<ProductToGLAccountMapping> findAllRegularMappings(@Param("productId") Long productId, @Param("productType") Integer productType);
|
List<ProductToGLAccountMapping> findAllRegularMappings(@Param("productId") Long productId, @Param("productType") Integer productType);
|
||||||
|
|
||||||
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.paymentType is not NULL")
|
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.paymentType is not NULL")
|
||||||
|
|||||||
+113
-61
@@ -29,6 +29,7 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
|
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
|
||||||
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
|
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
|
||||||
@@ -206,23 +207,25 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveChargeOffReasonToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
public void saveReasonToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||||
final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {
|
final Map<String, Object> changes, final PortfolioProductType portfolioProductType,
|
||||||
|
final LoanProductAccountingParams arrayNameParam, final LoanProductAccountingParams reasonCodeValueIdParam,
|
||||||
|
final CashAccountsForLoan cashAccountsForLoan) {
|
||||||
|
|
||||||
final String arrayName = LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue();
|
final String arrayName = arrayNameParam.getValue();
|
||||||
final JsonArray chargeOffReasonToExpenseAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);
|
final JsonArray reasonToExpenseAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);
|
||||||
|
|
||||||
if (chargeOffReasonToExpenseAccountMappingArray != null) {
|
if (reasonToExpenseAccountMappingArray != null) {
|
||||||
if (changes != null) {
|
if (changes != null) {
|
||||||
changes.put(arrayName, command.jsonFragment(arrayName));
|
changes.put(arrayName, command.jsonFragment(arrayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < chargeOffReasonToExpenseAccountMappingArray.size(); i++) {
|
for (int i = 0; i < reasonToExpenseAccountMappingArray.size(); i++) {
|
||||||
final JsonObject jsonObject = chargeOffReasonToExpenseAccountMappingArray.get(i).getAsJsonObject();
|
final JsonObject jsonObject = reasonToExpenseAccountMappingArray.get(i).getAsJsonObject();
|
||||||
final Long reasonId = jsonObject.get(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue()).getAsLong();
|
final Long reasonId = jsonObject.get(reasonCodeValueIdParam.getValue()).getAsLong();
|
||||||
final Long expenseAccountId = jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();
|
final Long expenseAccountId = jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();
|
||||||
|
|
||||||
saveChargeOffReasonToExpenseMapping(productId, reasonId, expenseAccountId, portfolioProductType);
|
saveReasonToExpenseMapping(productId, reasonId, expenseAccountId, portfolioProductType, cashAccountsForLoan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,60 +416,74 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateChargeOffReasonToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
private Long getReasonIdByCashAccountForLoan(final ProductToGLAccountMapping productToGLAccountMapping,
|
||||||
final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {
|
final CashAccountsForLoan cashAccountsForLoan) {
|
||||||
|
return switch (cashAccountsForLoan) {
|
||||||
|
case LOSSES_WRITTEN_OFF -> productToGLAccountMapping != null && productToGLAccountMapping.getWriteOffReason() != null
|
||||||
|
? productToGLAccountMapping.getWriteOffReason().getId()
|
||||||
|
: null;
|
||||||
|
case CHARGE_OFF_EXPENSE -> productToGLAccountMapping != null && productToGLAccountMapping.getChargeOffReason() != null
|
||||||
|
? productToGLAccountMapping.getChargeOffReason().getId()
|
||||||
|
: null;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + cashAccountsForLoan);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
final List<ProductToGLAccountMapping> existingChargeOffReasonToGLAccountMappings = this.accountMappingRepository
|
public void updateReasonToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||||
.findAllChargeOffReasonsMappings(productId, portfolioProductType.getValue());
|
final Map<String, Object> changes, final PortfolioProductType portfolioProductType,
|
||||||
final JsonArray chargeOffReasonToGLAccountMappingArray = this.fromApiJsonHelper
|
final List<ProductToGLAccountMapping> existingReasonToGLAccountMappings,
|
||||||
.extractJsonArrayNamed(LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue(), element);
|
final LoanProductAccountingParams reasonToExpenseAccountMappingsParam, final LoanProductAccountingParams reasonCodeValueIdParam,
|
||||||
|
final CashAccountsForLoan cashAccountsForLoan) {
|
||||||
|
|
||||||
final Map<Long, Long> inputChargeOffReasonToGLAccountMap = new HashMap<>();
|
final JsonArray reasonToGLAccountMappingArray = this.fromApiJsonHelper
|
||||||
|
.extractJsonArrayNamed(reasonToExpenseAccountMappingsParam.getValue(), element);
|
||||||
|
|
||||||
final Set<Long> existingChargeOffReasons = new HashSet<>();
|
final Map<Long, Long> inputReasonToGLAccountMap = new HashMap<>();
|
||||||
if (chargeOffReasonToGLAccountMappingArray != null) {
|
|
||||||
|
final Set<Long> existingReasons = new HashSet<>();
|
||||||
|
if (reasonToGLAccountMappingArray != null) {
|
||||||
if (changes != null) {
|
if (changes != null) {
|
||||||
changes.put(LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue(),
|
changes.put(reasonToExpenseAccountMappingsParam.getValue(),
|
||||||
command.jsonFragment(LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue()));
|
command.jsonFragment(reasonToExpenseAccountMappingsParam.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < chargeOffReasonToGLAccountMappingArray.size(); i++) {
|
for (int i = 0; i < reasonToGLAccountMappingArray.size(); i++) {
|
||||||
final JsonObject jsonObject = chargeOffReasonToGLAccountMappingArray.get(i).getAsJsonObject();
|
final JsonObject jsonObject = reasonToGLAccountMappingArray.get(i).getAsJsonObject();
|
||||||
final Long expenseGlAccountId = jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();
|
final Long expenseGlAccountId = jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();
|
||||||
final Long chargeOffReasonCodeValueId = jsonObject
|
final Long reasonCodeValueId = jsonObject.get(reasonCodeValueIdParam.getValue()).getAsLong();
|
||||||
.get(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue()).getAsLong();
|
inputReasonToGLAccountMap.put(reasonCodeValueId, expenseGlAccountId);
|
||||||
inputChargeOffReasonToGLAccountMap.put(chargeOffReasonCodeValueId, expenseGlAccountId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If input map is empty, delete all existing mappings
|
// If input map is empty, delete all existing mappings
|
||||||
if (inputChargeOffReasonToGLAccountMap.isEmpty()) {
|
if (inputReasonToGLAccountMap.isEmpty()) {
|
||||||
this.accountMappingRepository.deleteAll(existingChargeOffReasonToGLAccountMappings);
|
this.accountMappingRepository.deleteAll(existingReasonToGLAccountMappings);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for (final ProductToGLAccountMapping existingChargeOffReasonToGLAccountMapping : existingChargeOffReasonToGLAccountMappings) {
|
for (final ProductToGLAccountMapping existingReasonToGLAccountMapping : existingReasonToGLAccountMappings) {
|
||||||
final Long currentChargeOffReasonId = existingChargeOffReasonToGLAccountMapping.getChargeOffReason().getId();
|
final Long currentReasonId = getReasonIdByCashAccountForLoan(existingReasonToGLAccountMapping, cashAccountsForLoan);
|
||||||
if (currentChargeOffReasonId != null) {
|
if (currentReasonId != null) {
|
||||||
existingChargeOffReasons.add(currentChargeOffReasonId);
|
existingReasons.add(currentReasonId);
|
||||||
// update existing mappings (if required)
|
// update existing mappings (if required)
|
||||||
if (inputChargeOffReasonToGLAccountMap.containsKey(currentChargeOffReasonId)) {
|
if (inputReasonToGLAccountMap.containsKey(currentReasonId)) {
|
||||||
final Long newGLAccountId = inputChargeOffReasonToGLAccountMap.get(currentChargeOffReasonId);
|
final Long newGLAccountId = inputReasonToGLAccountMap.get(currentReasonId);
|
||||||
if (!newGLAccountId.equals(existingChargeOffReasonToGLAccountMapping.getGlAccount().getId())) {
|
if (!newGLAccountId.equals(existingReasonToGLAccountMapping.getGlAccount().getId())) {
|
||||||
final Optional<GLAccount> glAccount = accountRepository.findById(newGLAccountId);
|
final Optional<GLAccount> glAccount = accountRepository.findById(newGLAccountId);
|
||||||
if (glAccount.isPresent()) {
|
if (glAccount.isPresent()) {
|
||||||
existingChargeOffReasonToGLAccountMapping.setGlAccount(glAccount.get());
|
existingReasonToGLAccountMapping.setGlAccount(glAccount.get());
|
||||||
this.accountMappingRepository.saveAndFlush(existingChargeOffReasonToGLAccountMapping);
|
this.accountMappingRepository.saveAndFlush(existingReasonToGLAccountMapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // deleted payment type
|
} // deleted payment type
|
||||||
else {
|
else {
|
||||||
this.accountMappingRepository.delete(existingChargeOffReasonToGLAccountMapping);
|
this.accountMappingRepository.delete(existingReasonToGLAccountMapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only the newly added
|
// only the newly added
|
||||||
for (Map.Entry<Long, Long> entry : inputChargeOffReasonToGLAccountMap.entrySet().stream()
|
for (Map.Entry<Long, Long> entry : inputReasonToGLAccountMap.entrySet().stream()
|
||||||
.filter(e -> !existingChargeOffReasons.contains(e.getKey())).toList()) {
|
.filter(e -> !existingReasons.contains(e.getKey())).toList()) {
|
||||||
saveChargeOffReasonToExpenseMapping(productId, entry.getKey(), entry.getValue(), portfolioProductType);
|
saveReasonToExpenseMapping(productId, entry.getKey(), entry.getValue(), portfolioProductType, cashAccountsForLoan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -587,21 +604,37 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
this.accountMappingRepository.saveAndFlush(accountMapping);
|
this.accountMappingRepository.saveAndFlush(accountMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveChargeOffReasonToExpenseMapping(final Long productId, final Long reasonId, final Long expenseAccountId,
|
private Predicate<? super ProductToGLAccountMapping> matching(final CashAccountsForLoan typeDef, final Long reasonId) {
|
||||||
final PortfolioProductType portfolioProductType) {
|
return switch (typeDef) {
|
||||||
|
case CHARGE_OFF_EXPENSE -> (mapping) -> (mapping.getChargeOffReason() != null && mapping.getChargeOffReason().getId() != null
|
||||||
|
&& mapping.getChargeOffReason().getId().equals(reasonId));
|
||||||
|
case LOSSES_WRITTEN_OFF -> (mapping) -> (mapping.getWriteOffReason() != null && mapping.getWriteOffReason().getId() != null
|
||||||
|
&& mapping.getWriteOffReason().getId().equals(reasonId));
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + typeDef);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveReasonToExpenseMapping(final Long productId, final Long reasonId, final Long expenseAccountId,
|
||||||
|
final PortfolioProductType portfolioProductType, final CashAccountsForLoan cashAccountsForLoan) {
|
||||||
|
|
||||||
final Optional<GLAccount> glAccount = accountRepository.findById(expenseAccountId);
|
final Optional<GLAccount> glAccount = accountRepository.findById(expenseAccountId);
|
||||||
|
|
||||||
final boolean reasonMappingExists = this.accountMappingRepository
|
|
||||||
.findAllChargeOffReasonsMappings(productId, portfolioProductType.getValue()).stream()
|
|
||||||
.anyMatch(mapping -> mapping.getChargeOffReason().getId().equals(reasonId));
|
|
||||||
|
|
||||||
final Optional<CodeValue> codeValueOptional = codeValueRepository.findById(reasonId);
|
final Optional<CodeValue> codeValueOptional = codeValueRepository.findById(reasonId);
|
||||||
|
|
||||||
if (glAccount.isPresent() && !reasonMappingExists && codeValueOptional.isPresent()) {
|
final boolean reasonMappingExists = this.accountMappingRepository
|
||||||
|
.findAllProductToGLAccountMappingsByProductIdAndProductTypeAndFinancialAccountType(productId,
|
||||||
|
portfolioProductType.getValue(), cashAccountsForLoan.getValue())
|
||||||
|
.stream().anyMatch(matching(cashAccountsForLoan, reasonId));
|
||||||
|
|
||||||
|
if (!reasonMappingExists && glAccount.isPresent() && codeValueOptional.isPresent()) {
|
||||||
final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount.get())
|
final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount.get())
|
||||||
.setProductId(productId).setProductType(portfolioProductType.getValue())
|
.setProductId(productId).setProductType(portfolioProductType.getValue())
|
||||||
.setFinancialAccountType(CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue()).setChargeOffReason(codeValueOptional.get());
|
.setFinancialAccountType(cashAccountsForLoan.getValue());
|
||||||
|
|
||||||
|
switch (cashAccountsForLoan) {
|
||||||
|
case CHARGE_OFF_EXPENSE -> accountMapping.setChargeOffReason(codeValueOptional.get());
|
||||||
|
case LOSSES_WRITTEN_OFF -> accountMapping.setWriteOffReason(codeValueOptional.get());
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + cashAccountsForLoan);
|
||||||
|
}
|
||||||
|
|
||||||
this.accountMappingRepository.saveAndFlush(accountMapping);
|
this.accountMappingRepository.saveAndFlush(accountMapping);
|
||||||
}
|
}
|
||||||
@@ -703,22 +736,25 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateChargeOffMappingsInDatabase(final List<JsonObject> mappings) {
|
public void validateWriteOffMappingsInDatabase(final List<ApiParameterError> validationErrors, final List<JsonObject> mappings) {
|
||||||
final List<ApiParameterError> validationErrors = new ArrayList<>();
|
for (JsonObject jsonObject : mappings) {
|
||||||
|
final Long writeOffReasonCodeValueId = this.fromApiJsonHelper
|
||||||
|
.extractLongNamed(LoanProductAccountingParams.WRITE_OFF_REASON_CODE_VALUE_ID.getValue(), jsonObject);
|
||||||
|
|
||||||
|
// Validation: writeOffReasonCodeValueId must exist in the database
|
||||||
|
CodeValue codeValue = this.codeValueRepository.findByCodeNameAndId("WriteOffReasons", writeOffReasonCodeValueId);
|
||||||
|
if (codeValue == null) {
|
||||||
|
validationErrors.add(ApiParameterError.parameterError("validation.msg.writeoffreason.invalid",
|
||||||
|
"Write-off reason with ID " + writeOffReasonCodeValueId + " does not exist",
|
||||||
|
LoanProductAccountingParams.WRITE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateGLAccountInDatabase(final List<ApiParameterError> validationErrors, final List<JsonObject> mappings) {
|
||||||
for (JsonObject jsonObject : mappings) {
|
for (JsonObject jsonObject : mappings) {
|
||||||
final Long expenseGlAccountId = this.fromApiJsonHelper
|
final Long expenseGlAccountId = this.fromApiJsonHelper
|
||||||
.extractLongNamed(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), jsonObject);
|
.extractLongNamed(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), jsonObject);
|
||||||
final Long chargeOffReasonCodeValueId = this.fromApiJsonHelper
|
|
||||||
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(), jsonObject);
|
|
||||||
|
|
||||||
// Validation: chargeOffReasonCodeValueId must exist in the database
|
|
||||||
CodeValue codeValue = this.codeValueRepository.findByCodeNameAndId("ChargeOffReasons", chargeOffReasonCodeValueId);
|
|
||||||
if (codeValue == null) {
|
|
||||||
validationErrors.add(ApiParameterError.parameterError("validation.msg.chargeoffreason.invalid",
|
|
||||||
"Charge-off reason with ID " + chargeOffReasonCodeValueId + " does not exist",
|
|
||||||
LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation: expenseGLAccountId must exist as a valid Expense GL account
|
// Validation: expenseGLAccountId must exist as a valid Expense GL account
|
||||||
final Optional<GLAccount> glAccount = accountRepository.findById(expenseGlAccountId);
|
final Optional<GLAccount> glAccount = accountRepository.findById(expenseGlAccountId);
|
||||||
@@ -730,6 +766,22 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateChargeOffMappingsInDatabase(final List<ApiParameterError> validationErrors, final List<JsonObject> mappings) {
|
||||||
|
|
||||||
|
for (JsonObject jsonObject : mappings) {
|
||||||
|
final Long chargeOffReasonCodeValueId = this.fromApiJsonHelper
|
||||||
|
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(), jsonObject);
|
||||||
|
|
||||||
|
// Validation: chargeOffReasonCodeValueId must exist in the database
|
||||||
|
CodeValue codeValue = this.codeValueRepository.findByCodeNameAndId("ChargeOffReasons", chargeOffReasonCodeValueId);
|
||||||
|
if (codeValue == null) {
|
||||||
|
validationErrors.add(ApiParameterError.parameterError("validation.msg.chargeoffreason.invalid",
|
||||||
|
"Charge-off reason with ID " + chargeOffReasonCodeValueId + " does not exist",
|
||||||
|
LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Throw all collected validation errors, if any
|
// Throw all collected validation errors, if any
|
||||||
if (!validationErrors.isEmpty()) {
|
if (!validationErrors.isEmpty()) {
|
||||||
|
|||||||
+3
@@ -25,6 +25,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReas
|
|||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
||||||
|
import org.apache.fineract.accounting.producttoaccountmapping.data.WriteOffReasonsToExpenseAccountMapper;
|
||||||
|
|
||||||
public interface ProductToGLAccountMappingReadPlatformService {
|
public interface ProductToGLAccountMappingReadPlatformService {
|
||||||
|
|
||||||
@@ -52,6 +53,8 @@ public interface ProductToGLAccountMappingReadPlatformService {
|
|||||||
|
|
||||||
List<ChargeOffReasonToGLAccountMapper> fetchChargeOffReasonMappingsForLoanProduct(Long loanProductId);
|
List<ChargeOffReasonToGLAccountMapper> fetchChargeOffReasonMappingsForLoanProduct(Long loanProductId);
|
||||||
|
|
||||||
|
List<WriteOffReasonsToExpenseAccountMapper> fetchWriteOffReasonMappingsForLoanProduct(Long loanProductId);
|
||||||
|
|
||||||
List<ClassificationToGLAccountData> fetchClassificationMappingsForLoanProduct(Long loanProductId,
|
List<ClassificationToGLAccountData> fetchClassificationMappingsForLoanProduct(Long loanProductId,
|
||||||
LoanProductAccountingParams classificationParameter);
|
LoanProductAccountingParams classificationParameter);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -40,6 +40,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReas
|
|||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
||||||
|
import org.apache.fineract.accounting.producttoaccountmapping.data.WriteOffReasonsToExpenseAccountMapper;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
|
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
|
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
|
||||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||||
@@ -293,6 +294,22 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
|||||||
return chargeOffReasonToGLAccountMappers;
|
return chargeOffReasonToGLAccountMappers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<WriteOffReasonsToExpenseAccountMapper> fetchWriteOffReasonMappings(final PortfolioProductType portfolioProductType,
|
||||||
|
final Long loanProductId) {
|
||||||
|
final List<ProductToGLAccountMapping> mappings = productToGLAccountMappingRepository.findAllWriteOffReasonsMappings(loanProductId,
|
||||||
|
portfolioProductType.getValue());
|
||||||
|
List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseAccountMappers = mappings.isEmpty() ? null : new ArrayList<>();
|
||||||
|
for (final ProductToGLAccountMapping mapping : mappings) {
|
||||||
|
final String glCode = String.valueOf(mapping.getGlAccount().getId());
|
||||||
|
final String writeOffReasonId = String.valueOf(mapping.getWriteOffReason().getId());
|
||||||
|
|
||||||
|
final WriteOffReasonsToExpenseAccountMapper writeOffReasonToGLAccountMapper = new WriteOffReasonsToExpenseAccountMapper()
|
||||||
|
.setWriteOffReasonCodeValueId(writeOffReasonId).setExpenseAccountId(glCode);
|
||||||
|
writeOffReasonsToExpenseAccountMappers.add(writeOffReasonToGLAccountMapper);
|
||||||
|
}
|
||||||
|
return writeOffReasonsToExpenseAccountMappers;
|
||||||
|
}
|
||||||
|
|
||||||
private List<ClassificationToGLAccountData> fetchClassificationMappings(final PortfolioProductType portfolioProductType,
|
private List<ClassificationToGLAccountData> fetchClassificationMappings(final PortfolioProductType portfolioProductType,
|
||||||
final Long loanProductId, LoanProductAccountingParams classificationParameter) {
|
final Long loanProductId, LoanProductAccountingParams classificationParameter) {
|
||||||
final List<ProductToGLAccountMapping> mappings = classificationParameter
|
final List<ProductToGLAccountMapping> mappings = classificationParameter
|
||||||
@@ -367,6 +384,11 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
|||||||
return fetchChargeOffReasonMappings(PortfolioProductType.LOAN, loanProductId);
|
return fetchChargeOffReasonMappings(PortfolioProductType.LOAN, loanProductId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<WriteOffReasonsToExpenseAccountMapper> fetchWriteOffReasonMappingsForLoanProduct(Long loanProductId) {
|
||||||
|
return fetchWriteOffReasonMappings(PortfolioProductType.LOAN, loanProductId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ClassificationToGLAccountData> fetchClassificationMappingsForLoanProduct(Long loanProductId,
|
public List<ClassificationToGLAccountData> fetchClassificationMappingsForLoanProduct(Long loanProductId,
|
||||||
LoanProductAccountingParams classificationParameter) {
|
LoanProductAccountingParams classificationParameter) {
|
||||||
|
|||||||
+2
@@ -181,8 +181,10 @@ public final class AccountingConstants {
|
|||||||
INCOME_FROM_GOODWILL_CREDIT_FEES("incomeFromGoodwillCreditFeesAccountId"), //
|
INCOME_FROM_GOODWILL_CREDIT_FEES("incomeFromGoodwillCreditFeesAccountId"), //
|
||||||
INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccountId"), //
|
INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccountId"), //
|
||||||
CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS("chargeOffReasonToExpenseAccountMappings"), //
|
CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS("chargeOffReasonToExpenseAccountMappings"), //
|
||||||
|
WRITE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS("writeOffReasonsToExpenseMappings"), //
|
||||||
EXPENSE_GL_ACCOUNT_ID("expenseAccountId"), //
|
EXPENSE_GL_ACCOUNT_ID("expenseAccountId"), //
|
||||||
CHARGE_OFF_REASON_CODE_VALUE_ID("chargeOffReasonCodeValueId"), //
|
CHARGE_OFF_REASON_CODE_VALUE_ID("chargeOffReasonCodeValueId"), //
|
||||||
|
WRITE_OFF_REASON_CODE_VALUE_ID("writeOffReasonCodeValueId"), //
|
||||||
DEFERRED_INCOME_LIABILITY("deferredIncomeLiabilityAccountId"), //
|
DEFERRED_INCOME_LIABILITY("deferredIncomeLiabilityAccountId"), //
|
||||||
INCOME_FROM_CAPITALIZATION("incomeFromCapitalizationAccountId"), //
|
INCOME_FROM_CAPITALIZATION("incomeFromCapitalizationAccountId"), //
|
||||||
BUY_DOWN_EXPENSE("buyDownExpenseAccountId"), //
|
BUY_DOWN_EXPENSE("buyDownExpenseAccountId"), //
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.accounting.producttoaccountmapping.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WriteOffReasonsToExpenseAccountMapper implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private String writeOffReasonCodeValueId;
|
||||||
|
private String expenseAccountId;
|
||||||
|
}
|
||||||
+14
-7
@@ -531,13 +531,20 @@ public class DataValidatorBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.value != null) {
|
if (this.value != null) {
|
||||||
final long number = Long.parseLong(this.value.toString());
|
try {
|
||||||
if (number < 1) {
|
final long number = Long.parseLong(this.value.toString());
|
||||||
String validationErrorCode = "validation.msg." + this.resource + "." + this.parameter + ".not.greater.than.zero";
|
if (number < 1) {
|
||||||
String defaultEnglishMessage = "The parameter `" + this.parameter + "` must be greater than 0.";
|
String validationErrorCode = "validation.msg." + this.resource + "." + this.parameter + ".not.greater.than.zero";
|
||||||
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode, defaultEnglishMessage, this.parameter,
|
String defaultEnglishMessage = "The parameter `" + this.parameter + "` must be greater than 0.";
|
||||||
number, 0);
|
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode, defaultEnglishMessage,
|
||||||
this.dataValidationErrors.add(error);
|
this.parameter, number, 0);
|
||||||
|
this.dataValidationErrors.add(error);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
String validationErrorCode = "validation.msg." + this.resource + "." + this.parameter + ".not.a.number";
|
||||||
|
String defaultEnglishMessage = "The parameter `" + this.parameter + "` must be a number.";
|
||||||
|
this.dataValidationErrors.add(ApiParameterError.parameterError(validationErrorCode, defaultEnglishMessage, this.parameter));
|
||||||
|
throwValidationErrors();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
+30
-2
@@ -30,6 +30,7 @@ import org.apache.fineract.accounting.glaccount.domain.GLAccount;
|
|||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
|
||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
|
||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
||||||
|
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
|
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException;
|
import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingHelper;
|
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingHelper;
|
||||||
@@ -141,12 +142,39 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
|
|||||||
|
|
||||||
public void saveChargeOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
public void saveChargeOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||||
final Map<String, Object> changes) {
|
final Map<String, Object> changes) {
|
||||||
saveChargeOffReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN);
|
saveReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN,
|
||||||
|
LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS,
|
||||||
|
LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID, CashAccountsForLoan.CHARGE_OFF_EXPENSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveWriteOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||||
|
final Map<String, Object> changes) {
|
||||||
|
saveReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN,
|
||||||
|
LoanProductAccountingParams.WRITE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS,
|
||||||
|
LoanProductAccountingParams.WRITE_OFF_REASON_CODE_VALUE_ID, CashAccountsForLoan.LOSSES_WRITTEN_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateWriteOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||||
|
final Map<String, Object> changes) {
|
||||||
|
final List<ProductToGLAccountMapping> existingWriteOffReasonToGLAccountMappings = this.accountMappingRepository
|
||||||
|
.findAllWriteOffReasonsMappings(productId, PortfolioProductType.LOAN.getValue());
|
||||||
|
LoanProductAccountingParams reasonToExpenseAccountMappingsParam = LoanProductAccountingParams.WRITE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS;
|
||||||
|
LoanProductAccountingParams reasonCodeValueIdParam = LoanProductAccountingParams.WRITE_OFF_REASON_CODE_VALUE_ID;
|
||||||
|
CashAccountsForLoan cashAccountsForLoan = CashAccountsForLoan.LOSSES_WRITTEN_OFF;
|
||||||
|
updateReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN,
|
||||||
|
existingWriteOffReasonToGLAccountMappings, reasonToExpenseAccountMappingsParam, reasonCodeValueIdParam,
|
||||||
|
cashAccountsForLoan);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateChargeOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
public void updateChargeOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||||
final Map<String, Object> changes) {
|
final Map<String, Object> changes) {
|
||||||
updateChargeOffReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN);
|
final List<ProductToGLAccountMapping> chargeOffReasonsMappings = this.accountMappingRepository
|
||||||
|
.findAllChargeOffReasonsMappings(productId, PortfolioProductType.LOAN.getValue());
|
||||||
|
LoanProductAccountingParams reasonToExpenseAccountMappingsParam = LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS;
|
||||||
|
LoanProductAccountingParams reasonCodeValueIdParam = LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID;
|
||||||
|
CashAccountsForLoan cashAccountsForLoan = CashAccountsForLoan.CHARGE_OFF_EXPENSE;
|
||||||
|
updateReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, chargeOffReasonsMappings,
|
||||||
|
reasonToExpenseAccountMappingsParam, reasonCodeValueIdParam, cashAccountsForLoan);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveCapitalizedIncomeClassificationToIncomeAccountMappings(final JsonCommand command, final JsonElement element,
|
public void saveCapitalizedIncomeClassificationToIncomeAccountMappings(final JsonCommand command, final JsonElement element,
|
||||||
|
|||||||
+48
-1
@@ -301,6 +301,7 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
|
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
|
||||||
public List<LoanProductChargeToGLAccountMapper> feeToIncomeAccountMappings;
|
public List<LoanProductChargeToGLAccountMapper> feeToIncomeAccountMappings;
|
||||||
public List<PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
|
public List<PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
|
||||||
|
public List<PostWriteOffReasonToExpenseAccountMappings> writeOffReasonsToExpenseMappings;
|
||||||
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> buydownfeeClassificationToIncomeAccountMappings;
|
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> buydownfeeClassificationToIncomeAccountMappings;
|
||||||
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> capitalizedIncomeClassificationToIncomeAccountMappings;
|
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> capitalizedIncomeClassificationToIncomeAccountMappings;
|
||||||
public List<LoanProductChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
|
public List<LoanProductChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
|
||||||
@@ -372,7 +373,7 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
@Schema(example = "REGULAR")
|
@Schema(example = "REGULAR")
|
||||||
public String chargeOffBehaviour;
|
public String chargeOffBehaviour;
|
||||||
|
|
||||||
static final class PostChargeOffReasonToExpenseAccountMappings {
|
public static final class PostChargeOffReasonToExpenseAccountMappings {
|
||||||
|
|
||||||
private PostChargeOffReasonToExpenseAccountMappings() {}
|
private PostChargeOffReasonToExpenseAccountMappings() {}
|
||||||
|
|
||||||
@@ -382,6 +383,17 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
public Long expenseAccountId;
|
public Long expenseAccountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Schema(description = "PostWriteOffReasonToExpenseAccountMappings")
|
||||||
|
public static final class PostWriteOffReasonToExpenseAccountMappings {
|
||||||
|
|
||||||
|
private PostWriteOffReasonToExpenseAccountMappings() {}
|
||||||
|
|
||||||
|
@Schema(example = "1")
|
||||||
|
public String writeOffReasonCodeValueId;
|
||||||
|
@Schema(example = "1")
|
||||||
|
public String expenseAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
static final class PostClassificationToIncomeAccountMappings {
|
static final class PostClassificationToIncomeAccountMappings {
|
||||||
|
|
||||||
private PostClassificationToIncomeAccountMappings() {}
|
private PostClassificationToIncomeAccountMappings() {}
|
||||||
@@ -1154,6 +1166,7 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
public List<StringEnumOptionData> supportedInterestRefundTypes;
|
public List<StringEnumOptionData> supportedInterestRefundTypes;
|
||||||
public List<StringEnumOptionData> supportedInterestRefundTypesOptions;
|
public List<StringEnumOptionData> supportedInterestRefundTypesOptions;
|
||||||
public List<GetLoanProductsChargeOffReasonOptions> chargeOffReasonOptions;
|
public List<GetLoanProductsChargeOffReasonOptions> chargeOffReasonOptions;
|
||||||
|
public List<GetLoanProductsWriteOffReasonOptions> writeOffReasonOptions;
|
||||||
public StringEnumOptionData chargeOffBehaviour;
|
public StringEnumOptionData chargeOffBehaviour;
|
||||||
public List<StringEnumOptionData> chargeOffBehaviourOptions;
|
public List<StringEnumOptionData> chargeOffBehaviourOptions;
|
||||||
@Schema(example = "false")
|
@Schema(example = "false")
|
||||||
@@ -1380,6 +1393,17 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
public Long incomeAccountId;
|
public Long incomeAccountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Schema(description = "GetWriteOffReasonToExpenseAccountMappings")
|
||||||
|
public static final class GetWriteOffReasonToExpenseAccountMappings {
|
||||||
|
|
||||||
|
private GetWriteOffReasonToExpenseAccountMappings() {}
|
||||||
|
|
||||||
|
@Schema(example = "1")
|
||||||
|
public String writeOffReasonCodeValueId;
|
||||||
|
@Schema(example = "1")
|
||||||
|
public String expenseAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(example = "11")
|
@Schema(example = "11")
|
||||||
public Long id;
|
public Long id;
|
||||||
@Schema(example = "advanced accounting")
|
@Schema(example = "advanced accounting")
|
||||||
@@ -1468,6 +1492,7 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
public Set<GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
|
public Set<GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
|
||||||
public Set<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings;
|
public Set<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings;
|
||||||
public List<GetChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
|
public List<GetChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
|
||||||
|
public List<PostLoanProductsRequest.PostWriteOffReasonToExpenseAccountMappings> writeOffReasonsToExpenseMappings;
|
||||||
@Schema(example = "false")
|
@Schema(example = "false")
|
||||||
public Boolean isRatesEnabled;
|
public Boolean isRatesEnabled;
|
||||||
@Schema(example = "true")
|
@Schema(example = "true")
|
||||||
@@ -1511,6 +1536,7 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
public Boolean enableAccrualActivityPosting;
|
public Boolean enableAccrualActivityPosting;
|
||||||
public List<StringEnumOptionData> supportedInterestRefundTypes;
|
public List<StringEnumOptionData> supportedInterestRefundTypes;
|
||||||
public List<GetLoanProductsChargeOffReasonOptions> chargeOffReasonOptions;
|
public List<GetLoanProductsChargeOffReasonOptions> chargeOffReasonOptions;
|
||||||
|
public List<GetLoanProductsWriteOffReasonOptions> writeOffReasonOptions;
|
||||||
public StringEnumOptionData chargeOffBehaviour;
|
public StringEnumOptionData chargeOffBehaviour;
|
||||||
@Schema(example = "false")
|
@Schema(example = "false")
|
||||||
public Boolean interestRecognitionOnDisbursementDate;
|
public Boolean interestRecognitionOnDisbursementDate;
|
||||||
@@ -1774,6 +1800,7 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
|
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
|
||||||
public List<LoanProductChargeToGLAccountMapper> feeToIncomeAccountMappings;
|
public List<LoanProductChargeToGLAccountMapper> feeToIncomeAccountMappings;
|
||||||
public List<PostLoanProductsRequest.PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
|
public List<PostLoanProductsRequest.PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
|
||||||
|
public List<PostLoanProductsRequest.PostWriteOffReasonToExpenseAccountMappings> writeOffReasonsToExpenseMappings;
|
||||||
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> buydownfeeClassificationToIncomeAccountMappings;
|
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> buydownfeeClassificationToIncomeAccountMappings;
|
||||||
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> capitalizedIncomeClassificationToIncomeAccountMappings;
|
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> capitalizedIncomeClassificationToIncomeAccountMappings;
|
||||||
public List<LoanProductChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
|
public List<LoanProductChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
|
||||||
@@ -1904,4 +1931,24 @@ public final class LoanProductsApiResourceSwagger {
|
|||||||
@Schema(example = "false")
|
@Schema(example = "false")
|
||||||
public Boolean mandatory;
|
public Boolean mandatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Schema(description = "GetLoanProductsWriteOffReasonOptions")
|
||||||
|
public static final class GetLoanProductsWriteOffReasonOptions {
|
||||||
|
|
||||||
|
private GetLoanProductsWriteOffReasonOptions() {}
|
||||||
|
|
||||||
|
@Schema(example = "2")
|
||||||
|
public Long id;
|
||||||
|
@Schema(example = "debit_card")
|
||||||
|
public String name;
|
||||||
|
@Schema(example = "2")
|
||||||
|
public Integer position;
|
||||||
|
@Schema(example = "Write-Off reason description")
|
||||||
|
public String description;
|
||||||
|
@Schema(example = "true")
|
||||||
|
public Boolean active;
|
||||||
|
@Schema(example = "false")
|
||||||
|
public Boolean mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-8
@@ -34,6 +34,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReas
|
|||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
||||||
|
import org.apache.fineract.accounting.producttoaccountmapping.data.WriteOffReasonsToExpenseAccountMapper;
|
||||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||||
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
|
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
@@ -165,7 +166,8 @@ public class LoanProductData implements Serializable {
|
|||||||
private Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
|
private Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
|
||||||
private List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToExpenseAccountMappings;
|
private List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToExpenseAccountMappings;
|
||||||
private final boolean enableAccrualActivityPosting;
|
private final boolean enableAccrualActivityPosting;
|
||||||
|
private List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseMappings;
|
||||||
|
private final List<CodeValueData> writeOffReasonOptions;
|
||||||
// rates
|
// rates
|
||||||
private final boolean isRatesEnabled;
|
private final boolean isRatesEnabled;
|
||||||
private final Collection<RateData> rates;
|
private final Collection<RateData> rates;
|
||||||
@@ -379,6 +381,8 @@ public class LoanProductData implements Serializable {
|
|||||||
final StringEnumOptionData buyDownFeeStrategy = null;
|
final StringEnumOptionData buyDownFeeStrategy = null;
|
||||||
final StringEnumOptionData buyDownFeeIncomeType = null;
|
final StringEnumOptionData buyDownFeeIncomeType = null;
|
||||||
final boolean merchantBuyDownFee = false;
|
final boolean merchantBuyDownFee = false;
|
||||||
|
final List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseMappings = null;
|
||||||
|
final List<CodeValueData> writeOffReasonOptions = null;
|
||||||
|
|
||||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||||
@@ -402,7 +406,8 @@ public class LoanProductData implements Serializable {
|
|||||||
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
||||||
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
||||||
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
||||||
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee);
|
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
|
||||||
|
writeOffReasonOptions);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,6 +521,8 @@ public class LoanProductData implements Serializable {
|
|||||||
final StringEnumOptionData buyDownFeeStrategy = null;
|
final StringEnumOptionData buyDownFeeStrategy = null;
|
||||||
final StringEnumOptionData buyDownFeeIncomeType = null;
|
final StringEnumOptionData buyDownFeeIncomeType = null;
|
||||||
final boolean merchantBuyDownFee = false;
|
final boolean merchantBuyDownFee = false;
|
||||||
|
final List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseMappings = null;
|
||||||
|
final List<CodeValueData> writeOffReasonOptions = null;
|
||||||
|
|
||||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||||
@@ -539,7 +546,8 @@ public class LoanProductData implements Serializable {
|
|||||||
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
||||||
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
||||||
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
||||||
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee);
|
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
|
||||||
|
writeOffReasonOptions);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,6 +668,8 @@ public class LoanProductData implements Serializable {
|
|||||||
final StringEnumOptionData buyDownFeeStrategy = null;
|
final StringEnumOptionData buyDownFeeStrategy = null;
|
||||||
final StringEnumOptionData buyDownFeeIncomeType = null;
|
final StringEnumOptionData buyDownFeeIncomeType = null;
|
||||||
final boolean merchantBuyDownFee = false;
|
final boolean merchantBuyDownFee = false;
|
||||||
|
final List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseMappings = null;
|
||||||
|
final List<CodeValueData> writeOffReasonOptions = null;
|
||||||
|
|
||||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||||
@@ -683,7 +693,8 @@ public class LoanProductData implements Serializable {
|
|||||||
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
||||||
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
||||||
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
||||||
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee);
|
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
|
||||||
|
writeOffReasonOptions);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,6 +809,8 @@ public class LoanProductData implements Serializable {
|
|||||||
final StringEnumOptionData buyDownFeeStrategy = null;
|
final StringEnumOptionData buyDownFeeStrategy = null;
|
||||||
final StringEnumOptionData buyDownFeeIncomeType = null;
|
final StringEnumOptionData buyDownFeeIncomeType = null;
|
||||||
final boolean merchantBuyDownFee = false;
|
final boolean merchantBuyDownFee = false;
|
||||||
|
final List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseMappings = null;
|
||||||
|
final List<CodeValueData> writeOffReasonOptions = null;
|
||||||
|
|
||||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||||
@@ -821,7 +834,8 @@ public class LoanProductData implements Serializable {
|
|||||||
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
|
||||||
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
|
||||||
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
|
||||||
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee);
|
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
|
||||||
|
writeOffReasonOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
|
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
|
||||||
@@ -829,6 +843,7 @@ public class LoanProductData implements Serializable {
|
|||||||
final Collection<ChargeToGLAccountMapper> feeToGLAccountMappings,
|
final Collection<ChargeToGLAccountMapper> feeToGLAccountMappings,
|
||||||
final Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings,
|
final Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings,
|
||||||
final List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToGLAccountMappings,
|
final List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToGLAccountMappings,
|
||||||
|
final List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonToGLAccountMappings,
|
||||||
final List<ClassificationToGLAccountData> capitalizedIncomeClassificationToIncomeAccountMappings,
|
final List<ClassificationToGLAccountData> capitalizedIncomeClassificationToIncomeAccountMappings,
|
||||||
final List<ClassificationToGLAccountData> buydownFeeClassificationToIncomeAccountMappings) {
|
final List<ClassificationToGLAccountData> buydownFeeClassificationToIncomeAccountMappings) {
|
||||||
productData.accountingMappings = accountingMappings;
|
productData.accountingMappings = accountingMappings;
|
||||||
@@ -836,6 +851,7 @@ public class LoanProductData implements Serializable {
|
|||||||
productData.feeToIncomeAccountMappings = feeToGLAccountMappings;
|
productData.feeToIncomeAccountMappings = feeToGLAccountMappings;
|
||||||
productData.penaltyToIncomeAccountMappings = penaltyToGLAccountMappings;
|
productData.penaltyToIncomeAccountMappings = penaltyToGLAccountMappings;
|
||||||
productData.chargeOffReasonToExpenseAccountMappings = chargeOffReasonToGLAccountMappings;
|
productData.chargeOffReasonToExpenseAccountMappings = chargeOffReasonToGLAccountMappings;
|
||||||
|
productData.writeOffReasonsToExpenseMappings = writeOffReasonToGLAccountMappings;
|
||||||
productData.capitalizedIncomeClassificationToIncomeAccountMappings = capitalizedIncomeClassificationToIncomeAccountMappings;
|
productData.capitalizedIncomeClassificationToIncomeAccountMappings = capitalizedIncomeClassificationToIncomeAccountMappings;
|
||||||
productData.buydownFeeClassificationToIncomeAccountMappings = buydownFeeClassificationToIncomeAccountMappings;
|
productData.buydownFeeClassificationToIncomeAccountMappings = buydownFeeClassificationToIncomeAccountMappings;
|
||||||
return productData;
|
return productData;
|
||||||
@@ -884,7 +900,9 @@ public class LoanProductData implements Serializable {
|
|||||||
final StringEnumOptionData capitalizedIncomeCalculationType, final StringEnumOptionData capitalizedIncomeStrategy,
|
final StringEnumOptionData capitalizedIncomeCalculationType, final StringEnumOptionData capitalizedIncomeStrategy,
|
||||||
final StringEnumOptionData capitalizedIncomeType, final boolean enableBuyDownFee,
|
final StringEnumOptionData capitalizedIncomeType, final boolean enableBuyDownFee,
|
||||||
final StringEnumOptionData buyDownFeeCalculationType, final StringEnumOptionData buyDownFeeStrategy,
|
final StringEnumOptionData buyDownFeeCalculationType, final StringEnumOptionData buyDownFeeStrategy,
|
||||||
final StringEnumOptionData buyDownFeeIncomeType, final boolean merchantBuyDownFee) {
|
final StringEnumOptionData buyDownFeeIncomeType, final boolean merchantBuyDownFee,
|
||||||
|
final List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseMappings,
|
||||||
|
final List<CodeValueData> writeOffReasonOptions) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.shortName = shortName;
|
this.shortName = shortName;
|
||||||
@@ -971,6 +989,7 @@ public class LoanProductData implements Serializable {
|
|||||||
this.feeToIncomeAccountMappings = null;
|
this.feeToIncomeAccountMappings = null;
|
||||||
this.penaltyToIncomeAccountMappings = null;
|
this.penaltyToIncomeAccountMappings = null;
|
||||||
this.chargeOffReasonToExpenseAccountMappings = null;
|
this.chargeOffReasonToExpenseAccountMappings = null;
|
||||||
|
this.writeOffReasonsToExpenseMappings = null;
|
||||||
this.valueConditionTypeOptions = null;
|
this.valueConditionTypeOptions = null;
|
||||||
this.principalVariationsForBorrowerCycle = principalVariations;
|
this.principalVariationsForBorrowerCycle = principalVariations;
|
||||||
this.interestRateVariationsForBorrowerCycle = interestRateVariations;
|
this.interestRateVariationsForBorrowerCycle = interestRateVariations;
|
||||||
@@ -1046,6 +1065,8 @@ public class LoanProductData implements Serializable {
|
|||||||
this.buyDownFeeCalculationTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class);
|
this.buyDownFeeCalculationTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class);
|
||||||
this.buyDownFeeStrategyOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
|
this.buyDownFeeStrategyOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
|
||||||
this.buyDownFeeIncomeTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
|
this.buyDownFeeIncomeTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
|
||||||
|
this.writeOffReasonsToExpenseMappings = writeOffReasonsToExpenseMappings;
|
||||||
|
this.writeOffReasonOptions = writeOffReasonOptions;
|
||||||
this.capitalizedIncomeClassificationOptions = null;
|
this.capitalizedIncomeClassificationOptions = null;
|
||||||
this.buydownFeeClassificationOptions = null;
|
this.buydownFeeClassificationOptions = null;
|
||||||
this.capitalizedIncomeClassificationToIncomeAccountMappings = null;
|
this.capitalizedIncomeClassificationToIncomeAccountMappings = null;
|
||||||
@@ -1079,8 +1100,8 @@ public class LoanProductData implements Serializable {
|
|||||||
final List<StringEnumOptionData> capitalizedIncomeStrategyOptions,
|
final List<StringEnumOptionData> capitalizedIncomeStrategyOptions,
|
||||||
final List<StringEnumOptionData> capitalizedIncomeTypeOptions,
|
final List<StringEnumOptionData> capitalizedIncomeTypeOptions,
|
||||||
final List<StringEnumOptionData> buyDownFeeCalculationTypeOptions, final List<StringEnumOptionData> buyDownFeeStrategyOptions,
|
final List<StringEnumOptionData> buyDownFeeCalculationTypeOptions, final List<StringEnumOptionData> buyDownFeeStrategyOptions,
|
||||||
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions, final List<CodeValueData> capitalizedIncomeClassificationOptions,
|
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions, final List<CodeValueData> writeOffReasonOptions,
|
||||||
final List<CodeValueData> buydownFeeClassificationOptions) {
|
final List<CodeValueData> capitalizedIncomeClassificationOptions, final List<CodeValueData> buydownFeeClassificationOptions) {
|
||||||
|
|
||||||
this.id = productData.id;
|
this.id = productData.id;
|
||||||
this.name = productData.name;
|
this.name = productData.name;
|
||||||
@@ -1134,6 +1155,8 @@ public class LoanProductData implements Serializable {
|
|||||||
this.feeToIncomeAccountMappings = productData.feeToIncomeAccountMappings;
|
this.feeToIncomeAccountMappings = productData.feeToIncomeAccountMappings;
|
||||||
this.penaltyToIncomeAccountMappings = productData.penaltyToIncomeAccountMappings;
|
this.penaltyToIncomeAccountMappings = productData.penaltyToIncomeAccountMappings;
|
||||||
this.chargeOffReasonToExpenseAccountMappings = productData.chargeOffReasonToExpenseAccountMappings;
|
this.chargeOffReasonToExpenseAccountMappings = productData.chargeOffReasonToExpenseAccountMappings;
|
||||||
|
this.writeOffReasonsToExpenseMappings = productData.writeOffReasonsToExpenseMappings;
|
||||||
|
this.writeOffReasonOptions = writeOffReasonOptions;
|
||||||
|
|
||||||
this.chargeOptions = chargeOptions;
|
this.chargeOptions = chargeOptions;
|
||||||
this.penaltyOptions = penaltyOptions;
|
this.penaltyOptions = penaltyOptions;
|
||||||
|
|||||||
+4
@@ -140,6 +140,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
|||||||
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
|
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
|
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
|
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
|
||||||
|
this.loanProductToGLAccountMappingHelper.saveWriteOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveBuyDownFeeClassificationToIncomeAccountMappings(command, element,
|
this.loanProductToGLAccountMappingHelper.saveBuyDownFeeClassificationToIncomeAccountMappings(command, element,
|
||||||
loanProductId, null);
|
loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveCapitalizedIncomeClassificationToIncomeAccountMappings(command, element,
|
this.loanProductToGLAccountMappingHelper.saveCapitalizedIncomeClassificationToIncomeAccountMappings(command, element,
|
||||||
@@ -237,6 +238,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
|||||||
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
|
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
|
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
|
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
|
||||||
|
this.loanProductToGLAccountMappingHelper.saveWriteOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveBuyDownFeeClassificationToIncomeAccountMappings(command, element,
|
this.loanProductToGLAccountMappingHelper.saveBuyDownFeeClassificationToIncomeAccountMappings(command, element,
|
||||||
loanProductId, null);
|
loanProductId, null);
|
||||||
this.loanProductToGLAccountMappingHelper.saveCapitalizedIncomeClassificationToIncomeAccountMappings(command, element,
|
this.loanProductToGLAccountMappingHelper.saveCapitalizedIncomeClassificationToIncomeAccountMappings(command, element,
|
||||||
@@ -419,6 +421,8 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
|||||||
this.loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, loanProductId, changes);
|
this.loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, loanProductId, changes);
|
||||||
this.loanProductToGLAccountMappingHelper.updateChargeOffReasonToExpenseAccountMappings(command, element, loanProductId,
|
this.loanProductToGLAccountMappingHelper.updateChargeOffReasonToExpenseAccountMappings(command, element, loanProductId,
|
||||||
changes);
|
changes);
|
||||||
|
|
||||||
|
this.loanProductToGLAccountMappingHelper.updateWriteOffReasonToExpenseAccountMappings(command, element, loanProductId, changes);
|
||||||
this.loanProductToGLAccountMappingHelper.updateBuyDownFeeClassificationToIncomeAccountMappings(command, element, loanProductId,
|
this.loanProductToGLAccountMappingHelper.updateBuyDownFeeClassificationToIncomeAccountMappings(command, element, loanProductId,
|
||||||
changes);
|
changes);
|
||||||
this.loanProductToGLAccountMappingHelper.updateCapitalizedIncomeClassificationToIncomeAccountMappings(command, element,
|
this.loanProductToGLAccountMappingHelper.updateCapitalizedIncomeClassificationToIncomeAccountMappings(command, element,
|
||||||
|
|||||||
+9
-2
@@ -53,6 +53,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReas
|
|||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
||||||
|
import org.apache.fineract.accounting.producttoaccountmapping.data.WriteOffReasonsToExpenseAccountMapper;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
|
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
@@ -355,6 +356,7 @@ public class LoanProductsApiResource {
|
|||||||
Collection<ChargeToGLAccountMapper> feeToGLAccountMappings;
|
Collection<ChargeToGLAccountMapper> feeToGLAccountMappings;
|
||||||
Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings;
|
Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings;
|
||||||
List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToGLAccountMappings;
|
List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToGLAccountMappings;
|
||||||
|
List<WriteOffReasonsToExpenseAccountMapper> writeOffReasonsToExpenseAccountMappings;
|
||||||
List<ClassificationToGLAccountData> capitalizedIncomeClassificationToGLAccountMappings;
|
List<ClassificationToGLAccountData> capitalizedIncomeClassificationToGLAccountMappings;
|
||||||
List<ClassificationToGLAccountData> buydowFeeClassificationToGLAccountMappings;
|
List<ClassificationToGLAccountData> buydowFeeClassificationToGLAccountMappings;
|
||||||
if (loanProduct.hasAccountingEnabled()) {
|
if (loanProduct.hasAccountingEnabled()) {
|
||||||
@@ -367,6 +369,8 @@ public class LoanProductsApiResource {
|
|||||||
.fetchPenaltyToIncomeAccountMappingsForLoanProduct(productId);
|
.fetchPenaltyToIncomeAccountMappingsForLoanProduct(productId);
|
||||||
chargeOffReasonToGLAccountMappings = this.accountMappingReadPlatformService
|
chargeOffReasonToGLAccountMappings = this.accountMappingReadPlatformService
|
||||||
.fetchChargeOffReasonMappingsForLoanProduct(productId);
|
.fetchChargeOffReasonMappingsForLoanProduct(productId);
|
||||||
|
writeOffReasonsToExpenseAccountMappings = this.accountMappingReadPlatformService
|
||||||
|
.fetchWriteOffReasonMappingsForLoanProduct(productId);
|
||||||
capitalizedIncomeClassificationToGLAccountMappings = accountMappingReadPlatformService
|
capitalizedIncomeClassificationToGLAccountMappings = accountMappingReadPlatformService
|
||||||
.fetchClassificationMappingsForLoanProduct(productId,
|
.fetchClassificationMappingsForLoanProduct(productId,
|
||||||
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
|
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
|
||||||
@@ -374,7 +378,8 @@ public class LoanProductsApiResource {
|
|||||||
productId, LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
|
productId, LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
|
||||||
loanProduct = LoanProductData.withAccountingDetails(loanProduct, accountingMappings, paymentChannelToFundSourceMappings,
|
loanProduct = LoanProductData.withAccountingDetails(loanProduct, accountingMappings, paymentChannelToFundSourceMappings,
|
||||||
feeToGLAccountMappings, penaltyToGLAccountMappings, chargeOffReasonToGLAccountMappings,
|
feeToGLAccountMappings, penaltyToGLAccountMappings, chargeOffReasonToGLAccountMappings,
|
||||||
capitalizedIncomeClassificationToGLAccountMappings, buydowFeeClassificationToGLAccountMappings);
|
writeOffReasonsToExpenseAccountMappings, capitalizedIncomeClassificationToGLAccountMappings,
|
||||||
|
buydowFeeClassificationToGLAccountMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.isTemplate()) {
|
if (settings.isTemplate()) {
|
||||||
@@ -475,6 +480,8 @@ public class LoanProductsApiResource {
|
|||||||
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
|
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
|
||||||
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions = ApiFacingEnum
|
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions = ApiFacingEnum
|
||||||
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
|
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
|
||||||
|
final List<CodeValueData> writeOffReasonOptions = codeValueReadPlatformService
|
||||||
|
.retrieveCodeValuesByCode(LoanApiConstants.WRITEOFFREASONS);
|
||||||
final List<CodeValueData> capitalizedIncomeClassificationOptions = codeValueReadPlatformService
|
final List<CodeValueData> capitalizedIncomeClassificationOptions = codeValueReadPlatformService
|
||||||
.retrieveCodeValuesByCode(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
|
.retrieveCodeValuesByCode(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
|
||||||
final List<CodeValueData> buydownFeeClassificationOptions = codeValueReadPlatformService
|
final List<CodeValueData> buydownFeeClassificationOptions = codeValueReadPlatformService
|
||||||
@@ -493,7 +500,7 @@ public class LoanProductsApiResource {
|
|||||||
creditAllocationAllocationTypes, supportedInterestRefundTypesOptions, chargeOffBehaviourOptions, chargeOffReasonOptions,
|
creditAllocationAllocationTypes, supportedInterestRefundTypesOptions, chargeOffBehaviourOptions, chargeOffReasonOptions,
|
||||||
daysInYearCustomStrategyOptions, capitalizedIncomeCalculationTypeOptions, capitalizedIncomeStrategyOptions,
|
daysInYearCustomStrategyOptions, capitalizedIncomeCalculationTypeOptions, capitalizedIncomeStrategyOptions,
|
||||||
capitalizedIncomeTypeOptions, buyDownFeeCalculationTypeOptions, buyDownFeeStrategyOptions, buyDownFeeIncomeTypeOptions,
|
capitalizedIncomeTypeOptions, buyDownFeeCalculationTypeOptions, buyDownFeeStrategyOptions, buyDownFeeIncomeTypeOptions,
|
||||||
capitalizedIncomeClassificationOptions, buydownFeeClassificationOptions);
|
writeOffReasonOptions, capitalizedIncomeClassificationOptions, buydownFeeClassificationOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-21
@@ -33,6 +33,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
|
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
|
||||||
@@ -152,7 +153,9 @@ public final class LoanProductDataValidator {
|
|||||||
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
|
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
|
||||||
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
|
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
|
||||||
LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue(),
|
LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue(),
|
||||||
LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(),
|
LoanProductAccountingParams.WRITE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue(),
|
||||||
|
LoanProductAccountingParams.WRITE_OFF_REASON_CODE_VALUE_ID.getValue(),
|
||||||
|
LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(),
|
||||||
LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(),
|
LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(),
|
||||||
LoanProductAccountingParams.INCOME_FROM_CAPITALIZATION.getValue(),
|
LoanProductAccountingParams.INCOME_FROM_CAPITALIZATION.getValue(),
|
||||||
LoanProductAccountingParams.DEFERRED_INCOME_LIABILITY.getValue(), LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(),
|
LoanProductAccountingParams.DEFERRED_INCOME_LIABILITY.getValue(), LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(),
|
||||||
@@ -748,6 +751,7 @@ public final class LoanProductDataValidator {
|
|||||||
validatePaymentChannelFundSourceMappings(baseDataValidator, element);
|
validatePaymentChannelFundSourceMappings(baseDataValidator, element);
|
||||||
validateChargeToIncomeAccountMappings(baseDataValidator, element);
|
validateChargeToIncomeAccountMappings(baseDataValidator, element);
|
||||||
validateChargeOffToExpenseMappings(baseDataValidator, element);
|
validateChargeOffToExpenseMappings(baseDataValidator, element);
|
||||||
|
validateWriteOffToExpenseMappings(baseDataValidator, element);
|
||||||
validateClassificationToIncomeMappings(baseDataValidator, element,
|
validateClassificationToIncomeMappings(baseDataValidator, element,
|
||||||
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
|
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
|
||||||
validateClassificationToIncomeMappings(baseDataValidator, element,
|
validateClassificationToIncomeMappings(baseDataValidator, element,
|
||||||
@@ -2068,54 +2072,79 @@ public final class LoanProductDataValidator {
|
|||||||
|
|
||||||
private void validateChargeOffToExpenseMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
private void validateChargeOffToExpenseMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||||
String parameterName = LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue();
|
String parameterName = LoanProductAccountingParams.CHARGE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue();
|
||||||
|
LoanProductAccountingParams reasonCodeValueId = LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID;
|
||||||
|
String failCode = "chargeOffReason";
|
||||||
|
validateAdditionalAccountMappings(baseDataValidator, element, parameterName, reasonCodeValueId, failCode,
|
||||||
|
productToGLAccountMappingHelper::validateChargeOffMappingsInDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateWriteOffToExpenseMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||||
|
String parameterName = LoanProductAccountingParams.WRITE_OFF_REASON_TO_EXPENSE_ACCOUNT_MAPPINGS.getValue();
|
||||||
|
LoanProductAccountingParams reasonCodeValueId = LoanProductAccountingParams.WRITE_OFF_REASON_CODE_VALUE_ID;
|
||||||
|
String failCode = "writeOffReason";
|
||||||
|
validateAdditionalAccountMappings(baseDataValidator, element, parameterName, reasonCodeValueId, failCode,
|
||||||
|
productToGLAccountMappingHelper::validateWriteOffMappingsInDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateAdditionalAccountMappings(DataValidatorBuilder baseDataValidator, JsonElement element, String parameterName,
|
||||||
|
LoanProductAccountingParams reasonCodeValueIdParam, String failCode,
|
||||||
|
BiConsumer<List<ApiParameterError>, List<JsonObject>> additionalMappingValidator) {
|
||||||
if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
|
if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
|
||||||
final JsonArray chargeOffToExpenseMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
|
final JsonArray reasonToExpenseMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
|
||||||
if (chargeOffToExpenseMappingArray != null && chargeOffToExpenseMappingArray.size() > 0) {
|
if (reasonToExpenseMappingArray != null && !reasonToExpenseMappingArray.isEmpty()) {
|
||||||
Map<Long, Set<Long>> chargeOffReasonToAccounts = new HashMap<>();
|
Map<Long, Set<Long>> reasonToAccounts = new HashMap<>();
|
||||||
List<JsonObject> processedMappings = new ArrayList<>(); // Collect processed mappings for the new method
|
List<JsonObject> processedMappings = new ArrayList<>(); // Collect processed mappings for the new method
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
do {
|
do {
|
||||||
final JsonObject jsonObject = chargeOffToExpenseMappingArray.get(i).getAsJsonObject();
|
final JsonObject jsonObject = reasonToExpenseMappingArray.get(i).getAsJsonObject();
|
||||||
final Long expenseGlAccountId = this.fromApiJsonHelper
|
|
||||||
.extractLongNamed(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), jsonObject);
|
final String expenseGlAccountIdString = this.fromApiJsonHelper
|
||||||
final Long chargeOffReasonCodeValueId = this.fromApiJsonHelper
|
.extractStringNamed(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), jsonObject);
|
||||||
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(), jsonObject);
|
final String reasonCodeValueIdString = this.fromApiJsonHelper.extractStringNamed(reasonCodeValueIdParam.getValue(),
|
||||||
|
jsonObject);
|
||||||
|
|
||||||
// Validate parameters locally
|
// Validate parameters locally
|
||||||
baseDataValidator.reset()
|
baseDataValidator.reset()
|
||||||
.parameter(parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET + DOT
|
.parameter(parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET + DOT
|
||||||
+ LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue())
|
+ LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue())
|
||||||
.value(expenseGlAccountId).notNull().integerGreaterThanZero();
|
.value(expenseGlAccountIdString).notNull().longGreaterThanZero();
|
||||||
baseDataValidator.reset()
|
baseDataValidator.reset().parameter(
|
||||||
.parameter(parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET + DOT
|
parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET + DOT + reasonCodeValueIdParam.getValue())
|
||||||
+ LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue())
|
.value(reasonCodeValueIdString).notNull().longGreaterThanZero();
|
||||||
.value(chargeOffReasonCodeValueId).notNull().integerGreaterThanZero();
|
|
||||||
|
|
||||||
// Handle duplicate charge-off reason and GL Account validation
|
final Long reasonCodeValueId = Long.valueOf(reasonCodeValueIdString);
|
||||||
chargeOffReasonToAccounts.putIfAbsent(chargeOffReasonCodeValueId, new HashSet<>());
|
final Long expenseGlAccountId = Long.valueOf(expenseGlAccountIdString);
|
||||||
Set<Long> associatedAccounts = chargeOffReasonToAccounts.get(chargeOffReasonCodeValueId);
|
// Handle duplicate reason and GL Account validation
|
||||||
|
reasonToAccounts.putIfAbsent(reasonCodeValueId, new HashSet<>());
|
||||||
|
Set<Long> associatedAccounts = reasonToAccounts.get(reasonCodeValueId);
|
||||||
|
|
||||||
if (associatedAccounts.contains(expenseGlAccountId)) {
|
if (associatedAccounts.contains(expenseGlAccountId)) {
|
||||||
baseDataValidator.reset().parameter(parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
|
baseDataValidator.reset().parameter(parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
|
||||||
.failWithCode("duplicate.chargeOffReason.and.glAccount");
|
.failWithCode("duplicate." + failCode + ".and.glAccount");
|
||||||
}
|
}
|
||||||
associatedAccounts.add(expenseGlAccountId);
|
associatedAccounts.add(expenseGlAccountId);
|
||||||
|
|
||||||
if (associatedAccounts.size() > 1) {
|
if (associatedAccounts.size() > 1) {
|
||||||
baseDataValidator.reset().parameter(parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
|
baseDataValidator.reset().parameter(parameterName + OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
|
||||||
.failWithCode("multiple.glAccounts.for.chargeOffReason");
|
.failWithCode("multiple.glAccounts.for." + failCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect mapping for additional validations
|
// Collect mapping for additional validations
|
||||||
processedMappings.add(jsonObject);
|
processedMappings.add(jsonObject);
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
} while (i < chargeOffToExpenseMappingArray.size());
|
} while (i < reasonToExpenseMappingArray.size());
|
||||||
|
|
||||||
// Call the new validation method for additional checks
|
// Call the new validation method for additional checks
|
||||||
productToGLAccountMappingHelper.validateChargeOffMappingsInDatabase(processedMappings);
|
final List<ApiParameterError> validationErrors = new ArrayList<>();
|
||||||
|
productToGLAccountMappingHelper.validateGLAccountInDatabase(validationErrors, processedMappings);
|
||||||
|
if (additionalMappingValidator != null) {
|
||||||
|
additionalMappingValidator.accept(validationErrors, processedMappings);
|
||||||
|
}
|
||||||
|
if (!validationErrors.isEmpty()) {
|
||||||
|
throw new PlatformApiDataValidationException(validationErrors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -606,7 +606,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
|||||||
loanChargeOffBehaviour.getValueAsStringEnumOptionData(), interestRecognitionOnDisbursementDate,
|
loanChargeOffBehaviour.getValueAsStringEnumOptionData(), interestRecognitionOnDisbursementDate,
|
||||||
daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
|
daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
|
||||||
capitalizedIncome, enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType,
|
capitalizedIncome, enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType,
|
||||||
merchantBuyDownFee);
|
merchantBuyDownFee, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,4 +217,5 @@
|
|||||||
<include file="parts/0196_add_deleted_and_closed_to_buy_down_fee_balance.xml" relativeToChangelogFile="true" />
|
<include file="parts/0196_add_deleted_and_closed_to_buy_down_fee_balance.xml" relativeToChangelogFile="true" />
|
||||||
<include file="parts/0197_add_deleted_and_closed_to_capitalized_income_balance.xml" relativeToChangelogFile="true" />
|
<include file="parts/0197_add_deleted_and_closed_to_capitalized_income_balance.xml" relativeToChangelogFile="true" />
|
||||||
<include file="parts/0198_add_classification_id_to_acc_product_mapping.xml" relativeToChangelogFile="true" />
|
<include file="parts/0198_add_classification_id_to_acc_product_mapping.xml" relativeToChangelogFile="true" />
|
||||||
|
<include file="parts/0199_write_off_reason_mapping_loan.xml" relativeToChangelogFile="true" />
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||||
|
|
||||||
|
<changeSet id="17588202536-1" author="fineract">
|
||||||
|
<addColumn tableName="acc_product_mapping">
|
||||||
|
<column name="write_off_reason_id" type="BIGINT">
|
||||||
|
<constraints nullable="true"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+98
@@ -85,6 +85,7 @@ import org.apache.fineract.client.models.PostLoansResponse;
|
|||||||
import org.apache.fineract.client.models.PostRolesRequest;
|
import org.apache.fineract.client.models.PostRolesRequest;
|
||||||
import org.apache.fineract.client.models.PostUsersRequest;
|
import org.apache.fineract.client.models.PostUsersRequest;
|
||||||
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||||
|
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
|
||||||
import org.apache.fineract.client.models.PutLoansApprovedAmountRequest;
|
import org.apache.fineract.client.models.PutLoansApprovedAmountRequest;
|
||||||
import org.apache.fineract.client.models.PutLoansApprovedAmountResponse;
|
import org.apache.fineract.client.models.PutLoansApprovedAmountResponse;
|
||||||
import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountRequest;
|
import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountRequest;
|
||||||
@@ -507,6 +508,103 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
|||||||
.loanScheduleType(LoanScheduleType.CUMULATIVE.toString());//
|
.loanScheduleType(LoanScheduleType.CUMULATIVE.toString());//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PutLoanProductsProductIdRequest update4IProgressive(String name, String shortName, Long delinquencyBucketId) {
|
||||||
|
return new PutLoanProductsProductIdRequest().name(name).shortName(shortName).description("4 installment product - progressive")//
|
||||||
|
.includeInBorrowerCycle(false)//
|
||||||
|
.useBorrowerCycle(false)//
|
||||||
|
.currencyCode("EUR")//
|
||||||
|
.digitsAfterDecimal(2)//
|
||||||
|
.principal(1000.0)//
|
||||||
|
.minPrincipal(100.0)//
|
||||||
|
.maxPrincipal(10000.0)//
|
||||||
|
.numberOfRepayments(4)//
|
||||||
|
.repaymentEvery(1)//
|
||||||
|
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L.intValue())//
|
||||||
|
.interestRatePerPeriod(10D)//
|
||||||
|
.minInterestRatePerPeriod(0D)//
|
||||||
|
.maxInterestRatePerPeriod(120D)//
|
||||||
|
.interestRateFrequencyType(InterestRateFrequencyType.YEARS)//
|
||||||
|
.isLinkedToFloatingInterestRates(false)//
|
||||||
|
.isLinkedToFloatingInterestRates(false)//
|
||||||
|
.allowVariableInstallments(false)//
|
||||||
|
.amortizationType(AmortizationType.EQUAL_INSTALLMENTS)//
|
||||||
|
.interestType(InterestType.DECLINING_BALANCE)//
|
||||||
|
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||||
|
.allowPartialPeriodInterestCalcualtion(false)//
|
||||||
|
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
|
||||||
|
.paymentAllocation(List.of(createDefaultPaymentAllocation("NEXT_INSTALLMENT")))//
|
||||||
|
.creditAllocation(List.of())//
|
||||||
|
.overdueDaysForNPA(179)//
|
||||||
|
.daysInMonthType(30L)//
|
||||||
|
.daysInYearType(360L)//
|
||||||
|
.isInterestRecalculationEnabled(true)//
|
||||||
|
.interestRecalculationCompoundingMethod(0)//
|
||||||
|
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
||||||
|
.recalculationRestFrequencyInterval(1)//
|
||||||
|
.isArrearsBasedOnOriginalSchedule(false)//
|
||||||
|
.isCompoundingToBePostedAsTransaction(false)//
|
||||||
|
.preClosureInterestCalculationStrategy(1)//
|
||||||
|
.allowCompoundingOnEod(false)//
|
||||||
|
.canDefineInstallmentAmount(true)//
|
||||||
|
.repaymentStartDateType(1)//
|
||||||
|
.charges(List.of())//
|
||||||
|
.principalVariationsForBorrowerCycle(List.of())//
|
||||||
|
.interestRateVariationsForBorrowerCycle(List.of())//
|
||||||
|
.numberOfRepaymentVariationsForBorrowerCycle(List.of())//
|
||||||
|
.accountingRule(3)//
|
||||||
|
.canUseForTopup(false)//
|
||||||
|
.fundSourceAccountId(fundSource.getAccountID().longValue())//
|
||||||
|
.loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())//
|
||||||
|
.transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())//
|
||||||
|
.interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromFeeAccountId(feeIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromPenaltyAccountId(penaltyIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromRecoveryAccountId(recoveriesAccount.getAccountID().longValue())//
|
||||||
|
.writeOffAccountId(writtenOffAccount.getAccountID().longValue())//
|
||||||
|
.overpaymentLiabilityAccountId(overpaymentAccount.getAccountID().longValue())//
|
||||||
|
.receivableInterestAccountId(interestReceivableAccount.getAccountID().longValue())//
|
||||||
|
.receivableFeeAccountId(feeReceivableAccount.getAccountID().longValue())//
|
||||||
|
.receivablePenaltyAccountId(penaltyReceivableAccount.getAccountID().longValue())//
|
||||||
|
.goodwillCreditAccountId(goodwillExpenseAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromGoodwillCreditInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromGoodwillCreditFeesAccountId(feeChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromGoodwillCreditPenaltyAccountId(feeChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromChargeOffInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())//
|
||||||
|
.chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())//
|
||||||
|
.dateFormat(DATETIME_PATTERN)//
|
||||||
|
.locale("en")//
|
||||||
|
.enableAccrualActivityPosting(false)//
|
||||||
|
.multiDisburseLoan(true)//
|
||||||
|
.maxTrancheCount(10)//
|
||||||
|
.outstandingLoanBalance(10000.0)//
|
||||||
|
.disallowExpectedDisbursements(true)//
|
||||||
|
.allowApprovedDisbursedAmountsOverApplied(true)//
|
||||||
|
.overAppliedCalculationType("percentage")//
|
||||||
|
.overAppliedNumber(50)//
|
||||||
|
.principalThresholdForLastInstallment(50)//
|
||||||
|
.holdGuaranteeFunds(false)//
|
||||||
|
.accountMovesOutOfNPAOnlyOnArrearsCompletion(false)//
|
||||||
|
.allowAttributeOverrides(new AllowAttributeOverrides()//
|
||||||
|
.amortizationType(true)//
|
||||||
|
.interestType(true)//
|
||||||
|
.transactionProcessingStrategyCode(true)//
|
||||||
|
.interestCalculationPeriodType(true)//
|
||||||
|
.inArrearsTolerance(true)//
|
||||||
|
.repaymentEvery(true)//
|
||||||
|
.graceOnPrincipalAndInterestPayment(true)//
|
||||||
|
.graceOnArrearsAgeing(true)//
|
||||||
|
).isEqualAmortization(false)//
|
||||||
|
.delinquencyBucketId(delinquencyBucketId)//
|
||||||
|
.enableDownPayment(false)//
|
||||||
|
.enableInstallmentLevelDelinquency(false)//
|
||||||
|
.loanScheduleType("PROGRESSIVE")//
|
||||||
|
.loanScheduleProcessingType("HORIZONTAL");
|
||||||
|
}
|
||||||
|
|
||||||
protected PostLoanProductsRequest create4IProgressive() {
|
protected PostLoanProductsRequest create4IProgressive() {
|
||||||
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
|
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
|
||||||
Assertions.assertNotNull(delinquencyBucketId);
|
Assertions.assertNotNull(delinquencyBucketId);
|
||||||
|
|||||||
+125
@@ -19,13 +19,23 @@
|
|||||||
package org.apache.fineract.integrationtests;
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||||
|
import org.apache.fineract.client.models.GetLoanProductsTemplateResponse;
|
||||||
|
import org.apache.fineract.client.models.GetLoanProductsWriteOffReasonOptions;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
import org.apache.fineract.client.models.PostClientsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostCodeValueDataResponse;
|
||||||
|
import org.apache.fineract.client.models.PostCodeValuesDataRequest;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostWriteOffReasonToExpenseAccountMappings;
|
||||||
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
|
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.FineractClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
|
||||||
@@ -36,6 +46,7 @@ import org.junit.jupiter.api.Assertions;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class LoanProductTest extends BaseLoanIntegrationTest {
|
public class LoanProductTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@@ -458,4 +469,118 @@ public class LoanProductTest extends BaseLoanIntegrationTest {
|
|||||||
.buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue())));
|
.buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
public class WriteOffReasonsToExpenseMappings {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteOffReasonToExpenseAccountMapping_shouldFail_on_nonExistingGLAccount_And_nonExistingWriteOffReason() {
|
||||||
|
try {
|
||||||
|
loanProductHelper.createLoanProduct(
|
||||||
|
create4IProgressive().addWriteOffReasonsToExpenseMappingsItem(new PostWriteOffReasonToExpenseAccountMappings()
|
||||||
|
.expenseAccountId("101230023").writeOffReasonCodeValueId("201230023")));
|
||||||
|
Assertions.fail("Should have thrown an IllegalArgumentException");
|
||||||
|
} catch (final RuntimeException ex) {
|
||||||
|
Assertions.assertTrue(
|
||||||
|
ex.getMessage().contains("GL Account with ID 101230023 does not exist or is not an Expense GL account"));
|
||||||
|
Assertions.assertTrue(ex.getMessage().contains("Write-off reason with ID 201230023 does not exist"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteOffReasonToExpenseAccountMapping_shouldFail_on_nonExistingGLAccount_And_Invalid_expenseAccountId() {
|
||||||
|
try {
|
||||||
|
loanProductHelper.createLoanProduct(create4IProgressive().addWriteOffReasonsToExpenseMappingsItem(
|
||||||
|
new PostWriteOffReasonToExpenseAccountMappings().expenseAccountId("asdf323").writeOffReasonCodeValueId("111")));
|
||||||
|
Assertions.fail("Should have thrown an IllegalArgumentException");
|
||||||
|
} catch (final RuntimeException ex) {
|
||||||
|
Assertions.assertTrue(ex.getMessage()
|
||||||
|
.contains("validation.msg.loanproduct.writeOffReasonsToExpenseMappings[0].expenseAccountId.not.a.number"));
|
||||||
|
Assertions.assertTrue(
|
||||||
|
ex.getMessage().contains("The parameter `writeOffReasonsToExpenseMappings[0].expenseAccountId` must be a number."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteOffReasonToExpenseAccountMapping_shouldFail_on_nonExistingGLAccount_And_Invalid_writeOffReasonCodeValueId() {
|
||||||
|
try {
|
||||||
|
loanProductHelper.createLoanProduct(create4IProgressive().addWriteOffReasonsToExpenseMappingsItem(
|
||||||
|
new PostWriteOffReasonToExpenseAccountMappings().expenseAccountId("111").writeOffReasonCodeValueId("asdf323")));
|
||||||
|
Assertions.fail("Should have thrown an IllegalArgumentException");
|
||||||
|
} catch (final RuntimeException ex) {
|
||||||
|
log.info("Exception: {}", ex.getMessage());
|
||||||
|
Assertions.assertTrue(ex.getMessage()
|
||||||
|
.contains("validation.msg.loanproduct.writeOffReasonsToExpenseMappings[0].writeOffReasonCodeValueId.not.a.number"));
|
||||||
|
Assertions.assertTrue(ex.getMessage()
|
||||||
|
.contains("The parameter `writeOffReasonsToExpenseMappings[0].writeOffReasonCodeValueId` must be a number."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteOffReasonsToExpenseMappings() {
|
||||||
|
|
||||||
|
// create Write Off reasons
|
||||||
|
Long reasonCode1 = createTestWriteOffReason();
|
||||||
|
Long reasonCode2 = createTestWriteOffReason();
|
||||||
|
|
||||||
|
// check if write Off reasons appears on loan product template
|
||||||
|
GetLoanProductsTemplateResponse loanProductTemplate = loanProductHelper.getLoanProductTemplate(false);
|
||||||
|
List<GetLoanProductsWriteOffReasonOptions> writeOffReasonOptions = loanProductTemplate.getWriteOffReasonOptions();
|
||||||
|
Assertions.assertNotNull(writeOffReasonOptions);
|
||||||
|
|
||||||
|
boolean isReasonCode1InTemplate = writeOffReasonOptions.stream().map(GetLoanProductsWriteOffReasonOptions::getId)
|
||||||
|
.anyMatch(id -> Objects.equals(id, reasonCode1));
|
||||||
|
boolean isReasonCode2InTemplate = writeOffReasonOptions.stream().map(GetLoanProductsWriteOffReasonOptions::getId)
|
||||||
|
.anyMatch(id -> Objects.equals(id, reasonCode2));
|
||||||
|
Assertions.assertTrue(isReasonCode1InTemplate);
|
||||||
|
Assertions.assertTrue(isReasonCode2InTemplate);
|
||||||
|
|
||||||
|
// Create Test Loan Product
|
||||||
|
String reasonCodeId = reasonCode1.toString();
|
||||||
|
String expenseAccountId = buyDownExpenseAccount.getAccountID().toString();
|
||||||
|
|
||||||
|
Long loanProductId = loanProductHelper.createLoanProduct(
|
||||||
|
create4IProgressive().addWriteOffReasonsToExpenseMappingsItem(new PostWriteOffReasonToExpenseAccountMappings()
|
||||||
|
.expenseAccountId(expenseAccountId).writeOffReasonCodeValueId(reasonCodeId)))
|
||||||
|
.getResourceId();
|
||||||
|
|
||||||
|
// Verify that get loan product API has the corresponding fields
|
||||||
|
GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper.retrieveLoanProductById(loanProductId);
|
||||||
|
List<PostWriteOffReasonToExpenseAccountMappings> writeOffReasonToExpenseAccountMappings = getLoanProductsProductIdResponse
|
||||||
|
.getWriteOffReasonsToExpenseMappings();
|
||||||
|
Assertions.assertNotNull(writeOffReasonToExpenseAccountMappings);
|
||||||
|
Assertions.assertEquals(1, writeOffReasonToExpenseAccountMappings.size());
|
||||||
|
PostWriteOffReasonToExpenseAccountMappings writeOffMapping = writeOffReasonToExpenseAccountMappings.getFirst();
|
||||||
|
Assertions.assertNotNull(writeOffMapping);
|
||||||
|
Assertions.assertEquals(expenseAccountId, writeOffMapping.getExpenseAccountId());
|
||||||
|
Assertions.assertEquals(reasonCodeId, writeOffMapping.getWriteOffReasonCodeValueId());
|
||||||
|
|
||||||
|
List<GetLoanProductsWriteOffReasonOptions> writeOffReasonOptionsResultNonTemplate = getLoanProductsProductIdResponse
|
||||||
|
.getWriteOffReasonOptions();
|
||||||
|
if (writeOffReasonOptionsResultNonTemplate != null && !writeOffReasonOptionsResultNonTemplate.isEmpty()) {
|
||||||
|
Assertions.fail("Write-off reason options with no template setting should be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// test Update loan product API - delete writeOffReasonsToExpenseMappings
|
||||||
|
|
||||||
|
GetLoanProductsProductIdResponse getLoanProductsProductId = loanProductHelper.retrieveLoanProductById(loanProductId);
|
||||||
|
|
||||||
|
loanProductHelper.updateLoanProductById(loanProductId,
|
||||||
|
update4IProgressive(getLoanProductsProductId.getName(), getLoanProductsProductId.getShortName(),
|
||||||
|
getLoanProductsProductId.getDelinquencyBucket().getId()).writeOffReasonsToExpenseMappings(List.of()));
|
||||||
|
|
||||||
|
// Verify that get loan product API has the corresponding fields
|
||||||
|
Assertions.assertNull(loanProductHelper.retrieveLoanProductById(loanProductId).getWriteOffReasonsToExpenseMappings());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long createTestWriteOffReason() {
|
||||||
|
PostCodeValueDataResponse response = okR(FineractClientHelper.getFineractClient().codeValues.createCodeValue(26L,
|
||||||
|
new PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("TestWriteOffReason_1_", 6))
|
||||||
|
.description("Test write off reason value 1").isActive(true).position(0)))
|
||||||
|
.body();
|
||||||
|
Assertions.assertNotNull(response);
|
||||||
|
Assertions.assertNotNull(response.getSubResourceId());
|
||||||
|
return response.getSubResourceId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -834,7 +834,7 @@ public class LoanProductTestBuilder {
|
|||||||
}
|
}
|
||||||
Map<String, Long> newMap = new HashMap<>();
|
Map<String, Long> newMap = new HashMap<>();
|
||||||
newMap.put("chargeOffReasonCodeValueId", reasonId);
|
newMap.put("chargeOffReasonCodeValueId", reasonId);
|
||||||
newMap.put("expenseGLAccountId", accountId);
|
newMap.put("expenseAccountId", accountId);
|
||||||
this.chargeOffReasonToExpenseAccountMappings.add(newMap);
|
this.chargeOffReasonToExpenseAccountMappings.add(newMap);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user