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;
}
+1
View File
@@ -71,6 +71,7 @@ $status-approved: #0f36e4;
$status-chargeoff: #f39c12;
$status-overpaid: #800080;
$status-downpayment: #16a085;
$status-reage: #e08e0b;
$status-active-overdue: #ff0000;
$status-matured: #ff4000;
$state-done: #00ce00;
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Počet dní do Escheat",
"Number of Days to Inactive sub-status": "Počet dní do neaktivního dílčího stavu",
"Number of Groups": "Počet skupin",
"Number of Installments": "Počet splátek",
"Number of Repayments": "Počet splátek",
"Number of Staff": "Počet zaměstnanců",
"Number of days a loan may be overdue before moving into arrears": "Počet dní, po které může být půjčka po splatnosti, než se dostane do prodlení",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Půjčka před výplatou",
"Products": "produkty",
"Profile": "Profil",
"Re-Ageing": "Stárnutí",
"Recover From Guarantor": "Vymáhání od ručitele",
"Recovery Payment": "Platba obnovy",
"Reject": "Odmítnout",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Anzahl der Tage bis zur Pfändung",
"Number of Days to Inactive sub-status": "Anzahl der Tage bis zum Unterstatus „Inaktiv“.",
"Number of Groups": "Anzahl der Gruppen",
"Number of Installments": "Anzahl der Raten",
"Number of Repayments": "Anzahl der Rückzahlungen",
"Number of Staff": "Anzahl der Mitarbeiter",
"Number of days a loan may be overdue before moving into arrears": "Anzahl der Tage, die ein Kredit überfällig sein kann, bevor er in Verzug gerät",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Darlehen mit Vorauszahlung",
"Products": "Produkte",
"Profile": "Profil",
"Re-Ageing": "Altern",
"Recover From Guarantor": "Rückforderung vom Bürgen",
"Recovery Payment": "Wiederherstellungszahlung",
"Reject": "Ablehnen",
+2
View File
@@ -1612,6 +1612,7 @@
"Number of Days to Escheat": "Number of Days to Escheat",
"Number of Days to Inactive sub-status": "Number of Days to Inactive sub-status",
"Number of Groups": "Number of Groups",
"Number of Installments": "Number of Installments",
"Number of Repayments": "Number of Repayments",
"Number of Staff": "Number of Staff",
"Number of days a loan may be overdue before moving into arrears": "Number of days a loan may be overdue before moving into arrears",
@@ -2143,6 +2144,7 @@
"Prepay Loan": "Prepay Loan",
"Products": "Products",
"Profile": "Profile",
"Re-Ageing": "Re-Ageing",
"Recover From Guarantor": "Recover From Guarantor",
"Recovery Payment": "Recovery Payment",
"Reject": "Reject",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Número de días para evitar",
"Number of Days to Inactive sub-status": "Número de días hasta el subestado inactivo",
"Number of Groups": "Número de grupos",
"Number of Installments": "Número de cuotas",
"Number of Repayments": "Número de reembolsos",
"Number of Staff": "Numero de empleado",
"Number of days a loan may be overdue before moving into arrears": "Número de días que un Crédito puede estar vencido antes de entrar en mora",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Prepagar crédito",
"Products": "Productos",
"Profile": "Perfil",
"Re-Ageing": "Envejecimiento",
"Recover From Guarantor": "Recuperar del Garante",
"Recovery Payment": "Pago de recuperación",
"Reject": "Rechazar",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Nombre de jours pour éviter",
"Number of Days to Inactive sub-status": "Nombre de jours avant le sous-statut inactif",
"Number of Groups": "Nombre de groupes",
"Number of Installments": "Nombre d'installations",
"Number of Repayments": "Nombre de remboursements",
"Number of Staff": "Nombre d'employés",
"Number of days a loan may be overdue before moving into arrears": "Nombre de jours pendant lesquels un prêt peut être en retard avant de se retrouver en arriérés",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Prêt à remboursement anticipé",
"Products": "Des produits",
"Profile": "Profil",
"Re-Ageing": "Vieillissement",
"Recover From Guarantor": "Récupération auprès du garant",
"Recovery Payment": "Paiement de récupération",
"Reject": "Rejeter",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Numero di giorni per l'evasione",
"Number of Days to Inactive sub-status": "Numero di giorni allo stato secondario Inattivo",
"Number of Groups": "Numero di gruppi",
"Number of Installments": "Numero di installazioni",
"Number of Repayments": "Numero di rimborsi",
"Number of Staff": "Numero di personale",
"Number of days a loan may be overdue before moving into arrears": "Numero di giorni in cui un prestito può essere scaduto prima di entrare in arretrato",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Prestito con pagamento anticipato",
"Products": "Prodotti",
"Profile": "Profilo",
"Re-Ageing": "Invecchiamento",
"Recover From Guarantor": "Recupero dal garante",
"Recovery Payment": "Pagamento di recupero",
"Reject": "Rifiutare",
+2
View File
@@ -1614,6 +1614,7 @@
"Number of Days to Escheat": "탈퇴까지 남은 일수",
"Number of Days to Inactive sub-status": "비활성 하위 상태까지 남은 일수",
"Number of Groups": "그룹 수",
"Number of Installments": "할부 수",
"Number of Repayments": "상환횟수",
"Number of Staff": "직원 수",
"Number of days a loan may be overdue before moving into arrears": "연체 상태로 전환되기 전까지 대출이 연체될 수 있는 일수",
@@ -2145,6 +2146,7 @@
"Prepay Loan": "선불대출",
"Products": "제품",
"Profile": "프로필",
"Re-Ageing": "노화",
"Recover From Guarantor": "보증인으로부터의 회복",
"Reject": "거부하다",
"Reports": "보고서",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Dienų skaičius iki Escheat",
"Number of Days to Inactive sub-status": "Dienų skaičius iki antrinės būsenos Neaktyvus",
"Number of Groups": "Grupių skaičius",
"Number of Installments": "Įmokų skaičius",
"Number of Repayments": "Grąžinimų skaičius",
"Number of Staff": "Darbuotojų skaičius",
"Number of days a loan may be overdue before moving into arrears": "Dienų skaičius, kada paskola gali būti pradelsta, kol bus pradelsta skola",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Išankstinio mokėjimo paskola",
"Products": "Produktai",
"Profile": "Profilis",
"Re-Ageing": "Senėjimas",
"Recover From Guarantor": "Išieškojimas iš garanto",
"Recovery Payment": "Išieškojimo mokėjimas",
"Reject": "Atmesti",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Dienu skaits līdz Escheat",
"Number of Days to Inactive sub-status": "Dienu skaits līdz apakšstatusam Neaktīvs",
"Number of Groups": "Grupu skaits",
"Number of Installments": "Maksāžu skaits",
"Number of Repayments": "Atmaksu skaits",
"Number of Staff": "Personāla skaits",
"Number of days a loan may be overdue before moving into arrears": "Dienu skaits, kad aizdevums var būt nokavēts, pirms tiek iekasēts parāds",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Priekšapmaksas aizdevums",
"Products": "Produkti",
"Profile": "Profils",
"Re-Ageing": "Novecošanās",
"Recover From Guarantor": "Atgūšana no galvotāja",
"Recovery Payment": "Atgūšanas maksājums",
"Reject": "Noraidīt",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Escheat गर्न दिनहरूको संख्या",
"Number of Days to Inactive sub-status": "निष्क्रिय उप-स्थितिमा दिनहरूको संख्या",
"Number of Groups": "समूहहरूको संख्या",
"Number of Installments": "किस्ताहरूको संख्या",
"Number of Repayments": "भुक्तानीको संख्या",
"Number of Staff": "कर्मचारी संख्या",
"Number of days a loan may be overdue before moving into arrears": "ऋण बक्यौतामा जानु भन्दा पहिले दिनको संख्या ओभरड्यू हुन सक्छ",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "पूर्व भुक्तानी ऋण",
"Products": "उत्पादनहरू",
"Profile": "प्रोफाइल",
"Re-Ageing": "बुढ्यौडो",
"Recover From Guarantor": "ग्यारेन्टरबाट रिकभरी",
"Recovery Payment": "रिकभरी भुक्तानी",
"Reject": "अस्वीकार गर्नुहोस्",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Número de dias para escapar",
"Number of Days to Inactive sub-status": "Número de dias até o substatus Inativo",
"Number of Groups": "Número de grupos",
"Number of Installments": "Número de parcelas",
"Number of Repayments": "Número de reembolsos",
"Number of Staff": "Número de funcionários",
"Number of days a loan may be overdue before moving into arrears": "Número de dias que um empréstimo pode estar vencido antes de entrar em atraso",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Empréstimo pré-pago",
"Products": "Produtos",
"Profile": "Perfil",
"Re-Ageing": "Envelhecimento",
"Recover From Guarantor": "Recuperação do Fiador",
"Recovery Payment": "Pagamento de recuperação",
"Reject": "Rejeitar",
+2
View File
@@ -1613,6 +1613,7 @@
"Number of Days to Escheat": "Idadi ya Siku za Escheat",
"Number of Days to Inactive sub-status": "Idadi ya Siku hadi hali ndogo Isiyotumika",
"Number of Groups": "Idadi ya Vikundi",
"Number of Installments": "Idadi ya awamu",
"Number of Repayments": "Idadi ya Marejesho",
"Number of Staff": "Idadi ya Wafanyakazi",
"Number of days a loan may be overdue before moving into arrears": "Idadi ya siku mkopo unaweza kuchelewa kabla ya kuhamia kwenye malimbikizo",
@@ -2144,6 +2145,7 @@
"Prepay Loan": "Lipa Mkopo",
"Products": "Bidhaa",
"Profile": "Wasifu",
"Re-Ageing": "Kuzeeka",
"Recover From Guarantor": "Urejeshaji Kutoka kwa Mdhamini",
"Recovery Payment": "Malipo ya uokoaji",
"Reject": "Kataa",