Allow Loan re-aging transaction

This commit is contained in:
Jose Alberto Hernandez
2024-02-28 09:20:36 +01:00
committed by Adam Saghy
parent c6b134fc17
commit 36a8e275be
25 changed files with 297 additions and 3 deletions
+28
View File
@@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { OptionData } from 'app/shared/models/option-data.model';
@Injectable({
providedIn: 'root'
})
export class DropdownOptions {
public retrievePeriodFrequencyTypeOptions(includeAll: boolean): OptionData[] {
if (includeAll) {
return [
{ id: 0, code: 'DAYS', value: 'Days' },
{ id: 1, code: 'WEEKS', value: 'Weeks' },
{ id: 2, code: 'MONTHS', value: 'Months' },
{ id: 3, code: 'YEARS', value: 'Years' },
{ id: 4, code: 'WHOLE_TERM', value: 'Whole Term' },
{ id: 5, code: 'INVALID', value: 'Invalid' },
];
} else {
return [
{ id: 0, code: 'DAYS', value: 'Days' },
{ id: 1, code: 'WEEKS', value: 'Weeks' },
{ id: 2, code: 'MONTHS', value: 'Months' },
{ id: 3, code: 'YEARS', value: 'Years' }
];
}
}
}
@@ -28,3 +28,4 @@
<mifosx-charge-off [dataObject]="actionButtonData" *ngIf="actions['Charge-Off']"></mifosx-charge-off>
<mifosx-undo-command [actionName]="actionName" *ngIf="actions['Undo Charge-Off']"></mifosx-undo-command>
<mifosx-asset-transfer-loan *ngIf="actions['Sell Loan'] || actions['Buy Back Loan']"></mifosx-asset-transfer-loan>
<mifosx-loan-reaging [dataObject]="actionButtonData" *ngIf="actions['Re-Ageing']"></mifosx-loan-reaging>
@@ -44,7 +44,8 @@ export class LoanAccountActionsComponent {
'Charge-Off': boolean,
'Undo Charge-Off': boolean,
'Sell Loan': boolean,
'Buy Back Loan': boolean
'Buy Back Loan': boolean,
'Re-Ageing': boolean
} = {
'Close': false,
'Undo Approval': false,
@@ -77,7 +78,8 @@ export class LoanAccountActionsComponent {
'Charge-Off': false,
'Undo Charge-Off': false,
'Sell Loan': false,
'Buy Back Loan': false
'Buy Back Loan': false,
'Re-Ageing': false
};
actionButtonData: any;
@@ -0,0 +1,53 @@
<div class="container mat-elevation-z8">
<mat-card>
<form [formGroup]="reagingLoanForm" (ngSubmit)="submit()">
<mat-card-content>
<div fxLayout="column">
<mat-form-field>
<mat-label>{{"labels.inputs.Number of Installments" | translate}}</mat-label>
<input type="number" matInput required formControlName="numberOfInstallments">
</mat-form-field>
<mat-form-field>
<mat-label>{{"labels.inputs.Frequency Type" | translate}}</mat-label>
<mat-select formControlName="frequency">
<mat-option *ngFor="let frequency of frequencyOptions" [value]="frequency.code">
{{ frequency.value }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field (click)="startDatePicker.open()">
<mat-label>{{"labels.inputs.Start Date" | translate}}</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="startDatePicker" required
formControlName="startDate">
<mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
<mat-datepicker #startDatePicker></mat-datepicker>
<mat-error *ngIf="reagingLoanForm.controls.startDate.hasError('required')">
{{"labels.inputs.Start Date" | translate}} {{"labels.commons.is" | translate}}
<strong>{{"labels.commons.required" | translate}}</strong>
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>{{"labels.inputs.External Id" | translate}}</mat-label>
<input matInput formControlName="externalId">
</mat-form-field>
</div>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['../../general']">{{"labels.buttons.Cancel" |
translate}}</button>
<button mat-raised-button color="primary" [disabled]="!reagingLoanForm.valid"
*mifosxHasPermission="'REAGING_LOAN'">{{"labels.buttons.Submit" | translate}}</button>
</mat-card-actions>
</mat-card-content>
</form>
</mat-card>
</div>
@@ -0,0 +1,3 @@
.container {
max-width: 37rem;
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoanReagingComponent } from './loan-reaging.component';
describe('LoanReagingComponent', () => {
let component: LoanReagingComponent;
let fixture: ComponentFixture<LoanReagingComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoanReagingComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(LoanReagingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,73 @@
import { Component, Input, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Dates } from 'app/core/utils/dates';
import { DropdownOptions } from 'app/core/utils/dropdownOptions';
import { LoansService } from 'app/loans/loans.service';
import { SettingsService } from 'app/settings/settings.service';
import { OptionData } from 'app/shared/models/option-data.model';
@Component({
selector: 'mifosx-loan-reaging',
templateUrl: './loan-reaging.component.html',
styleUrls: ['./loan-reaging.component.scss']
})
export class LoanReagingComponent implements OnInit {
@Input() dataObject: any;
/** Loan Id */
loanId: string;
/** Repayment Loan Form */
reagingLoanForm: UntypedFormGroup;
frequencyOptions: OptionData[] = [];
/** Minimum Date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum Date allowed. */
maxDate = new Date();
constructor(private formBuilder: UntypedFormBuilder,
private route: ActivatedRoute,
private router: Router,
private settingsService: SettingsService,
private dropdownOptions: DropdownOptions,
private loanService: LoansService,
private dateUtils: Dates) {
this.loanId = this.route.snapshot.params['loanId'];
this.frequencyOptions = this.dropdownOptions.retrievePeriodFrequencyTypeOptions(false);
}
ngOnInit(): void {
this.maxDate = this.settingsService.maxFutureDate;
this.createReagingLoanForm();
}
createReagingLoanForm() {
this.reagingLoanForm = this.formBuilder.group({
'numberOfInstallments': [1, Validators.required],
'startDate': [this.settingsService.businessDate, Validators.required],
'frequency': [, Validators.required],
'externalId': ''
});
}
submit(): void {
const reagingLoanFormData = this.reagingLoanForm.value;
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const startDate: Date = this.reagingLoanForm.value.startDate;
if (reagingLoanFormData.startDate instanceof Date) {
reagingLoanFormData.startDate = this.dateUtils.formatDate(startDate, dateFormat);
}
const data = {
...reagingLoanFormData,
dateFormat,
locale
};
this.loanService.submitLoanActionButton(this.loanId, data, 'reAge')
.subscribe((response: any) => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
});
}
}
@@ -16,6 +16,7 @@ import { LoanStatus } from '../models/loan-status.model';
import { Currency } from 'app/shared/models/general.model';
import { DelinquencyPausePeriod } from '../models/loan-account.model';
import { TranslateService } from '@ngx-translate/core';
import { LoanTransaction } from 'app/products/loan-products/models/loan-account.model';
@Component({
selector: 'mifosx-loans-view',
@@ -47,6 +48,7 @@ export class LoansViewComponent implements OnInit {
loanDelinquencyClassificationStyle = '';
loanStatus: LoanStatus;
currency: Currency;
loanReAged = false;
constructor(private route: ActivatedRoute,
private router: Router,
@@ -59,6 +61,11 @@ export class LoansViewComponent implements OnInit {
this.loanDisplayArrearsDelinquency = data.loanArrearsDelinquencyConfig.value || 0;
this.loanStatus = this.loanDetailsData.status;
this.currency = this.loanDetailsData.currency;
this.loanDetailsData.transactions.some((lt: LoanTransaction) => {
if (lt.type.reAge) {
this.loanReAged = true;
}
});
});
this.loanId = this.route.snapshot.params['loanId'];
this.clientId = this.loanDetailsData.clientId;
@@ -164,6 +171,14 @@ export class LoansViewComponent implements OnInit {
});
}
// Allow Re-Ageing only when there is not any Re-Age transaction
if (!this.loanReAged) {
this.buttonConfig.addButton({
name: 'Re-Ageing',
icon: 'calendar',
taskPermissionName: 'REAGE_LOAN',
});
}
}
}
@@ -38,6 +38,9 @@
.chargeoff {
color: $status-chargeoff;
}
.reage {
color: $status-reage;
}
}
}
@@ -142,6 +142,9 @@ export class TransactionsTabComponent implements OnInit {
if (this.isDownPayment(transaction.type)) {
return 'down-payment';
}
if (this.isReAge(transaction.type)) {
return 'reage';
}
return '';
}
@@ -209,6 +212,10 @@ export class TransactionsTabComponent implements OnInit {
return (transactionType.downPayment || transactionType.code === 'loanTransactionType.downPayment');
}
private isReAge(transactionType: any): boolean {
return (transactionType.code === 'loanTransactionType.reAge');
}
private reload() {
const clientId = this.route.parent.parent.snapshot.params['clientId'];
const loanId = this.route.parent.parent.snapshot.params['loanId'];
+3 -1
View File
@@ -80,6 +80,7 @@ import { ExternalAssetOwnerTabComponent } from './loans-view/external-asset-owne
import { ExternalAssetTransferComponent } from './loans-view/external-asset-owner-tab/external-asset-transfer/external-asset-transfer.component';
import { TranslateModule } from '@ngx-translate/core';
import { LoanDelinquencyActionDialogComponent } from './custom-dialog/loan-delinquency-action-dialog/loan-delinquency-action-dialog.component';
import { LoanReagingComponent } from './loans-view/loan-account-actions/loan-reaging/loan-reaging.component';
/**
* Loans Module
@@ -163,7 +164,8 @@ import { LoanDelinquencyActionDialogComponent } from './custom-dialog/loan-delin
AssetTransferLoanComponent,
ExternalAssetOwnerTabComponent,
ExternalAssetTransferComponent,
LoanDelinquencyActionDialogComponent
LoanDelinquencyActionDialogComponent,
LoanReagingComponent
],
providers: [ ],
})
@@ -0,0 +1,59 @@
import { Currency } from 'app/shared/models/general.model';
export interface LoanTransaction {
id: number;
loanId: number;
officeId: number;
officeName: string;
type: LoanTransactionType;
date: number[];
currency: Currency;
amount: number;
netDisbursalAmount: number;
principalPortion: number;
interestPortion: number;
feeChargesPortion: number;
penaltyChargesPortion: number;
overpaymentPortion: number;
unrecognizedIncomePortion: number;
externalId: string;
outstandingLoanBalance: number;
submittedOnDate: number[];
manuallyReversed: boolean;
loanChargePaidByList: any[];
numberOfRepayments: number;
transactionRelations: any[];
}
export interface LoanTransactionType {
id: number;
code: string;
value: string;
disbursement: boolean;
repaymentAtDisbursement: boolean;
repayment: boolean;
merchantIssuedRefund: boolean;
payoutRefund: boolean;
goodwillCredit: boolean;
chargeRefund: boolean;
contra: boolean;
waiveInterest: boolean;
waiveCharges: boolean;
accrual: boolean;
writeOff: boolean;
recoveryRepayment: boolean;
initiateTransfer: boolean;
approveTransfer: boolean;
withdrawTransfer: boolean;
rejectTransfer: boolean;
chargePayment: boolean;
refund: boolean;
refundForActiveLoans: boolean;
creditBalanceRefund: boolean;
chargeAdjustment: boolean;
chargeback: boolean;
chargeoff: boolean;
downPayment: boolean;
reAge: boolean;
}