Files
cbsnexora/src/app/organization/institution-profile/institution-profile.component.ts
T

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')}`;
}
}