feat: letter-mgmt module + digital UPI/IMPS/ATM-POS reports + header redesign
letter-mgmt (new module): - Notice page: overdue loan list with filters, KPI strip, bulk actions, call disposition logging (Promise-to-Pay, Not Reachable, etc.), Speed Post dispatch dialog with consignment + post-office tracking, PDF preview + download, call-contact dialog with primary / alt / family / nominee numbers and per-contact disposition history. - Certificates: Loan Interest, Share, Balance, Form 60 (no-PAN flow), Form 15G/H (auto age-based), Hypothecation, NOC - each with shared preview dialog (six tone variants) and jsPDF generator using a common letterhead / summary-grid / signature-seal helper. digital: - IMPS module: report (P2A/P2P/Aadhaar tabs) + entry form with MMID/mobile lookup, NPCI switch resolve-to-A/C+IFSC BANL flow, sender autocomplete, hide sender field for P2P/Aadhaar, charges + 18% GST + waive checkbox, per-sender today-only history dialog. - UPI report with app-mix tab and full transaction filtering. - ATM/POS/ECOM hub with three ticket cards routing to today/status/cycle. - NEFT/RTGS entry: sender-account autocomplete, charges + GST + waive, field reorder (sender before amount), NEFT cap / RTGS floor validation, History button opening per-sender today's transactions. - NEFT/RTGS report: horizontal-bar trend leaderboard replacing tall bars. shell + theme: - Header redesigned with IBM Plex Sans 12/500 typography, 11-column grid per row, top-accent hover indicator, bordered cells via grid-gap. - Live KPI ticker below menu (CASH, TXN TODAY, CLEARING IN/OUT, NPA, CASA, CD RATIO, RBI CIRC, REPO) with marquee scroll, pause-on-hover, brand-blue gradient, amber bottom strip, pulsing red LIVE chip. - Sidebar 'KYC Management' -> 'KYC & ReKYC Management'. - Letter Mgmt / certificate menu items wired with green icon hints for items that have a working page. access-control: - MFA card layout compacted; method header inlines back button + title. icons: - Register faTruck/TruckFast, faEllipsisV/H, faCommentDots. - Add IBM Plex Sans Google Fonts import to index.html. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
4322f37c6d
commit
325228c514
+703
@@ -0,0 +1,703 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="cpd-root">
|
||||
<!-- TOOLBAR -->
|
||||
<div class="cpd-toolbar no-print">
|
||||
<div class="cpd-toolbar-l">
|
||||
<fa-icon [icon]="isInterest ? 'file-invoice' : 'certificate'" class="cpd-toolbar-ic"></fa-icon>
|
||||
<div>
|
||||
<div class="cpd-toolbar-t">{{ title }}</div>
|
||||
<div class="cpd-toolbar-s">{{ refLine }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cpd-toolbar-r">
|
||||
<button class="cpd-tb-btn" (click)="printCertificate()" matTooltip="Print or Save as PDF">
|
||||
<fa-icon icon="print"></fa-icon> Print / PDF
|
||||
</button>
|
||||
<button class="cpd-tb-btn" (click)="downloadPdf()" matTooltip="Download as PDF">
|
||||
<fa-icon icon="download"></fa-icon> Download PDF
|
||||
</button>
|
||||
<button class="cpd-tb-btn cpd-tb-x" (click)="close()" matTooltip="Close">
|
||||
<fa-icon icon="times"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CERTIFICATE PAGE -->
|
||||
<div class="cpd-page" [ngClass]="pageToneClass">
|
||||
<!-- Bank letterhead -->
|
||||
<div class="cpd-letterhead">
|
||||
<div class="cpd-bank-name">{{ bankName }}</div>
|
||||
<div class="cpd-bank-sub">{{ bankAddress }}</div>
|
||||
<div class="cpd-bank-sub">{{ bankRegNo }} · {{ bankContact }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Certificate title strip -->
|
||||
<div class="cpd-title-strip">
|
||||
<span class="cpd-title-l">{{ title }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Meta strip -->
|
||||
<div class="cpd-meta-row">
|
||||
<div>
|
||||
<div class="cpd-meta-l">Certificate Reference</div>
|
||||
<div class="cpd-meta-v mono">
|
||||
@if (isInterest && interest) {
|
||||
INT-{{ interest.record.accountNo }}-{{ interest.fy.fy | slice: 3 }}
|
||||
} @else if (isShare && share) {
|
||||
{{ share.certificateNo }}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="cpd-meta-l">Date of Issue</div>
|
||||
<div class="cpd-meta-v">{{ issuedOn | date: 'd MMMM y' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="cpd-meta-l">
|
||||
@if (isInterest) {
|
||||
Financial Year
|
||||
} @else if (isShare) {
|
||||
Folio No.
|
||||
} @else if (isBalance) {
|
||||
As-on Date
|
||||
} @else if (isForm60) {
|
||||
Trigger
|
||||
} @else if (isForm15gh) {
|
||||
Form
|
||||
} @else if (isHypothecation) {
|
||||
Status
|
||||
} @else if (isNoc) {
|
||||
Status
|
||||
}
|
||||
</div>
|
||||
<div class="cpd-meta-v">
|
||||
@if (isInterest && interest) {
|
||||
<span class="cpd-stage-pill">{{ interest.fy.fy }}</span>
|
||||
} @else if (isShare && share) {
|
||||
<span class="cpd-stage-pill cpd-stage-pill--amber">{{ share.folioNo }}</span>
|
||||
} @else if (isBalance && balance) {
|
||||
<span class="cpd-stage-pill">{{ balance.asOnDate | date: 'd MMM y' }}</span>
|
||||
} @else if (isForm60 && form60) {
|
||||
<span class="cpd-stage-pill cpd-stage-pill--amber">No PAN — Form 60</span>
|
||||
} @else if (isForm15gh && form15gh) {
|
||||
<span class="cpd-stage-pill cpd-stage-pill--teal">Form {{ form15gh.kind }}</span>
|
||||
} @else if (isHypothecation && hypothecation) {
|
||||
<span class="cpd-stage-pill cpd-stage-pill--red">{{ hypothecation.status }}</span>
|
||||
} @else if (isNoc && noc) {
|
||||
<span class="cpd-stage-pill cpd-stage-pill--green">
|
||||
{{ noc.status === 'READY_TO_ISSUE' ? 'Ready to Issue' : noc.status }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── LOAN INTEREST CERTIFICATE ── -->
|
||||
@if (isInterest && interest) {
|
||||
<div class="cpd-block">
|
||||
<div class="cpd-block-l">To,</div>
|
||||
<div class="cpd-block-v">
|
||||
<div>
|
||||
<strong>{{ interest.record.customerName }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Customer ID: <span class="mono">{{ interest.record.customerId }}</span> · PAN:
|
||||
<span class="mono">{{ interest.record.customerPan }}</span>
|
||||
</div>
|
||||
<div>{{ interest.record.customerAddress }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cpd-para">
|
||||
This is to certify that <strong>{{ interest.record.customerName }}</strong> (Customer ID
|
||||
<strong>{{ interest.record.customerId }}</strong
|
||||
>) is availing the following loan facility from our bank, and the interest paid by the above customer during the
|
||||
financial year <strong>{{ interest.fy.fy }}</strong> is as detailed below.
|
||||
</p>
|
||||
|
||||
<table class="cpd-summary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Loan Account No.</td>
|
||||
<td class="mono">
|
||||
<strong>{{ interest.record.accountNo }}</strong>
|
||||
</td>
|
||||
<td>Branch</td>
|
||||
<td>{{ interest.record.branch }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Loan Type</td>
|
||||
<td>{{ interest.record.loanType }}</td>
|
||||
<td>Disbursed On</td>
|
||||
<td>{{ interest.record.disbursementDate | date: 'd MMM y' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sanctioned Amount</td>
|
||||
<td>₹{{ interest.record.sanctionedAmount | number: '1.2-2' }}</td>
|
||||
<td>Interest Rate</td>
|
||||
<td>{{ interest.record.interestRate }}% p.a.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Opening Balance ({{ interest.fy.fy | slice: 0 : 8 }})</td>
|
||||
<td>₹{{ interest.fy.openingBalance | number: '1.2-2' }}</td>
|
||||
<td>Closing Balance</td>
|
||||
<td>₹{{ interest.fy.closingBalance | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Principal Repaid</td>
|
||||
<td>₹{{ interest.fy.principalPaid | number: '1.2-2' }}</td>
|
||||
<td>Interest Paid</td>
|
||||
<td>
|
||||
<strong class="cpd-amount">₹{{ interest.fy.interestPaid | number: '1.2-2' }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">TOTAL INTEREST PAID DURING {{ interest.fy.fy }}</td>
|
||||
<td>
|
||||
<strong>₹{{ interest.fy.interestPaid | number: '1.2-2' }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="cpd-para">
|
||||
This certificate is issued at the request of the borrower for income-tax / financial record purposes and
|
||||
reflects the interest credited to the captioned loan account during the said financial year as per our books.
|
||||
</p>
|
||||
}
|
||||
|
||||
<!-- ── SHARE CERTIFICATE ── -->
|
||||
@if (isShare && share) {
|
||||
<div class="cpd-block">
|
||||
<div class="cpd-block-l">Issued To,</div>
|
||||
<div class="cpd-block-v">
|
||||
<div>
|
||||
<strong>{{ share.memberName }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Member ID: <span class="mono">{{ share.memberId }}</span> · PAN:
|
||||
<span class="mono">{{ share.memberPan }}</span>
|
||||
</div>
|
||||
<div>{{ share.memberAddress }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cpd-para cpd-para--centered">
|
||||
This is to certify that <strong>{{ share.memberName }}</strong> is the registered holder of
|
||||
<strong>{{ share.shareCount | number }}</strong> ({{ classLabelText() }}) shares of
|
||||
<strong>₹{{ share.faceValue | number: '1.0-0' }}</strong> each, fully paid up, in <strong>{{ bankName }}</strong
|
||||
>, subject to the bye-laws of the bank.
|
||||
</p>
|
||||
|
||||
<table class="cpd-summary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Certificate No.</td>
|
||||
<td class="mono">
|
||||
<strong>{{ share.certificateNo }}</strong>
|
||||
</td>
|
||||
<td>Folio No.</td>
|
||||
<td class="mono">{{ share.folioNo }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Branch</td>
|
||||
<td>{{ share.branch }}</td>
|
||||
<td>Member Since</td>
|
||||
<td>{{ share.joiningDate | date: 'd MMM y' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Share Class</td>
|
||||
<td>{{ classLabelText() }}</td>
|
||||
<td>Date of Issue</td>
|
||||
<td>{{ share.issueDate | date: 'd MMM y' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Share Numbers</td>
|
||||
<td class="mono">{{ share.shareNoFrom | number }} – {{ share.shareNoTo | number }}</td>
|
||||
<td>Dividend Paid (Cumulative)</td>
|
||||
<td>₹{{ share.dividendYtd | number: '1.0-0' }}</td>
|
||||
</tr>
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">TOTAL FACE VALUE</td>
|
||||
<td>
|
||||
<strong> ₹{{ share.shareCount * share.faceValue | number: '1.2-2' }} </strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="cpd-para">
|
||||
Given under the common seal of the bank this {{ issuedOn | date: 'd' }} day of {{ issuedOn | date: 'MMMM y' }}.
|
||||
</p>
|
||||
}
|
||||
|
||||
<!-- ── BALANCE CERTIFICATE ── -->
|
||||
@if (isBalance && balance) {
|
||||
<div class="cpd-block">
|
||||
<div class="cpd-block-l">To Whom It May Concern,</div>
|
||||
<div class="cpd-block-v">
|
||||
<div>
|
||||
<strong>{{ balance.record.customerName }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Customer ID: <span class="mono">{{ balance.record.customerId }}</span> · PAN:
|
||||
<span class="mono">{{ balance.record.customerPan }}</span>
|
||||
</div>
|
||||
<div>{{ balance.record.customerAddress }}</div>
|
||||
@if (balance.record.jointHolders) {
|
||||
<div class="small">Jointly with: {{ balance.record.jointHolders }}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cpd-para">
|
||||
This is to certify that <strong>{{ balance.record.customerName }}</strong> (Customer ID
|
||||
<strong>{{ balance.record.customerId }}</strong
|
||||
>) maintains the following account with our bank, and the balance available in the said account as on
|
||||
<strong>{{ balance.asOnDate | date: 'd MMMM y' }}</strong> is as detailed below.
|
||||
</p>
|
||||
|
||||
<table class="cpd-summary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Account No.</td>
|
||||
<td class="mono">
|
||||
<strong>{{ balance.record.accountNo }}</strong>
|
||||
</td>
|
||||
<td>Branch</td>
|
||||
<td>{{ balance.record.branch }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Account Type</td>
|
||||
<td>{{ accountTypeLabelText() }}</td>
|
||||
<td>Product</td>
|
||||
<td>{{ balance.record.productName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Account Opened</td>
|
||||
<td>{{ balance.record.openedOn | date: 'd MMM y' }}</td>
|
||||
<td>Interest Rate</td>
|
||||
<td>
|
||||
@if (balance.record.interestRate) {
|
||||
{{ balance.record.interestRate }}% p.a.
|
||||
} @else {
|
||||
—
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Available Balance</td>
|
||||
<td>₹{{ balance.record.availableBalance | number: '1.2-2' }}</td>
|
||||
<td>Accrued Interest</td>
|
||||
<td>₹{{ balance.record.accruedInterest | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">TOTAL LEDGER BALANCE AS ON {{ balance.asOnDate | date: 'd MMM y' }}</td>
|
||||
<td>
|
||||
<strong>₹{{ balance.record.ledgerBalance | number: '1.2-2' }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="cpd-para">
|
||||
This certificate is issued at the request of the customer for record purposes and reflects the balance available
|
||||
in the said account as per our books as on the date mentioned above.
|
||||
</p>
|
||||
}
|
||||
|
||||
<!-- ── FORM 60 ── -->
|
||||
@if (isForm60 && form60) {
|
||||
<p class="cpd-para">
|
||||
Declaration under <strong>Section 114B</strong> of the Income-tax Rules, 1962, to be made by a person who does
|
||||
not have a Permanent Account Number (PAN) and who enters into any transaction specified in rule 114B.
|
||||
</p>
|
||||
|
||||
<table class="cpd-summary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1. Full Name (in Block Letters)</td>
|
||||
<td colspan="3">
|
||||
<strong>{{ form60.customerName }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2. Father's / Spouse's Name</td>
|
||||
<td colspan="3">{{ form60.fatherName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3. Date of Birth</td>
|
||||
<td>{{ form60.dob | date: 'd MMM y' }}</td>
|
||||
<td>4. Customer ID</td>
|
||||
<td class="mono">{{ form60.customerId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5. Address</td>
|
||||
<td colspan="3">{{ form60.customerAddress }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6. ID Proof Submitted</td>
|
||||
<td>{{ idProofLabelText() }}</td>
|
||||
<td>7. ID Document No.</td>
|
||||
<td class="mono">{{ form60.idProofNumber }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8. Nature of Transaction</td>
|
||||
<td colspan="3">{{ form60.transactionType }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9. Transaction Amount</td>
|
||||
<td>₹{{ form60.transactionAmount | number: '1.2-2' }}</td>
|
||||
<td>10. Transaction Date</td>
|
||||
<td>{{ form60.transactionDate | date: 'd MMM y' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11. Estimated Total Income (Current FY)</td>
|
||||
<td>₹{{ form60.estimatedIncome | number: '1.0-0' }}</td>
|
||||
<td>12. Agricultural Income</td>
|
||||
<td>₹{{ form60.agriIncome | number: '1.0-0' }}</td>
|
||||
</tr>
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">PAN STATUS</td>
|
||||
<td>
|
||||
<strong>NOT ALLOTTED — Form 60 declared</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="cpd-para">
|
||||
I, <strong>{{ form60.customerName }}</strong
|
||||
>, do hereby declare that the particulars given above are true and correct to the best of my knowledge and
|
||||
belief. I further declare that I have applied / not applied for a Permanent Account Number with the Income-tax
|
||||
department, and that I have neither been assessed nor am liable to be assessed under the Income-tax Act, 1961.
|
||||
</p>
|
||||
}
|
||||
|
||||
<!-- ── FORM 15G / 15H ── -->
|
||||
@if (isForm15gh && form15gh) {
|
||||
<p class="cpd-para">
|
||||
Declaration under <strong>Section {{ form15gh.kind === '15H' ? '197A(1C)' : '197A(1) / 197A(1A)' }}</strong>
|
||||
of the Income-tax Act, 1961, for non-deduction of tax at source on certain incomes —
|
||||
{{
|
||||
form15gh.kind === '15H'
|
||||
? 'applicable to resident individuals aged 60 years or above'
|
||||
: 'applicable to resident individuals (other than companies / firms) below 60 years'
|
||||
}}.
|
||||
</p>
|
||||
|
||||
<table class="cpd-summary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1. Name of Assessee</td>
|
||||
<td colspan="3">
|
||||
<strong>{{ form15gh.record.customerName }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2. PAN of Assessee</td>
|
||||
<td class="mono">{{ form15gh.record.customerPan }}</td>
|
||||
<td>3. Status</td>
|
||||
<td>Individual</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4. Previous Year</td>
|
||||
<td>{{ form15gh.record.fy }}</td>
|
||||
<td>5. Age (years)</td>
|
||||
<td>{{ form15gh.age }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6. Address</td>
|
||||
<td colspan="3">{{ form15gh.record.customerAddress }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7. Branch</td>
|
||||
<td>{{ form15gh.record.branch }}</td>
|
||||
<td>8. Customer ID</td>
|
||||
<td class="mono">{{ form15gh.record.customerId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9. Deposit Principal</td>
|
||||
<td>₹{{ form15gh.record.depositPrincipal | number: '1.0-0' }}</td>
|
||||
<td>10. Estimated Interest ({{ form15gh.record.fy }})</td>
|
||||
<td>
|
||||
<strong>₹{{ form15gh.record.estimatedInterest | number: '1.0-0' }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11. Estimated Total Income</td>
|
||||
<td>₹{{ form15gh.record.estimatedIncome | number: '1.0-0' }}</td>
|
||||
<td>12. Previously Assessed?</td>
|
||||
<td>{{ form15gh.record.assessed ? 'Yes' : 'No' }}</td>
|
||||
</tr>
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">TAX PAYABLE ON ESTIMATED INCOME</td>
|
||||
<td><strong>NIL</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="cpd-para">
|
||||
I, <strong>{{ form15gh.record.customerName }}</strong
|
||||
>, do hereby declare that the particulars given above are true and correct, and that the tax on my estimated
|
||||
total income for the previous year {{ form15gh.record.fy }} will be NIL. I therefore request that no tax be
|
||||
deducted at source on the interest paid to me by the bank on the captioned deposits during the said previous
|
||||
year.
|
||||
</p>
|
||||
}
|
||||
|
||||
<!-- ── HYPOTHECATION CERTIFICATE ── -->
|
||||
@if (isHypothecation && hypothecation) {
|
||||
<div class="cpd-block">
|
||||
<div class="cpd-block-l">Issued To,</div>
|
||||
<div class="cpd-block-v">
|
||||
<div>
|
||||
<strong>{{ hypothecation.customerName }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Customer ID: <span class="mono">{{ hypothecation.customerId }}</span> · PAN:
|
||||
<span class="mono">{{ hypothecation.customerPan }}</span>
|
||||
</div>
|
||||
<div>{{ hypothecation.customerAddress }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cpd-para">
|
||||
This is to certify that the asset described below stands hypothecated to
|
||||
<strong>{{ bankName }}</strong> as security for the captioned loan facility extended to
|
||||
<strong>{{ hypothecation.customerName }}</strong
|
||||
>, and the bank holds the first and exclusive charge over the said asset until the loan is fully discharged.
|
||||
</p>
|
||||
|
||||
<table class="cpd-summary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Certificate No.</td>
|
||||
<td class="mono">
|
||||
<strong>{{ hypothecation.certificateNo }}</strong>
|
||||
</td>
|
||||
<td>Loan Account No.</td>
|
||||
<td class="mono">{{ hypothecation.accountNo }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Loan Type</td>
|
||||
<td>{{ hypothecation.loanType }}</td>
|
||||
<td>Branch</td>
|
||||
<td>{{ hypothecation.branch }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sanctioned Amount</td>
|
||||
<td>₹{{ hypothecation.sanctionedAmount | number: '1.0-0' }}</td>
|
||||
<td>Disbursed On</td>
|
||||
<td>{{ hypothecation.disbursementDate | date: 'd MMM y' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Asset Type</td>
|
||||
<td>{{ assetLabelText() }}</td>
|
||||
<td>Identifier</td>
|
||||
<td class="mono">{{ hypothecation.assetIdentifier }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Asset Description</td>
|
||||
<td colspan="3">{{ hypothecation.assetDescription }}</td>
|
||||
</tr>
|
||||
@if (hypothecation.insuredValue > 0) {
|
||||
<tr>
|
||||
<td>Insured Value</td>
|
||||
<td>₹{{ hypothecation.insuredValue | number: '1.0-0' }}</td>
|
||||
<td>Insurance Valid Till</td>
|
||||
<td>{{ hypothecation.insuranceValidTill | date: 'd MMM y' }}</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">OUTSTANDING PRINCIPAL</td>
|
||||
<td>
|
||||
<strong>₹{{ hypothecation.outstandingPrincipal | number: '1.2-2' }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="cpd-para">
|
||||
The lien on the said asset shall stand released only upon the bank issuing a No-Objection Certificate (NOC)
|
||||
after the entire outstanding under the captioned facility is paid in full, together with all interest, charges,
|
||||
and incidental costs.
|
||||
</p>
|
||||
}
|
||||
|
||||
<!-- ── NO-OBJECTION CERTIFICATE ── -->
|
||||
@if (isNoc && noc) {
|
||||
<div class="cpd-block">
|
||||
<div class="cpd-block-l">Issued To,</div>
|
||||
<div class="cpd-block-v">
|
||||
<div>
|
||||
<strong>{{ noc.customerName }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Customer ID: <span class="mono">{{ noc.customerId }}</span> · PAN:
|
||||
<span class="mono">{{ noc.customerPan }}</span>
|
||||
</div>
|
||||
<div>{{ noc.customerAddress }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="cpd-para">
|
||||
This is to certify that the captioned loan account in the name of
|
||||
<strong>{{ noc.customerName }}</strong>
|
||||
has been
|
||||
@if (noc.status === 'ISSUED') {
|
||||
duly closed and the bank
|
||||
} @else {
|
||||
closed and the bank
|
||||
}
|
||||
has <strong>NO OBJECTION</strong> to the release of the lien hereinafter recorded over the asset described
|
||||
below. All dues under the said facility, including principal, interest, and incidental charges, have been
|
||||
received by the bank as on the closure date.
|
||||
</p>
|
||||
|
||||
<table class="cpd-summary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>NOC No.</td>
|
||||
<td class="mono">
|
||||
<strong>{{ noc.nocNo }}</strong>
|
||||
</td>
|
||||
<td>Loan Account No.</td>
|
||||
<td class="mono">{{ noc.accountNo }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Loan Type</td>
|
||||
<td>{{ nocLoanTypeLabel() }}</td>
|
||||
<td>Branch</td>
|
||||
<td>{{ noc.branch }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sanctioned Amount</td>
|
||||
<td>₹{{ noc.sanctionedAmount | number: '1.0-0' }}</td>
|
||||
<td>Disbursed On</td>
|
||||
<td>{{ noc.disbursementDate | date: 'd MMM y' }}</td>
|
||||
</tr>
|
||||
@if (noc.closureDate) {
|
||||
<tr>
|
||||
<td>Account Closed On</td>
|
||||
<td>
|
||||
<strong>{{ noc.closureDate | date: 'd MMM y' }}</strong>
|
||||
</td>
|
||||
<td>Total Paid</td>
|
||||
<td>₹{{ noc.totalPaid | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (noc.assetType !== 'NONE') {
|
||||
<tr>
|
||||
<td>Asset Type</td>
|
||||
<td>{{ nocAssetLabel() }}</td>
|
||||
<td>Identifier</td>
|
||||
<td class="mono">{{ noc.assetIdentifier }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Asset Description</td>
|
||||
<td colspan="3">{{ noc.assetDescription }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (noc.forwardTo) {
|
||||
<tr>
|
||||
<td>Forward To</td>
|
||||
<td colspan="3">{{ noc.forwardTo }}</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">
|
||||
@if (noc.assetType === 'NONE') {
|
||||
ACCOUNT CLOSED — NO DUES OUTSTANDING
|
||||
} @else {
|
||||
LIEN RELEASED — NO OBJECTION TO TRANSFER
|
||||
}
|
||||
</td>
|
||||
<td><strong>NIL</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="cpd-para">
|
||||
@if (noc.assetType === 'VEHICLE') {
|
||||
The Regional Transport Authority is hereby requested to delete the hypothecation endorsement of the bank from
|
||||
the registration certificate of the captioned vehicle.
|
||||
} @else if (noc.assetType === 'PROPERTY') {
|
||||
The Sub-Registrar's office is hereby requested to delete the equitable / registered mortgage of the bank from
|
||||
the property records of the captioned property.
|
||||
} @else if (noc.assetType === 'GOLD') {
|
||||
The pledged gold ornaments described above may be released to the customer / authorised representative after
|
||||
due verification at the branch counter.
|
||||
} @else if (noc.assetType !== 'NONE') {
|
||||
The bank's lien over the captioned asset stands released with effect from the closure date, and the customer
|
||||
is free to deal with the said asset.
|
||||
} @else {
|
||||
This certificate is issued at the request of the customer for record purposes and confirms that no dues are
|
||||
outstanding under the captioned facility.
|
||||
}
|
||||
</p>
|
||||
|
||||
@if (noc.status === 'ISSUED' && noc.issuedOn) {
|
||||
<p class="cpd-para">
|
||||
<strong>Issued on:</strong> {{ noc.issuedOn | date: 'd MMM y' }}
|
||||
@if (noc.issuedBy) {
|
||||
<strong>· By:</strong> {{ noc.issuedBy }}
|
||||
}
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- Common sign-off -->
|
||||
<div class="cpd-signoff">
|
||||
<div class="cpd-sign-block">
|
||||
<div class="cpd-sig-line"></div>
|
||||
<div class="cpd-sig-l">Authorised Signatory</div>
|
||||
@if (isInterest && interest) {
|
||||
<div class="cpd-sig-sub">{{ interest.record.branch }}</div>
|
||||
}
|
||||
@if (isShare && share) {
|
||||
<div class="cpd-sig-sub">{{ share.branch }}</div>
|
||||
}
|
||||
@if (isBalance && balance) {
|
||||
<div class="cpd-sig-sub">{{ balance.record.branch }}</div>
|
||||
}
|
||||
@if (isForm60 && form60) {
|
||||
<div class="cpd-sig-sub">{{ form60.branch }}</div>
|
||||
}
|
||||
@if (isForm15gh && form15gh) {
|
||||
<div class="cpd-sig-sub">{{ form15gh.record.branch }}</div>
|
||||
}
|
||||
@if (isHypothecation && hypothecation) {
|
||||
<div class="cpd-sig-sub">{{ hypothecation.branch }}</div>
|
||||
}
|
||||
@if (isNoc && noc) {
|
||||
<div class="cpd-sig-sub">{{ noc.branch }}</div>
|
||||
}
|
||||
</div>
|
||||
<div class="cpd-seal">
|
||||
<div class="cpd-seal-ring">
|
||||
<div class="cpd-seal-inner">
|
||||
<div class="cpd-seal-bank">NEXORA</div>
|
||||
<div class="cpd-seal-line"></div>
|
||||
<div class="cpd-seal-sub">CO-OPERATIVE BANK</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="cpd-footer">
|
||||
<span>
|
||||
Issued by {{ bankName }} on {{ issuedOn | date: 'd MMM y' }}. Computer generated — does not require physical
|
||||
signature for record purposes.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+607
@@ -0,0 +1,607 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
:host {
|
||||
--cpd-blue: #1d4ed8;
|
||||
--cpd-blue-d: #1e40af;
|
||||
--cpd-blue-l: #eff6ff;
|
||||
--cpd-blue-tint: #dbeafe;
|
||||
--cpd-amber: #d97706;
|
||||
--cpd-amber-l: #fef3c7;
|
||||
--cpd-amber-tint: #fde68a;
|
||||
--cpd-red: #b91c1c;
|
||||
--cpd-red-l: #fee2e2;
|
||||
--cpd-red-tint: #fecaca;
|
||||
--cpd-green: #15803d;
|
||||
--cpd-green-l: #dcfce7;
|
||||
--cpd-purple: #6d28d9;
|
||||
--cpd-g50: #f8fafc;
|
||||
--cpd-g100: #f1f5f9;
|
||||
--cpd-g200: #e2e8f0;
|
||||
--cpd-g300: #cbd5e1;
|
||||
--cpd-g500: #64748b;
|
||||
--cpd-g700: #334155;
|
||||
--cpd-g900: #0f172a;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cpd-root {
|
||||
background: var(--cpd-g100);
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ───────── Toolbar (hidden on print) ───────── */
|
||||
.cpd-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid var(--cpd-g200);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cpd-toolbar-l {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cpd-toolbar-ic {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
background: var(--cpd-blue-d);
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.cpd-toolbar-t {
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
color: var(--cpd-g900);
|
||||
}
|
||||
|
||||
.cpd-toolbar-s {
|
||||
font-size: 11.5px;
|
||||
color: var(--cpd-g500);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
.cpd-toolbar-r {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cpd-tb-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 12px;
|
||||
border: 1px solid var(--cpd-g200);
|
||||
background: #fff;
|
||||
color: var(--cpd-g700);
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
fa-icon {
|
||||
font-size: 12px;
|
||||
color: var(--cpd-blue);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--cpd-blue-l);
|
||||
border-color: var(--cpd-blue-tint);
|
||||
color: var(--cpd-blue-d);
|
||||
}
|
||||
|
||||
&.cpd-tb-x {
|
||||
width: 34px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background: var(--cpd-red-l);
|
||||
color: var(--cpd-red);
|
||||
border-color: var(--cpd-red-tint);
|
||||
|
||||
fa-icon {
|
||||
color: var(--cpd-red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────── Certificate page (A4 feel) ───────── */
|
||||
.cpd-page {
|
||||
background: #fff;
|
||||
border: 1px solid var(--cpd-g200);
|
||||
padding: 38px 48px 24px;
|
||||
margin: 18px auto;
|
||||
width: min(720px, 94%);
|
||||
border-top: 6px solid var(--cpd-blue-d);
|
||||
font-family: Georgia, 'Times New Roman', Times, serif;
|
||||
color: var(--cpd-g900);
|
||||
line-height: 1.6;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
|
||||
&.cpd-page--share,
|
||||
&.cpd-page--amber {
|
||||
border-top-color: var(--cpd-amber);
|
||||
}
|
||||
|
||||
&.cpd-page--teal {
|
||||
border-top-color: #0d9488;
|
||||
}
|
||||
|
||||
&.cpd-page--red {
|
||||
border-top-color: var(--cpd-red);
|
||||
}
|
||||
|
||||
&.cpd-page--green {
|
||||
border-top-color: var(--cpd-green);
|
||||
}
|
||||
|
||||
/* Subtle decorative border inside the page edge to give the cert a "framed" feel. */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 18px;
|
||||
border: 1px solid var(--cpd-g200);
|
||||
pointer-events: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.cpd-page--share::before,
|
||||
&.cpd-page--amber::before {
|
||||
border-color: var(--cpd-amber-tint);
|
||||
}
|
||||
|
||||
&.cpd-page--teal::before {
|
||||
border-color: #99f6e4;
|
||||
}
|
||||
|
||||
&.cpd-page--red::before {
|
||||
border-color: var(--cpd-red-tint);
|
||||
}
|
||||
|
||||
&.cpd-page--green::before {
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────── Letterhead ───────── */
|
||||
.cpd-letterhead {
|
||||
text-align: center;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 14px;
|
||||
border-bottom: 1px solid var(--cpd-g200);
|
||||
}
|
||||
|
||||
.cpd-bank-name {
|
||||
font-size: 19px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--cpd-blue-d);
|
||||
|
||||
.cpd-page--share &,
|
||||
.cpd-page--amber & {
|
||||
color: var(--cpd-amber);
|
||||
}
|
||||
|
||||
.cpd-page--teal & {
|
||||
color: #0d9488;
|
||||
}
|
||||
|
||||
.cpd-page--red & {
|
||||
color: var(--cpd-red);
|
||||
}
|
||||
|
||||
.cpd-page--green & {
|
||||
color: var(--cpd-green);
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-bank-sub {
|
||||
font-size: 11.5px;
|
||||
color: var(--cpd-g500);
|
||||
margin-top: 2px;
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
/* ───────── Title strip ───────── */
|
||||
.cpd-title-strip {
|
||||
text-align: center;
|
||||
margin: 6px 0 14px;
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.cpd-title-l {
|
||||
display: inline-block;
|
||||
padding: 5px 18px;
|
||||
border-radius: 999px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
background: var(--cpd-blue-l);
|
||||
color: var(--cpd-blue-d);
|
||||
border: 1px solid var(--cpd-blue-tint);
|
||||
|
||||
.cpd-page--share &,
|
||||
.cpd-page--amber & {
|
||||
background: var(--cpd-amber-l);
|
||||
color: var(--cpd-amber);
|
||||
border-color: var(--cpd-amber-tint);
|
||||
}
|
||||
|
||||
.cpd-page--teal & {
|
||||
background: #ccfbf1;
|
||||
color: #0d9488;
|
||||
border-color: #99f6e4;
|
||||
}
|
||||
|
||||
.cpd-page--red & {
|
||||
background: var(--cpd-red-l);
|
||||
color: var(--cpd-red);
|
||||
border-color: var(--cpd-red-tint);
|
||||
}
|
||||
|
||||
.cpd-page--green & {
|
||||
background: var(--cpd-green-l);
|
||||
color: var(--cpd-green);
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-stage-pill--teal {
|
||||
background: #ccfbf1;
|
||||
color: #0d9488;
|
||||
}
|
||||
|
||||
.cpd-stage-pill--red {
|
||||
background: var(--cpd-red-l);
|
||||
color: var(--cpd-red);
|
||||
}
|
||||
|
||||
.cpd-stage-pill--green {
|
||||
background: var(--cpd-green-l);
|
||||
color: var(--cpd-green);
|
||||
}
|
||||
|
||||
/* ───────── Meta row ───────── */
|
||||
.cpd-meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
font-size: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 1px dashed var(--cpd-g200);
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-meta-l {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--cpd-g500);
|
||||
}
|
||||
|
||||
.cpd-meta-v {
|
||||
font-size: 12.5px;
|
||||
color: var(--cpd-g900);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cpd-stage-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 10.5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
background: var(--cpd-blue-l);
|
||||
color: var(--cpd-blue-d);
|
||||
|
||||
&--amber {
|
||||
background: var(--cpd-amber-l);
|
||||
color: var(--cpd-amber);
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────── Addressee + body ───────── */
|
||||
.cpd-block {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.cpd-block-l {
|
||||
font-weight: 700;
|
||||
color: var(--cpd-g700);
|
||||
width: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cpd-block-v {
|
||||
flex: 1;
|
||||
color: var(--cpd-g900);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cpd-para {
|
||||
margin: 12px 0;
|
||||
font-size: 13px;
|
||||
text-align: justify;
|
||||
color: var(--cpd-g900);
|
||||
|
||||
&--centered {
|
||||
text-align: center;
|
||||
font-size: 13.5px;
|
||||
padding: 6px 12px;
|
||||
background: var(--cpd-amber-l);
|
||||
border-left: 4px solid var(--cpd-amber);
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-amount {
|
||||
color: var(--cpd-blue-d);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ───────── Summary table ───────── */
|
||||
.cpd-summary {
|
||||
width: 100%;
|
||||
margin: 14px 0;
|
||||
border-collapse: collapse;
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
font-size: 12px;
|
||||
|
||||
td {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--cpd-g200);
|
||||
vertical-align: top;
|
||||
color: var(--cpd-g900);
|
||||
}
|
||||
|
||||
td:nth-child(odd) {
|
||||
background: var(--cpd-g50);
|
||||
color: var(--cpd-g500);
|
||||
font-weight: 700;
|
||||
font-size: 10.5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
width: 22%;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
.cpd-summary-total td {
|
||||
background: var(--cpd-blue-l);
|
||||
color: var(--cpd-blue-d);
|
||||
font-weight: 800;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-page--share & .cpd-summary-total td {
|
||||
background: var(--cpd-amber-l);
|
||||
color: var(--cpd-amber);
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────── Sign-off + bank seal ───────── */
|
||||
.cpd-signoff {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 24px;
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.cpd-sign-block {
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.cpd-sig-line {
|
||||
width: 220px;
|
||||
border-top: 1px solid var(--cpd-g500);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.cpd-sig-l {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: var(--cpd-g900);
|
||||
}
|
||||
|
||||
.cpd-sig-sub {
|
||||
font-size: 11px;
|
||||
color: var(--cpd-g500);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Decorative round "common seal" for the share certificate (also looks good on
|
||||
the interest certificate). Pure CSS, prints fine. */
|
||||
.cpd-seal-ring {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--cpd-amber);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
|
||||
.cpd-page--interest & {
|
||||
border-color: var(--cpd-blue);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 5px;
|
||||
border-radius: 50%;
|
||||
border: 1px dashed var(--cpd-amber);
|
||||
|
||||
.cpd-page--interest & {
|
||||
border-color: var(--cpd-blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-seal-inner {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cpd-seal-bank {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.18em;
|
||||
color: var(--cpd-amber);
|
||||
|
||||
.cpd-page--interest & {
|
||||
color: var(--cpd-blue);
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-seal-line {
|
||||
width: 36px;
|
||||
border-top: 1px solid currentcolor;
|
||||
margin: 3px auto;
|
||||
color: var(--cpd-amber);
|
||||
|
||||
.cpd-page--interest & {
|
||||
color: var(--cpd-blue);
|
||||
}
|
||||
}
|
||||
|
||||
.cpd-seal-sub {
|
||||
font-size: 7.5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--cpd-g500);
|
||||
}
|
||||
|
||||
/* ───────── Footer ───────── */
|
||||
.cpd-footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed var(--cpd-g200);
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
font-size: 10.5px;
|
||||
color: var(--cpd-g500);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* ───────── Print — only the page, no toolbar / dialog chrome ───────── */
|
||||
@media print {
|
||||
:host {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cpd-root {
|
||||
background: #fff;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.cpd-page {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 24px 32px;
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
border-top: 4px solid var(--cpd-blue-d);
|
||||
|
||||
&.cpd-page--share {
|
||||
border-top-color: var(--cpd-amber);
|
||||
}
|
||||
}
|
||||
}
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* 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, 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 { 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
|
||||
} 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 {
|
||||
private dialogRef = inject(MatDialogRef<CertificatePreviewDialogComponent>);
|
||||
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';
|
||||
|
||||
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 60 Declaration';
|
||||
case 'form15gh':
|
||||
return this.form15gh?.kind === '15H' ? 'Form 15H Declaration' : 'Form 15G 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 '';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user