Add new Advanced accounting mapping: Charge-off reason

This commit is contained in:
Adam Saghy
2024-11-25 10:20:47 -06:00
committed by Jose Alberto Hernandez
parent a736f4bf74
commit c2d72c96ae
18 changed files with 183 additions and 28 deletions
@@ -914,6 +914,34 @@
</div>
<div *ngIf="chargeOffReasonsToExpenseMappings.length" fxFlexFill fxLayout="row wrap"
fxLayout.lt-md="column">
<h4 class="mat-h4" fxFlexFill>{{'labels.heading.Map Charge-off reasons to Expense accounts' | translate}}</h4>
<table fxFlexFill class="mat-elevation-z1" mat-table [dataSource]="chargeOffReasonsToExpenseMappings">
<ng-container matColumnDef="chargeOffReasonCodeValueId">
<th mat-header-cell *matHeaderCellDef> {{'labels.inputs.Charge-off reason' | translate}} </th>
<td mat-cell *matCellDef="let chargeOffReasonsToExpenseMapping">
{{ chargeOffReasonsToExpenseMapping.chargeOffReason.name }}
</td>
</ng-container>
<ng-container matColumnDef="expenseGLAccountId">
<th mat-header-cell *matHeaderCellDef> {{'labels.inputs.Expense Account' | translate}} </th>
<td mat-cell *matCellDef="let chargeOffReasonsToExpenseMapping">
{{ chargeOffReasonsToExpenseMapping.expenseGLAccount.name }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="chargeOffReasonExpenseDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: chargeOffReasonExpenseDisplayedColumns;"></tr>
</table>
</div>
</div>
</div>
@@ -1,6 +1,6 @@
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { DelinquencyBucket, LoanProduct } from '../../models/loan-product.model';
import { AccountingMapping, Charge, ChargeToIncomeAccountMapping, GLAccount, PaymentChannelToFundSourceMapping, PaymentType, PaymentTypeOption } from '../../../../shared/models/general.model';
import {AccountingMapping, Charge, ChargeOffReasonsToExpenseMapping, ChargeToIncomeAccountMapping, GLAccount, PaymentChannelToFundSourceMapping, PaymentType, PaymentTypeOption} from '../../../../shared/models/general.model';
import { AdvancePaymentAllocationData, CreditAllocation, PaymentAllocation } from '../../loan-product-stepper/loan-product-payment-strategy-step/payment-allocation-model';
import { LoanProducts } from '../../loan-products';
import { CodeName, OptionData, StringEnumOptionData } from '../../../../shared/models/option-data.model';
@@ -25,6 +25,7 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
chargesDisplayedColumns: string[] = ['name', 'chargeCalculationType', 'amount', 'chargeTimeType'];
paymentFundSourceDisplayedColumns: string[] = ['paymentTypeId', 'fundSourceAccountId'];
feesPenaltyIncomeDisplayedColumns: string[] = ['chargeId', 'incomeAccountId'];
chargeOffReasonExpenseDisplayedColumns: string[] = ['chargeOffReasonCodeValueId', 'expenseGLAccountId'];
accountingRuleData: string[] = [];
isAdvancedPaymentAllocation = false;
@@ -35,6 +36,7 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
paymentChannelToFundSourceMappings: PaymentChannelToFundSourceMapping[] = [];
feeToIncomeAccountMappings: ChargeToIncomeAccountMapping[] = [];
penaltyToIncomeAccountMappings: ChargeToIncomeAccountMapping[] = [];
chargeOffReasonsToExpenseMappings: ChargeOffReasonsToExpenseMapping[] = [];
constructor(private accounting: Accounting) { }
@@ -63,6 +65,7 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
this.paymentChannelToFundSourceMappings = this.loanProduct.paymentChannelToFundSourceMappings || [];
this.feeToIncomeAccountMappings = this.loanProduct.feeToIncomeAccountMappings || [];
this.penaltyToIncomeAccountMappings = this.loanProduct.penaltyToIncomeAccountMappings || [];
this.chargeOffReasonsToExpenseMappings = this.loanProduct.chargeOffReasonsToExpenseMappings || [];
} else {
this.accountingMappings = {};
@@ -128,6 +131,18 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
});
});
}
this.chargeOffReasonsToExpenseMappings = [];
if (this.loanProduct.chargeOffReasonsToExpenseMappings?.length > 0) {
this.loanProduct.chargeOffReasonsToExpenseMappings.forEach((m: ChargeOffReasonsToExpenseMapping) => {
this.chargeOffReasonsToExpenseMappings.push({
chargeOffReasonCodeValueId: m.chargeOffReasonCodeValueId,
chargeOffReason: this.optionDataLookUp(m.chargeOffReasonCodeValueId, this.loanProductsTemplate.chargeOffReasonOptions),
expenseGLAccountId: m.expenseGLAccountId,
expenseGLAccount: this.glAccountLookUp(m.expenseGLAccountId, expenseAccountData)
});
});
}
}
if (this.loanProduct.isInterestRecalculationEnabled) {
@@ -326,7 +341,8 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
isAdvancedAccountingEnabled(): boolean {
return (this.loanProduct.paymentChannelToFundSourceMappings?.length > 0
|| this.loanProduct.feeToIncomeAccountMappings?.length > 0
|| this.loanProduct.penaltyToIncomeAccountMappings?.length > 0);
|| this.loanProduct.penaltyToIncomeAccountMappings?.length > 0
|| this.loanProduct.chargeOffReasonsToExpenseMappings?.length > 0);
}
getAccountingRuleName(value: string): string {
@@ -334,6 +334,49 @@
</table>
<h4 fxFlex="33%" class="mat-h4">{{'labels.heading.Map Charge-off reasons to Expense accounts' | translate}}</h4>
<div fxFlex="63%">
<button type="button" mat-raised-button color="primary" (click)="add('ChargeOffReasonExpense', chargeOffReasonsToExpenseMappings)">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{'labels.buttons.Add' | translate}}
</button>
</div>
<table fxFlex="98%" class="mat-elevation-z1" mat-table [dataSource]="chargeOffReasonsToExpenseMappings.value" *ngIf="chargeOffReasonsToExpenseMappings.value.length !== 0">
<ng-container matColumnDef="chargeOffReasonCodeValueId">
<th mat-header-cell *matHeaderCellDef> {{'labels.inputs.Charge-off reason' | translate}} </th>
<td mat-cell *matCellDef="let chargeOffReasonsToExpenseMapping">
{{ chargeOffReasonsToExpenseMapping.chargeOffReasonCodeValueId | find:chargeOffReasonOptions:'id':'name' }}
</td>
</ng-container>
<ng-container matColumnDef="expenseGLAccountId">
<th mat-header-cell *matHeaderCellDef> {{'labels.inputs.Expense Account' | translate}} </th>
<td mat-cell *matCellDef="let chargeOffReasonsToExpenseMapping">
{{ chargeOffReasonsToExpenseMapping.expenseGLAccountId | find:expenseAccountData:'id':'name' }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> {{'labels.inputs.Actions' | translate}} </th>
<td mat-cell *matCellDef="let chargeOffReasonsToExpenseMapping; let i = index">
<button mat-icon-button color="primary" (click)="edit('ChargeOffReasonExpense', chargeOffReasonsToExpenseMappings, i)">
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(chargeOffReasonsToExpenseMappings, i)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="chargeOffReasonExpenseDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: chargeOffReasonExpenseDisplayedColumns;"></tr>
</table>
</div>
</div>
@@ -8,6 +8,7 @@ import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.componen
import { TranslateService } from '@ngx-translate/core';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
import {ChargeOffReasonsToExpenseMapping} from '../../../../shared/models/general.model';
@Component({
selector: 'mifosx-loan-product-accounting-step',
@@ -31,9 +32,11 @@ export class LoanProductAccountingStepComponent implements OnInit {
liabilityAccountData: any;
incomeAndLiabilityAccountData: any;
assetAndLiabilityAccountData: any;
chargeOffReasonOptions: any;
paymentFundSourceDisplayedColumns: string[] = ['paymentTypeId', 'fundSourceAccountId', 'actions'];
feesPenaltyIncomeDisplayedColumns: string[] = ['chargeId', 'incomeAccountId', 'actions'];
chargeOffReasonExpenseDisplayedColumns: string[] = ['chargeOffReasonCodeValueId', 'expenseGLAccountId', 'actions'];
constructor(private formBuilder: UntypedFormBuilder,
public dialog: MatDialog,
@@ -52,6 +55,7 @@ export class LoanProductAccountingStepComponent implements OnInit {
this.liabilityAccountData = this.loanProductsTemplate.accountingMappingOptions.liabilityAccountOptions || [];
this.incomeAndLiabilityAccountData = this.incomeAccountData.concat(this.liabilityAccountData);
this.assetAndLiabilityAccountData = this.loanProductsTemplate.accountingMappingOptions.assetAndLiabilityAccountOptions || [];
this.chargeOffReasonOptions = this.loanProductsTemplate.chargeOffReasonOptions || [];
this.loanProductAccountingForm.patchValue({
'accountingRule': this.loanProductsTemplate.accountingRule.id
@@ -90,7 +94,7 @@ export class LoanProductAccountingStepComponent implements OnInit {
'incomeFromGoodwillCreditInterestAccountId': accountingMappings.incomeFromGoodwillCreditInterestAccount ? accountingMappings.incomeFromGoodwillCreditInterestAccount.id : '',
'incomeFromGoodwillCreditFeesAccountId': accountingMappings.incomeFromGoodwillCreditFeesAccount ? accountingMappings.incomeFromGoodwillCreditFeesAccount.id : '',
'incomeFromGoodwillCreditPenaltyAccountId': accountingMappings.incomeFromGoodwillCreditPenaltyAccount ? accountingMappings.incomeFromGoodwillCreditPenaltyAccount.id : '',
'advancedAccountingRules': (this.loanProductsTemplate.paymentChannelToFundSourceMappings || this.loanProductsTemplate.feeToIncomeAccountMappings || this.loanProductsTemplate.penaltyToIncomeAccountMappings) ? true : false
'advancedAccountingRules': (this.loanProductsTemplate.paymentChannelToFundSourceMappings || this.loanProductsTemplate.feeToIncomeAccountMappings || this.loanProductsTemplate.penaltyToIncomeAccountMappings || this.loanProductsTemplate.chargeOffReasonsToExpenseMappings) ? true : false
});
this.loanProductAccountingForm.setControl('paymentChannelToFundSourceMappings',
@@ -102,6 +106,9 @@ export class LoanProductAccountingStepComponent implements OnInit {
this.loanProductAccountingForm.setControl('penaltyToIncomeAccountMappings',
this.formBuilder.array((this.loanProductsTemplate.penaltyToIncomeAccountMappings || []).map((penaltyIncome: any) =>
({ chargeId: penaltyIncome.charge.id, incomeAccountId: penaltyIncome.incomeAccount.id }))));
this.loanProductAccountingForm.setControl('chargeOffReasonsToExpenseMappings',
this.formBuilder.array((this.loanProductsTemplate.chargeOffReasonsToExpenseMappings || []).map((m: ChargeOffReasonsToExpenseMapping) =>
({ chargeOffReasonCodeValueId: m.chargeOffReasonCodeValueId, expenseGLAccountId: m.expenseGLAccountId }))));
}
}
@@ -141,10 +148,12 @@ export class LoanProductAccountingStepComponent implements OnInit {
this.loanProductAccountingForm.addControl('paymentChannelToFundSourceMappings', this.formBuilder.array([]));
this.loanProductAccountingForm.addControl('feeToIncomeAccountMappings', this.formBuilder.array([]));
this.loanProductAccountingForm.addControl('penaltyToIncomeAccountMappings', this.formBuilder.array([]));
this.loanProductAccountingForm.addControl('chargeOffReasonsToExpenseMappings', this.formBuilder.array([]));
} else {
this.loanProductAccountingForm.removeControl('paymentChannelToFundSourceMappings');
this.loanProductAccountingForm.removeControl('feeToIncomeAccountMappings');
this.loanProductAccountingForm.removeControl('penaltyToIncomeAccountMappings');
this.loanProductAccountingForm.removeControl('chargeOffReasonsToExpenseMappings');
}
});
} else {
@@ -195,6 +204,10 @@ export class LoanProductAccountingStepComponent implements OnInit {
return this.loanProductAccountingForm.get('penaltyToIncomeAccountMappings') as UntypedFormArray;
}
get chargeOffReasonsToExpenseMappings(): UntypedFormArray {
return this.loanProductAccountingForm.get('chargeOffReasonsToExpenseMappings') as UntypedFormArray;
}
setLoanProductAccountingFormDirty() {
if (this.loanProductAccountingForm.pristine) {
this.loanProductAccountingForm.markAsDirty();
@@ -240,6 +253,7 @@ export class LoanProductAccountingStepComponent implements OnInit {
case 'PaymentFundSource': return { title: 'Configure Fund Sources for Payment Channels', formfields: this.getPaymentFundSourceFormfields(values) };
case 'FeesIncome': return { title: 'Map Fees to Income Accounts', formfields: this.getFeesIncomeFormfields(values) };
case 'PenaltyIncome': return { title: 'Map Penalties to Specific Income Accounts', formfields: this.getPenaltyIncomeFormfields(values) };
case 'ChargeOffReasonExpense': return { title: 'Map Charge-off reasons to Expense accounts', formfields: this.getChargeOffReasonExpenseFormfields(values) };
}
}
@@ -309,6 +323,28 @@ export class LoanProductAccountingStepComponent implements OnInit {
return formfields;
}
getChargeOffReasonExpenseFormfields(values?: any) {
const formfields: FormfieldBase[] = [
new SelectBase({
controlName: 'chargeOffReasonCodeValueId',
label: 'Charge-off reason',
value: values ? values.chargeOffReasonCodeValueId : this.chargeOffReasonOptions[0].id,
options: { label: 'name', value: 'id', data: this.chargeOffReasonOptions },
required: true,
order: 1
}),
new SelectBase({
controlName: 'expenseGLAccountId',
label: 'Expense Account',
value: values ? values.expenseGLAccountId : this.expenseAccountData[0].id,
options: { label: 'name', value: 'id', data: this.expenseAccountData },
required: true,
order: 2
})
];
return formfields;
}
get isAccountingAccrualBased() {
const accountingRule = this.loanProductAccountingForm.value.accountingRule;
return accountingRule === 3 || accountingRule === 4;
@@ -1,4 +1,4 @@
import { AccountingMapping, ChargeToIncomeAccountMapping, Currency, PaymentChannelToFundSourceMapping } from 'app/shared/models/general.model';
import {AccountingMapping, ChargeOffReasonsToExpenseMapping, ChargeToIncomeAccountMapping, Currency, PaymentChannelToFundSourceMapping} from 'app/shared/models/general.model';
import { OptionData, StringEnumOptionData } from 'app/shared/models/option-data.model';
import { CreditAllocation, PaymentAllocation } from '../loan-product-stepper/loan-product-payment-strategy-step/payment-allocation-model';
@@ -124,6 +124,7 @@ export interface LoanProduct {
paymentChannelToFundSourceMappings?: PaymentChannelToFundSourceMapping[];
feeToIncomeAccountMappings?: ChargeToIncomeAccountMapping[];
penaltyToIncomeAccountMappings?: ChargeToIncomeAccountMapping[];
chargeOffReasonsToExpenseMappings?: ChargeOffReasonsToExpenseMapping[];
enableAccrualActivityPosting?: boolean;
supportedInterestRefundTypes?: StringEnumOptionData[];
}
+7
View File
@@ -39,6 +39,13 @@ export interface PaymentChannelToFundSourceMapping {
fundSourceAccount: AccountingMapping;
}
export interface ChargeOffReasonsToExpenseMapping {
chargeOffReasonCodeValueId: number;
expenseGLAccountId: number;
chargeOffReason?: OptionData;
expenseGLAccount?: AccountingMapping;
}
export interface PaymentType {
id: number;
name: string;