Advanced Payment Allocation for Loan Transactions

This commit is contained in:
Jose Alberto Hernandez
2023-09-20 14:08:09 +02:00
committed by Adam Saghy
parent 08474a54e0
commit 0ad0579743
29 changed files with 592 additions and 235 deletions
@@ -1,5 +1,4 @@
@import "assets/styles/helper";
@import "assets/styles/helper";
table {
width: 100%;
@@ -72,8 +72,8 @@
<td class="field-name"> Effective From </td>
<td> {{ record.effectiveFrom | dateFormat }} </td>
</tr>
</table>
</mat-panel-title>
</table>
</mat-panel-title>
</mat-expansion-panel-header>
@@ -63,13 +63,16 @@
<ng-template matStepLabel>PAYMENT ALLOCATION</ng-template>
<mifosx-loan-product-payment-strategy-step *ngFor="let transactionType of transactionTypes"
[loanProductsTemplate]="loanProductsTemplate"
(paymentAllocation)="setPaymentAllocation($event)"
(transactionType)="transactionType"
<mifosx-loan-product-payment-strategy-step
[advancedPaymentAllocations]="advancedPaymentAllocations"
[advancedPaymentAllocationTransactionTypes]="loanProductAndTemplate.advancedPaymentAllocationTransactionTypes"
[paymentAllocationOrderDefault]="loanProductAndTemplate.advancedPaymentAllocationTypes"
(setPaymentAllocation)="setPaymentAllocation($event)"
>
</mifosx-loan-product-payment-strategy-step>
<mifosx-stepper-buttons></mifosx-stepper-buttons>
</mat-step>
<mat-step>
@@ -13,7 +13,7 @@ import { LoanProductAccountingStepComponent } from '../loan-product-stepper/loan
/** Custom Services */
import { ProductsService } from 'app/products/products.service';
import { LoanProducts } from '../loan-products';
import { PaymentAllocation, PaymentAllocationTransactionType, PaymentAllocationTransactionTypes, PaymentAllocationTypes } from '../loan-product-stepper/loan-product-payment-strategy-step/payment-allocation-model';
import { AdvancedPaymentAllocation, AdvancedPaymentStrategy, PaymentAllocation } from '../loan-product-stepper/loan-product-payment-strategy-step/payment-allocation-model';
@Component({
selector: 'mifosx-create-loan-product',
@@ -35,9 +35,7 @@ export class CreateLoanProductComponent implements OnInit {
isAdvancedPaymentStrategy = false;
paymentAllocation: PaymentAllocation[] = [];
transactionTypes: PaymentAllocationTransactionType[] = [
PaymentAllocationTransactionTypes.DEFAULT_TRANSACTION
];
advancedPaymentAllocations: AdvancedPaymentAllocation[] = [];
/**
* @param {ActivatedRoute} route Activated Route.
@@ -45,11 +43,11 @@ export class CreateLoanProductComponent implements OnInit {
* @param {LoanProducts} loanProducts LoanProducts
* @param {Router} router Router for navigation.
*/
constructor(private route: ActivatedRoute,
private productsService: ProductsService,
private loanProducts: LoanProducts,
private router: Router) {
private router: Router,
private advancedPaymentStrategy: AdvancedPaymentStrategy) {
this.route.data.subscribe((data: { loanProductsTemplate: any, configurations: any }) => {
this.loanProductsTemplate = data.loanProductsTemplate;
const assetAccountData = this.loanProductsTemplate.accountingMappingOptions.assetAccountOptions || [];
@@ -82,7 +80,12 @@ export class CreateLoanProductComponent implements OnInit {
this.isAdvancedPaymentStrategy = (value === 'advanced-payment-allocation-strategy');
}
setPaymentAllocation(paymentAllocation: PaymentAllocation[]) {
buildAdvancedPaymentAllocation(): void {
this.advancedPaymentAllocations = this.advancedPaymentStrategy.buildAdvancedPaymentAllocationList(this.loanProductsTemplate);
}
setPaymentAllocation(paymentAllocation: PaymentAllocation[]): void {
console.log(paymentAllocation);
this.paymentAllocation = paymentAllocation;
}
@@ -63,11 +63,11 @@
<ng-template matStepLabel>PAYMENT ALLOCATION</ng-template>
<mifosx-loan-product-payment-strategy-step *ngFor="let transactionType of transactionTypes"
[loanProductsTemplate]="loanProductsTemplate"
(paymentAllocation)="setPaymentAllocation($event)"
(paymentAllocationChange)="paymentAllocationChanged($event)"
(transactionType)="transactionType"
<mifosx-loan-product-payment-strategy-step
[advancedPaymentAllocations]="advancedPaymentAllocations"
[advancedPaymentAllocationTransactionTypes]="loanProductAndTemplate.advancedPaymentAllocationTransactionTypes"
[paymentAllocationOrderDefault]="loanProductAndTemplate.advancedPaymentAllocationTypes"
(setPaymentAllocation)="setPaymentAllocation($event)"
>
</mifosx-loan-product-payment-strategy-step>
@@ -14,7 +14,7 @@ import { LoanProductAccountingStepComponent } from '../loan-product-stepper/loan
import { ProductsService } from 'app/products/products.service';
import { GlobalConfiguration } from 'app/system/configurations/global-configurations-tab/configuration.model';
import { LoanProducts } from '../loan-products';
import { PaymentAllocation, PaymentAllocationTransactionType, PaymentAllocationTransactionTypes } from '../loan-product-stepper/loan-product-payment-strategy-step/payment-allocation-model';
import { AdvancedPaymentAllocation, AdvancedPaymentStrategy, PaymentAllocation, PaymentAllocationOrder, PaymentAllocationTransactionTypes } from '../loan-product-stepper/loan-product-payment-strategy-step/payment-allocation-model';
@Component({
selector: 'mifosx-edit-loan-product',
@@ -37,9 +37,7 @@ export class EditLoanProductComponent implements OnInit {
isAdvancedPaymentStrategy = false;
wasPaymentAllocationChanged = false;
paymentAllocation: PaymentAllocation[] = [];
transactionTypes: PaymentAllocationTransactionType[] = [
PaymentAllocationTransactionTypes.DEFAULT_TRANSACTION
];
advancedPaymentAllocations: AdvancedPaymentAllocation[] = [];
/**
* @param {ActivatedRoute} route Activated Route.
@@ -51,7 +49,8 @@ export class EditLoanProductComponent implements OnInit {
constructor(private route: ActivatedRoute,
private productsService: ProductsService,
private loanProducts: LoanProducts,
private router: Router) {
private router: Router,
private advancedPaymentStrategy: AdvancedPaymentStrategy) {
this.route.data.subscribe((data: { loanProductAndTemplate: any, configurations: any }) => {
this.loanProductAndTemplate = data.loanProductAndTemplate;
const assetAccountData = this.loanProductAndTemplate.accountingMappingOptions.assetAccountOptions || [];
@@ -65,6 +64,7 @@ export class EditLoanProductComponent implements OnInit {
}
ngOnInit() {
this.buildAdvancedPaymentAllocation();
}
get loanProductDetailsForm() {
@@ -87,8 +87,13 @@ export class EditLoanProductComponent implements OnInit {
this.isAdvancedPaymentStrategy = (value === 'advanced-payment-allocation-strategy');
}
buildAdvancedPaymentAllocation(): void {
this.advancedPaymentAllocations = this.advancedPaymentStrategy.buildAdvancedPaymentAllocationList(this.loanProductAndTemplate);
}
setPaymentAllocation(paymentAllocation: PaymentAllocation[]): void {
this.paymentAllocation = paymentAllocation;
this.wasPaymentAllocationChanged = true;
}
paymentAllocationChanged(value: boolean): void {
@@ -0,0 +1,39 @@
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<h4 class="m-b-15 right" fxFlex="90%">You can drag and drop the rows to set a Payment Allocations order</h4>
<div class="right" fxFlex="4%" *ngIf="!isDefault()">
<button mat-button color="warn" (click)="removeTransaction()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
</button>
</div>
<div class="mat-elevation-z8 container m-t-15">
<table mat-table [dataSource]="paymentAllocationsData" #table cdkDropList
[cdkDropListData]="paymentAllocationsData" (cdkDropListDropped)="dropTable($event)">
<ng-container matColumnDef="paymentAllocation">
<th mat-header-cell *matHeaderCellDef> Payment Allocation </th>
<td mat-cell *matCellDef="let paymentAllocation"> {{ paymentAllocation.value }} </td>
</ng-container>
<ng-container matColumnDef="order">
<th mat-header-cell *matHeaderCellDef> Order </th>
<td mat-cell *matCellDef="let paymentAllocation; let rowIndex = index"> {{ (rowIndex + 1) }} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row cdkDrag [cdkDragData]="row" *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<mat-form-field fxFlex="33%" class="m-t-20">
<mat-label>Future Installment Allocation Rule</mat-label>
<mat-select [formControl]="futureInstallmentAllocationRule">
<mat-option *ngFor="let futureInstallmentAllocationRule of advancedPaymentAllocation.futureInstallmentAllocationRules"
[value]="futureInstallmentAllocationRule.code">
{{ futureInstallmentAllocationRule.value | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdvancePaymentAllocationTabComponent } from './advance-payment-allocation-tab.component';
describe('AdvancePaymentAllocationTabComponent', () => {
let component: AdvancePaymentAllocationTabComponent;
let fixture: ComponentFixture<AdvancePaymentAllocationTabComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AdvancePaymentAllocationTabComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(AdvancePaymentAllocationTabComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,70 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { AdvancedPaymentAllocation, AdvancedPaymentStrategy, FutureInstallmentAllocationRule, PaymentAllocationOrder, PaymentAllocationTransactionType } from '../payment-allocation-model';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { MatTable } from '@angular/material/table';
import { MatDialog } from '@angular/material/dialog';
import { UntypedFormControl, Validators } from '@angular/forms';
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
@Component({
selector: 'mifosx-advance-payment-allocation-tab',
templateUrl: './advance-payment-allocation-tab.component.html',
styleUrls: ['./advance-payment-allocation-tab.component.scss']
})
export class AdvancePaymentAllocationTabComponent implements OnInit {
@Input() advancedPaymentAllocation: AdvancedPaymentAllocation;
@Output() paymentAllocationChange = new EventEmitter<boolean>();
@Output() transactionTypeRemoved = new EventEmitter<PaymentAllocationTransactionType>();
paymentAllocationsData: PaymentAllocationOrder[] = [];
/** Columns to be displayed in the table. */
displayedColumns: string[] = ['paymentAllocation', 'order'];
futureInstallmentAllocationRule = new UntypedFormControl('', Validators.required);
@ViewChild('table') table: MatTable<any>;
constructor(private dialog: MatDialog,
private advancedPaymentStrategy: AdvancedPaymentStrategy) { }
ngOnInit(): void {
this.paymentAllocationsData = this.advancedPaymentAllocation.paymentAllocationOrder;
this.futureInstallmentAllocationRule.patchValue(this.advancedPaymentAllocation.futureInstallmentAllocationRule.code);
this.futureInstallmentAllocationRule.valueChanges.subscribe((value: any) => {
this.advancedPaymentAllocation.futureInstallmentAllocationRules.forEach((item: FutureInstallmentAllocationRule) => {
if (value === item.code) {
this.advancedPaymentAllocation.futureInstallmentAllocationRule = item;
this.paymentAllocationChange.emit(true);
}
});
});
}
dropTable(event: CdkDragDrop<any[]>) {
const prevIndex = this.paymentAllocationsData.findIndex((d: any) => d === event.item.data);
moveItemInArray(this.paymentAllocationsData, prevIndex, event.currentIndex);
this.paymentAllocationsData = [...this.paymentAllocationsData];
this.advancedPaymentAllocation.paymentAllocationOrder = this.paymentAllocationsData;
this.table.renderRows();
this.paymentAllocationChange.emit(true);
}
isDefault(): boolean {
return this.advancedPaymentStrategy.isDefault(this.advancedPaymentAllocation.transaction);
}
removeTransaction(): void {
const dialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: ` the Transaction Type ${this.advancedPaymentAllocation.transaction.value}` }
});
dialogRef.afterClosed().subscribe((response: any) => {
if (response.delete) {
this.transactionTypeRemoved.emit(this.advancedPaymentAllocation.transaction);
}
});
}
}
@@ -1,55 +1,25 @@
<div class="container space-top" fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-tab-group animationDuration="0ms" fxLayout="row wrap" fxLayoutGap="2%"
fxLayout.lt-md="column">
<mat-form-field fxFlex="33%">
<mat-label>Transaction Type</mat-label>
<mat-select [formControl]="transactionTypeCtrl" disabled>
<mat-option *ngFor="let transactionTypeOption of transactionTypeOptions" [value]="transactionTypeOption.code">
{{ transactionTypeOption.name | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-tab *ngFor="let advancedPaymentAllocation of advancedPaymentAllocations">
<ng-template mat-tab-label>
{{advancedPaymentAllocation.transaction.code}}
</ng-template>
<mifosx-advance-payment-allocation-tab [advancedPaymentAllocation]="advancedPaymentAllocation"
(paymentAllocationChange)="paymentAllocationChanged($event)"
(transactionTypeRemoved)="transactionTypeRemoved($event)">
</mifosx-advance-payment-allocation-tab>
</mat-tab>
<h4 class="m-b-15 right" fxFlex="63%">You can drag and drop the rows to set a Payment Allocations order</h4>
<mat-tab disabled>
<ng-template mat-tab-label>
<button mat-icon-button (click)="addTransaction()">
<mat-icon>add</mat-icon>
</button>
</ng-template>
</mat-tab>
</mat-tab-group>
<div class="mat-elevation-z8 container m-t-15">
<table mat-table [dataSource]="paymentAllocationsData" #table cdkDropList
[cdkDropListData]="paymentAllocationsData" (cdkDropListDropped)="dropTable($event)">
<ng-container matColumnDef="paymentAllocation">
<th mat-header-cell *matHeaderCellDef> Payment Allocation </th>
<td mat-cell *matCellDef="let paymentAllocation"> {{ paymentAllocation.name }} </td>
</ng-container>
<ng-container matColumnDef="order">
<th mat-header-cell *matHeaderCellDef> Order </th>
<td mat-cell *matCellDef="let paymentAllocation; let rowIndex = index"> {{ (rowIndex + 1) }} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row cdkDrag [cdkDragData]="row" *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<mat-form-field fxFlex="33%" class="m-t-20">
<mat-label>Future Installment Allocation Rule</mat-label>
<mat-select [formControl]="futureInstallmentAllocationRule">
<mat-option *ngFor="let installmentAllocation of installmentAllocationOptions"
[value]="installmentAllocation.code">
{{ installmentAllocation.name | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlexFill fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
Previous
</button>
<button mat-raised-button matStepperNext>
Next
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
</div>
<div fxLayout="row wrap" fxLayout.lt-md="column" fxLayoutAlign="center" >
<mifosx-stepper-buttons fxFlex="100%" fxLayoutGap="2%"></mifosx-stepper-buttons>
</div>
@@ -0,0 +1,13 @@
@import "assets/styles/colours";
::ng-deep .mat-tab-body-wrapper {
width: 96%;
margin: auto;
}
::ng-deep .mat-tab-label.mat-tab-disabled {
padding: 0px;
margin: 0;
justify-content: left;
color: $status-approved;
}
@@ -1,9 +1,10 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { UntypedFormControl, Validators } from '@angular/forms';
import { AdvancedPaymentAllocation, AdvancedPaymentStrategy, PaymentAllocation, PaymentAllocationOrder, PaymentAllocationTransactionType } from './payment-allocation-model';
import { MatDialog } from '@angular/material/dialog';
import { MatTable } from '@angular/material/table';
import { FutureInstallmentAllocationRules, PaymentAllocation, PaymentAllocationOrder, PaymentAllocationTransactionType, PaymentAllocationTransactionTypes, PaymentAllocationTypes, PaymentCode } from './payment-allocation-model';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
import { MatTabGroup } from '@angular/material/tabs';
@Component({
selector: 'mifosx-loan-product-payment-strategy-step',
@@ -12,69 +13,85 @@ import { FutureInstallmentAllocationRules, PaymentAllocation, PaymentAllocationO
})
export class LoanProductPaymentStrategyStepComponent implements OnInit {
@Input() loanProductsTemplate: any;
@Input() transactionType: PaymentAllocationTransactionType;
@Output() paymentAllocation = new EventEmitter<PaymentAllocation[]>();
@Input() advancedPaymentAllocations: AdvancedPaymentAllocation[] = [];
@Input() advancedPaymentAllocationTransactionTypes: PaymentAllocationTransactionType[] = [];
@Input() paymentAllocationOrderDefault: PaymentAllocationOrder[];
@Output() paymentAllocationChange = new EventEmitter<boolean>();
@Output() setPaymentAllocation = new EventEmitter<PaymentAllocation[]>();
paymentAllocationsData: PaymentCode[] = [];
installmentAllocationOptions: PaymentCode[] = [];
transactionTypeOptions: PaymentCode[] = [];
@ViewChild(MatTabGroup) tabGroup: MatTabGroup;
transactionTypeCtrl = new UntypedFormControl('');
futureInstallmentAllocationRule = new UntypedFormControl('', Validators.required);
constructor(private dialog: MatDialog,
private advancedPaymentStrategy: AdvancedPaymentStrategy) { }
@ViewChild('table') table: MatTable<any>;
/** Columns to be displayed in the table. */
displayedColumns: string[] = ['paymentAllocation', 'order'];
constructor() { }
ngOnInit(): void {
this.paymentAllocationsData = [];
this.setOptions();
this.sendPaymentAllocation();
}
setOptions(): void {
this.paymentAllocationsData = PaymentAllocationTypes.options;
this.transactionTypeOptions = PaymentAllocationTransactionTypes.options;
this.transactionTypeCtrl.patchValue(this.transactionTypeOptions[0].code);
if (this.transactionType) {
this.transactionTypeCtrl.patchValue(this.transactionType.code);
}
this.installmentAllocationOptions = FutureInstallmentAllocationRules.options;
this.futureInstallmentAllocationRule.patchValue(this.installmentAllocationOptions[0].code);
}
dropTable(event: CdkDragDrop<any[]>) {
const prevIndex = this.paymentAllocationsData.findIndex((d: any) => d === event.item.data);
moveItemInArray(this.paymentAllocationsData, prevIndex, event.currentIndex);
this.paymentAllocationsData = [...this.paymentAllocationsData];
this.table.renderRows();
this.sendPaymentAllocation();
this.paymentAllocationChange.emit(true);
}
ngOnInit(): void { }
sendPaymentAllocation(): void {
let transactionTypeVal = PaymentAllocationTransactionTypes.DEFAULT_TRANSACTION.code;
if (this.transactionType) {
transactionTypeVal = this.transactionType.code;
}
const paymentAllocationOrder: PaymentAllocationOrder[] = [];
this.paymentAllocationsData.forEach((txType: PaymentCode, index: number) => {
paymentAllocationOrder.push({ 'paymentAllocationRule': txType.code, order: (index + 1) });
const data = this.advancedPaymentStrategy.buildPaymentAllocations(this.advancedPaymentAllocations);
this.setPaymentAllocation.emit(data);
}
paymentAllocationChanged(changed: boolean): void {
this.paymentAllocationChange.emit(changed);
this.sendPaymentAllocation();
}
addTransaction(): void {
const transactionTypesCurrent: String[] = [];
this.advancedPaymentAllocations.forEach((item: AdvancedPaymentAllocation) => {
transactionTypesCurrent.push(item.transaction.code);
});
const paymentAllocation: PaymentAllocation[] = [];
paymentAllocation.push({
'futureInstallmentAllocationRule': this.futureInstallmentAllocationRule.value,
'transactionType': transactionTypeVal,
'paymentAllocationOrder': paymentAllocationOrder
const transactionTypesOptions: PaymentAllocationTransactionType[] = [];
this.advancedPaymentAllocationTransactionTypes.forEach((option: PaymentAllocationTransactionType) => {
if (!this.advancedPaymentStrategy.isDefault(option) && transactionTypesCurrent.indexOf(option.code) < 0) {
transactionTypesOptions.push(option);
}
});
this.paymentAllocation.emit(paymentAllocation);
const formfields: FormfieldBase[] = [
new SelectBase({
controlName: 'code',
label: 'Transaction Type',
options: { label: 'code', value: 'code', data: transactionTypesOptions },
order: 1
})
];
const data = {
title: 'Advanced Payment Allocation Transaction Type',
layout: { addButtonText: 'Add' },
formfields: formfields
};
const transactionTypeDialogRef = this.dialog.open(FormDialogComponent, { data });
transactionTypeDialogRef.afterClosed().subscribe((response: any) => {
if (response.data) {
const defaultPaymentAllocation: AdvancedPaymentAllocation = this.advancedPaymentAllocations[0];
transactionTypesOptions.forEach((transactionType: PaymentAllocationTransactionType) => {
if (transactionType.code === response.data.value.code) {
this.advancedPaymentAllocations.push(
this.advancedPaymentStrategy.buildAdvancedPaymentAllocation(true, transactionType,
this.paymentAllocationOrderDefault,
defaultPaymentAllocation.futureInstallmentAllocationRules)
);
this.paymentAllocationChange.emit(true);
this.tabGroup.selectedIndex = (this.advancedPaymentAllocations.length - 1);
this.sendPaymentAllocation();
}
});
}
});
}
transactionTypeRemoved(transaction: PaymentAllocationTransactionType): void {
this.advancedPaymentAllocations.forEach((item: AdvancedPaymentAllocation, index: number) => {
if (item.transaction.code === transaction.code) {
this.advancedPaymentAllocations.splice(index, 1);
this.paymentAllocationChange.emit(true);
this.tabGroup.selectedIndex = (index - 1);
this.sendPaymentAllocation();
}
});
}
}
@@ -1,9 +1,25 @@
export interface PaymentCode {
name: string;
import { Injectable } from '@angular/core';
export interface PaymentAllocationTransactionType {
id: number;
value: string;
code: string;
}
export interface PaymentAllocationType {
id: number;
value: string;
code: string;
}
export interface FutureInstallmentAllocationRule {
id: number;
value: string;
code: string;
}
export interface PaymentAllocationOrder {
id?: number;
paymentAllocationRule: string;
order: number;
}
@@ -14,40 +30,125 @@ export interface PaymentAllocation {
futureInstallmentAllocationRule: string;
}
export interface PaymentAllocationTransactionType {
name: string;
code: string;
}
export class PaymentAllocationTransactionTypes {
public static DEFAULT_TRANSACTION: PaymentAllocationTransactionType = { name: 'Default', code: 'DEFAULT' };
public static options: PaymentCode[] = [
this.DEFAULT_TRANSACTION
];
public static DEFAULT_TRANSACTION: PaymentAllocationTransactionType = { id: 1, value: 'Default', code: 'DEFAULT' };
}
export class PaymentAllocationTypes {
public static options: PaymentCode[] = [
{ name: 'Past due penalty', code: 'PAST_DUE_PENALTY' },
{ name: 'Past due fee', code: 'PAST_DUE_FEE' },
{ name: 'Past due principal', code: 'PAST_DUE_PRINCIPAL' },
{ name: 'Past due interest', code: 'PAST_DUE_INTEREST' },
{ name: 'Due penalty', code: 'DUE_PENALTY' },
{ name: 'Due fee', code: 'DUE_FEE' },
{ name: 'Due principal', code: 'DUE_PRINCIPAL' },
{ name: 'Due interest', code: 'DUE_INTEREST' },
{ name: 'In advance penalty', code: 'IN_ADVANCE_PENALTY' },
{ name: 'In advance fee', code: 'IN_ADVANCE_FEE' },
{ name: 'In advance principal', code: 'IN_ADVANCE_PRINCIPAL' },
{ name: 'In advance interest', code: 'IN_ADVANCE_INTEREST' }
];
export class AdvancedPaymentAllocation {
transaction: PaymentAllocationTransactionType;
paymentAllocationOrder: PaymentAllocationOrder[];
futureInstallmentAllocationRule: FutureInstallmentAllocationRule;
futureInstallmentAllocationRules: FutureInstallmentAllocationRule[];
}
export class FutureInstallmentAllocationRules {
public static options: PaymentCode[] = [
{ name: 'Next Installment', code: 'NEXT_INSTALLMENT' },
{ name: 'Last Installment', code: 'LAST_INSTALLMENT' },
{ name: 'Reamortization', code: 'REAMORTIZATION' }
];
@Injectable({
providedIn: 'root'
})
export class AdvancedPaymentStrategy {
public buildAdvancedPaymentAllocationList(loanProduct: any): AdvancedPaymentAllocation[] {
const advancedPaymentAllocation: AdvancedPaymentAllocation[] = [];
const advancedPaymentAllocationTransactionTypes: PaymentAllocationTransactionType[] =
loanProduct.advancedPaymentAllocationTransactionTypes;
const advancedPaymentAllocationTypes: PaymentAllocationOrder[] = loanProduct.advancedPaymentAllocationTypes;
const advancedPaymentAllocationFutureInstallmentAllocationRules: FutureInstallmentAllocationRule[] =
loanProduct.advancedPaymentAllocationFutureInstallmentAllocationRules;
loanProduct.paymentAllocation.forEach((item: PaymentAllocation) => {
advancedPaymentAllocation.push({
transaction: this.getItemByCode(item.transactionType, advancedPaymentAllocationTransactionTypes),
paymentAllocationOrder: this.buildCurrentPaymentAllocationOrder(item.paymentAllocationOrder,
advancedPaymentAllocationTypes),
futureInstallmentAllocationRule: this.getItemByCode(item.futureInstallmentAllocationRule,
advancedPaymentAllocationFutureInstallmentAllocationRules),
futureInstallmentAllocationRules: advancedPaymentAllocationFutureInstallmentAllocationRules,
});
});
// If this is Empty, add the Default
if (advancedPaymentAllocation.length === 0) {
advancedPaymentAllocation.push({
transaction: PaymentAllocationTransactionTypes.DEFAULT_TRANSACTION,
paymentAllocationOrder: this.buildPaymentAllocationTransactionOrder(true, [], loanProduct.advancedPaymentAllocationTypes),
futureInstallmentAllocationRule: loanProduct.advancedPaymentAllocationFutureInstallmentAllocationRules[0],
futureInstallmentAllocationRules: loanProduct.advancedPaymentAllocationFutureInstallmentAllocationRules,
});
}
return advancedPaymentAllocation;
}
public buildAdvancedPaymentAllocation(forDefault: boolean, transaction: PaymentAllocationTransactionType,
currentItems: PaymentAllocationOrder[],
futureInstallmentAllocationRules: FutureInstallmentAllocationRule[]): AdvancedPaymentAllocation {
return {
transaction: transaction,
paymentAllocationOrder: this.buildPaymentAllocationTransactionOrder(forDefault, currentItems, currentItems),
futureInstallmentAllocationRule: futureInstallmentAllocationRules[0],
futureInstallmentAllocationRules: futureInstallmentAllocationRules,
};
}
public buildPaymentAllocationTransactionOrder(forDefault: boolean, currentItems: PaymentAllocationOrder[], defaultItems: PaymentAllocationOrder[]): PaymentAllocationOrder[] {
const paymentAllocationOrder: PaymentAllocationOrder[] = [];
if (forDefault) {
return defaultItems;
} else {
currentItems.forEach((currentItem: PaymentAllocationOrder) => {
defaultItems.forEach((item: PaymentAllocationOrder) => {
if (currentItem.id === item.id) {
paymentAllocationOrder.push(item);
}
});
});
}
return paymentAllocationOrder;
}
public buildPaymentAllocations(advancedPaymentAllocations: AdvancedPaymentAllocation[]): PaymentAllocation[] {
const paymentAllocations: PaymentAllocation[] = [];
advancedPaymentAllocations.forEach((paymentAllocation: AdvancedPaymentAllocation) => {
paymentAllocations.push({
transactionType: paymentAllocation.transaction.code,
paymentAllocationOrder: this.buildPaymentAllocationOrder(paymentAllocation.paymentAllocationOrder),
futureInstallmentAllocationRule: paymentAllocation.futureInstallmentAllocationRule.code
});
});
return paymentAllocations;
}
public buildPaymentAllocationOrder(paymentAllocationOrder: PaymentAllocationOrder[]): PaymentAllocationOrder[] {
const paymentAllocations: any[] = [];
paymentAllocationOrder.forEach((item: any, index: number) => {
paymentAllocations.push({
order: (index + 1),
paymentAllocationRule: item.code
});
});
return paymentAllocations;
}
public buildCurrentPaymentAllocationOrder(currentItems: any[], defaultItems: PaymentAllocationOrder[]):
PaymentAllocationOrder[] {
const paymentAllocationOrder: PaymentAllocationOrder[] = [];
currentItems.forEach((item: any) => {
paymentAllocationOrder.push(this.getItemByCode(item.paymentAllocationRule, defaultItems));
});
return paymentAllocationOrder;
}
public getItemByCode(code: string,
options: any[]): any {
let transaction: any;
options.forEach((option: any) => {
if (option.code === code) {
transaction = option;
}
});
return transaction;
}
public isDefault(transaction: PaymentAllocationTransactionType): boolean {
return (transaction.code === PaymentAllocationTransactionTypes.DEFAULT_TRANSACTION.code);
}
}
@@ -324,42 +324,19 @@
<div fxFlexFill>
<span fxFlex="40%">Repayment Strategy:</span>
<span fxFlex="60%">{{ loanProduct.transactionProcessingStrategyCode |
<span fxFlex="60%" class="repayment-strategy">{{ loanProduct.transactionProcessingStrategyCode |
find:loanProductsTemplate.transactionProcessingStrategyOptions:'code':'name' }}</span>
</div>
<div fxFlexFill *ngIf="loanProduct.paymentAllocation?.length">
<div fxFlexFill *ngFor="let paymentAllocation of loanProduct.paymentAllocation">
<div fxFlexFill>
<span fxFlex="40%">Transaction Type:</span>
<span fxFlex="60%">{{ paymentAllocation.transactionType }}</span>
</div>
<div fxFlexFill>
<span fxFlex="40%">Future Installment Allocation:</span>
<span fxFlex="60%">{{ paymentAllocation.futureInstallmentAllocationRule }}</span>
</div>
<div fxFlexFill>
<span fxFlex="40%">Payment Allocation Order</span>
<span fxFlex="60%">
<table>
<thead>
<tr>
<th>Payment Allocation Rule</th>
<th>Order</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let paymentAllocation of paymentAllocation.paymentAllocationOrder; let idx = index;">
<td>{{ paymentAllocation.paymentAllocationRule }}</td>
<td>{{ (idx + 1) }}</td>
</tr>
</tbody>
</table>
</span>
</div>
</div>
<div fxFlexFill *ngIf="isAdvancedPaymentAllocation">
<span fxFlex="40%">Advanced Payment Allocation Transactions:</span>
<span fxFlex="60%">
<mat-accordion>
<mifosx-view-advance-paymeny-allocation *ngFor="let paymentAllocation of loanProduct.paymentAllocation"
[paymentAllocation]="paymentAllocation">
</mifosx-view-advance-paymeny-allocation>
</mat-accordion>
</span>
</div>
<div fxFlexFill *ngIf="loanProduct.graceOnPrincipalPayment">
@@ -1,3 +1,5 @@
@import "assets/styles/colours";
table {
width: 100%;
}
@@ -22,3 +24,7 @@ mat-divider {
.margin-t {
margin-top: 1em;
}
.repayment-strategy {
color: $status-approved;
}
@@ -1,11 +1,11 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'mifosx-loan-product-preview-step',
templateUrl: './loan-product-preview-step.component.html',
styleUrls: ['./loan-product-preview-step.component.scss']
})
export class LoanProductPreviewStepComponent implements OnInit {
export class LoanProductPreviewStepComponent implements OnInit, OnChanges {
@Input() loanProductsTemplate: any;
@Input() accountingRuleData: any;
@@ -17,9 +17,16 @@ export class LoanProductPreviewStepComponent implements OnInit {
paymentFundSourceDisplayedColumns: string[] = ['paymentTypeId', 'fundSourceAccountId'];
feesPenaltyIncomeDisplayedColumns: string[] = ['chargeId', 'incomeAccountId'];
isAdvancedPaymentAllocation = false;
constructor() { }
ngOnInit() {
this.isAdvancedPaymentAllocation = (this.loanProduct.transactionProcessingStrategyCode === 'advanced-payment-allocation-strategy');
}
ngOnChanges(changes: SimpleChanges): void {
this.isAdvancedPaymentAllocation = (this.loanProduct.transactionProcessingStrategyCode === 'advanced-payment-allocation-strategy');
}
}
@@ -328,41 +328,18 @@
<div fxFlexFill>
<span fxFlex="40%">Repayment Strategy:</span>
<span fxFlex="60%">{{ loanProduct.transactionProcessingStrategyName }}</span>
<span fxFlex="60%" class="repayment-strategy">{{ loanProduct.transactionProcessingStrategyName }}</span>
</div>
<div fxFlexFill *ngIf="loanProduct.paymentAllocation?.length">
<div fxFlexFill *ngFor="let paymentAllocation of loanProduct.paymentAllocation">
<div fxFlexFill>
<span fxFlex="40%">Transaction Type:</span>
<span fxFlex="60%">{{ paymentAllocation.transactionType }}</span>
</div>
<div fxFlexFill>
<span fxFlex="40%">Future Installment Allocation:</span>
<span fxFlex="60%">{{ paymentAllocation.futureInstallmentAllocationRule }}</span>
</div>
<div fxFlexFill>
<span fxFlex="40%">Payment Allocation Order</span>
<span fxFlex="60%">
<table>
<thead>
<tr>
<th>Payment Allocation Rule</th>
<th>Order</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let paymentAllocation of paymentAllocation.paymentAllocationOrder; let idx = index;">
<td>{{ paymentAllocation.paymentAllocationRule }}</td>
<td>{{ (idx + 1) }}</td>
</tr>
</tbody>
</table>
</span>
</div>
</div>
<span fxFlex="40%">Advanced Payment Allocation Transactions:</span>
<span fxFlex="60%">
<mat-accordion>
<mifosx-view-advance-paymeny-allocation *ngFor="let paymentAllocation of loanProduct.paymentAllocation"
[paymentAllocation]="paymentAllocation">
</mifosx-view-advance-paymeny-allocation>
</mat-accordion>
</span>
</div>
<div fxFlexFill *ngIf="loanProduct.graceOnPrincipalPayment">
@@ -20,3 +20,7 @@
font-weight: 200;
color: $status-approved;
}
.repayment-strategy {
color: $status-approved;
}
@@ -0,0 +1,42 @@
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<div fxFlexFill>
<span fxFlex="40%">Transaction Type:</span>
<span fxFlex="60%">{{ paymentAllocation.transactionType }}</span>
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div fxFlexFill>
<div fxFlexFill>
<span fxFlex="40%">Transaction Type:</span>
<span fxFlex="60%">{{ paymentAllocation.transactionType }}</span>
</div>
<div fxFlexFill>
<span fxFlex="40%">Future Installment Allocation:</span>
<span fxFlex="60%">{{ paymentAllocation.futureInstallmentAllocationRule }}</span>
</div>
<div fxFlexFill>
<span fxFlex="40%">Payment Allocation Order</span>
<span fxFlex="60%">
<table>
<thead>
<tr>
<th>Payment Allocation Rule</th>
<th>Order</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let paymentAllocation of paymentAllocation.paymentAllocationOrder; let idx = index;">
<td>{{ paymentAllocation.paymentAllocationRule }}</td>
<td>{{ (idx + 1) }}</td>
</tr>
</tbody>
</table>
</span>
</div>
</div>
</mat-expansion-panel>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ViewAdvancePaymenyAllocationComponent } from './view-advance-paymeny-allocation.component';
describe('ViewAdvancePaymenyAllocationComponent', () => {
let component: ViewAdvancePaymenyAllocationComponent;
let fixture: ComponentFixture<ViewAdvancePaymenyAllocationComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ViewAdvancePaymenyAllocationComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ViewAdvancePaymenyAllocationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
import { PaymentAllocation } from 'app/products/loan-products/loan-product-stepper/loan-product-payment-strategy-step/payment-allocation-model';
@Component({
selector: 'mifosx-view-advance-paymeny-allocation',
templateUrl: './view-advance-paymeny-allocation.component.html',
styleUrls: ['./view-advance-paymeny-allocation.component.scss']
})
export class ViewAdvancePaymenyAllocationComponent implements OnInit {
@Input() paymentAllocation: PaymentAllocation;
constructor() { }
ngOnInit(): void {
}
}
+5 -1
View File
@@ -117,6 +117,8 @@ import { RecurringDepositGeneralTabComponent } from './recurring-deposit-product
import { ShareProductGeneralTabComponent } from './share-products/view-share-product/share-product-general-tab/share-product-general-tab.component';
import { ShareProductDatatableTabComponent } from './share-products/view-share-product/share-product-datatable-tab/share-product-datatable-tab.component';
import { LoanProductPaymentStrategyStepComponent } from './loan-products/loan-product-stepper/loan-product-payment-strategy-step/loan-product-payment-strategy-step.component';
import { ViewAdvancePaymenyAllocationComponent } from './loan-products/view-loan-product/shared/view-advance-paymeny-allocation/view-advance-paymeny-allocation.component';
import { AdvancePaymentAllocationTabComponent } from './loan-products/loan-product-stepper/loan-product-payment-strategy-step/advance-payment-allocation-tab/advance-payment-allocation-tab.component';
/**
* Products Module
@@ -241,7 +243,9 @@ import { LoanProductPaymentStrategyStepComponent } from './loan-products/loan-pr
RecurringDepositDatatableTabComponent,
RecurringDepositGeneralTabComponent,
ShareProductGeneralTabComponent,
ShareProductDatatableTabComponent
ShareProductDatatableTabComponent,
ViewAdvancePaymenyAllocationComponent,
AdvancePaymentAllocationTabComponent
]
})
export class ProductsModule { }
+5 -2
View File
@@ -42,6 +42,7 @@ import { ViewJournalEntryTransactionComponent } from './accounting/view-journal-
import { AccountNumberComponent } from './account-number/account-number.component';
import { EntityNameComponent } from './entity-name/entity-name.component';
import { TransactionPaymentDetailComponent } from './transaction-payment-detail/transaction-payment-detail.component';
import { StepperButtonsComponent } from './steppers/stepper-buttons/stepper-buttons.component';
/**
* Shared Module
@@ -90,7 +91,8 @@ import { TransactionPaymentDetailComponent } from './transaction-payment-detail/
ViewJournalEntryTransactionComponent,
AccountNumberComponent,
EntityNameComponent,
TransactionPaymentDetailComponent
TransactionPaymentDetailComponent,
StepperButtonsComponent
],
exports: [
FileUploadComponent,
@@ -117,7 +119,8 @@ import { TransactionPaymentDetailComponent } from './transaction-payment-detail/
ViewJournalEntryTransactionComponent,
SvgIconComponent,
EntityNameComponent,
TransactionPaymentDetailComponent
TransactionPaymentDetailComponent,
StepperButtonsComponent
]
})
export class SharedModule { }
@@ -0,0 +1,11 @@
<div fxFlexFill fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
<button mat-raised-button matStepperPrevious [disabled]="disablePrevious">
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
Previous
</button>
<button mat-raised-button matStepperNext [disabled]="disableNext">
Next
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
</div>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StepperButtonsComponent } from './stepper-buttons.component';
describe('StepperButtonsComponent', () => {
let component: StepperButtonsComponent;
let fixture: ComponentFixture<StepperButtonsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ StepperButtonsComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(StepperButtonsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'mifosx-stepper-buttons',
templateUrl: './stepper-buttons.component.html',
styleUrls: ['./stepper-buttons.component.scss']
})
export class StepperButtonsComponent implements OnInit {
@Input() disablePrevious = false;
@Input() disableNext = false;
constructor() { }
ngOnInit(): void {
}
}