Merge pull request #2665 from alberto-art3ch/WEB-316/advance-accounting-rule-can-be-set-for-each-classification-type

WEB-316: Advance Accounting rule can be set for each classification type
This commit is contained in:
Felix van Hove
2025-09-21 23:32:38 +02:00
committed by GitHub
24 changed files with 639 additions and 14 deletions
@@ -722,7 +722,7 @@
<span class="flex-60">{{ loanProduct.multiDisburseLoan | yesNo }}</span>
</div>
<div class="flex-fill layout-row-wrap responsive-column" *ngIf="loanProduct.multiDisburseLoan">
<div class="flex-100 layout-row-wrap responsive-column" *ngIf="loanProduct.multiDisburseLoan">
<span class="flex-40">{{ 'labels.inputs.Maximum Tranche count' | translate }}:</span>
<span class="flex-60">{{ loanProduct.maxTrancheCount }}</span>
<div class="flex-fill layout-row" *ngIf="loanProduct.outstandingLoanBalance">
@@ -737,7 +737,7 @@
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Event Settings' | translate }}</h3>
<div class="flex-fill">
<div class="flex-100">
<span class="flex-40"
>{{
'labels.inputs.Use the Global Configurations values to the Repayment Event (notifications)' | translate
@@ -1252,6 +1252,72 @@
<tr mat-row *matRowDef="let row; columns: chargeOffReasonExpenseDisplayedColumns"></tr>
</table>
</div>
<div
*ngIf="buydownFeeClassificationToIncomeAccountMappings?.length > 0"
class="flex-100 layout-row-wrap responsive-column"
>
<h4 class="mat-h4 flex-100">
{{ 'labels.heading.Buydown Fee classifications to Income accounts' | translate }}
</h4>
<table
class="mat-elevation-z1 flex-100"
mat-table
[dataSource]="buydownFeeClassificationToIncomeAccountMappings"
>
<ng-container matColumnDef="chargeOffReasonCodeValueId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Classification' | translate }}</th>
<td mat-cell *matCellDef="let classificationToIncomeAccountMapping">
{{ classificationToIncomeAccountMapping.classificationCodeValue.name }}
</td>
</ng-container>
<ng-container matColumnDef="expenseAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
<td mat-cell *matCellDef="let classificationToIncomeAccountMapping">
({{ classificationToIncomeAccountMapping.incomeAccount.glCode }})
{{ classificationToIncomeAccountMapping.incomeAccount.name }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="chargeOffReasonExpenseDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: chargeOffReasonExpenseDisplayedColumns"></tr>
</table>
</div>
<div
*ngIf="capitalizedIncomeClassificationToIncomeAccountMappings?.length > 0"
class="flex-100 layout-row-wrap responsive-column"
>
<h4 class="mat-h4 flex-100">
{{ 'labels.heading.Capitalized Income classifications to Income accounts' | translate }}
</h4>
<table
class="mat-elevation-z1 flex-100"
mat-table
[dataSource]="capitalizedIncomeClassificationToIncomeAccountMappings"
>
<ng-container matColumnDef="chargeOffReasonCodeValueId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Classification' | translate }}</th>
<td mat-cell *matCellDef="let capitalizedIncomeClassificationToIncomeAccountMapping">
{{ capitalizedIncomeClassificationToIncomeAccountMapping.classificationCodeValue.name }}
</td>
</ng-container>
<ng-container matColumnDef="expenseAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
<td mat-cell *matCellDef="let capitalizedIncomeClassificationToIncomeAccountMapping">
({{ capitalizedIncomeClassificationToIncomeAccountMapping.incomeAccount.glCode }})
{{ capitalizedIncomeClassificationToIncomeAccountMapping.incomeAccount.name }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="chargeOffReasonExpenseDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: chargeOffReasonExpenseDisplayedColumns"></tr>
</table>
</div>
</div>
</div>
</div>
@@ -1,11 +1,12 @@
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { DelinquencyBucket, LoanProduct } from '../../models/loan-product.model';
import { AccountingMappingDTO, DelinquencyBucket, LoanProduct } from '../../models/loan-product.model';
import {
AccountingMapping,
Charge,
ChargeOffReasonCodeValue,
CodeValue,
ChargeOffReasonToExpenseAccountMapping,
ChargeToIncomeAccountMapping,
ClassificationToIncomeAccountMapping,
GLAccount,
PaymentChannelToFundSourceMapping,
PaymentType,
@@ -114,6 +115,8 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
feeToIncomeAccountMappings: ChargeToIncomeAccountMapping[] = [];
penaltyToIncomeAccountMappings: ChargeToIncomeAccountMapping[] = [];
chargeOffReasonToExpenseAccountMappings: ChargeOffReasonToExpenseAccountMapping[] = [];
buydownFeeClassificationToIncomeAccountMappings: ClassificationToIncomeAccountMapping[] = [];
capitalizedIncomeClassificationToIncomeAccountMappings: ClassificationToIncomeAccountMapping[] = [];
constructor(private accounting: Accounting) {}
@@ -145,6 +148,10 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
this.feeToIncomeAccountMappings = this.loanProduct.feeToIncomeAccountMappings || [];
this.penaltyToIncomeAccountMappings = this.loanProduct.penaltyToIncomeAccountMappings || [];
this.chargeOffReasonToExpenseAccountMappings = this.loanProduct.chargeOffReasonToExpenseAccountMappings || [];
this.buydownFeeClassificationToIncomeAccountMappings =
this.loanProduct.buydownFeeClassificationToIncomeAccountMappings || [];
this.capitalizedIncomeClassificationToIncomeAccountMappings =
this.loanProduct.capitalizedIncomeClassificationToIncomeAccountMappings || [];
} else {
this.accountingMappings = {};
@@ -159,6 +166,9 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
const assetAndLiabilityAccountData =
this.loanProductsTemplate.accountingMappingOptions.assetAndLiabilityAccountOptions || [];
const chargeOffReasonOptions: any = this.loanProductsTemplate.chargeOffReasonOptions || [];
const buydownFeeClassificationOptions: any = this.loanProductsTemplate.buydownFeeClassificationOptions || [];
const capitalizedIncomeClassificationOptions: any =
this.loanProductsTemplate.capitalizedIncomeClassificationOptions || [];
this.accountingMappings = {
fundSourceAccount: this.glAccountLookUp(this.loanProduct.fundSourceAccountId, assetAndLiabilityAccountData),
@@ -262,11 +272,42 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
chargeOffReasonCodeValue: {
id: optionData.id,
name: optionData.value
} as ChargeOffReasonCodeValue
} as CodeValue
});
}
);
}
this.buydownFeeClassificationToIncomeAccountMappings = [];
if (this.loanProduct.buydownFeeClassificationToIncomeAccountMappings?.length > 0) {
this.loanProduct.buydownFeeClassificationToIncomeAccountMappings.forEach((m: any) => {
let optionData = this.optionDataLookUp(m.classificationCodeValueId, buydownFeeClassificationOptions);
if (optionData !== null) {
this.buydownFeeClassificationToIncomeAccountMappings.push({
incomeAccount: this.glAccountLookUp(m.incomeAccountId, incomeAccountData),
classificationCodeValue: {
id: optionData.id,
name: optionData.value
} as CodeValue
});
}
});
}
this.capitalizedIncomeClassificationToIncomeAccountMappings = [];
if (this.loanProduct.capitalizedIncomeClassificationToIncomeAccountMappings?.length > 0) {
this.loanProduct.capitalizedIncomeClassificationToIncomeAccountMappings.forEach((m: any) => {
let optionData = this.optionDataLookUp(m.classificationCodeValueId, capitalizedIncomeClassificationOptions);
if (optionData !== null) {
this.capitalizedIncomeClassificationToIncomeAccountMappings.push({
incomeAccount: this.glAccountLookUp(m.incomeAccountId, incomeAccountData),
classificationCodeValue: {
id: optionData.id,
name: optionData.value
} as CodeValue
});
}
});
}
}
if (this.loanProduct.isInterestRecalculationEnabled) {
@@ -453,7 +494,7 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
}
optionDataLookUp(itemId: any, optionsData: any[]): OptionData {
let optionData: OptionData | null;
let optionData: OptionData | null = null;
optionsData.some((o: any) => {
if (o.id === itemId) {
optionData = {
@@ -576,7 +617,9 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges {
this.loanProduct.paymentChannelToFundSourceMappings?.length > 0 ||
this.loanProduct.feeToIncomeAccountMappings?.length > 0 ||
this.loanProduct.penaltyToIncomeAccountMappings?.length > 0 ||
this.loanProduct.chargeOffReasonToExpenseAccountMappings?.length > 0
this.loanProduct.chargeOffReasonToExpenseAccountMappings?.length > 0 ||
this.loanProduct.buydownFeeClassificationToIncomeAccountMappings?.length > 0 ||
this.loanProduct.capitalizedIncomeClassificationToIncomeAccountMappings?.length > 0
);
}
@@ -0,0 +1,40 @@
<div class="flex-100 layout-row-wrap responsive-column">
<h4 class="mat-h4 flex-33">
{{ textHeading | translateKey: 'heading' }}
</h4>
<div class="flex-63">
<button type="button" mat-raised-button color="primary" [disabled]="!allowAddAccountingMapping" (click)="add()">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<table class="flex-98 mat-elevation-z1" mat-table [dataSource]="tableData" *ngIf="tableData.length !== 0">
<ng-container matColumnDef="codeValueId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.' + textField | translate }}</th>
<td mat-cell *matCellDef="let item">
{{ item.value.name }}
</td>
</ng-container>
<ng-container matColumnDef="glAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.GL Account Name or Code' | translate }}</th>
<td mat-cell *matCellDef="let item">({{ item.glAccount.glCode }}) {{ item.glAccount.name }}</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let chargeOffReasonExpense; let i = index">
<button mat-icon-button color="primary" (click)="edit(i)">
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(i)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="tableDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: tableDisplayedColumns"></tr>
</table>
</div>
@@ -0,0 +1,12 @@
table {
width: 100%;
}
.mat-elevation-z1 {
margin: 1em 0 1.5em;
}
h3,
h4 {
font-weight: 500;
}
@@ -0,0 +1,297 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { UntypedFormArray } from '@angular/forms';
import { MatIconButton } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import {
MatCell,
MatCellDef,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef,
MatHeaderRow,
MatHeaderRowDef,
MatRow,
MatRowDef,
MatTable
} from '@angular/material/table';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateService } from '@ngx-translate/core';
import { AccountingMappingDTO, AdvancedMappingDTO } from 'app/products/loan-products/models/loan-product.model';
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
selector: 'mifosx-advanced-accounting-mapping-rule',
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatIconButton,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow
],
templateUrl: './advanced-accounting-mapping-rule.component.html',
styleUrl: './advanced-accounting-mapping-rule.component.scss'
})
export class AdvancedAccountingMappingRuleComponent implements OnInit {
@Input() formType: string;
@Input() formArray: UntypedFormArray;
@Input() textHeading: string;
@Input() textField: string;
@Input() allowAddAccountingMapping: boolean = true;
@Input() accountingMappingOptions: any[] = [];
currentFormValues: any[] = [];
@Input() chargeData: any;
@Input() penaltyData: any;
@Input() paymentTypeData: any;
@Input() assetAccountData: any;
@Input() incomeAccountData: any;
@Input() expenseAccountData: any;
@Input() liabilityAccountData: any;
@Input() incomeAndLiabilityAccountData: any;
@Input() assetAndLiabilityAccountData: any;
@Output() formChangeEvent: EventEmitter<AdvancedMappingDTO> = new EventEmitter<AdvancedMappingDTO>();
tableData: any[] = [];
tableDisplayedColumns: string[] = [
'codeValueId',
'glAccountId',
'actions'
];
constructor(
public dialog: MatDialog,
public translateService: TranslateService
) {}
ngOnInit(): void {
this.tableData = this.formArray?.value || [];
this.sendParentData();
}
add() {
this.currentFormValues = [];
if (this.formType == 'ChargeOffReasonExpense') {
this.allowAddAccountingMapping = true;
this.tableData.forEach((item: any) => this.currentFormValues.push(item.chargeOffReasonCodeValueId));
if (this.accountingMappingOptions.length == this.currentFormValues.length) {
this.allowAddAccountingMapping = false;
return;
}
}
const data = { ...this.getData(this.formType), pristine: false };
const dialogRef = this.dialog.open(FormDialogComponent, { data });
dialogRef.afterClosed().subscribe((response: any) => {
if (response.data) {
const addData: AccountingMappingDTO = {
value: this.getValueData(response.data.value.valueId),
glAccount: this.getGlAccountData(response.data.value.glAccountId)
};
this.tableData.push(addData);
this.sendParentData();
if (this.formType == 'ChargeOffReasonExpense') {
this.allowAddAccountingMapping = this.tableData.length < this.accountingMappingOptions.length;
}
}
});
}
delete(index: number) {
const dialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: this.translateService.instant('labels.text.this') }
});
dialogRef.afterClosed().subscribe((response: any) => {
if (response.delete) {
this.tableData.splice(index, 1);
this.sendParentData();
}
});
}
sendParentData() {
const advancedMappingDTO: AdvancedMappingDTO = {
formType: this.formType,
values: this.tableData
};
this.formChangeEvent.emit(advancedMappingDTO);
}
getData(formType: string, values?: any) {
switch (formType) {
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)
};
case 'BuydownFeeClassificationToIncome':
return {
title: 'Buydown Fee classifications to Income accounts',
formfields: this.getClassificationIncomeFormfields(values)
};
case 'CapitalizedIncomeClassificationToIncome':
return {
title: 'Capitalized Income classifications to Income accounts',
formfields: this.getClassificationIncomeFormfields(values)
};
}
}
getPaymentFundSourceFormfields(values?: any) {
const formfields: FormfieldBase[] = [
new SelectBase({
controlName: 'paymentTypeId',
label: 'Payment Type',
value: values ? values.paymentTypeId : this.paymentTypeData[0].id,
options: { label: 'name', value: 'id', data: this.paymentTypeData },
required: true,
order: 1
}),
new SelectBase({
controlName: 'fundSourceAccountId',
label: 'Fund Source',
value: values ? values.fundSourceAccountId : this.assetAccountData[0].id,
options: { label: 'name', value: 'id', data: this.assetAccountData },
required: true,
order: 2
})
];
return formfields;
}
getFeesIncomeFormfields(values?: any) {
const formfields: FormfieldBase[] = [
new SelectBase({
controlName: 'chargeId',
label: 'Fees',
value: values ? values.chargeId : this.chargeData[0].id,
options: { label: 'name', value: 'id', data: this.chargeData },
required: true,
order: 1
}),
new SelectBase({
controlName: 'incomeAccountId',
label: 'Income Account',
value: values ? values.incomeAccountId : this.incomeAndLiabilityAccountData[0].id,
options: { label: 'name', value: 'id', data: this.incomeAndLiabilityAccountData },
required: true,
order: 2
})
];
return formfields;
}
getPenaltyIncomeFormfields(values?: any) {
const formfields: FormfieldBase[] = [
new SelectBase({
controlName: 'chargeId',
label: 'Penalty',
value: values ? values.chargeId : this.penaltyData[0].id,
options: { label: 'name', value: 'id', data: this.penaltyData },
required: true,
order: 1
}),
new SelectBase({
controlName: 'incomeAccountId',
label: 'Income Account',
value: values ? values.incomeAccountId : this.incomeAccountData[0].id,
options: { label: 'name', value: 'id', data: this.incomeAccountData },
required: true,
order: 2
})
];
return formfields;
}
getChargeOffReasonExpenseFormfields(values?: any) {
const reasonOptions = this.accountingMappingOptions.filter(
(item: any) => !this.currentFormValues.includes(item.id)
);
const formfields: FormfieldBase[] = [
new SelectBase({
controlName: 'chargeOffReasonCodeValueId',
label: 'Charge-off reason',
value: values ? values.chargeOffReasonCodeValueId : reasonOptions[0].id,
options: { label: 'name', value: 'id', data: reasonOptions },
required: true,
order: 1
}),
new SelectBase({
controlName: 'expenseAccountId',
label: 'Expense Account',
value: values ? values.expenseAccountId : this.expenseAccountData[0].id,
options: { label: 'name', value: 'id', data: this.expenseAccountData },
required: true,
order: 2
})
];
return formfields;
}
getClassificationIncomeFormfields(values?: any) {
const classificationOptions = this.accountingMappingOptions.filter(
(item: any) => !this.currentFormValues.includes(item.id)
);
const formfields: FormfieldBase[] = [
new SelectBase({
controlName: 'valueId',
label: 'Classification',
value: values ? values.chargeOffReasonCodeValueId : classificationOptions[0].id,
options: { label: 'name', value: 'id', data: classificationOptions },
required: true,
order: 1
}),
new SelectBase({
controlName: 'glAccountId',
label: 'Income Account',
value: values ? values.incomeAccountId : this.incomeAccountData[0].id,
options: { label: 'name', value: 'id', data: this.incomeAccountData },
required: true,
order: 2
})
];
return formfields;
}
getValueData(valueId: number): any {
return this.accountingMappingOptions.find((i: any) => {
return i.id === valueId;
});
}
getGlAccountData(valueId: number): any {
return this.incomeAccountData.find((i: any) => {
return i.id === valueId;
});
}
}
@@ -514,6 +514,28 @@
<tr mat-header-row *matHeaderRowDef="chargeOffReasonExpenseDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: chargeOffReasonExpenseDisplayedColumns"></tr>
</table>
<mifosx-advanced-accounting-mapping-rule
class="flex-100 m-t-10"
[textField]="'Classification'"
[formType]="'BuydownFeeClassificationToIncome'"
[formArray]="buydownfeeClassificationToIncomeAccountMappings"
[textHeading]="'Buydown Fee classifications to Income accounts'"
[incomeAccountData]="incomeAccountData"
[accountingMappingOptions]="buydownFeeClassificationOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
<mifosx-advanced-accounting-mapping-rule
class="flex-100 m-t-10"
[textField]="'Classification'"
[formType]="'CapitalizedIncomeClassificationToIncome'"
[formArray]="capitalizedIncomeClassificationToIncomeAccountMappings"
[textHeading]="'Capitalized Income classifications to Income accounts'"
[incomeAccountData]="incomeAccountData"
[accountingMappingOptions]="capitalizedIncomeClassificationOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
</div>
</div>
</div>
@@ -8,7 +8,10 @@ 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 { ChargeOffReasonToExpenseAccountMapping } from 'app/shared/models/general.model';
import {
ChargeOffReasonToExpenseAccountMapping,
ClassificationToIncomeAccountMapping
} from 'app/shared/models/general.model';
import { DeferredIncomeRecognition } from '../loan-product-payment-strategy-step/payment-allocation-model';
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
import { MatDivider } from '@angular/material/divider';
@@ -31,6 +34,8 @@ import {
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FindPipe } from '../../../../pipes/find.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { AdvancedAccountingMappingRuleComponent } from './advanced-accounting-mapping-rule/advanced-accounting-mapping-rule.component';
import { AccountingMappingDTO, AdvancedMappingDTO } from '../../models/loan-product.model';
@Component({
selector: 'mifosx-loan-product-accounting-step',
@@ -57,7 +62,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatRow,
MatStepperPrevious,
MatStepperNext,
FindPipe
FindPipe,
AdvancedAccountingMappingRuleComponent
]
})
export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
@@ -78,6 +84,8 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
incomeAndLiabilityAccountData: any;
assetAndLiabilityAccountData: any;
chargeOffReasonOptions: any;
capitalizedIncomeClassificationOptions: any[] = [];
buydownFeeClassificationOptions: any[] = [];
currentFormValues: any[] = [];
allowAddChargeOffReasonExpense = true;
@@ -123,6 +131,9 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
this.assetAndLiabilityAccountData =
this.loanProductsTemplate.accountingMappingOptions.assetAndLiabilityAccountOptions || [];
this.chargeOffReasonOptions = this.loanProductsTemplate.chargeOffReasonOptions || [];
this.capitalizedIncomeClassificationOptions =
this.loanProductsTemplate.capitalizedIncomeClassificationOptions || [];
this.buydownFeeClassificationOptions = this.loanProductsTemplate.buydownFeeClassificationOptions || [];
this.loanProductAccountingForm.patchValue({
accountingRule: this.loanProductsTemplate.accountingRule.id
@@ -201,7 +212,9 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
this.loanProductsTemplate.paymentChannelToFundSourceMappings ||
this.loanProductsTemplate.feeToIncomeAccountMappings ||
this.loanProductsTemplate.penaltyToIncomeAccountMappings ||
this.loanProductsTemplate.chargeOffReasonToExpenseAccountMappings
this.loanProductsTemplate.chargeOffReasonToExpenseAccountMappings ||
this.loanProductsTemplate.buydownFeeClassificationToIncomeAccountMappings ||
this.loanProductsTemplate.capitalizedIncomeClassificationToIncomeAccountMappings
? true
: false
});
@@ -244,6 +257,28 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
)
)
);
this.loanProductAccountingForm.setControl(
'buydownfeeClassificationToIncomeAccountMappings',
this.formBuilder.array(
(this.loanProductsTemplate.buydownFeeClassificationToIncomeAccountMappings || []).map(
(m: ClassificationToIncomeAccountMapping) => ({
value: m.classificationCodeValue,
glAccount: m.incomeAccount
})
)
)
);
this.loanProductAccountingForm.setControl(
'capitalizedIncomeClassificationToIncomeAccountMappings',
this.formBuilder.array(
(this.loanProductsTemplate.capitalizedIncomeClassificationToIncomeAccountMappings || []).map(
(m: ClassificationToIncomeAccountMapping) => ({
value: m.classificationCodeValue,
glAccount: m.incomeAccount
})
)
)
);
}
}
@@ -341,6 +376,14 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
'chargeOffReasonToExpenseAccountMappings',
this.formBuilder.array([])
);
this.loanProductAccountingForm.addControl(
'buydownfeeClassificationToIncomeAccountMappings',
this.formBuilder.array([])
);
this.loanProductAccountingForm.addControl(
'capitalizedIncomeClassificationToIncomeAccountMappings',
this.formBuilder.array([])
);
} else {
this.loanProductAccountingForm.setControl(
'paymentChannelToFundSourceMappings',
@@ -352,6 +395,14 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
'chargeOffReasonToExpenseAccountMappings',
this.formBuilder.array([])
);
this.loanProductAccountingForm.setControl(
'buydownfeeClassificationToIncomeAccountMappings',
this.formBuilder.array([])
);
this.loanProductAccountingForm.setControl(
'capitalizedIncomeClassificationToIncomeAccountMappings',
this.formBuilder.array([])
);
}
});
} else {
@@ -415,6 +466,16 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
return this.loanProductAccountingForm.get('chargeOffReasonToExpenseAccountMappings') as UntypedFormArray;
}
get buydownfeeClassificationToIncomeAccountMappings(): UntypedFormArray {
return this.loanProductAccountingForm.get('buydownfeeClassificationToIncomeAccountMappings') as UntypedFormArray;
}
get capitalizedIncomeClassificationToIncomeAccountMappings(): UntypedFormArray {
return this.loanProductAccountingForm.get(
'capitalizedIncomeClassificationToIncomeAccountMappings'
) as UntypedFormArray;
}
setLoanProductAccountingFormDirty() {
if (this.loanProductAccountingForm.pristine) {
this.loanProductAccountingForm.markAsDirty();
@@ -635,4 +696,31 @@ export class LoanProductAccountingStepComponent implements OnInit, OnChanges {
}
}
}
formChangeEvent(accountingData: AdvancedMappingDTO) {
if (accountingData.formType === 'BuydownFeeClassificationToIncome') {
this.loanProductAccountingForm.setControl(
'buydownfeeClassificationToIncomeAccountMappings',
this.formBuilder.array(
(accountingData.values || []).map((m: AccountingMappingDTO) => ({
classificationCodeValueId: m.value.id,
incomeAccountId: m.glAccount.id
}))
)
);
console.log(this.loanProductAccountingForm.value.buydownfeeClassificationToIncomeAccountMappings);
} else if (accountingData.formType === 'CapitalizedIncomeClassificationToIncome') {
this.loanProductAccountingForm.setControl(
'capitalizedIncomeClassificationToIncomeAccountMappings',
this.formBuilder.array(
(accountingData.values || []).map((m: AccountingMappingDTO) => ({
classificationCodeValueId: m.value.id,
incomeAccountId: m.glAccount.id
}))
)
);
console.log(this.loanProductAccountingForm.value.capitalizedIncomeClassificationToIncomeAccountMappings);
}
this.setLoanProductAccountingFormDirty();
}
}
@@ -2,7 +2,10 @@ import {
AccountingMapping,
ChargeOffReasonToExpenseAccountMapping,
ChargeToIncomeAccountMapping,
ClassificationToIncomeAccountMapping,
CodeValue,
Currency,
GLAccount,
PaymentChannelToFundSourceMapping
} from 'app/shared/models/general.model';
import { OptionData, StringEnumOptionData } from 'app/shared/models/option-data.model';
@@ -152,6 +155,8 @@ export interface LoanProduct {
enableAccrualActivityPosting?: boolean;
supportedInterestRefundTypes?: StringEnumOptionData[];
chargeOffBehaviour?: StringEnumOptionData;
buydownFeeClassificationToIncomeAccountMappings?: ClassificationToIncomeAccountMapping[];
capitalizedIncomeClassificationToIncomeAccountMappings?: ClassificationToIncomeAccountMapping[];
}
export interface AllowAttributeOverrides {
@@ -193,3 +198,13 @@ export interface InterestRecalculationData {
allowCompoundingOnEod: boolean;
disallowInterestCalculationOnPastDue: boolean;
}
export interface AdvancedMappingDTO {
formType: string;
values: AccountingMappingDTO[];
}
export interface AccountingMappingDTO {
value: CodeValue;
glAccount: GLAccount;
}
+3 -1
View File
@@ -122,6 +122,7 @@ import { ViewAdvancePaymenyAllocationComponent } from './loan-products/view-loan
import { AdvancePaymentAllocationTabComponent } from './loan-products/loan-product-stepper/loan-product-payment-strategy-step/advance-payment-allocation-tab/advance-payment-allocation-tab.component';
import { LoanProductSummaryComponent } from './loan-products/common/loan-product-summary/loan-product-summary.component';
import { LoanProductDeferredIncomeRecognitionStepComponent } from './loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component';
import { AdvancedAccountingMappingRuleComponent } from './loan-products/loan-product-stepper/loan-product-accounting-step/advanced-accounting-mapping-rule/advanced-accounting-mapping-rule.component';
/**
* Products Module
@@ -249,7 +250,8 @@ import { LoanProductDeferredIncomeRecognitionStepComponent } from './loan-produc
ViewAdvancePaymenyAllocationComponent,
AdvancePaymentAllocationTabComponent,
LoanProductSummaryComponent,
LoanProductDeferredIncomeRecognitionStepComponent
LoanProductDeferredIncomeRecognitionStepComponent,
AdvancedAccountingMappingRuleComponent
]
})
export class ProductsModule {}
+16 -2
View File
@@ -42,11 +42,25 @@ export interface PaymentChannelToFundSourceMapping {
export interface ChargeOffReasonToExpenseAccountMapping {
chargeOffReasonCodeValueId?: number;
expenseAccountId?: number;
chargeOffReasonCodeValue?: ChargeOffReasonCodeValue;
chargeOffReasonCodeValue?: CodeValue;
expenseAccount?: AccountingMapping;
}
export interface ChargeOffReasonCodeValue {
export interface AccountMapping {
codeValueId?: number;
glAccountId?: number;
codeValue?: CodeValue;
glAccount?: AccountingMapping;
}
export interface ClassificationToIncomeAccountMapping {
classificationCodeValueId?: number;
incomeAccountId?: number;
classificationCodeValue?: CodeValue;
incomeAccount?: AccountingMapping;
}
export interface CodeValue {
active: boolean;
description: string;
id: number;
@@ -10,7 +10,7 @@ import {
import { CustomParametersTableComponent } from './custom-parameters-table/custom-parameters-table.component';
import { SystemService } from 'app/system/system.service';
import { CdkScrollable } from '@angular/cdk/scrolling';
import { NgFor, NgClass } from '@angular/common';
import { NgClass } from '@angular/common';
import { MatList, MatListItem } from '@angular/material/list';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+2
View File
@@ -839,6 +839,8 @@
"Bulk Import": "Hromadný import",
"Bulk Loan Reassignment": "Hromadné přeřazení půjčky",
"Business Rule Parameters": "Parametry obchodních pravidel",
"Buydown Fee classifications to Income accounts": "Klasifikace poplatků za odkup na příjmové účty",
"Capitalized Income classifications to Income accounts": "Klasifikace kapitalizovaných výnosů do výnosových účtů",
"COB Catch-Up is": "COB Catch-Up je",
"Calculate Interest": "Vypočítejte úrok",
"Campaign Message": "Zpráva kampaně",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "Massenimport",
"Bulk Loan Reassignment": "Neuzuweisung von Massenkrediten",
"Business Rule Parameters": "Geschäftsregelparameter",
"Buydown Fee classifications to Income accounts": "Buydown-Gebührenklassifizierungen für Einkommenskonten",
"Capitalized Income classifications to Income accounts": "Klassifizierung kapitalisierter Einkünfte in Einkommenskonten",
"COB Catch-Up is": "COB Catch-Up ist",
"Calculate Interest": "Zinsen berechnen",
"Campaign Message": "Kampagnenbotschaft",
+2
View File
@@ -845,6 +845,8 @@
"Bulk Import": "Bulk Import",
"Bulk Loan Reassignment": "Bulk Loan Reassignment",
"Business Rule Parameters": "Business Rule Parameters",
"Buydown Fee classifications to Income accounts": "Buydown Fee classifications to Income accounts",
"Capitalized Income classifications to Income accounts": "Capitalized Income classifications to Income accounts",
"COB Catch-Up is": "COB Catch-Up is",
"Calculate Interest": "Calculate Interest",
"Campaign Message": "Campaign Message",
+2
View File
@@ -839,6 +839,8 @@
"Bulk Import": "Importación masiva",
"Bulk Loan Reassignment": "Reasignación de Créditos másiva",
"Business Rule Parameters": "Parámetros de reglas de negocio",
"Buydown Fee classifications to Income accounts": "Clasificaciones de comisiones por reducción de precio en cuentas de ingresos",
"Capitalized Income classifications to Income accounts": "Clasificaciones de ingresos capitalizados a cuentas de ingresos",
"COB Catch-Up is": "La puesta al día de COB es",
"Calculate Interest": "Calcular interés",
"Campaign Message": "Mensaje de campaña",
+2
View File
@@ -839,6 +839,8 @@
"Bulk Import": "Importación masiva",
"Bulk Loan Reassignment": "Reasignación de Créditos másiva",
"Business Rule Parameters": "Parámetros de reglas de negocio",
"Buydown Fee classifications to Income accounts": "Clasificaciones de comisiones por reducción de precio en cuentas de ingresos",
"Capitalized Income classifications to Income accounts": "Clasificaciones de ingresos capitalizados a cuentas de ingresos",
"COB Catch-Up is": "La puesta al día de COB es",
"Calculate Interest": "Calcular interés",
"Campaign Message": "Mensaje de campaña",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "Importation en masse",
"Bulk Loan Reassignment": "Réaffectation groupée de prêts",
"Business Rule Parameters": "Paramètres des règles métier",
"Buydown Fee classifications to Income accounts": "Classification des frais de rachat dans les comptes de revenus",
"Capitalized Income classifications to Income accounts": "Classifications des revenus capitalisés vers les comptes de revenus",
"COB Catch-Up is": "Le rattrapage COB est",
"Calculate Interest": "Calculer les intérêts",
"Campaign Message": "Message de campagne",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "Importazione in blocco",
"Bulk Loan Reassignment": "Riassegnazione del prestito in blocco",
"Business Rule Parameters": "Parametri delle regole aziendali",
"Buydown Fee classifications to Income accounts": "Classificazioni delle commissioni di riacquisto nei conti di reddito",
"Capitalized Income classifications to Income accounts": "Classificazioni del reddito capitalizzato nei conti di reddito",
"COB Catch-Up is": "Il recupero del COB lo è",
"Calculate Interest": "Calcola gli interessi",
"Campaign Message": "Messaggio della campagna",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "대량 가져오기",
"Bulk Loan Reassignment": "대량대출 재할당",
"Business Rule Parameters": "비즈니스 규칙 매개변수",
"Buydown Fee classifications to Income accounts": "소득 계정에 대한 Buydown 수수료 분류",
"Capitalized Income classifications to Income accounts": "자본화된 소득 분류를 소득 계정으로",
"COB Catch-Up is": "COB 따라잡기는",
"Calculate Interest": "이자 계산",
"Campaign Message": "캠페인 메시지",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "Masinis importas",
"Bulk Loan Reassignment": "Masinės paskolos perskirstymas",
"Business Rule Parameters": "Verslo taisyklių parametrai",
"Buydown Fee classifications to Income accounts": "Išpirkimo mokesčių klasifikavimas į pajamų sąskaitas",
"Capitalized Income classifications to Income accounts": "Kapitalizuotų pajamų klasifikavimas į pajamų sąskaitas",
"COB Catch-Up is": "COB Catch-Up yra",
"Calculate Interest": "Apskaičiuokite palūkanas",
"Campaign Message": "Kampanijos pranešimas",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "Lielapjoma importēšana",
"Bulk Loan Reassignment": "Lielapjoma aizdevuma pārcelšana",
"Business Rule Parameters": "Biznesa noteikumu parametri",
"Buydown Fee classifications to Income accounts": "Izpirkšanas maksu klasifikācija ienākumu kontos",
"Capitalized Income classifications to Income accounts": "Kapitalizēto ienākumu klasifikācija ienākumu kontos",
"COB Catch-Up is": "COB Catch-Up ir",
"Calculate Interest": "Aprēķināt procentus",
"Campaign Message": "Kampaņas ziņojums",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "थोक आयात",
"Bulk Loan Reassignment": "बल्क ऋण पुन: असाइनमेन्ट",
"Business Rule Parameters": "व्यापार नियम प्यारामिटरहरू",
"Buydown Fee classifications to Income accounts": "आय खाताहरूमा खरिद शुल्क वर्गीकरण",
"Capitalized Income classifications to Income accounts": "आय खाताहरूमा पूंजीकृत आय वर्गीकरण",
"COB Catch-Up is": "COB क्याच-अप हो",
"Calculate Interest": "ब्याज गणना गर्नुहोस्",
"Campaign Message": "अभियान सन्देश",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "Importação em massa",
"Bulk Loan Reassignment": "Reatribuição de empréstimo em massa",
"Business Rule Parameters": "Parâmetros de regras de negócios",
"Buydown Fee classifications to Income accounts": "Classificações de taxas de recompra para contas de renda",
"Capitalized Income classifications to Income accounts": "Classificações de renda capitalizada para contas de renda",
"COB Catch-Up is": "A recuperação do COB é",
"Calculate Interest": "Calcular juros",
"Campaign Message": "Mensagem da campanha",
+2
View File
@@ -840,6 +840,8 @@
"Bulk Import": "Ingiza Wingi",
"Bulk Loan Reassignment": "Ugawaji upya wa mkopo kwa wingi",
"Business Rule Parameters": "Vigezo vya Sheria ya Biashara",
"Buydown Fee classifications to Income accounts": "Uainishaji wa Ada ya Kununua kwa akaunti za Mapato",
"Capitalized Income classifications to Income accounts": "Uainishaji wa Mapato ya Mtaji kwa akaunti za Mapato",
"COB Catch-Up is": "COB Catch-Up ni",
"Calculate Interest": "Hesabu Riba",
"Campaign Message": "Ujumbe wa Kampeni",