Merge branch 'dev' of https://git.lincpay.tech/CBSNexora/cbsnexora into dev
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
<h2 mat-dialog-title class="dialog-header no-print">
|
||||
<span>{{ accountType === 'FD' ? 'Fixed Deposit' : 'Recurring Deposit' }} Certificate</span>
|
||||
<span class="flex-auto"></span>
|
||||
</h2>
|
||||
|
||||
<div mat-dialog-content class="dialog-content-container">
|
||||
<!-- Certificate Page Frame -->
|
||||
<div class="certificate-container" id="deposit-certificate-print">
|
||||
<div class="certificate-border-outer">
|
||||
<div class="certificate-border-inner">
|
||||
<!-- Certificate Watermark Icon/Seal -->
|
||||
<div class="certificate-watermark">
|
||||
<mat-icon class="watermark-icon">{{ accountType === 'FD' ? 'savings' : 'event_repeat' }}</mat-icon>
|
||||
</div>
|
||||
|
||||
<div class="certificate-header">
|
||||
<div class="bank-name">Nexora Co-operative Bank Ltd.</div>
|
||||
<div class="bank-sub">Mumbai, Maharashtra, India</div>
|
||||
<div class="cert-title">
|
||||
{{ accountType === 'FD' ? 'FIXED DEPOSIT CERTIFICATE' : 'RECURRING DEPOSIT CERTIFICATE' }}
|
||||
</div>
|
||||
<div class="cert-subtitle">Issued under the Common Seal of the Bank</div>
|
||||
</div>
|
||||
|
||||
<div class="cert-meta-row">
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">Certificate No.</span>
|
||||
<span class="meta-value">{{ certificateNo }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">Account No.</span>
|
||||
<span class="meta-value">{{ accountData.fdAccountNumber || accountData.accountNo }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">Date of Issue</span>
|
||||
<span class="meta-value">{{ issueDate | dateFormat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cert-body-section">
|
||||
<p class="cert-main-text">
|
||||
This is to certify that
|
||||
<strong class="highlight-text">{{ accountData.clientName || accountData.groupName }}</strong>
|
||||
holds a {{ accountType === 'FD' ? 'Fixed Deposit' : 'Recurring Deposit' }} account bearing account number
|
||||
<strong class="highlight-text">{{ accountData.fdAccountNumber || accountData.accountNo }}</strong>
|
||||
with <strong class="highlight-text">Nexora Co-operative Bank Ltd.</strong>, as per the particulars set out
|
||||
below.
|
||||
<ng-container *ngIf="renewalCount">
|
||||
This account was renewed on maturity {{ renewalCount }} {{ renewalCount === 1 ? 'time' : 'times' }}; the
|
||||
full renewal history is set out below.
|
||||
</ng-container>
|
||||
</p>
|
||||
|
||||
<div class="issued-to-details">
|
||||
<div class="issued-to-title">ISSUED TO:</div>
|
||||
<div class="client-name-details">{{ accountData.clientName || accountData.groupName }}</div>
|
||||
<div class="client-ids">
|
||||
<span *ngIf="accountData.clientAccountNo"
|
||||
><strong>Customer ID:</strong> {{ accountData.clientAccountNo }}</span
|
||||
>
|
||||
<span *ngIf="panNumber && panNumber !== 'Not Available'">
|
||||
· <strong>PAN:</strong> {{ panNumber }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="client-address" *ngIf="clientAddress">
|
||||
{{ clientAddress }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Details Grid -->
|
||||
<table class="certificate-details-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="lbl-col">Product</td>
|
||||
<td class="val-col">{{ accountData.depositProductName }}</td>
|
||||
<td class="lbl-col">{{ accountType === 'FD' ? 'Deposit Date' : 'First Deposit Date' }}</td>
|
||||
<td class="val-col">{{ depositDate | dateFormat }}</td>
|
||||
</tr>
|
||||
<tr *ngIf="accountType === 'FD'">
|
||||
<td class="lbl-col">Deposit Amount</td>
|
||||
<td class="val-col">
|
||||
{{ accountData.depositAmount | currency: accountData.currency.code : 'symbol-narrow' }}
|
||||
</td>
|
||||
<td class="lbl-col">Tenure</td>
|
||||
<td class="val-col">{{ tenureLabel }}</td>
|
||||
</tr>
|
||||
<tr *ngIf="accountType === 'RD'">
|
||||
<td class="lbl-col">Installment Amount</td>
|
||||
<td class="val-col">
|
||||
{{
|
||||
accountData.mandatoryRecommendedDepositAmount | currency: accountData.currency.code : 'symbol-narrow'
|
||||
}}
|
||||
</td>
|
||||
<td class="lbl-col">Installment Frequency</td>
|
||||
<td class="val-col">
|
||||
Every {{ accountData.recurringFrequency }} {{ accountData.recurringFrequencyType?.value }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="accountType === 'RD'">
|
||||
<td class="lbl-col">Tenure</td>
|
||||
<td class="val-col">{{ tenureLabel }}</td>
|
||||
<td class="lbl-col">Interest Rate (p.a.)</td>
|
||||
<td class="val-col">{{ accountData.nominalAnnualInterestRate }}%</td>
|
||||
</tr>
|
||||
<tr *ngIf="accountType === 'FD'">
|
||||
<td class="lbl-col">Interest Rate (p.a.)</td>
|
||||
<td class="val-col">{{ accountData.nominalAnnualInterestRate }}%</td>
|
||||
<td class="lbl-col">Interest Compounding</td>
|
||||
<td class="val-col">{{ accountData.interestCompoundingPeriodType?.value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl-col">Maturity Date</td>
|
||||
<td class="val-col">{{ accountData.maturityDate | dateFormat }}</td>
|
||||
<td class="lbl-col">Current Balance</td>
|
||||
<td class="val-col">
|
||||
{{ accountData.summary?.accountBalance | currency: accountData.currency.code : 'symbol-narrow' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="highlight-row">
|
||||
<td colspan="2" class="lbl-col-highlight">MATURITY AMOUNT</td>
|
||||
<td colspan="2" class="val-col-highlight">
|
||||
{{ accountData.maturityAmount | currency: accountData.currency.code : 'symbol-narrow' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Renewal History -- one row per term of this FD, oldest first. Deliberately shows only the
|
||||
term number, not each term's internal account number: the customer holds one FD under one
|
||||
number, and listing the per-term account numbers here would read as several deposits. Staff
|
||||
can still see them on the account view's own renewal-history card. -->
|
||||
<table class="certificate-details-table renewal-history-table" *ngIf="renewalHistory.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="7" class="renewal-history-title">RENEWAL HISTORY</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Term</th>
|
||||
<th>From</th>
|
||||
<th>To</th>
|
||||
<th>Rate</th>
|
||||
<th>Principal</th>
|
||||
<th>Interest</th>
|
||||
<th>Maturity Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let term of renewalHistory" [class.current-term-row]="term.accountid === accountData.id">
|
||||
<td>{{ term.renewalcycleno === 0 ? 'Original' : 'Renewal ' + term.renewalcycleno }}</td>
|
||||
<td>{{ term.activatedondate | dateFormat }}</td>
|
||||
<td>{{ term.maturitydate | dateFormat }}</td>
|
||||
<td>{{ term.interestrate }}%</td>
|
||||
<td>{{ term.depositamount | currency: accountData.currency.code : 'symbol-narrow' }}</td>
|
||||
<td>
|
||||
{{
|
||||
term.interestearned === null || term.interestearned === undefined
|
||||
? '—'
|
||||
: (term.interestearned | currency: accountData.currency.code : 'symbol-narrow')
|
||||
}}
|
||||
</td>
|
||||
<td>{{ maturityFor(term) | currency: accountData.currency.code : 'symbol-narrow' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="cert-footer">
|
||||
<div class="seal-container">
|
||||
<div class="seal-ring">
|
||||
<div class="seal-text">COMMON SEAL</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="closing-text">
|
||||
Given under the common seal of the bank this {{ issueDate | date: 'd' }} day of
|
||||
{{ issueDate | date: 'MMMM yyyy' }}.
|
||||
</div>
|
||||
<div class="signatures-container">
|
||||
<div class="signature-block">
|
||||
<div class="signature-line"></div>
|
||||
<div class="signatory-title">Authorised Signatory</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-dialog-actions align="end" class="no-print">
|
||||
<button mat-button mat-dialog-close>{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="printCertificate()">
|
||||
<mat-icon class="m-r-5">print</mat-icon>
|
||||
Print Certificate
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,556 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 500;
|
||||
color: #1a252c;
|
||||
}
|
||||
|
||||
.dialog-content-container {
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Certificate Outer Container */
|
||||
.certificate-container {
|
||||
background-color: #faf8f5;
|
||||
padding: 20px;
|
||||
font-family: Georgia, 'Times New Roman', Times, serif;
|
||||
color: #2c2520;
|
||||
box-shadow: 0 4px 20px rgb(0 0 0 / 8%);
|
||||
border-radius: 8px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Classic Ornate Border System */
|
||||
.certificate-border-outer {
|
||||
border: 5px double #c5a059; /* Gold double border */
|
||||
padding: 8px;
|
||||
background-color: #fdfcf7; /* Textured light cream */
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.certificate-border-inner {
|
||||
border: 1px solid #dcd1bd;
|
||||
padding: 30px;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Watermark */
|
||||
.certificate-watermark {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0.03;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
|
||||
.watermark-icon {
|
||||
font-size: 280px;
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
color: #c5a059;
|
||||
}
|
||||
}
|
||||
|
||||
.certificate-header,
|
||||
.cert-meta-row,
|
||||
.cert-body-section,
|
||||
.certificate-details-table,
|
||||
.cert-footer {
|
||||
position: relative;
|
||||
z-index: 2; /* Ensure content is above watermark */
|
||||
}
|
||||
|
||||
/* Header styling */
|
||||
.certificate-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #f2edd5;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.bank-name {
|
||||
font-size: 26px;
|
||||
font-weight: bold;
|
||||
color: #0b3c5d; /* Dark Premium Blue */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.bank-sub {
|
||||
font-size: 13px;
|
||||
color: #7b6f63;
|
||||
font-style: italic;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cert-title {
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: #c5a059; /* Classic Gold */
|
||||
letter-spacing: 4px;
|
||||
margin-bottom: 5px;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
.cert-subtitle {
|
||||
font-size: 11px;
|
||||
color: #7b6f63;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Meta Data Grid (Top of cert) */
|
||||
.cert-meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #f2edd5;
|
||||
padding-bottom: 15px;
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.meta-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
color: #928374;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #322f2b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Main Body text */
|
||||
.cert-body-section {
|
||||
margin-bottom: 35px;
|
||||
|
||||
.cert-main-text {
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
color: #4a453f;
|
||||
margin-bottom: 25px;
|
||||
|
||||
.highlight-text {
|
||||
color: #1a202c;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px dotted #c5a059;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Issued To Details Block */
|
||||
.issued-to-details {
|
||||
border: 1px solid #f0e6d2;
|
||||
background-color: #fbfaf5;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
margin: 20px auto;
|
||||
max-width: 90%;
|
||||
text-align: left;
|
||||
|
||||
.issued-to-title {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
color: #c5a059;
|
||||
letter-spacing: 1.5px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.client-name-details {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #0b3c5d;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.client-ids {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.client-address {
|
||||
font-size: 12px;
|
||||
color: #7b6f63;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Certificate Data Table */
|
||||
.certificate-details-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 40px;
|
||||
|
||||
td {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ede8db;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.lbl-col {
|
||||
background-color: #f7f5ee;
|
||||
font-weight: bold;
|
||||
color: #7b6f63;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.val-col {
|
||||
color: #322f2b;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.highlight-row {
|
||||
background-color: #f5f2e9;
|
||||
border-top: 2px solid #c5a059;
|
||||
}
|
||||
|
||||
.lbl-col-highlight {
|
||||
font-weight: bold;
|
||||
color: #0b3c5d;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.val-col-highlight {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #b8860b; /* Dark Goldenrod */
|
||||
}
|
||||
}
|
||||
|
||||
/* Renewal History Table -- lists each earlier term this FD was renewed from */
|
||||
.renewal-history-table {
|
||||
th {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ede8db;
|
||||
font-size: 11px;
|
||||
text-align: left;
|
||||
background-color: #f7f5ee;
|
||||
color: #7b6f63;
|
||||
}
|
||||
|
||||
.renewal-history-title {
|
||||
text-align: center;
|
||||
background-color: #f5f2e9;
|
||||
color: #0b3c5d;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1.5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.current-term-row {
|
||||
background-color: #fbfaf5;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
/* Certificate Footer Seal / Signatures */
|
||||
.cert-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
|
||||
.seal-container {
|
||||
width: 25%;
|
||||
|
||||
.seal-ring {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border: 2px dashed #b8860b;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
transform: rotate(-15deg);
|
||||
|
||||
.seal-text {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
color: #b8860b;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.closing-text {
|
||||
width: 45%;
|
||||
font-size: 12px;
|
||||
color: #7b6f63;
|
||||
font-style: italic;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.signatures-container {
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
|
||||
.signature-block {
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
|
||||
.signature-line {
|
||||
border-bottom: 1px solid #7b6f63;
|
||||
margin-bottom: 8px;
|
||||
height: 40px; /* Space for signature */
|
||||
}
|
||||
|
||||
.signatory-title {
|
||||
font-size: 12px;
|
||||
color: #7b6f63;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Print CSS */
|
||||
@media print {
|
||||
/* Everything below `::ng-deep` reaches outside this component's own template -- html/body and
|
||||
the whole Angular Material CDK overlay chain never carry this component's scoping attribute
|
||||
under the default emulated encapsulation, so a plain (scoped) selector would silently never
|
||||
match any of them. `::ng-deep` is deprecated but still the correct tool for a component that
|
||||
genuinely needs to override its host application's shell during print. */
|
||||
|
||||
/* The app shell pins html/body to a fixed viewport height with overflow hidden (its inner
|
||||
panels handle their own scrolling on screen) -- left in place, that caps every printed page
|
||||
at one viewport's worth of content and silently cuts off the rest instead of paginating. */
|
||||
::ng-deep html,
|
||||
::ng-deep body {
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Hide non-overlay contents during print */
|
||||
::ng-deep body > :not(.cdk-overlay-container) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* The dialog's own scroll region clips anything past 80vh -- printing only captures what a
|
||||
scrollable/max-height box shows on screen, so both constraints must be lifted or the
|
||||
certificate gets cut off after its first visible slice. */
|
||||
.dialog-content-container {
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* CDK's overlay wrapper/backdrop/pane are all `position: fixed` (needed to center the dialog
|
||||
on screen) and the wrapper flex-centers its content -- during print, a fixed-position
|
||||
ancestor is clipped to one page's height, so anything taller than that gets cut off and only
|
||||
a thin cross-section (here, just the certificate's own border) survives. Every ancestor in
|
||||
that chain has to become a normal, statically-flowing block for the full certificate to print. */
|
||||
::ng-deep .cdk-overlay-backdrop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::ng-deep .cdk-overlay-container,
|
||||
::ng-deep .cdk-global-overlay-wrapper,
|
||||
::ng-deep .cdk-overlay-pane,
|
||||
::ng-deep .mat-mdc-dialog-container,
|
||||
::ng-deep .mdc-dialog__container,
|
||||
::ng-deep .mdc-dialog__surface {
|
||||
position: static !important;
|
||||
display: block !important;
|
||||
max-height: none !important;
|
||||
max-width: none !important;
|
||||
height: auto !important;
|
||||
width: auto !important;
|
||||
overflow: visible !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Make certificate fill page */
|
||||
#deposit-certificate-print {
|
||||
position: static !important;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background-color: #fff !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.certificate-border-outer {
|
||||
border-width: 8px !important;
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-dialog-container {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Compact every spacing/font value so the whole certificate fits a single printed page --
|
||||
the on-screen sizing is generous by design, but that same height doesn't fit one page. */
|
||||
.certificate-container {
|
||||
padding: 6px !important;
|
||||
}
|
||||
|
||||
.certificate-border-inner {
|
||||
padding: 14px !important;
|
||||
}
|
||||
|
||||
.certificate-header {
|
||||
margin-bottom: 10px !important;
|
||||
padding-bottom: 10px !important;
|
||||
|
||||
.bank-name {
|
||||
font-size: 19px !important;
|
||||
margin-bottom: 2px !important;
|
||||
}
|
||||
|
||||
.bank-sub {
|
||||
font-size: 10px !important;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.cert-title {
|
||||
font-size: 19px !important;
|
||||
letter-spacing: 2px !important;
|
||||
margin-bottom: 2px !important;
|
||||
}
|
||||
|
||||
.cert-subtitle {
|
||||
font-size: 9px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cert-meta-row {
|
||||
margin-bottom: 10px !important;
|
||||
padding-bottom: 8px !important;
|
||||
|
||||
.meta-label {
|
||||
font-size: 9px !important;
|
||||
margin-bottom: 1px !important;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cert-body-section {
|
||||
margin-bottom: 10px !important;
|
||||
|
||||
.cert-main-text {
|
||||
font-size: 11px !important;
|
||||
line-height: 1.4 !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.issued-to-details {
|
||||
padding: 8px 14px !important;
|
||||
margin: 8px auto !important;
|
||||
|
||||
.client-name-details {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.client-ids,
|
||||
.client-address {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.certificate-details-table {
|
||||
margin-bottom: 12px !important;
|
||||
|
||||
td {
|
||||
padding: 5px 10px !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.lbl-col-highlight {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.val-col-highlight {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.renewal-history-table {
|
||||
margin-bottom: 12px !important;
|
||||
|
||||
th {
|
||||
padding: 4px 8px !important;
|
||||
font-size: 8px !important;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 4px 8px !important;
|
||||
font-size: 9px !important;
|
||||
}
|
||||
|
||||
.renewal-history-title {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cert-footer {
|
||||
margin-top: 8px !important;
|
||||
|
||||
.seal-ring {
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
}
|
||||
|
||||
.seal-text {
|
||||
font-size: 8px !important;
|
||||
}
|
||||
|
||||
.closing-text {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.signature-line {
|
||||
height: 22px !important;
|
||||
}
|
||||
|
||||
.signatory-title {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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 {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { DateFormatPipe } from '../../pipes/date-format.pipe';
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
|
||||
/**
|
||||
* Printable deposit certificate for a Fixed Deposit or Recurring Deposit account -- handed to the
|
||||
* customer as physical proof of their deposit. Shared between FD and RD since both account shapes
|
||||
* carry the same core fields (depositAmount, nominalAnnualInterestRate, depositPeriod, maturityDate,
|
||||
* maturityAmount); RD additionally shows its installment/frequency details.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-deposit-certificate-dialog',
|
||||
templateUrl: './deposit-certificate-dialog.component.html',
|
||||
styleUrls: ['./deposit-certificate-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
DateFormatPipe,
|
||||
CurrencyPipe,
|
||||
MatButton,
|
||||
MatIcon
|
||||
]
|
||||
})
|
||||
export class DepositCertificateDialogComponent implements OnInit {
|
||||
dialogRef = inject<MatDialogRef<DepositCertificateDialogComponent>>(MatDialogRef);
|
||||
data = inject(MAT_DIALOG_DATA);
|
||||
clientsService = inject(ClientsService);
|
||||
|
||||
/** 'FD' or 'RD' -- controls the title and which extra rows show. */
|
||||
accountType: 'FD' | 'RD';
|
||||
accountData: any;
|
||||
|
||||
clientData: any;
|
||||
clientAddress = '';
|
||||
panNumber = 'Not Available';
|
||||
|
||||
certificateNo = '';
|
||||
issueDate: Date = new Date();
|
||||
|
||||
/**
|
||||
* Every term of this FD, oldest first, as resolved by the backend -- a renewal opens each term as
|
||||
* its own account, so the certificate lists them all to show the deposit's full history. Empty for
|
||||
* an FD that has never been renewed. Keys are lower-cased, arriving from SQL column labels.
|
||||
*/
|
||||
renewalHistory: any[] = [];
|
||||
|
||||
/**
|
||||
* How many times the deposit has actually rolled over. {@link renewalHistory} lists every TERM, and the
|
||||
* first of those is the original deposit rather than a renewal, so the count is one less than the number
|
||||
* of rows. Stated on a customer-facing certificate, so an off-by-one here misreports the deposit.
|
||||
*/
|
||||
get renewalCount(): number {
|
||||
return Math.max(this.renewalHistory.length - 1, 0);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.accountType = this.data.accountType;
|
||||
this.accountData = this.data.accountData;
|
||||
const clientId = this.data.clientId != null ? String(this.data.clientId) : null;
|
||||
|
||||
this.certificateNo = `${this.accountType}-CERT-${this.accountData.fdAccountNumber || this.accountData.accountNo}`;
|
||||
this.renewalHistory = this.accountData?.renewalHistory ?? [];
|
||||
|
||||
if (!clientId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clientsService.getClientData(clientId).subscribe({
|
||||
next: (client: any) => {
|
||||
this.clientData = client;
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
|
||||
this.clientsService.getClientAddressData(clientId).subscribe({
|
||||
next: (addresses: any[]) => {
|
||||
if (addresses && addresses.length > 0) {
|
||||
const permanent = addresses.find((a) => /permanent/i.test(a?.addressType || '')) || addresses[0];
|
||||
this.clientAddress = this.formatAddress(permanent);
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
|
||||
this.clientsService.getClientIdentifiers(clientId).subscribe({
|
||||
next: (identifiers: any[]) => {
|
||||
const idList = Array.isArray(identifiers) ? identifiers : (identifiers as any)?.pageItems || [];
|
||||
const panId = idList.find((id: any) => /pan/i.test(id?.documentType?.name || ''));
|
||||
if (panId) {
|
||||
this.panNumber = panId.documentKey;
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A term's settled maturity amount, or its projection while it is still running. Resolved here rather
|
||||
* than inline so the choice isn't left to operator precedence between `??` and the currency pipe.
|
||||
*/
|
||||
maturityFor(term: any): number | null {
|
||||
return term?.maturedamount ?? term?.projectedmaturityamount ?? null;
|
||||
}
|
||||
|
||||
formatAddress(addr: any): string {
|
||||
if (!addr) return '';
|
||||
return [
|
||||
addr.addressLine1,
|
||||
addr.addressLine2,
|
||||
addr.townVillage,
|
||||
addr.city,
|
||||
addr.stateName,
|
||||
addr.countryName,
|
||||
addr.postalCode
|
||||
]
|
||||
.filter((p) => !!p && String(p).trim())
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
get depositDate(): any {
|
||||
return this.accountData?.timeline?.activatedOnDate ?? this.accountData?.timeline?.submittedOnDate ?? null;
|
||||
}
|
||||
|
||||
get tenureLabel(): string {
|
||||
const period = this.accountData?.depositPeriod;
|
||||
const freq = this.accountData?.depositPeriodFrequency?.value;
|
||||
if (period == null) return 'N/A';
|
||||
return freq ? `${period} ${freq}` : String(period);
|
||||
}
|
||||
|
||||
printCertificate() {
|
||||
window.print();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user