WEB-558:feat add loan product import via JSON upload (#2972)
fix:Added optional chaining and fallback defaults
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
|||||||
|
<h1 mat-dialog-title>Import Loan Product</h1>
|
||||||
|
|
||||||
|
<mat-dialog-content>
|
||||||
|
<form [formGroup]="importLoanProductForm">
|
||||||
|
<p>Select a JSON file containing the loan product definition.</p>
|
||||||
|
|
||||||
|
<!-- File Upload Component -->
|
||||||
|
<mifosx-file-upload (change)="onFileSelect($event)" acceptFilter=".json"> </mifosx-file-upload>
|
||||||
|
</form>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-raised-button mat-dialog-close>
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
[disabled]="!importLoanProductForm.valid"
|
||||||
|
[mat-dialog-close]="importLoanProductForm.value"
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
mat-dialog-content {
|
||||||
|
padding: 20px 24px;
|
||||||
|
min-height: 150px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: rgb(0 0 0 / 60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-dialog-actions {
|
||||||
|
padding: 8px 24px 16px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
|
import {
|
||||||
|
MatDialogRef,
|
||||||
|
MAT_DIALOG_DATA,
|
||||||
|
MatDialogTitle,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogClose,
|
||||||
|
MatDialogContent
|
||||||
|
} from '@angular/material/dialog';
|
||||||
|
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||||
|
import { FileUploadComponent } from '../../../shared/file-upload/file-upload.component';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-import-loan-product-dialog',
|
||||||
|
templateUrl: './import-loan-product-dialog.component.html',
|
||||||
|
styleUrls: ['./import-loan-product-dialog.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatDialogTitle,
|
||||||
|
MatDialogContent,
|
||||||
|
FileUploadComponent,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogClose
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ImportLoanProductDialogComponent implements OnInit {
|
||||||
|
dialogRef = inject<MatDialogRef<ImportLoanProductDialogComponent>>(MatDialogRef);
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
data = inject(MAT_DIALOG_DATA);
|
||||||
|
|
||||||
|
/** Import Loan Product form. */
|
||||||
|
importLoanProductForm: UntypedFormGroup;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.createImportLoanProductForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the import loan product form.
|
||||||
|
*/
|
||||||
|
createImportLoanProductForm() {
|
||||||
|
this.importLoanProductForm = this.formBuilder.group({
|
||||||
|
file: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets file form control value.
|
||||||
|
* @param {any} $event file change event.
|
||||||
|
*/
|
||||||
|
onFileSelect($event: any) {
|
||||||
|
if ($event.target.files.length > 0) {
|
||||||
|
const file = $event.target.files[0];
|
||||||
|
this.importLoanProductForm.get('file').setValue(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,12 @@
|
|||||||
{{ 'labels.buttons.Create Loan Product' | translate }}
|
{{ 'labels.buttons.Create Loan Product' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="in-block">
|
||||||
|
<button mat-raised-button color="primary" (click)="openImportDialog()" *mifosxHasPermission="'CREATE_LOANPRODUCT'">
|
||||||
|
<fa-icon icon="upload" class="m-r-10"></fa-icon>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { Component, OnInit, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
|
import { Component, OnInit, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
|
||||||
import { MatPaginator } from '@angular/material/paginator';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import {
|
import {
|
||||||
MatTableDataSource,
|
MatTableDataSource,
|
||||||
MatTable,
|
MatTable,
|
||||||
@@ -17,9 +18,17 @@ import {
|
|||||||
} from '@angular/material/table';
|
} from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { switchMap, catchError, finalize } from 'rxjs/operators';
|
||||||
|
import { EMPTY } from 'rxjs';
|
||||||
|
|
||||||
/* Custom Services */
|
/* Custom Services */
|
||||||
import { PopoverService } from '../../configuration-wizard/popover/popover.service';
|
import { PopoverService } from '../../configuration-wizard/popover/popover.service';
|
||||||
import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service';
|
import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service';
|
||||||
|
import { ProductsService } from '../products.service';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { ErrorHandlerService } from 'app/core/error-handler/error-handler.service';
|
||||||
|
import { ImportLoanProductDialogComponent } from './import-loan-product-dialog/import-loan-product-dialog.component';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { StatusLookupPipe } from '../../pipes/status-lookup.pipe';
|
import { StatusLookupPipe } from '../../pipes/status-lookup.pipe';
|
||||||
@@ -56,6 +65,10 @@ export class LoanProductsComponent implements OnInit, AfterViewInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private configurationWizardService = inject(ConfigurationWizardService);
|
private configurationWizardService = inject(ConfigurationWizardService);
|
||||||
private popoverService = inject(PopoverService);
|
private popoverService = inject(PopoverService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
private productsService = inject(ProductsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
private errorHandler = inject(ErrorHandlerService);
|
||||||
|
|
||||||
loanProductsData: any;
|
loanProductsData: any;
|
||||||
displayedColumns: string[] = [
|
displayedColumns: string[] = [
|
||||||
@@ -157,4 +170,75 @@ export class LoanProductsComponent implements OnInit, AfterViewInit {
|
|||||||
this.configurationWizardService.showLoanProducts = true;
|
this.configurationWizardService.showLoanProducts = true;
|
||||||
this.router.navigate(['/products']);
|
this.router.navigate(['/products']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the import loan product dialog.
|
||||||
|
*/
|
||||||
|
openImportDialog(): void {
|
||||||
|
const importDialogRef = this.dialog.open(ImportLoanProductDialogComponent, {
|
||||||
|
width: '50rem'
|
||||||
|
});
|
||||||
|
|
||||||
|
importDialogRef.afterClosed().subscribe((response: any) => {
|
||||||
|
if (response && response.file) {
|
||||||
|
this.importLoanProduct(response.file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports a loan product from a JSON file.
|
||||||
|
* @param {File} file The JSON file containing loan product definition.
|
||||||
|
*/
|
||||||
|
importLoanProduct(file: File): void {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e: any) => {
|
||||||
|
try {
|
||||||
|
const loanProductData = JSON.parse(e.target.result);
|
||||||
|
|
||||||
|
// Remove fields that shouldn't be included in creation
|
||||||
|
delete loanProductData.id;
|
||||||
|
delete loanProductData.status;
|
||||||
|
|
||||||
|
// Add required fields for API with null safety checks
|
||||||
|
const locale = this.settingsService.language?.code || 'en';
|
||||||
|
const dateFormat = this.settingsService.dateFormat || 'dd MMMM yyyy';
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...loanProductData,
|
||||||
|
locale,
|
||||||
|
dateFormat,
|
||||||
|
// Add default required fields if not present
|
||||||
|
currencyCode: loanProductData.currencyCode || 'USD',
|
||||||
|
digitsAfterDecimal: loanProductData.digitsAfterDecimal ?? 2,
|
||||||
|
charges: loanProductData.charges || []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call API to create loan product with proper error handling
|
||||||
|
this.productsService
|
||||||
|
.createLoanProduct(payload)
|
||||||
|
.pipe(
|
||||||
|
switchMap(() => this.productsService.getLoanProducts()),
|
||||||
|
catchError((error) => this.errorHandler.handleError(error, 'Loan Product Import'))
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (data: any) => {
|
||||||
|
this.loanProductsData = data;
|
||||||
|
this.dataSource.data = this.loanProductsData;
|
||||||
|
this.errorHandler.showSuccess('Loan product imported successfully!');
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
// Error already handled by ErrorHandlerService
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.errorHandler.showInfo(
|
||||||
|
'The selected file is not a valid JSON file. Please check the file format and try again.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user