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 @@
-
Nexora Co-operative Bank Ltd.
-
Mumbai, Maharashtra, India
+
{{ bankName }}
+ @if (bankLocation) { +
{{ bankLocation }}
+ }
SHARE CERTIFICATE
Issued under the Common Seal of the Bank
@@ -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;