Allow Loan re-aging transaction
This commit is contained in:
committed by
Adam Saghy
parent
c6b134fc17
commit
36a8e275be
@@ -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;
|
||||
|
||||
+53
@@ -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;
|
||||
}
|
||||
+23
@@ -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'];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user