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
@@ -544,82 +544,315 @@ export function downloadLoanInterestPdf(record: LoanInterestRecord, fy: FyIntere
// ───────────────────── Share Certificate ─────────────────────
export function buildShareCertificatePdf(r: ShareCertificateRecord): jsPDF {
const c = newCtx(ACCENT_AMBER);
const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
const pageW = doc.internal.pageSize.getWidth();
const pageH = doc.internal.pageSize.getHeight();
const margin = 20;
const contentW = pageW - 40;
const gold: [number, number, number] = [
197,
160,
89
]; // #c5a059
const darkBlue: [number, number, number] = [
11,
60,
93
]; // #0b3c5d
const textDark: [number, number, number] = [
44,
37,
32
]; // #2c2520
const textMuted: [number, number, number] = [
123,
111,
99
]; // #7b6f63
const lightBg: [number, number, number] = [
253,
252,
247
]; // #fdfcf7
const containerBg: [number, number, number] = [
250,
248,
245
]; // #faf8f5
// 1. Draw Cream Background (page body)
doc.setFillColor(containerBg[0], containerBg[1], containerBg[2]);
doc.rect(0, 0, pageW, pageH, 'F');
// 2. Draw Textured cream background inside the outer border
doc.setFillColor(lightBg[0], lightBg[1], lightBg[2]);
doc.rect(12, 12, pageW - 24, pageH - 24, 'F');
// 3. Draw Gold Double Borders (Outer and Inner)
doc.setDrawColor(gold[0], gold[1], gold[2]);
// Outer double line
doc.setLineWidth(1.2);
doc.rect(12, 12, pageW - 24, pageH - 24, 'D');
doc.setLineWidth(0.3);
doc.rect(13.8, 13.8, pageW - 27.6, pageH - 27.6, 'D');
// Thin interior border line
doc.setDrawColor(220, 209, 189); // light brown/grey
doc.setLineWidth(0.2);
doc.rect(17, 17, pageW - 34, pageH - 34, 'D');
// 4. Certificate Header
let y = 28;
doc.setFont('times', 'bold');
doc.setFontSize(22);
doc.setTextColor(darkBlue[0], darkBlue[1], darkBlue[2]);
doc.text('NEXORA CO-OPERATIVE BANK LTD.', pageW / 2, y, { align: 'center' });
y += 5;
doc.setFont('times', 'italic');
doc.setFontSize(10.5);
doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
doc.text('Mumbai, Maharashtra, India', pageW / 2, y, { align: 'center' });
y += 4;
doc.setDrawColor(242, 237, 213); // light gold separator
doc.setLineWidth(1.5);
doc.line(22, y, pageW - 22, y);
y += 9;
doc.setFont('times', 'bold');
doc.setFontSize(24);
doc.setTextColor(gold[0], gold[1], gold[2]);
doc.text('SHARE CERTIFICATE', pageW / 2, y, { align: 'center' });
y += 5.5;
doc.setFont('times', 'bold');
doc.setFontSize(7.5);
doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
doc.text('ISSUED UNDER THE COMMON SEAL OF THE BANK', pageW / 2, y, { align: 'center' });
y += 4;
doc.setDrawColor(242, 237, 213); // separator
doc.setLineWidth(0.3);
doc.line(22, y, pageW - 22, y);
// 5. Meta Row (Certificate No, Folio No, Date of Issue)
y += 7;
// Column 1: Certificate No
doc.setFont('helvetica', 'bold');
doc.setFontSize(7.5);
doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
doc.text('CERTIFICATE NO.', 25, y);
doc.setFont('times', 'bold');
doc.setFontSize(10);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
doc.text(r.certificateNo, 25, y + 4.5);
// Column 2: Folio No (Centered)
doc.setFont('helvetica', 'bold');
doc.setFontSize(7.5);
doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
doc.text('FOLIO NO.', pageW / 2, y, { align: 'center' });
doc.setFont('times', 'bold');
doc.setFontSize(10);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
doc.text(r.folioNo, pageW / 2, y + 4.5, { align: 'center' });
// Column 3: Date of Issue (Right Aligned)
doc.setFont('helvetica', 'bold');
doc.setFontSize(7.5);
doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
doc.text('DATE OF ISSUE', pageW - 25, y, { align: 'right' });
doc.setFont('times', 'bold');
doc.setFontSize(10);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
doc.text(formatLongDate(r.issueDate), pageW - 25, y + 4.5, { align: 'right' });
y += 8;
doc.setDrawColor(242, 237, 213);
doc.setLineWidth(0.3);
doc.line(22, y, pageW - 22, y);
// 6. Centered Body Section
y += 9;
const classLabel = r.shareClass === 'PREFERENCE' ? 'Preference' : 'Ordinary';
let y = paintLetterhead(c, 'Share Certificate', 'CERTIFICATE REFERENCE', r.certificateNo, 'Folio No.', r.folioNo);
doc.setFont('times', 'normal');
doc.setFontSize(11);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
y = paintAddressee(c, y, 'Issued To,', r.memberName, r.memberId, r.memberPan, r.memberAddress);
// Centered, dramatic body paragraph (mirrors the highlighted "centered" style in the preview)
if (y > c.pageH - c.margin - 80) {
c.doc.addPage();
setFill(c, c.accent);
c.doc.rect(0, 0, c.pageW, 4, 'F');
y = c.margin;
const bodyText = `This is to certify that ${r.memberName} is the registered holder of ${r.shareCount.toLocaleString('en-IN')} (${classLabel}) shares of Rs. ${r.faceValue.toLocaleString('en-IN')} each, fully paid up, in Nexora Co-operative Bank Ltd., subject to the memorandum, articles of association, and bye-laws of the bank.`;
const bodyLines = doc.splitTextToSize(bodyText, pageW - 50);
for (const line of bodyLines) {
doc.text(line, pageW / 2, y, { align: 'center' });
y += 5.5;
}
const ctaText = `This is to certify that ${r.memberName} is the registered holder of ${r.shareCount.toLocaleString('en-IN')} (${classLabel}) shares of Rs. ${r.faceValue.toLocaleString('en-IN')} each, fully paid up, in ${BANK_NAME}, subject to the bye-laws of the bank.`;
const ctaLines = c.doc.splitTextToSize(ctaText, c.contentW - 10);
const ctaH = ctaLines.length * 5 + 6;
setFill(c, c.accentTint);
c.doc.rect(c.margin, y, c.contentW, ctaH, 'F');
setFill(c, c.accent);
c.doc.rect(c.margin, y, 1.8, ctaH, 'F');
c.doc.setFont('times', 'bold');
c.doc.setFontSize(11);
setText(c, C_TEXT);
c.doc.text(ctaLines, c.margin + 6, y + 5, { align: 'justify', maxWidth: c.contentW - 10 });
y += ctaH + 6;
y = paintSummary(
c,
y,
[
[
'CERTIFICATE NO.',
r.certificateNo,
'FOLIO NO.',
r.folioNo
],
[
'BRANCH',
r.branch,
'MEMBER SINCE',
formatLongDate(r.joiningDate)
],
[
'SHARE CLASS',
classLabel,
'DATE OF ISSUE',
formatLongDate(r.issueDate)
],
[
'SHARE NUMBERS',
`${r.shareNoFrom.toLocaleString('en-IN')} – ${r.shareNoTo.toLocaleString('en-IN')}`,
'DIVIDEND PAID',
`Rs. ${r.dividendYtd.toLocaleString('en-IN')}`
]
],
{
label: 'TOTAL FACE VALUE',
value: `Rs. ${(r.shareCount * r.faceValue).toLocaleString('en-IN')}`
// 7. ISSUED TO Box
y += 3;
const addrLines = doc.splitTextToSize(r.memberAddress || '', pageW - 60);
const boxH = 26 + addrLines.length * 4;
doc.setFillColor(253, 252, 247);
doc.setDrawColor(230, 225, 210);
doc.rect(25, y, pageW - 50, boxH, 'FD');
let boxY = y + 6;
doc.setFont('helvetica', 'bold');
doc.setFontSize(8);
doc.setTextColor(gold[0], gold[1], gold[2]);
doc.text('ISSUED TO:', 30, boxY);
boxY += 5.5;
doc.setFont('times', 'bold');
doc.setFontSize(13);
doc.setTextColor(darkBlue[0], darkBlue[1], darkBlue[2]);
doc.text(r.memberName, 30, boxY);
boxY += 5.5;
doc.setFont('helvetica', 'normal');
doc.setFontSize(9);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
let idLine = `Member ID: ${r.memberId}`;
if (r.memberPan && r.memberPan !== 'Not Available' && r.memberPan !== '—') {
idLine += ` · PAN: ${r.memberPan}`;
}
doc.text(idLine, 30, boxY);
boxY += 5;
doc.setFontSize(8.5);
doc.setTextColor(textMuted[0], textMuted[1], textMuted[2]);
doc.text(addrLines, 30, boxY);
y += boxH + 8;
// 8. Details Grid
const tableW = pageW - 50;
const tableColW = tableW / 4;
const tableRowH = 8.5;
const drawTableCell = (
x: number,
tblY: number,
w: number,
h: number,
text: string,
isLabel: boolean,
align: 'left' | 'right' = 'left'
) => {
if (isLabel) {
doc.setFillColor(253, 252, 247);
doc.rect(x, tblY, w, h, 'F');
doc.setFont('helvetica', 'bold');
doc.setFontSize(8);
doc.setTextColor(gold[0], gold[1], gold[2]);
} else {
doc.setFont('times', 'bold');
doc.setFontSize(9.5);
doc.setTextColor(darkBlue[0], darkBlue[1], darkBlue[2]);
}
);
doc.setDrawColor(220, 209, 189);
doc.setLineWidth(0.25);
doc.rect(x, tblY, w, h, 'D');
doc.text(text, align === 'right' ? x + w - 3 : x + 3, tblY + h / 2 + 1.2, { align });
};
y = paintPara(
c,
// Row 1
drawTableCell(25, y, tableColW, tableRowH, 'Branch', true);
drawTableCell(25 + tableColW, y, tableColW, tableRowH, r.branch || 'Mumbai Main Branch', false);
drawTableCell(25 + tableColW * 2, y, tableColW, tableRowH, 'Member Since', true);
drawTableCell(25 + tableColW * 3, y, tableColW, tableRowH, formatLongDate(r.joiningDate), false);
y += tableRowH;
// Row 2
drawTableCell(25, y, tableColW, tableRowH, 'Share Class', true);
drawTableCell(25 + tableColW, y, tableColW, tableRowH, classLabel, false);
drawTableCell(25 + tableColW * 2, y, tableColW, tableRowH, 'Date of Issue', true);
drawTableCell(25 + tableColW * 3, y, tableColW, tableRowH, formatLongDate(r.issueDate), false);
y += tableRowH;
// Row 3
drawTableCell(25, y, tableColW, tableRowH, 'Share Numbers', true);
drawTableCell(
25 + tableColW,
y,
`Given under the common seal of the bank this ${new Date().toLocaleDateString('en-IN', {
day: 'numeric',
month: 'long',
year: 'numeric'
})}.`
tableColW,
tableRowH,
`${r.shareNoFrom.toLocaleString('en-IN')} – ${r.shareNoTo.toLocaleString('en-IN')}`,
false
);
drawTableCell(25 + tableColW * 2, y, tableColW, tableRowH, 'Dividend Paid', true);
drawTableCell(25 + tableColW * 3, y, tableColW, tableRowH, `Rs. ${r.dividendYtd.toLocaleString('en-IN')}`, false);
y += tableRowH;
y = paintSignoff(c, y + 6, r.branch);
paintFooter(c, y + 4);
return c.doc;
// Row 4 (Highlight)
doc.setFillColor(darkBlue[0], darkBlue[1], darkBlue[2]);
doc.rect(25, y, tableColW * 2, tableRowH, 'F');
doc.rect(25 + tableColW * 2, y, tableColW * 2, tableRowH, 'F');
doc.setFont('helvetica', 'bold');
doc.setFontSize(8.5);
doc.setTextColor(255, 255, 255);
doc.text('TOTAL FACE VALUE', 28, y + tableRowH / 2 + 1.2);
doc.setFont('times', 'bold');
doc.setFontSize(11);
doc.text(`Rs. ${(r.shareCount * r.faceValue).toLocaleString('en-IN')}`, pageW - 28, y + tableRowH / 2 + 1.2, {
align: 'right'
});
y += tableRowH + 8;
// 9. Footer Text (Given under common seal...)
doc.setFont('times', 'italic');
doc.setFontSize(10.5);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
const sealDay = new Date().getDate();
const sealMonthYear = new Date().toLocaleDateString('en-IN', { month: 'long', year: 'numeric' });
const sealPhrase = `Given under the common seal of the bank this ${sealDay} day of ${sealMonthYear}.`;
doc.text(sealPhrase, pageW / 2, y, { align: 'center' });
y += 8;
// 10. Seal Circle (left) and Signatory block (right)
const sealX = 50;
const sealY = y + 14;
doc.setDrawColor(gold[0], gold[1], gold[2]); // Gold
doc.setLineWidth(0.6);
doc.circle(sealX, sealY, 14, 'D');
doc.setLineWidth(0.3);
doc.setLineDashPattern(
[
1,
1
],
0
);
doc.circle(sealX, sealY, 11, 'D');
doc.setLineDashPattern([], 0);
doc.setFont('helvetica', 'bold');
doc.setFontSize(7.5);
doc.setTextColor(gold[0], gold[1], gold[2]);
doc.text('COMMON', sealX, sealY - 1, { align: 'center' });
doc.text('SEAL', sealX, sealY + 3.5, { align: 'center' });
// Signatory Line
doc.setDrawColor(textMuted[0], textMuted[1], textMuted[2]);
doc.setLineWidth(0.3);
doc.line(pageW - 75, y + 20, pageW - 25, y + 20);
doc.setFont('helvetica', 'bold');
doc.setFontSize(9.5);
doc.setTextColor(textDark[0], textDark[1], textDark[2]);
doc.text('Authorised Signatory', pageW - 50, y + 25, { align: 'center' });
return doc;
}
export function downloadShareCertificatePdf(r: ShareCertificateRecord, dsc?: BankUserDsc): void {