Skip to content

Commit

Permalink
Add new Advanced accounting mapping: Charge-off reason
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsaghy authored and alberto-art3ch committed Nov 25, 2024
1 parent a736f4b commit c2d72c9
Show file tree
Hide file tree
Showing 18 changed files with 183 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,34 @@ <h4 class="mat-h4" fxFlexFill>{{'labels.heading.Map Penalties to Specific Income

</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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -35,6 +36,7 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
paymentChannelToFundSourceMappings: PaymentChannelToFundSourceMapping[] = [];
feeToIncomeAccountMappings: ChargeToIncomeAccountMapping[] = [];
penaltyToIncomeAccountMappings: ChargeToIncomeAccountMapping[] = [];
chargeOffReasonsToExpenseMappings: ChargeOffReasonsToExpenseMapping[] = [];

constructor(private accounting: Accounting) { }

Expand Down Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,49 @@ <h4 fxFlex="33%" class="mat-h4">{{'labels.heading.Map Penalties to Specific Inco

</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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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',
Expand All @@ -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 }))));
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) };
}
}

Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/app/products/loan-products/models/loan-product.model.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -124,6 +124,7 @@ export interface LoanProduct {
paymentChannelToFundSourceMappings?: PaymentChannelToFundSourceMapping[];
feeToIncomeAccountMappings?: ChargeToIncomeAccountMapping[];
penaltyToIncomeAccountMappings?: ChargeToIncomeAccountMapping[];
chargeOffReasonsToExpenseMappings?: ChargeOffReasonsToExpenseMapping[];
enableAccrualActivityPosting?: boolean;
supportedInterestRefundTypes?: StringEnumOptionData[];
}
Expand Down
7 changes: 7 additions & 0 deletions src/app/shared/models/general.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions src/assets/translations/cs-CS.json
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,8 @@
"Working Days": "Pracovní dny",
"You have created": "Vytvořili jste",
"You can drag and drop the rows to set a Payment Allocations order": "Řádky můžete přetáhnout a nastavit tak příkaz přidělení plateb",
"successfully select option": "úspěšně. Chcete-li pokračovat dále, vyberte si z níže uvedených možností."
"successfully select option": "úspěšně. Chcete-li pokračovat dále, vyberte si z níže uvedených možností.",
"Map Charge-off reasons to Expense accounts": "Mapujte důvody pro zúčtování na výdajové účty"
},
"inputs": {
"accounting": {
Expand Down Expand Up @@ -2422,7 +2423,8 @@
"valid": "platný",
"INTEREST REFUND": "VRÁCENÍ ÚROKU",
"Supported Interest Refund Types": "Unterstützte Zinsrückerstattungsarten",
"SUPPORTED REFUND TRANSACTIONS FOR INTEREST REFUND": "Podporované typy vrácení úroků"
"SUPPORTED REFUND TRANSACTIONS FOR INTEREST REFUND": "Podporované typy vrácení úroků",
"Charge-off reason": "Důvod vyúčtování"
},
"links": {
"Community": "Společenství",
Expand Down
6 changes: 4 additions & 2 deletions src/assets/translations/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,8 @@
"Working Days": "Arbeitstage",
"You have created": "Du hast erstellt",
"You can drag and drop the rows to set a Payment Allocations order": "Sie können die Zeilen per Drag-and-Drop verschieben, um eine Zahlungszuordnungsreihenfolge festzulegen",
"successfully select option": "erfolgreich. Bitte wählen Sie eine der folgenden Optionen aus, um fortzufahren."
"successfully select option": "erfolgreich. Bitte wählen Sie eine der folgenden Optionen aus, um fortzufahren.",
"Map Charge-off reasons to Expense accounts": "Abschreibungsgründe Aufwandskonten zuordnen"
},
"inputs": {
"accounting": {
Expand Down Expand Up @@ -2422,7 +2423,8 @@
"valid": "gültig",
"INTEREST REFUND": "ZINSRÜCKERSTATTUNG",
"Supported Interest Refund Types": "Unterstützte Zinsrückerstattungsarten",
"SUPPORTED REFUND TRANSACTIONS FOR INTEREST REFUND": "Unterstützte Zinsrückerstattungsarten"
"SUPPORTED REFUND TRANSACTIONS FOR INTEREST REFUND": "Unterstützte Zinsrückerstattungsarten",
"Charge-off reason": "Abschreibungsgrund"
},
"links": {
"Community": "Gemeinschaft",
Expand Down
Loading

0 comments on commit c2d72c9

Please sign in to comment.