share certificate and share product maker checker

This commit is contained in:
2026-07-27 13:57:49 +05:30
parent d0536dbb11
commit 0c99dc3686
47 changed files with 2790 additions and 245 deletions
@@ -0,0 +1,139 @@
<!--
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>Share Certificate View</span>
<span class="flex-auto"></span>
</h2>
<div mat-dialog-content class="dialog-content-container">
<!-- Certificate Page Frame -->
<div class="certificate-container" id="share-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">verified_user</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">SHARE 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">Folio No.</span>
<span class="meta-value">{{ folioNo }}</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">{{ sharesAccountData.clientName }}</strong> is the
registered holder of
<strong class="highlight-text">{{ sharesAccountData.summary?.totalApprovedShares | formatNumber }}</strong>
({{ sharesAccountData.productName || 'Ordinary' }}) shares of
<strong class="highlight-text">{{
sharesAccountData.currentMarketPrice | currency: sharesAccountData.currency.code : 'symbol-narrow'
}}</strong>
each, fully paid up, in <strong class="highlight-text">Nexora Co-operative Bank Ltd.</strong>, subject to
the memorandum, articles of association, and bye-laws of the bank.
</p>
<div class="issued-to-details">
<div class="issued-to-title">ISSUED TO:</div>
<div class="client-name-details">{{ sharesAccountData.clientName }}</div>
<div class="client-ids">
<span *ngIf="memberId"><strong>Member ID:</strong> {{ memberId }}</span>
<span *ngIf="panNumber && panNumber !== 'Not Available'">
&middot; <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">Branch</td>
<td class="val-col">{{ clientData?.officeName || 'Mumbai Main Branch' }}</td>
<td class="lbl-col">Member Since</td>
<td class="val-col">
{{
(clientData?.activationDate
? (clientData.activationDate | dateFormat)
: (clientData?.timeline?.submittedOnDate | dateFormat)) || 'N/A'
}}
</td>
</tr>
<tr>
<td class="lbl-col">Share Class</td>
<td class="val-col">{{ sharesAccountData.productName || 'Ordinary' }}</td>
<td class="lbl-col">Date of Issue</td>
<td class="val-col">{{ issueDate | dateFormat }}</td>
</tr>
<tr>
<td class="lbl-col">Share Numbers</td>
<td class="val-col">{{ shareNumbersRange }}</td>
<td class="lbl-col">Dividend Paid (Cumulative)</td>
<td class="val-col">
{{ cumulativeDividend | currency: sharesAccountData.currency.code : 'symbol-narrow' }}
</td>
</tr>
<tr class="highlight-row">
<td colspan="2" class="lbl-col-highlight">TOTAL FACE VALUE</td>
<td colspan="2" class="val-col-highlight">
{{ totalFaceValue | currency: sharesAccountData.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 {{ todayDate | date: 'd' }} day of
{{ todayDate | 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,351 @@
/**
* 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 */
}
}
/* 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 {
/* Hide non-overlay contents during print */
body > :not(.cdk-overlay-container) {
display: none !important;
}
/* Make certificate fill page */
#share-certificate-print {
position: absolute;
left: 0;
top: 0;
width: 100% !important;
height: 100% !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;
}
.mat-mdc-dialog-container {
padding: 0 !important;
}
}
@@ -0,0 +1,145 @@
/**
* 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 { FormatNumberPipe } from '../../../pipes/format-number.pipe';
import { CurrencyPipe } from '@angular/common';
import { MatButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
@Component({
selector: 'mifosx-share-certificate-dialog',
templateUrl: './share-certificate-dialog.component.html',
styleUrls: ['./share-certificate-dialog.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatDialogTitle,
MatDialogContent,
MatDialogActions,
MatDialogClose,
DateFormatPipe,
FormatNumberPipe,
CurrencyPipe,
MatButton,
MatIcon
]
})
export class ShareCertificateDialogComponent implements OnInit {
dialogRef = inject<MatDialogRef<ShareCertificateDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
clientsService = inject(ClientsService);
sharesAccountData: any;
clientId: string;
clientData: any;
clientAddress: string = '';
panNumber: string = 'Not Available';
memberId: string = '';
certificateNo: string = '';
folioNo: string = '';
shareNumbersRange: string = '';
cumulativeDividend: number = 0;
totalFaceValue: number = 0;
issueDate: Date = new Date();
todayDate: Date = new Date();
ngOnInit() {
this.sharesAccountData = this.data.sharesAccountData;
this.clientId = String(this.data.clientId);
// Initialize shares certificate values
this.issueDate = this.sharesAccountData.timeline?.activatedDate
? new Date(this.sharesAccountData.timeline.activatedDate)
: new Date();
this.certificateNo = `SC-2026-${this.sharesAccountData.accountNo || String(this.sharesAccountData.id).padStart(6, '0')}`;
// Calculate total face value
const approvedShares = this.sharesAccountData.summary?.totalApprovedShares || 0;
const price = this.sharesAccountData.currentMarketPrice || 0;
this.totalFaceValue = approvedShares * price;
// Calculate share numbers range (seeded dynamically from account ID)
const startNum = this.sharesAccountData.id * 5000 + 201;
const endNum = startNum + approvedShares - 1;
this.shareNumbersRange = approvedShares > 0 ? `${startNum.toLocaleString()} – ${endNum.toLocaleString()}` : 'None';
// Calculate cumulative dividends
if (this.sharesAccountData.dividends) {
this.cumulativeDividend = this.sharesAccountData.dividends.reduce(
(sum: number, d: any) => sum + (d.amount || 0),
0
);
}
// Fetch client details
this.clientsService.getClientData(this.clientId).subscribe({
next: (client: any) => {
this.clientData = client;
this.memberId = client.accountNo || '';
this.folioNo = `F-${client.accountNo || String(client.id).padStart(5, '0')}`;
},
error: () => {}
});
// Fetch address details
this.clientsService.getClientAddressData(this.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: () => {}
});
// Fetch identifiers (for PAN number)
this.clientsService.getClientIdentifiers(this.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: () => {}
});
}
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(', ');
}
printCertificate() {
window.print();
}
}