221 lines
7.2 KiB
TypeScript
221 lines
7.2 KiB
TypeScript
/**
|
|
* Copyright since 2025 Mifos Initiative
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
import { Component, OnInit, inject } from '@angular/core';
|
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
|
import { catchError, of } from 'rxjs';
|
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
import { OrganizationService } from '../organization.service';
|
|
import { SettingsService } from 'app/settings/settings.service';
|
|
import { DISABLED_ACCOUNTING_FEATURES } from '../../accounting/disabled-features';
|
|
|
|
const BANK_TYPES = [
|
|
{ value: 'COOPERATIVE', label: 'Co-operative Bank' },
|
|
{ value: 'COMMERCIAL', label: 'Commercial Bank' },
|
|
{ value: 'RRB', label: 'Regional Rural Bank' },
|
|
{ value: 'NBFC', label: 'NBFC' },
|
|
{ value: 'OTHER', label: 'Other' }
|
|
];
|
|
|
|
const OWNERSHIP_TYPES = [
|
|
{ value: 'GOVERNMENT', label: 'Government' },
|
|
{ value: 'PRIVATE', label: 'Private' },
|
|
{ value: 'COOPERATIVE', label: 'Cooperative' },
|
|
{ value: 'OTHER', label: 'Other' }
|
|
];
|
|
|
|
const LICENSE_STATUSES = [
|
|
{ value: 'ACTIVE', label: 'Active' },
|
|
{ value: 'EXPIRED', label: 'Expired' },
|
|
{ value: 'SUSPENDED', label: 'Suspended' },
|
|
{ value: 'NOT_APPLICABLE', label: 'Not Applicable' }
|
|
];
|
|
|
|
const STATUSES = [
|
|
{ value: 'ACTIVE', label: 'Active' },
|
|
{ value: 'INACTIVE', label: 'Inactive' }
|
|
];
|
|
|
|
/**
|
|
* The bank's own "Bank Master" profile — identity/classification, regulatory details, address
|
|
* (registered, with optional head-office/communication overrides), contact, the institution-level
|
|
* banking identifier (SWIFT/BIC), document header names and terms, the authorised representative,
|
|
* and audit fields. A singleton screen: there is only ever one institution running this tenant.
|
|
*
|
|
* IFSC/MICR are deliberately NOT here — those identify a specific branch, not the institution, and
|
|
* live on Branch Profile instead. Business Date, Financial Year, Time Zone, Working Days and Holiday
|
|
* Calendar are likewise not stored here — they already exist as their own configuration, and this
|
|
* screen only links to them (see the Operational Configuration section) instead of holding a second,
|
|
* driftable copy.
|
|
*/
|
|
@Component({
|
|
selector: 'mifosx-institution-profile',
|
|
templateUrl: './institution-profile.component.html',
|
|
styleUrls: ['./institution-profile.component.scss'],
|
|
imports: [...STANDALONE_SHARED_IMPORTS]
|
|
})
|
|
export class InstitutionProfileComponent implements OnInit {
|
|
private formBuilder = inject(UntypedFormBuilder);
|
|
private organizationService = inject(OrganizationService);
|
|
private settingsService = inject(SettingsService);
|
|
|
|
/** Accounting features switched off for this installation — see accounting/disabled-features.ts. */
|
|
disabledFeatures = DISABLED_ACCOUNTING_FEATURES;
|
|
bankTypes = BANK_TYPES;
|
|
ownershipTypes = OWNERSHIP_TYPES;
|
|
licenseStatuses = LICENSE_STATUSES;
|
|
statuses = STATUSES;
|
|
|
|
form: UntypedFormGroup;
|
|
loading = true;
|
|
saving = false;
|
|
errorMsg = '';
|
|
successMsg = '';
|
|
currencyCode = '';
|
|
audit: any = null;
|
|
|
|
private static readonly DATE_FIELDS = [
|
|
'establishedDate',
|
|
'registrationDate',
|
|
'licenseExpiryDate'
|
|
];
|
|
|
|
ngOnInit(): void {
|
|
this.form = this.formBuilder.group({
|
|
bankName: [''],
|
|
legalName: [''],
|
|
shortName: [''],
|
|
bankCode: [''],
|
|
institutionCode: [''],
|
|
bankType: [null],
|
|
ownershipType: [null],
|
|
parentInstitutionName: [''],
|
|
establishedDate: [null],
|
|
status: ['ACTIVE'],
|
|
country: ['India'],
|
|
|
|
registrationNumber: [''],
|
|
licenseNumber: [''],
|
|
regulatoryAuthority: [''],
|
|
registrationDate: [null],
|
|
licenseStatus: [null],
|
|
licenseExpiryDate: [null],
|
|
pan: [''],
|
|
tan: [''],
|
|
gstin: [''],
|
|
|
|
addressLine1: [''],
|
|
addressLine2: [''],
|
|
city: [''],
|
|
district: [''],
|
|
state: [''],
|
|
pincode: [''],
|
|
headOfficeSameAsRegistered: [true],
|
|
headOfficeAddress: [''],
|
|
communicationSameAsRegistered: [true],
|
|
communicationAddress: [''],
|
|
|
|
contactPhone: [''],
|
|
contactEmail: [''],
|
|
customerCareNumber: [''],
|
|
grievanceEmail: [''],
|
|
website: [''],
|
|
|
|
swiftBic: [''],
|
|
|
|
statementHeaderName: [''],
|
|
passbookHeaderName: [''],
|
|
receiptHeaderName: [''],
|
|
termsAndConditions: [''],
|
|
|
|
authorizedPersonName: [''],
|
|
authorizedPersonDesignation: [''],
|
|
authorizedPersonContact: [''],
|
|
authorizedPersonEmail: ['']
|
|
});
|
|
this.load();
|
|
}
|
|
|
|
load(): void {
|
|
this.loading = true;
|
|
this.errorMsg = '';
|
|
this.organizationService
|
|
.getInstitutionProfile()
|
|
.pipe(
|
|
catchError((err: any) => {
|
|
this.errorMsg = err?.error?.errors?.[0]?.defaultUserMessage ?? 'Could not load the bank profile.';
|
|
return of(null);
|
|
})
|
|
)
|
|
.subscribe((res: any) => {
|
|
this.loading = false;
|
|
if (!res) return;
|
|
this.applyResult(res);
|
|
this.form.markAsPristine();
|
|
});
|
|
}
|
|
|
|
submit(): void {
|
|
if (this.saving) return;
|
|
this.saving = true;
|
|
this.errorMsg = '';
|
|
this.successMsg = '';
|
|
const raw = this.form.value;
|
|
const payload: any = { ...raw, locale: this.settingsService.language.code };
|
|
for (const field of InstitutionProfileComponent.DATE_FIELDS) {
|
|
payload[field] = raw[field] ? this.formatDate(raw[field]) : null;
|
|
}
|
|
this.organizationService
|
|
.updateInstitutionProfile(payload)
|
|
.pipe(
|
|
catchError((err: any) => {
|
|
this.saving = false;
|
|
this.errorMsg = err?.error?.errors?.[0]?.defaultUserMessage ?? 'Could not save the bank profile.';
|
|
return of(null);
|
|
})
|
|
)
|
|
.subscribe((res: any) => {
|
|
this.saving = false;
|
|
if (!res) return;
|
|
if (res.rollbackTransaction) {
|
|
// Parked for checker approval - the profile has not actually changed yet, so the form must
|
|
// not be patched with the submitted (not-yet-applied) values as if it had.
|
|
this.successMsg = 'Bank profile update has been submitted for approval.';
|
|
this.form.markAsPristine();
|
|
return;
|
|
}
|
|
this.successMsg = 'Bank profile saved.';
|
|
// The response is now a CommandProcessingResult, not the updated row (the write goes through
|
|
// the command pipeline) - reload the saved row from the server instead of patching the form
|
|
// from this response.
|
|
this.load();
|
|
});
|
|
}
|
|
|
|
private applyResult(res: any): void {
|
|
const patch: any = { ...res };
|
|
for (const field of InstitutionProfileComponent.DATE_FIELDS) {
|
|
patch[field] = res[field] ? new Date(res[field]) : null;
|
|
}
|
|
this.form.patchValue(patch);
|
|
this.currencyCode = res.currencyCode ?? '';
|
|
this.audit = {
|
|
createdBy: res.createdByUsername,
|
|
createdAt: res.createdAt,
|
|
updatedBy: res.updatedByUsername,
|
|
updatedAt: res.updatedAt,
|
|
activationDate: res.activationDate,
|
|
deactivationDate: res.deactivationDate
|
|
};
|
|
}
|
|
|
|
private formatDate(d: Date): string {
|
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
}
|
|
}
|