From b03efef84e2a4f84f9b61da942673c8c780a85c1 Mon Sep 17 00:00:00 2001
From: Nitesh Yadav
Date: Mon, 14 Sep 2026 16:58:18 +0530
Subject: [PATCH] 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 ) 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.
---
.../certificates/certificate-pdf.ts | 111 ++++++++++++++----
.../certificate-preview-dialog.component.html | 4 +-
.../certificate-preview-dialog.component.scss | 34 +++++-
.../certificate-preview-dialog.component.ts | 54 +++++++--
.../real-share-accounts-view.component.ts | 12 +-
.../share-certificate.component.ts | 11 +-
.../share-certificate-dialog.component.html | 12 +-
.../share-certificate-dialog.component.scss | 18 +++
.../share-certificate-dialog.component.ts | 25 ++++
src/environments/.env.ts | 2 +-
src/main.scss | 37 +++++-
11 files changed, 279 insertions(+), 41 deletions(-)
diff --git a/src/app/letter-mgmt/certificates/certificate-pdf.ts b/src/app/letter-mgmt/certificates/certificate-pdf.ts
index 91aebd362..ddf2fb004 100644
--- a/src/app/letter-mgmt/certificates/certificate-pdf.ts
+++ b/src/app/letter-mgmt/certificates/certificate-pdf.ts
@@ -25,10 +25,68 @@ import { Form15Kind, Form15ghRecord } from './form-15gh/form-15gh.component';
import { HypothecationRecord } from './hypothecation/hypothecation.component';
import { NocRecord } from './noc/noc.component';
-const BANK_NAME = 'Nexora Co-operative Bank Ltd.';
-const BANK_ADDRESS = 'Head Office, 4th Floor, Nexora Tower, Mumbai 400051';
-const BANK_REG = 'RBI Lic. No. NX-2018/MH/482';
-const BANK_CONTACT = 'info@nexorabank.in · 022 6789 0000';
+/**
+ * The issuing bank's own identity, as set on the Institution Profile (Admin > Organization) — this
+ * file is shared by every certificate type (loan interest, share, balance, Form 60/15G/15H,
+ * hypothecation, NOC) and every tenant this app runs for, so nothing here can be a fixed name.
+ * These defaults only cover the moment before whichever screen opened this module has fetched the
+ * real profile and called {@link setBankIdentity}; they are never meant to reach a printed PDF.
+ */
+const bankIdentity = {
+ name: 'Bank',
+ address: '',
+ regNo: '',
+ contact: '',
+ /** City/state/country line used by the Share Certificate's own letterhead style. */
+ location: '',
+ /** Short label for the circular seal graphic — two lines, e.g. bank's short name + type. */
+ sealPrimary: 'BANK',
+ sealSecondary: ''
+};
+
+/** Called once the caller has fetched the real Institution Profile, so every certificate built
+ * after this point — of any type — carries the actual bank's own details. */
+export function setBankIdentity(profile: {
+ bankName?: string;
+ statementHeaderName?: string;
+ shortName?: string;
+ bankType?: string;
+ addressLine1?: string;
+ addressLine2?: string;
+ city?: string;
+ state?: string;
+ country?: string;
+ licenseNumber?: string;
+ contactEmail?: string;
+ contactPhone?: string;
+}): void {
+ bankIdentity.name = profile.statementHeaderName || profile.bankName || bankIdentity.name;
+ bankIdentity.address = [
+ profile.addressLine1,
+ profile.addressLine2,
+ profile.city
+ ]
+ .filter((p) => !!p && String(p).trim())
+ .join(', ');
+ bankIdentity.regNo = profile.licenseNumber ? `RBI Lic. No. ${profile.licenseNumber}` : '';
+ bankIdentity.contact = [
+ profile.contactEmail,
+ profile.contactPhone
+ ]
+ .filter((p) => !!p && String(p).trim())
+ .join(' · ');
+ bankIdentity.location = [
+ profile.city,
+ profile.state,
+ profile.country
+ ]
+ .filter((p) => !!p && String(p).trim())
+ .join(', ');
+ bankIdentity.sealPrimary = (profile.shortName || profile.bankName || bankIdentity.name).toUpperCase();
+ bankIdentity.sealSecondary = profile.bankType
+ ? profile.bankType.replace(/_/g, ' ').toUpperCase() + (/BANK$/i.test(profile.bankType) ? '' : ' BANK')
+ : '';
+}
const C_TEXT: [number, number, number] = [
15,
@@ -141,14 +199,24 @@ function paintLetterhead(
c.doc.setFont('times', 'bold');
c.doc.setFontSize(19);
setText(c, c.accent);
- c.doc.text(BANK_NAME, c.pageW / 2, y, { align: 'center' });
+ c.doc.text(bankIdentity.name, c.pageW / 2, y, { align: 'center' });
y += 5.5;
c.doc.setFont('helvetica', 'normal');
c.doc.setFontSize(9);
setText(c, C_MUTED);
- c.doc.text(BANK_ADDRESS, c.pageW / 2, y, { align: 'center' });
- y += 4;
- c.doc.text(`${BANK_REG} · ${BANK_CONTACT}`, c.pageW / 2, y, { align: 'center' });
+ if (bankIdentity.address) {
+ c.doc.text(bankIdentity.address, c.pageW / 2, y, { align: 'center' });
+ y += 4;
+ }
+ const regContactLine = [
+ bankIdentity.regNo,
+ bankIdentity.contact
+ ]
+ .filter((p) => !!p)
+ .join(' · ');
+ if (regContactLine) {
+ c.doc.text(regContactLine, c.pageW / 2, y, { align: 'center' });
+ }
y += 5;
setDraw(c, C_BORDER);
c.doc.setLineWidth(0.3);
@@ -350,11 +418,10 @@ function paintSignoff(c: PdfCtx, y: number, branch: string): number {
c.doc.setFont('helvetica', 'bold');
c.doc.setFontSize(7);
setText(c, c.accent);
- c.doc.text('NEXORA', cx, cy - 1, { align: 'center' });
+ c.doc.text(bankIdentity.sealPrimary, cx, cy - 1, { align: 'center' });
c.doc.setFontSize(5);
setText(c, C_MUTED);
- c.doc.text('CO-OPERATIVE', cx, cy + 3, { align: 'center' });
- c.doc.text('BANK', cx, cy + 5.5, { align: 'center' });
+ c.doc.text(bankIdentity.sealSecondary, cx, cy + 4, { align: 'center' });
return y + 28;
}
@@ -376,7 +443,7 @@ function paintFooter(c: PdfCtx, y: number): void {
c.doc.setFontSize(8);
setText(c, C_MUTED);
c.doc.text(
- `Issued by ${BANK_NAME} on ${formatLongDate(today())}. Computer generated — does not require physical signature for record purposes.`,
+ `Issued by ${bankIdentity.name} on ${formatLongDate(today())}. Computer generated — does not require physical signature for record purposes.`,
c.pageW / 2,
y,
{ align: 'center' }
@@ -609,13 +676,15 @@ export function buildShareCertificatePdf(r: ShareCertificateRecord): jsPDF {
doc.setFont('times', 'bold');
doc.setFontSize(22);
doc.setTextColor(darkBlue[0], darkBlue[1], darkBlue[2]);
- doc.text('NEXORA CO-OPERATIVE BANK LTD.', pageW / 2, y, { align: 'center' });
+ doc.text(bankIdentity.name.toUpperCase(), pageW / 2, y, { align: 'center' });
- y += 5;
- doc.setFont('times', 'italic');
- doc.setFontSize(10.5);
- doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
- doc.text('Mumbai, Maharashtra, India', pageW / 2, y, { align: 'center' });
+ if (bankIdentity.location) {
+ y += 5;
+ doc.setFont('times', 'italic');
+ doc.setFontSize(10.5);
+ doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
+ doc.text(bankIdentity.location, pageW / 2, y, { align: 'center' });
+ }
y += 4;
doc.setDrawColor(242, 237, 213); // light gold separator
@@ -685,7 +754,7 @@ export function buildShareCertificatePdf(r: ShareCertificateRecord): jsPDF {
doc.setFontSize(11);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
- const bodyText = `This is to certify that ${r.memberName} is the registered holder of ${r.shareCount.toLocaleString('en-IN')} (${classLabel}) shares of Rs. ${r.faceValue.toLocaleString('en-IN')} each, fully paid up, in Nexora Co-operative Bank Ltd., subject to the memorandum, articles of association, and bye-laws of the bank.`;
+ const bodyText = `This is to certify that ${r.memberName} is the registered holder of ${r.shareCount.toLocaleString('en-IN')} (${classLabel}) shares of Rs. ${r.faceValue.toLocaleString('en-IN')} each, fully paid up, in ${bankIdentity.name}, subject to the memorandum, articles of association, and bye-laws of the bank.`;
const bodyLines = doc.splitTextToSize(bodyText, pageW - 50);
for (const line of bodyLines) {
doc.text(line, pageW / 2, y, { align: 'center' });
@@ -763,7 +832,7 @@ export function buildShareCertificatePdf(r: ShareCertificateRecord): jsPDF {
// Row 1
drawTableCell(25, y, tableColW, tableRowH, 'Branch', true);
- drawTableCell(25 + tableColW, y, tableColW, tableRowH, r.branch || 'Mumbai Main Branch', false);
+ drawTableCell(25 + tableColW, y, tableColW, tableRowH, r.branch || 'Head Office', false);
drawTableCell(25 + tableColW * 2, y, tableColW, tableRowH, 'Member Since', true);
drawTableCell(25 + tableColW * 3, y, tableColW, tableRowH, formatLongDate(r.joiningDate), false);
y += tableRowH;
@@ -1168,7 +1237,7 @@ export function buildHypothecationPdf(r: HypothecationRecord): jsPDF {
y = paintPara(
c,
y,
- `This is to certify that the asset described below stands hypothecated to ${BANK_NAME} as security for the captioned loan facility extended to ${r.customerName}, and the bank holds the first and exclusive charge over the said asset until the loan is fully discharged.`
+ `This is to certify that the asset described below stands hypothecated to ${bankIdentity.name} as security for the captioned loan facility extended to ${r.customerName}, and the bank holds the first and exclusive charge over the said asset until the loan is fully discharged.`
);
const rows: [string, string, string, string][] = [
diff --git a/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.html b/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.html
index a0b6381fd..022a216f3 100644
--- a/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.html
+++ b/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.html
@@ -684,9 +684,9 @@
-
NEXORA
+
{{ bankSealPrimary }}
-
CO-OPERATIVE BANK
+
{{ bankSealSecondary }}
diff --git a/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.scss b/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.scss
index f099d25a1..13b5f7636 100644
--- a/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.scss
+++ b/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.scss
@@ -575,8 +575,19 @@
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
-/* ───────── Print — only the page, no toolbar / dialog chrome ───────── */
+/* ───────── Print — only the page, no toolbar / dialog chrome ─────────
+ This dialog is opened with `maxHeight: '92vh'`, which Angular Material carries onto the
+ overlay pane / .mat-mdc-dialog-container as an inline max-height + overflow:auto clip.
+ `position: absolute` still lives inside that clip and gets cut off — only the on-screen
+ scroll position prints. `position: fixed` escapes it: a fixed element's containing block is
+ the viewport itself, not the clipped ancestor (the classic "fixed child ignores an
+ overflow:hidden parent" behaviour), as long as nothing between here and the viewport sets a
+ transform/filter — the CDK overlay chrome doesn't. */
@media print {
+ body > :not(.cdk-overlay-container) {
+ display: none !important;
+ }
+
:host {
background: #fff;
}
@@ -585,11 +596,29 @@
display: none !important;
}
+ .cdk-overlay-container,
+ .cdk-global-overlay-wrapper,
+ .cdk-overlay-pane,
+ .mat-mdc-dialog-container,
+ .mdc-dialog__surface {
+ position: static !important;
+ overflow: visible !important;
+ max-height: none !important;
+ height: auto !important;
+ max-width: none !important;
+ box-shadow: none !important;
+ padding: 0 !important;
+ }
+
.cpd-root {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100% !important;
+ height: auto !important;
background: #fff;
max-height: none;
overflow: visible;
- height: auto;
}
.cpd-page {
@@ -599,6 +628,7 @@
width: 100%;
box-shadow: none;
border-top: 4px solid var(--cpd-blue-d);
+ overflow: visible;
&.cpd-page--share {
border-top-color: var(--cpd-amber);
diff --git a/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.ts b/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.ts
index 0484c415d..8b35a57d5 100644
--- a/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.ts
+++ b/src/app/letter-mgmt/certificates/certificate-preview-dialog/certificate-preview-dialog.component.ts
@@ -6,12 +6,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import { Component, inject } from '@angular/core';
+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';
@@ -27,7 +28,8 @@ import {
downloadHypothecationPdf,
downloadLoanInterestPdf,
downloadNocPdf,
- downloadShareCertificatePdf
+ downloadShareCertificatePdf,
+ setBankIdentity
} from '../certificate-pdf';
export interface InterestPreviewPayload {
@@ -70,14 +72,52 @@ export interface CertificatePreviewDialogData {
MatTooltip
]
})
-export class CertificatePreviewDialogComponent {
+export class CertificatePreviewDialogComponent implements OnInit {
private dialogRef = inject(MatDialogRef);
+ private organizationService = inject(OrganizationService);
data: CertificatePreviewDialogData = inject(MAT_DIALOG_DATA);
- readonly bankName = 'Nexora Co-operative Bank Ltd.';
- readonly bankAddress = 'Head Office, 4th Floor, Nexora Tower, Mumbai 400051';
- readonly bankRegNo = 'RBI Lic. No. NX-2018/MH/482';
- readonly bankContact = 'info@nexorabank.in · 022 6789 0000';
+ /** 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';
diff --git a/src/app/letter-mgmt/certificates/share-certificate/real-share-accounts-view/real-share-accounts-view.component.ts b/src/app/letter-mgmt/certificates/share-certificate/real-share-accounts-view/real-share-accounts-view.component.ts
index 655d42238..1df873a16 100644
--- a/src/app/letter-mgmt/certificates/share-certificate/real-share-accounts-view/real-share-accounts-view.component.ts
+++ b/src/app/letter-mgmt/certificates/share-certificate/real-share-accounts-view/real-share-accounts-view.component.ts
@@ -17,8 +17,9 @@ import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
import { SharesService } from '../../../../shares/shares.service';
import { ClientsService } from '../../../../clients/clients.service';
+import { OrganizationService } from '../../../../organization/organization.service';
import { ShareCertificateDialogComponent } from '../../../../shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component';
-import { downloadShareCertificatePdf } from '../../certificate-pdf';
+import { downloadShareCertificatePdf, setBankIdentity } from '../../certificate-pdf';
import { DscSignService } from '../../../dsc-sign-dialog/dsc-sign.service';
import type { ShareClass, ShareCertificateRecord } from '../share-certificate.component';
@@ -47,6 +48,7 @@ export class RealShareAccountsViewComponent implements OnInit {
private dialog = inject(MatDialog);
private sharesService = inject(SharesService);
private clientsService = inject(ClientsService);
+ private organizationService = inject(OrganizationService);
private dscSign = inject(DscSignService);
loading = true;
@@ -70,6 +72,14 @@ export class RealShareAccountsViewComponent implements OnInit {
ngOnInit(): void {
this.loadRealAccounts();
+
+ // The Download button below builds the PDF directly (no preview dialog in between), so this
+ // view must set the shared bank identity itself rather than relying on some other screen
+ // having opened first.
+ this.organizationService.getInstitutionProfile().subscribe({
+ next: (profile: any) => setBankIdentity(profile || {}),
+ error: () => {}
+ });
}
private loadRealAccounts(): void {
diff --git a/src/app/letter-mgmt/certificates/share-certificate/share-certificate.component.ts b/src/app/letter-mgmt/certificates/share-certificate/share-certificate.component.ts
index 47e65b44a..851899d5b 100644
--- a/src/app/letter-mgmt/certificates/share-certificate/share-certificate.component.ts
+++ b/src/app/letter-mgmt/certificates/share-certificate/share-certificate.component.ts
@@ -19,8 +19,9 @@ import {
CertificatePreviewDialogComponent,
CertificatePreviewDialogData
} from '../certificate-preview-dialog/certificate-preview-dialog.component';
-import { downloadShareCertificatePdf } from '../certificate-pdf';
+import { downloadShareCertificatePdf, setBankIdentity } from '../certificate-pdf';
import { DscSignService } from '../../dsc-sign-dialog/dsc-sign.service';
+import { OrganizationService } from '../../../organization/organization.service';
import { RealShareAccountsViewComponent } from './real-share-accounts-view/real-share-accounts-view.component';
export type ShareClass = 'ORDINARY' | 'PREFERENCE';
@@ -62,6 +63,7 @@ export class ShareCertificateComponent implements OnInit {
private dialog = inject(MatDialog);
private snack = inject(MatSnackBar);
private dscSign = inject(DscSignService);
+ private organizationService = inject(OrganizationService);
/** Toggles between the static sample table and the live m_share_account view. */
viewMode: 'demo' | 'real' = 'demo';
@@ -80,6 +82,13 @@ export class ShareCertificateComponent implements OnInit {
ngOnInit(): void {
this.records = seedShareCertificates();
+
+ // Download here builds the PDF directly, without necessarily opening the preview dialog first,
+ // so this list must set the shared bank identity itself.
+ this.organizationService.getInstitutionProfile().subscribe({
+ next: (profile: any) => setBankIdentity(profile || {}),
+ error: () => {}
+ });
}
// ── Derived list ──
diff --git a/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.html b/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.html
index 130f27404..78a310134 100644
--- a/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.html
+++ b/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.html
@@ -21,8 +21,10 @@
@@ -51,8 +53,8 @@
{{
sharesAccountData.currentMarketPrice | currency: sharesAccountData.currency.code : 'symbol-narrow'
}}
- each, fully paid up, in Nexora Co-operative Bank Ltd., subject to
- the memorandum, articles of association, and bye-laws of the bank.
+ each, fully paid up, in {{ bankName }}, subject to the memorandum, articles of association, and bye-laws of the bank.
@@ -75,7 +77,7 @@
| Branch |
- {{ clientData?.officeName || 'Mumbai Main Branch' }} |
+ {{ clientData?.officeName || 'Head Office' }} |
Member Since |
{{
diff --git a/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.scss b/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.scss
index 97e7362ac..840774130 100644
--- a/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.scss
+++ b/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.scss
@@ -140,6 +140,8 @@
}
.meta-value {
+ font-family: Arial, Helvetica, sans-serif;
+ font-variant-numeric: tabular-nums;
font-size: 15px;
font-weight: bold;
color: #322f2b;
@@ -226,12 +228,19 @@
.lbl-col {
background-color: #f7f5ee;
+ font-family: Georgia, 'Times New Roman', Times, serif;
font-weight: bold;
color: #7b6f63;
width: 25%;
}
+ // Every figure a member actually needs to read at a glance — share counts, amounts, dates, the
+ // resolution number — sits in this column. Georgia's numerals kept blurring into each other at
+ // this size, so values get a plain sans-serif with tabular figures instead; the label column
+ // keeps the certificate's own serif since it's static text, never a number to misread.
.val-col {
+ font-family: Arial, Helvetica, sans-serif;
+ font-variant-numeric: tabular-nums;
color: #322f2b;
width: 25%;
}
@@ -242,6 +251,7 @@
}
.lbl-col-highlight {
+ font-family: Georgia, 'Times New Roman', Times, serif;
font-weight: bold;
color: #0b3c5d;
font-size: 14px;
@@ -251,6 +261,8 @@
}
.val-col-highlight {
+ font-family: Arial, Helvetica, sans-serif;
+ font-variant-numeric: tabular-nums;
font-size: 16px;
font-weight: bold;
color: #b8860b; /* Dark Goldenrod */
@@ -270,6 +282,7 @@
padding: 8px 12px;
border: 1px solid #ede8db;
background-color: #f7f5ee;
+ font-family: Georgia, 'Times New Roman', Times, serif;
font-size: 11px;
font-weight: bold;
color: #7b6f63;
@@ -277,9 +290,14 @@
letter-spacing: 0.6px;
}
+ // Same reasoning as .val-col above: every cell here is a figure someone has to check against the
+ // register, so it reads in a plain sans-serif with tabular numerals rather than the certificate's
+ // decorative serif.
td {
padding: 9px 12px;
border: 1px solid #ede8db;
+ font-family: Arial, Helvetica, sans-serif;
+ font-variant-numeric: tabular-nums;
font-size: 13px;
color: #322f2b;
}
diff --git a/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.ts b/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.ts
index 2b4c517b9..61605cc0a 100644
--- a/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.ts
+++ b/src/app/shares/shares-account-view/share-certificate-dialog/share-certificate-dialog.component.ts
@@ -16,6 +16,7 @@ import {
MatDialogClose
} from '@angular/material/dialog';
import { ClientsService } from 'app/clients/clients.service';
+import { OrganizationService } from 'app/organization/organization.service';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
@@ -44,6 +45,7 @@ export class ShareCertificateDialogComponent implements OnInit {
dialogRef = inject>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
clientsService = inject(ClientsService);
+ private organizationService = inject(OrganizationService);
sharesAccountData: any;
clientId: string;
@@ -53,6 +55,11 @@ export class ShareCertificateDialogComponent implements OnInit {
panNumber: string = 'Not Available';
memberId: string = '';
+ /** The issuing bank's own name/location, as set on the Institution Profile — never a fixed name,
+ * since this certificate template is shared by every tenant this app runs for. */
+ bankName: string = 'Bank';
+ bankLocation: string = '';
+
certificateNo: string = '';
folioNo: string = '';
shareNumbersRange: string = '';
@@ -155,6 +162,24 @@ export class ShareCertificateDialogComponent implements OnInit {
},
error: () => {}
});
+
+ // Fetch the issuing bank's own name/location from the Institution Profile, set once under
+ // Admin > Organization for whichever bank this instance runs for — never a fixed name baked
+ // into the certificate itself.
+ this.organizationService.getInstitutionProfile().subscribe({
+ next: (profile: any) => {
+ this.bankName = profile?.statementHeaderName || profile?.bankName || this.bankName;
+ this.bankLocation = [
+ profile?.city,
+ profile?.state,
+ profile?.country
+ ]
+ .filter((p) => !!p && String(p).trim())
+ .join(', ');
+ },
+ // A missing/incomplete profile must not stop the certificate from rendering.
+ error: () => {}
+ });
}
/**
diff --git a/src/environments/.env.ts b/src/environments/.env.ts
index e4d9bc1c4..c3226b95c 100644
--- a/src/environments/.env.ts
+++ b/src/environments/.env.ts
@@ -10,7 +10,7 @@
export default {
'mifos_x': {
'version': '260914',
- 'hash': 'ab58d7f7'
+ 'hash': 'ee5a430b'
},
'allow_switching_backend_instance': true
};
diff --git a/src/main.scss b/src/main.scss
index aa0387a44..387d35837 100644
--- a/src/main.scss
+++ b/src/main.scss
@@ -978,9 +978,18 @@ mat-card {
mifosx-breadcrumb,
mifosx-footer,
.sidebar-panel,
+ .sidebar-handle,
.mat-mdc-tab-header,
.mat-stepper-vertical-line,
- .mat-step-header {
+ .mat-step-header,
+ /* The real 2-row nav menu (Client Master / Approvals / Cashier … and the row below it) and the
+ live KPI ticker live directly in shell.component.html as .main-header / .kpi-ticker, not
+ inside — hiding only the toolbar left this menu printing on every page. */
+ .main-header,
+ .kpi-ticker,
+ .cashier-nav,
+ .gl-backdrop,
+ .gl-mega-menu {
display: none !important;
}
@@ -988,6 +997,32 @@ mat-card {
margin-left: 0 !important;
}
+ /* The whole app shell scrolls inside CDK's own clipped viewport —
+ .mat-drawer-container { overflow: hidden } and .mat-drawer-content { overflow: auto; height: 100% }
+ — so without freeing these too, print is still capped to one screen's worth of the page
+ regardless of the tab-body fix below. */
+ #mifosx-shell-container,
+ .mat-drawer-container,
+ .mat-drawer-content,
+ mat-sidenav-content.sidenav {
+ position: static !important;
+ overflow: visible !important;
+ height: auto !important;
+ }
+
+ /* Angular Material clips a tab's content to a JS-measured on-screen height via
+ `.mat-mdc-tab-body-wrapper { overflow: hidden }` (for the tab-switch height animation) and
+ `.mat-mdc-tab-body-content { height: 100%; overflow: auto }` — printing only what fit that
+ clip left every account/tab page (Shares Account General included) blank apart from a
+ stray border. Printing needs the full content, not the animated viewport. */
+ .mat-mdc-tab-body-wrapper,
+ .mat-mdc-tab-body,
+ .mat-mdc-tab-body-content {
+ position: static !important;
+ overflow: visible !important;
+ height: auto !important;
+ }
+
body,
html {
background: #fff !important;
|