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 }}
|
||||
</button>
|
||||
</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 class="container">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Component, OnInit, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
MatTableDataSource,
|
||||
MatTable,
|
||||
@@ -17,9 +18,17 @@ import {
|
||||
} from '@angular/material/table';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { switchMap, catchError, finalize } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
|
||||
/* Custom Services */
|
||||
import { PopoverService } from '../../configuration-wizard/popover/popover.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 { MatTooltip } from '@angular/material/tooltip';
|
||||
import { StatusLookupPipe } from '../../pipes/status-lookup.pipe';
|
||||
@@ -56,6 +65,10 @@ export class LoanProductsComponent implements OnInit, AfterViewInit {
|
||||
private router = inject(Router);
|
||||
private configurationWizardService = inject(ConfigurationWizardService);
|
||||
private popoverService = inject(PopoverService);
|
||||
private dialog = inject(MatDialog);
|
||||
private productsService = inject(ProductsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
private errorHandler = inject(ErrorHandlerService);
|
||||
|
||||
loanProductsData: any;
|
||||
displayedColumns: string[] = [
|
||||
@@ -157,4 +170,75 @@ export class LoanProductsComponent implements OnInit, AfterViewInit {
|
||||
this.configurationWizardService.showLoanProducts = true;
|
||||
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