Skip to content

Commit

Permalink
FINERACT-2113: Advanced Charge-off Expense Accounting - Add new "Adva…
Browse files Browse the repository at this point in the history
…nced Accounting Rule"
  • Loading branch information
kulminsky committed Nov 20, 2024
1 parent 6286d1f commit 2c8773f
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,13 @@ public class ProductToGLAccountMapping extends AbstractPersistableCustom<Long> {
@Column(name = "financial_account_type", nullable = true)
private int financialAccountType;

@Column(name = "charge_off_reason_id", nullable = true)
private Long chargeOffReasonId;

public static ProductToGLAccountMapping createNew(final GLAccount glAccount, final Long productId, final int productType,
final int financialAccountType) {
final int financialAccountType, final Long chargeOffReasonId) {

return new ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId).setProductType(productType)
.setFinancialAccountType(financialAccountType);
.setFinancialAccountType(financialAccountType).setChargeOffReasonId(chargeOffReasonId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ProductToGLAccountMapping findProductIdAndProductTypeAndFinancialAccountTypeAndC
@Param("productType") int productType, @Param("financialAccountType") int financialAccountType,
@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")
@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.chargeOffReasonId is NULL")
ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType,
@Param("financialAccountType") int financialAccountType);

Expand All @@ -61,4 +61,8 @@ List<ProductToGLAccountMapping> findAllPenaltyToIncomeAccountMappings(@Param("pr
@Param("productType") int productType);

List<ProductToGLAccountMapping> findByProductIdAndProductType(Long productId, int productType);

@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.chargeOffReasonId is not NULL")
List<ProductToGLAccountMapping> findAllChargesOffReasonsMappings(@Param("productId") Long productId,
@Param("productType") int productType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,27 @@ public void saveChargesToGLAccountMappings(final JsonCommand command, final Json
}
}

public void saveChargeOffReasonToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {

final String arrayName = LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue();
final JsonArray chargeOffReasonToExpenseAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);

if (chargeOffReasonToExpenseAccountMappingArray != null) {
if (changes != null) {
changes.put(arrayName, command.jsonFragment(arrayName));
}

for (int i = 0; i < chargeOffReasonToExpenseAccountMappingArray.size(); i++) {
final JsonObject jsonObject = chargeOffReasonToExpenseAccountMappingArray.get(i).getAsJsonObject();
final Long reasonId = jsonObject.get(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue()).getAsLong();
final Long expenseAccountId = jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();

saveChargeOffReasonToExpenseMapping(productId, reasonId, expenseAccountId, portfolioProductType);
}
}
}

/**
* @param command
* @param element
Expand Down Expand Up @@ -356,6 +377,66 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command,
}
}

public void updateChargeOffReasonToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes, final PortfolioProductType portfolioProductType) {

final List<ProductToGLAccountMapping> existingChargeOffReasonToGLAccountMappings = this.accountMappingRepository
.findAllChargesOffReasonsMappings(productId, portfolioProductType.getValue());
final JsonArray chargeOffReasonToGLAccountMappingArray = this.fromApiJsonHelper
.extractJsonArrayNamed(LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue(), element);

final Map<Long, Long> inputChargeOffReasonToGLAccountMap = new HashMap<>();

final Set<Long> existingChargeOffReasons = new HashSet<>();
if (chargeOffReasonToGLAccountMappingArray != null) {
if (changes != null) {
changes.put(LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue(),
command.jsonFragment(LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue()));
}

for (int i = 0; i < chargeOffReasonToGLAccountMappingArray.size(); i++) {
final JsonObject jsonObject = chargeOffReasonToGLAccountMappingArray.get(i).getAsJsonObject();
final Long expenseGlAccountId = jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();
final Long chargeOffReasonCodeValueId = jsonObject
.get(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue()).getAsLong();
inputChargeOffReasonToGLAccountMap.put(chargeOffReasonCodeValueId, expenseGlAccountId);
}

// If input map is empty, delete all existing mappings
if (inputChargeOffReasonToGLAccountMap.isEmpty()) {
this.accountMappingRepository.deleteAllInBatch(existingChargeOffReasonToGLAccountMappings);
} else {
for (final ProductToGLAccountMapping existingChargeOffReasonToGLAccountMapping : existingChargeOffReasonToGLAccountMappings) {
final Long currentChargeOffReasonId = existingChargeOffReasonToGLAccountMapping.getChargeOffReasonId();
if (currentChargeOffReasonId != null) {
existingChargeOffReasons.add(currentChargeOffReasonId);
// update existing mappings (if required)
if (inputChargeOffReasonToGLAccountMap.containsKey(currentChargeOffReasonId)) {
final Long newGLAccountId = inputChargeOffReasonToGLAccountMap.get(currentChargeOffReasonId);
if (!newGLAccountId.equals(existingChargeOffReasonToGLAccountMapping.getGlAccount().getId())) {
final GLAccount glAccount = getAccountById(LoanProductAccountingParams.FUND_SOURCE.getValue(),
newGLAccountId);
if (glAccount != null) {
existingChargeOffReasonToGLAccountMapping.setGlAccount(glAccount);
this.accountMappingRepository.saveAndFlush(existingChargeOffReasonToGLAccountMapping);
}
}
} // deleted payment type
else {
this.accountMappingRepository.delete(existingChargeOffReasonToGLAccountMapping);
}
}
}

// only the newly added
for (Map.Entry<Long, Long> entry : inputChargeOffReasonToGLAccountMap.entrySet().stream()
.filter(e -> !existingChargeOffReasons.contains(e.getKey())).toList()) {
saveChargeOffReasonToExpenseMapping(productId, entry.getKey(), entry.getValue(), portfolioProductType);
}
}
}
}

/**
* @param productId
*
Expand Down Expand Up @@ -402,6 +483,25 @@ private void saveChargeToFundSourceMapping(final Long productId, final Long char
this.accountMappingRepository.saveAndFlush(accountMapping);
}

private void saveChargeOffReasonToExpenseMapping(final Long productId, final Long reasonId, final Long expenseAccountId,
final PortfolioProductType portfolioProductType) {

final GLAccount glAccount = getAccountByIdAndType(LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue(),
GLAccountType.EXPENSE, expenseAccountId);

final boolean reasonMappingExists = this.accountMappingRepository
.findAllChargesOffReasonsMappings(productId, portfolioProductType.getValue()).stream()
.anyMatch(mapping -> mapping.getChargeOffReasonId().equals(reasonId));

if (glAccount != null && !reasonMappingExists) {
final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId)
.setProductType(portfolioProductType.getValue())
.setFinancialAccountType(CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue()).setChargeOffReasonId(reasonId);

this.accountMappingRepository.saveAndFlush(accountMapping);
}
}

private List<GLAccountType> getAllowedAccountTypesForFeeMapping() {
List<GLAccountType> allowedAccountTypes = new ArrayList<>();
allowedAccountTypes.add(GLAccountType.INCOME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@ public interface ProductToGLAccountMappingReadPlatformService {
List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForShareProduct(Long productId);

List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForShareProduct(Long productId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,10 @@ public enum LoanProductAccountingParams {
INCOME_FROM_CHARGE_OFF_PENALTY("incomeFromChargeOffPenaltyAccountId"), //
INCOME_FROM_GOODWILL_CREDIT_INTEREST("incomeFromGoodwillCreditInterestAccountId"), //
INCOME_FROM_GOODWILL_CREDIT_FEES("incomeFromGoodwillCreditFeesAccountId"), //
INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccountId"); //
INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccountId"), //
CHARGE_OFF_REASONS_TO_EXPENSE("chargeOffReasonsToExpenseMappings"), //
EXPENSE_GL_ACCOUNT_ID("expenseGLAccountId"), //
CHARGE_OFF_REASON_CODE_VALUE_ID("chargeOffReasonCodeValueId"); //

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ public void saveChargesToIncomeAccountMappings(final JsonCommand command, final
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false);
}

public void saveChargeOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
saveChargeOffReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN);
}

public void updateChargeOffReasonToExpenseAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
updateChargeOffReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN);
}

public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// update both fee and penalty charges
Expand Down Expand Up @@ -387,5 +397,4 @@ private GLAccountType getGLAccountType(final JsonElement element, final String p
}
return gLAccountType;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ private PostLoanProductsRequest() {}
public Long incomeFromGoodwillCreditPenaltyAccountId;
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
public List<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings;
public List<GetLoanProductsProductIdResponse.GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings;
public List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;

// Multi Disburse
Expand Down Expand Up @@ -1190,6 +1191,16 @@ private GetLoanPaymentChannelToFundSourceMappings() {}
public Long fundSourceAccountId;
}

static final class GetChargeOffReasonsToExpenseMappings {

private GetChargeOffReasonsToExpenseMappings() {}

@Schema(example = "1")
public Long chargeOffReasonCodeValueId;
@Schema(example = "12")
public Long expenseGLAccountId;
}

static final class GetLoanFeeToIncomeAccountMappings {

private GetLoanFeeToIncomeAccountMappings() {}
Expand Down Expand Up @@ -1300,6 +1311,7 @@ private GetLoanCharge() {}
public GetLoanAccountingMappings accountingMappings;
public Set<GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
public Set<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings;
public Set<GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings;
@Schema(example = "false")
public Boolean isRatesEnabled;
@Schema(example = "true")
Expand Down Expand Up @@ -1557,6 +1569,7 @@ private PutLoanProductsProductIdRequest() {}
public Long incomeFromChargeOffPenaltyAccountId;
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
public List<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings;
public List<GetLoanProductsProductIdResponse.GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings;
public List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
@Schema(example = "false")
public Boolean enableAccrualActivityPosting;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public class LoanProductData implements Serializable {
private Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
private Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
private Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
private List<ChargeToGLAccountMapper> chargeOffReasonsToExpenseMappings;
private final boolean enableAccrualActivityPosting;

// rates
Expand Down Expand Up @@ -850,6 +851,7 @@ public LoanProductData(final Long id, final String name, final String shortName,
this.paymentChannelToFundSourceMappings = null;
this.feeToIncomeAccountMappings = null;
this.penaltyToIncomeAccountMappings = null;
this.chargeOffReasonsToExpenseMappings = null;
this.valueConditionTypeOptions = null;
this.principalVariationsForBorrowerCycle = principalVariations;
this.interestRateVariationsForBorrowerCycle = interestRateVariations;
Expand Down Expand Up @@ -988,6 +990,7 @@ public LoanProductData(final LoanProductData productData, final Collection<Charg
this.paymentChannelToFundSourceMappings = productData.paymentChannelToFundSourceMappings;
this.feeToIncomeAccountMappings = productData.feeToIncomeAccountMappings;
this.penaltyToIncomeAccountMappings = productData.penaltyToIncomeAccountMappings;
this.chargeOffReasonsToExpenseMappings = productData.chargeOffReasonsToExpenseMappings;

this.chargeOptions = chargeOptions;
this.penaltyOptions = penaltyOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public void createLoanProductToGLAccountMapping(final Long loanProductId, final
// advanced accounting mappings
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
break;
case ACCRUAL_UPFRONT:
// Fall Through
Expand Down Expand Up @@ -208,6 +209,7 @@ public void createLoanProductToGLAccountMapping(final Long loanProductId, final
// advanced accounting mappings
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command, element, loanProductId, null);
break;
}
}
Expand Down Expand Up @@ -379,6 +381,8 @@ public Map<String, Object> updateLoanProductToGLAccountMapping(final Long loanPr
accountingRuleType);
this.loanProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, loanProductId, changes);
this.loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, loanProductId, changes);
this.loanProductToGLAccountMappingHelper.updateChargeOffReasonToExpenseAccountMappings(command, element, loanProductId,
changes);
}
return changes;
}
Expand Down
Loading

0 comments on commit 2c8773f

Please sign in to comment.