Client-stepper: pre-fill existing KYC/address proofs in edit, fix identifier saves
Edit-mode now shows the previously selected KYC document type + uploaded file thumbnail
(loads from /clients/{id}/identifiers + /client_identifiers/{id}/documents). Same for
address proof on the India address step. Closes the gap where editing a client showed
an empty file picker even when proof had been uploaded on create.
Identifier-save hardened across all three callsites (create-client, edit-client,
identities-tab.createIdentifierWithDocs) - the KYC chip picker proofId is a CODE
string ('AADHAAR'), but backend ClientIdentifierRequest.documentTypeId is Long, which
Spring rejects with HttpMessageNotReadableException -> 400. resolveDocumentTypeId
helper looks up the code against /kyc/proof-types?type=id and substitutes the numeric
CodeValue id before posting. Skips silently for constitution-specific non-individual
docs that have no CodeValue mapping.
KYC payload normalises panNumber / enterprisePan / gstinNumber / cinNumber via
toUpperCase + whitespace strip so user-typed lowercase or trailing spaces don't trip
the backend regex (validation.msg.CLIENTKYC.panNumber.does.not.match.regexp).
State name match in client-address save now normalises both sides (lowercase, collapse
underscores + spaces) so legacy MADHYA_PRADESH resolves to 'Madhya Pradesh' returned
by api.postalpincode.in. Backed by tenant migration 0297.
Identities-tab resolver flattened - was returning identifiers before the per-row
forkJoin(getClientIdentificationDocuments) completed, so the view rendered empty
documents arrays. Now uses switchMap + forkJoin so the resolver awaits completion.
Also bundles concurrent work-in-progress on letter-mgmt, organization, users,
bank-calendar, shell, nominee/notes/identifier dialogs, and theming.
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
325228c514
commit
83ea57ccf0
@@ -15,6 +15,8 @@
|
||||
|
||||
import { jsPDF } from 'jspdf';
|
||||
|
||||
import { BankUserDsc } from 'app/users/dsc/dsc.model';
|
||||
|
||||
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';
|
||||
@@ -394,6 +396,73 @@ function newCtx(accent: [number, number, number]): PdfCtx {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Paint a "Digitally Signed" stamp + auditor footer line on the final page of
|
||||
* the given doc. Stamp = green-bordered rounded box on the bottom-right with
|
||||
* a ✓ icon, the signer's common name, masked DSC serial, and signing time.
|
||||
* Audit line = thin teal strip under the existing footer with the same info
|
||||
* in monospace, so the certificate also reads cleanly when printed in B/W.
|
||||
*
|
||||
* Call this just before `doc.save(...)` — it doesn't add a new page.
|
||||
*/
|
||||
export function paintDscSignatureStamp(doc: jsPDF, dsc: BankUserDsc): void {
|
||||
const pageW = doc.internal.pageSize.getWidth();
|
||||
const pageH = doc.internal.pageSize.getHeight();
|
||||
const margin = 20;
|
||||
const signedAt = new Date();
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
const ts = `${signedAt.getFullYear()}-${pad(signedAt.getMonth() + 1)}-${pad(signedAt.getDate())} ${pad(signedAt.getHours())}:${pad(signedAt.getMinutes())}:${pad(signedAt.getSeconds())}`;
|
||||
|
||||
// ── Bottom-right rounded green stamp ──
|
||||
const sW = 68;
|
||||
const sH = 22;
|
||||
const sX = pageW - margin - sW;
|
||||
const sY = pageH - margin - sH - 4;
|
||||
|
||||
// Tint background + green border (slight rounding gives it a "stamped" feel)
|
||||
doc.setFillColor(220, 252, 231); // green-100
|
||||
doc.setDrawColor(21, 128, 61); // green-700
|
||||
doc.setLineWidth(0.6);
|
||||
doc.roundedRect(sX, sY, sW, sH, 2, 2, 'FD');
|
||||
|
||||
// ✓ icon + heading
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setFontSize(9);
|
||||
doc.setTextColor(21, 128, 61);
|
||||
doc.text('✓ DIGITALLY SIGNED', sX + 3, sY + 5);
|
||||
|
||||
// Signer name (Common Name)
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setFontSize(8.5);
|
||||
doc.setTextColor(15, 23, 42);
|
||||
doc.text(dsc.commonName, sX + 3, sY + 10);
|
||||
|
||||
// DSC serial (masked) + type
|
||||
doc.setFont('courier', 'normal');
|
||||
doc.setFontSize(7);
|
||||
doc.setTextColor(71, 85, 105);
|
||||
doc.text(`DSC ${dsc.dscSerialNoMasked}`, sX + 3, sY + 14);
|
||||
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setFontSize(6.5);
|
||||
doc.setTextColor(100, 116, 139);
|
||||
doc.text(dsc.dscType, sX + 3, sY + 17.5);
|
||||
doc.text(`Signed at ${ts}`, sX + 3, sY + 20.5);
|
||||
|
||||
// ── Audit footer line (thin teal strip just below the existing footer) ──
|
||||
const fY = pageH - margin + 2;
|
||||
doc.setFillColor(204, 251, 241); // teal-100
|
||||
doc.rect(margin, fY, pageW - margin * 2, 4, 'F');
|
||||
doc.setFont('courier', 'normal');
|
||||
doc.setFontSize(7);
|
||||
doc.setTextColor(13, 148, 136); // teal-600
|
||||
doc.text(
|
||||
`[SIGNED] ${dsc.commonName} · DSC ${dsc.dscSerialNoMasked} · Issued by ${dsc.issuedBy.slice(0, 38)} · ${ts}`,
|
||||
margin + 2,
|
||||
fY + 2.8
|
||||
);
|
||||
}
|
||||
|
||||
// ───────────────────── Loan Interest Certificate ─────────────────────
|
||||
export function buildLoanInterestPdf(record: LoanInterestRecord, fy: FyInterestBreakup): jsPDF {
|
||||
const c = newCtx(ACCENT_BLUE);
|
||||
@@ -467,8 +536,9 @@ export function buildLoanInterestPdf(record: LoanInterestRecord, fy: FyInterestB
|
||||
return c.doc;
|
||||
}
|
||||
|
||||
export function downloadLoanInterestPdf(record: LoanInterestRecord, fy: FyInterestBreakup): void {
|
||||
export function downloadLoanInterestPdf(record: LoanInterestRecord, fy: FyInterestBreakup, dsc?: BankUserDsc): void {
|
||||
const doc = buildLoanInterestPdf(record, fy);
|
||||
if (dsc) paintDscSignatureStamp(doc, dsc);
|
||||
doc.save(`Interest-Certificate-${record.accountNo}-${fy.fy.replace(/\s+/g, '')}.pdf`);
|
||||
}
|
||||
|
||||
@@ -552,8 +622,9 @@ export function buildShareCertificatePdf(r: ShareCertificateRecord): jsPDF {
|
||||
return c.doc;
|
||||
}
|
||||
|
||||
export function downloadShareCertificatePdf(r: ShareCertificateRecord): void {
|
||||
export function downloadShareCertificatePdf(r: ShareCertificateRecord, dsc?: BankUserDsc): void {
|
||||
const doc = buildShareCertificatePdf(r);
|
||||
if (dsc) paintDscSignatureStamp(doc, dsc);
|
||||
doc.save(`Share-Certificate-${r.certificateNo}.pdf`);
|
||||
}
|
||||
|
||||
@@ -645,8 +716,9 @@ export function buildBalanceCertificatePdf(r: BalanceCertificateRecord, asOnDate
|
||||
return c.doc;
|
||||
}
|
||||
|
||||
export function downloadBalanceCertificatePdf(r: BalanceCertificateRecord, asOnDate: string): void {
|
||||
export function downloadBalanceCertificatePdf(r: BalanceCertificateRecord, asOnDate: string, dsc?: BankUserDsc): void {
|
||||
const doc = buildBalanceCertificatePdf(r, asOnDate);
|
||||
if (dsc) paintDscSignatureStamp(doc, dsc);
|
||||
doc.save(`Balance-Certificate-${r.accountNo}-${asOnDate}.pdf`);
|
||||
}
|
||||
|
||||
@@ -736,8 +808,9 @@ export function buildForm60Pdf(r: Form60Record): jsPDF {
|
||||
return c.doc;
|
||||
}
|
||||
|
||||
export function downloadForm60Pdf(r: Form60Record): void {
|
||||
export function downloadForm60Pdf(r: Form60Record, dsc?: BankUserDsc): void {
|
||||
const doc = buildForm60Pdf(r);
|
||||
if (dsc) paintDscSignatureStamp(doc, dsc);
|
||||
doc.save(`Form-60-${r.customerId}.pdf`);
|
||||
}
|
||||
|
||||
@@ -830,8 +903,9 @@ export function buildForm15ghPdf(r: Form15ghRecord, kind: Form15Kind): jsPDF {
|
||||
return c.doc;
|
||||
}
|
||||
|
||||
export function downloadForm15ghPdf(r: Form15ghRecord, kind: Form15Kind): void {
|
||||
export function downloadForm15ghPdf(r: Form15ghRecord, kind: Form15Kind, dsc?: BankUserDsc): void {
|
||||
const doc = buildForm15ghPdf(r, kind);
|
||||
if (dsc) paintDscSignatureStamp(doc, dsc);
|
||||
doc.save(`Form-${kind}-${r.customerId}-${r.fy.replace(/\s+/g, '')}.pdf`);
|
||||
}
|
||||
|
||||
@@ -922,8 +996,9 @@ export function buildHypothecationPdf(r: HypothecationRecord): jsPDF {
|
||||
return c.doc;
|
||||
}
|
||||
|
||||
export function downloadHypothecationPdf(r: HypothecationRecord): void {
|
||||
export function downloadHypothecationPdf(r: HypothecationRecord, dsc?: BankUserDsc): void {
|
||||
const doc = buildHypothecationPdf(r);
|
||||
if (dsc) paintDscSignatureStamp(doc, dsc);
|
||||
doc.save(`Hypothecation-${r.certificateNo}.pdf`);
|
||||
}
|
||||
|
||||
@@ -1052,7 +1127,8 @@ export function buildNocPdf(r: NocRecord): jsPDF {
|
||||
return c.doc;
|
||||
}
|
||||
|
||||
export function downloadNocPdf(r: NocRecord): void {
|
||||
export function downloadNocPdf(r: NocRecord, dsc?: BankUserDsc): void {
|
||||
const doc = buildNocPdf(r);
|
||||
if (dsc) paintDscSignatureStamp(doc, dsc);
|
||||
doc.save(`NOC-${r.nocNo}.pdf`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user