Files
cbsnexora/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.ts
T
nitesh b03efef84e fix(letter-mgmt): make share certificate bank identity dynamic and fix broken printing
- Certificate PDFs, the preview dialog, and both share-certificate list views
  (demo + live) now pull bank name/address/reg no/seal text from the
  Institution Profile instead of a hardcoded "Nexora" identity.
- Certificate preview dialog print CSS was clipped to the dialog's on-screen
  scroll viewport (maxHeight config); switched to position:fixed + unclipped
  overlay ancestors so the full certificate prints, not just a border sliver.
- Global print stylesheet now also hides the app shell's real 2-row nav menu
  (.main-header/.kpi-ticker, not routed through <mifosx-toolbar>) and frees
  the CDK sidenav/tab-body scroll clipping, so printing any page no longer
  captures the nav chrome or gets cut off to one screen's height.
2026-09-14 16:58:18 +05:30

363 lines
11 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 { CommonModule } from '@angular/common';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatTooltip } from '@angular/material/tooltip';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { OrganizationService } from 'app/organization/organization.service';
import { FyInterestBreakup, LoanInterestRecord } from '../loan-interest/loan-interest.component';
import { ShareCertificateRecord } from '../share-certificate/share-certificate.component';
import { BalanceCertificateRecord } from '../balance-certificate/balance-certificate.component';
import { Form60Record } from '../form-60/form-60.component';
import { Form15Kind, Form15ghRecord } from '../form-15gh/form-15gh.component';
import { HypothecationRecord } from '../hypothecation/hypothecation.component';
import { NocRecord } from '../noc/noc.component';
import {
downloadBalanceCertificatePdf,
downloadForm15ghPdf,
downloadForm60Pdf,
downloadHypothecationPdf,
downloadLoanInterestPdf,
downloadNocPdf,
downloadShareCertificatePdf,
setBankIdentity
} from '../certificate-pdf';
export interface InterestPreviewPayload {
record: LoanInterestRecord;
fy: FyInterestBreakup;
}
export interface BalancePreviewPayload {
record: BalanceCertificateRecord;
asOnDate: string;
}
export interface Form15ghPreviewPayload {
record: Form15ghRecord;
kind: Form15Kind;
age: number;
}
export type CertificateKind = 'interest' | 'share' | 'balance' | 'form60' | 'form15gh' | 'hypothecation' | 'noc';
export interface CertificatePreviewDialogData {
kind: CertificateKind;
loanInterest?: InterestPreviewPayload;
shareCertificate?: ShareCertificateRecord;
balance?: BalancePreviewPayload;
form60?: Form60Record;
form15gh?: Form15ghPreviewPayload;
hypothecation?: HypothecationRecord;
noc?: NocRecord;
}
@Component({
selector: 'mifosx-certificate-preview-dialog',
templateUrl: './certificate-preview-dialog.component.html',
styleUrls: ['./certificate-preview-dialog.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
CommonModule,
FaIconComponent,
MatTooltip
]
})
export class CertificatePreviewDialogComponent implements OnInit {
private dialogRef = inject(MatDialogRef<CertificatePreviewDialogComponent>);
private organizationService = inject(OrganizationService);
data: CertificatePreviewDialogData = inject(MAT_DIALOG_DATA);
/** The issuing bank's own details, fetched from the Institution Profile — this dialog is shared
* by every certificate type and every tenant, so nothing here is a fixed name. */
bankName = 'Bank';
bankAddress = '';
bankRegNo = '';
bankContact = '';
bankSealPrimary = 'BANK';
bankSealSecondary = '';
ngOnInit(): void {
this.organizationService.getInstitutionProfile().subscribe({
next: (profile: any) => {
this.bankName = profile?.statementHeaderName || profile?.bankName || this.bankName;
this.bankAddress = [
profile?.addressLine1,
profile?.addressLine2,
profile?.city
]
.filter((p: any) => !!p && String(p).trim())
.join(', ');
this.bankRegNo = profile?.licenseNumber ? `RBI Lic. No. ${profile.licenseNumber}` : '';
this.bankContact = [
profile?.contactEmail,
profile?.contactPhone
]
.filter((p: any) => !!p && String(p).trim())
.join(' · ');
this.bankSealPrimary = (profile?.shortName || this.bankName).toUpperCase();
this.bankSealSecondary = profile?.bankType
? profile.bankType.replace(/_/g, ' ').toUpperCase() + (/BANK$/i.test(profile.bankType) ? '' : ' BANK')
: '';
// Every certificate PDF this dialog can build (Download button below, or the list page's
// own download icon opened without previewing first) reads the same shared identity, so
// set it here once the real profile is in — never leave a PDF carrying the placeholder.
setBankIdentity(profile || {});
},
// A missing/incomplete profile must not stop the certificate from rendering.
error: () => {}
});
}
get isInterest(): boolean {
return this.data.kind === 'interest';
}
get isShare(): boolean {
return this.data.kind === 'share';
}
get isBalance(): boolean {
return this.data.kind === 'balance';
}
get isForm60(): boolean {
return this.data.kind === 'form60';
}
get isForm15gh(): boolean {
return this.data.kind === 'form15gh';
}
get isHypothecation(): boolean {
return this.data.kind === 'hypothecation';
}
get isNoc(): boolean {
return this.data.kind === 'noc';
}
get interest(): InterestPreviewPayload | undefined {
return this.data.loanInterest;
}
get share(): ShareCertificateRecord | undefined {
return this.data.shareCertificate;
}
get balance(): BalancePreviewPayload | undefined {
return this.data.balance;
}
get form60(): Form60Record | undefined {
return this.data.form60;
}
get form15gh(): Form15ghPreviewPayload | undefined {
return this.data.form15gh;
}
get hypothecation(): HypothecationRecord | undefined {
return this.data.hypothecation;
}
get noc(): NocRecord | undefined {
return this.data.noc;
}
/** Tone class on `.cpd-page` — drives the accent stripe + seal + pill colour. */
get pageToneClass(): string {
switch (this.data.kind) {
case 'share':
case 'form60':
return 'cpd-page--amber';
case 'form15gh':
return 'cpd-page--teal';
case 'hypothecation':
return 'cpd-page--red';
case 'noc':
return 'cpd-page--green';
case 'balance':
case 'interest':
default:
return '';
}
}
get title(): string {
switch (this.data.kind) {
case 'interest':
return 'Interest-Paid Certificate';
case 'share':
return 'Share Certificate';
case 'balance':
return 'Balance Certificate';
case 'form60':
return 'Form 97 Declaration';
case 'form15gh':
return 'Form 121 Declaration';
case 'hypothecation':
return 'Hypothecation Certificate';
case 'noc':
return 'No-Objection Certificate';
}
}
get refLine(): string {
if (this.isInterest && this.interest) {
return `${this.interest.record.accountNo} · ${this.interest.fy.fy}`;
}
if (this.isShare && this.share) {
return `${this.share.certificateNo} · Folio ${this.share.folioNo}`;
}
if (this.isBalance && this.balance) {
return `${this.balance.record.accountNo} · as on ${this.balance.asOnDate}`;
}
if (this.isForm60 && this.form60) {
return `${this.form60.customerId} · ${this.form60.transactionType}`;
}
if (this.isForm15gh && this.form15gh) {
return `${this.form15gh.record.customerId} · ${this.form15gh.kind} · ${this.form15gh.record.fy}`;
}
if (this.isHypothecation && this.hypothecation) {
return `${this.hypothecation.certificateNo} · ${this.hypothecation.accountNo}`;
}
if (this.isNoc && this.noc) {
return `${this.noc.nocNo} · ${this.noc.accountNo}`;
}
return '';
}
get issuedOn(): string {
return new Date().toISOString().slice(0, 10);
}
close(): void {
this.dialogRef.close();
}
printCertificate(): void {
window.print();
}
downloadPdf(): void {
if (this.isInterest && this.interest) {
downloadLoanInterestPdf(this.interest.record, this.interest.fy);
} else if (this.isShare && this.share) {
downloadShareCertificatePdf(this.share);
} else if (this.isBalance && this.balance) {
downloadBalanceCertificatePdf(this.balance.record, this.balance.asOnDate);
} else if (this.isForm60 && this.form60) {
downloadForm60Pdf(this.form60);
} else if (this.isForm15gh && this.form15gh) {
downloadForm15ghPdf(this.form15gh.record, this.form15gh.kind);
} else if (this.isHypothecation && this.hypothecation) {
downloadHypothecationPdf(this.hypothecation);
} else if (this.isNoc && this.noc) {
downloadNocPdf(this.noc);
}
}
nocLoanTypeLabel(): string {
const t = this.noc?.loanType;
switch (t) {
case 'AUTO':
return 'Auto Loan';
case 'HOUSING':
return 'Housing Loan';
case 'PERSONAL':
return 'Personal Loan';
case 'EDUCATION':
return 'Education Loan';
case 'GOLD':
return 'Gold Loan';
case 'BUSINESS':
return 'Business Loan';
case 'CC':
return 'Cash Credit';
case 'OD':
return 'Overdraft';
default:
return '';
}
}
nocAssetLabel(): string {
const t = this.noc?.assetType;
switch (t) {
case 'VEHICLE':
return 'Vehicle';
case 'PROPERTY':
return 'Property';
case 'GOLD':
return 'Gold / Bullion';
case 'EQUIPMENT':
return 'Equipment';
case 'STOCK_DEBTORS':
return 'Stock / Debtors';
case 'NONE':
return 'Unsecured';
default:
return '';
}
}
classLabelText(): string {
return this.share?.shareClass === 'PREFERENCE' ? 'Preference' : 'Ordinary';
}
accountTypeLabelText(): string {
const t = this.balance?.record.accountType;
switch (t) {
case 'SAVINGS':
return 'Savings';
case 'CURRENT':
return 'Current';
case 'FIXED_DEPOSIT':
return 'Fixed Deposit';
case 'RD':
return 'Recurring Deposit';
case 'NRE':
return 'NRE Savings';
case 'NRO':
return 'NRO Savings';
default:
return '';
}
}
idProofLabelText(): string {
const t = this.form60?.idProofType;
switch (t) {
case 'AADHAAR':
return 'Aadhaar Card';
case 'VOTER_ID':
return 'Voter Identity Card';
case 'DRIVING_LICENSE':
return 'Driving License';
case 'PASSPORT':
return 'Passport';
case 'RATION':
return 'Ration Card';
default:
return '';
}
}
assetLabelText(): string {
const t = this.hypothecation?.assetType;
switch (t) {
case 'VEHICLE':
return 'Vehicle';
case 'PLANT_MACHINERY':
return 'Plant & Machinery';
case 'STOCK_DEBTORS':
return 'Stock / Debtors';
case 'GOLD':
return 'Gold / Bullion';
case 'EQUIPMENT':
return 'Equipment';
default:
return '';
}
}
}