139 lines
5.8 KiB
TypeScript
139 lines
5.8 KiB
TypeScript
/**
|
|
* 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/.
|
|
*/
|
|
|
|
/** Shape the KYC document-type picker works with. */
|
|
export interface KycDocumentType {
|
|
name: string;
|
|
code: string;
|
|
mandatory: boolean;
|
|
category?: string;
|
|
}
|
|
|
|
export const ENTITY_KYC_CATEGORY = 'Entity KYC documents';
|
|
|
|
/**
|
|
* The KYC documents every legal entity must produce, whatever its constitution — RBI Master
|
|
* Direction on KYC, Annexure I. Codes match PROOF_META in kyc-proof-types.ts, which is what
|
|
* groups them under "Entity KYC documents" and supplies each chip's banner text.
|
|
*
|
|
* This is the single source for the entity list so a non-individual client sees the same
|
|
* documents wherever the picker is rendered — the create stepper, the edit stepper, before a
|
|
* constitution has been chosen and after, and once the backend KYC template has loaded.
|
|
*/
|
|
export const ENTITY_KYC_DOCUMENT_TYPES: { name: string; code: string }[] = [
|
|
{ name: 'Certificate of Incorporation / Registration Certificate', code: 'CERT_INCORPORATION' },
|
|
{ name: 'PAN Card of Entity', code: 'ENTITY_PAN' },
|
|
{ name: 'Memorandum & Articles of Association / Partnership Deed / Trust Deed', code: 'CONSTITUTIVE_DOC' },
|
|
{ name: 'Board Resolution / Authorization Letter', code: 'BOARD_RESOLUTION' },
|
|
{ name: 'KYC of Authorized Signatory / Power of Attorney Holder', code: 'AUTH_SIGNATORY_KYC' },
|
|
{ name: 'GST Registration Certificate', code: 'GST_CERTIFICATE' }
|
|
];
|
|
|
|
/**
|
|
* Codes the backend KYC template uses for documents the entity list already covers
|
|
* (ClientKycApiResource.getRequiredDocuments). Folding them onto the entity codes keeps one
|
|
* chip per document instead of "PAN Card of Company" sitting next to "PAN Card of Entity".
|
|
*/
|
|
const ENTITY_DOC_CODE_ALIASES: Record<string, string> = {
|
|
CERT_INCORPORATION: 'CERT_INCORPORATION',
|
|
COI: 'CERT_INCORPORATION',
|
|
REG_CERT: 'CERT_INCORPORATION',
|
|
ENTITY_PAN: 'ENTITY_PAN',
|
|
PAN: 'ENTITY_PAN',
|
|
CONSTITUTIVE_DOC: 'CONSTITUTIVE_DOC',
|
|
MOA: 'CONSTITUTIVE_DOC',
|
|
AOA: 'CONSTITUTIVE_DOC',
|
|
PARTNERSHIP_DEED: 'CONSTITUTIVE_DOC',
|
|
TRUST_DEED: 'CONSTITUTIVE_DOC',
|
|
BOARD_RESOLUTION: 'BOARD_RESOLUTION',
|
|
AUTH_SIGNATORY_KYC: 'AUTH_SIGNATORY_KYC',
|
|
SIGNATORY_KYC: 'AUTH_SIGNATORY_KYC',
|
|
GST_CERTIFICATE: 'GST_CERTIFICATE',
|
|
GST_CERT: 'GST_CERTIFICATE'
|
|
};
|
|
|
|
/**
|
|
* The per-constitution lists carry no codes of their own (code === name), so those entries are
|
|
* matched on the label instead. Patterns are anchored deliberately: a document that merely
|
|
* mentions another one ("Proof of Business Existence (GST Certificate / Shop License)") is a
|
|
* distinct requirement and has to survive as its own chip rather than vanish into GST.
|
|
*/
|
|
const ENTITY_DOC_NAME_PATTERNS: { pattern: RegExp; code: string }[] = [
|
|
{ pattern: /^certificate of incorporation\b/, code: 'CERT_INCORPORATION' },
|
|
{ pattern: /^registration certificate\b/, code: 'CERT_INCORPORATION' },
|
|
{
|
|
pattern: /^(memorandum|articles of association|partnership deed|trust deed|llp agreement|by-?laws)\b/,
|
|
code: 'CONSTITUTIVE_DOC'
|
|
},
|
|
{ pattern: /^pan\b/, code: 'ENTITY_PAN' },
|
|
{ pattern: /^(board resolution|designated partners resolution|resolution authorizing)\b/, code: 'BOARD_RESOLUTION' },
|
|
{ pattern: /^(kyc of|authorized signatory kyc|proprietor'?s kyc)\b/, code: 'AUTH_SIGNATORY_KYC' },
|
|
{ pattern: /^gst (registration )?certificate\b/, code: 'GST_CERTIFICATE' }
|
|
];
|
|
|
|
/** The entity document this one duplicates, or null when it is a requirement of its own. */
|
|
function resolveEntityDocCode(doc: { name?: string; code?: string }): string | null {
|
|
const code = (doc.code || '').toUpperCase().trim();
|
|
// A constitution list stores the label in `code`, so only treat it as a code when it is one.
|
|
if (code && !code.includes(' ') && ENTITY_DOC_CODE_ALIASES[code]) {
|
|
return ENTITY_DOC_CODE_ALIASES[code];
|
|
}
|
|
const name = (doc.name || '').toLowerCase().trim();
|
|
return ENTITY_DOC_NAME_PATTERNS.find((p) => p.pattern.test(name))?.code || null;
|
|
}
|
|
|
|
/**
|
|
* Puts the RBI entity documents in front of whatever list a caller has (per-constitution docs,
|
|
* or the backend KYC template) instead of letting that list replace them.
|
|
*
|
|
* Without this the officer sees a different set of documents on every screen for the same
|
|
* client: the entity list until a constitution is picked, the constitution's own list after,
|
|
* and the backend template's list once the client exists and is reopened for edit. Anything in
|
|
* `extra` that isn't already one of the six stays, grouped under `extraCategory` — those are
|
|
* genuine constitution-specific requirements (Karta declaration, Udyam certificate, government
|
|
* notification) and dropping them would lose real requirements.
|
|
*
|
|
* A mandatory flag on a duplicate carries over to the entity chip it folds into, so the
|
|
* "at least one mandatory document" validator still sees it.
|
|
*/
|
|
export function withEntityKycDocuments(
|
|
extra: { name: string; code?: string; mandatory?: boolean }[] = [],
|
|
extraCategory?: string | null
|
|
): KycDocumentType[] {
|
|
const mandatoryCodes = new Set<string>();
|
|
const leftovers: KycDocumentType[] = [];
|
|
|
|
extra.forEach((doc) => {
|
|
const entityCode = resolveEntityDocCode(doc);
|
|
if (entityCode) {
|
|
if (doc.mandatory) {
|
|
mandatoryCodes.add(entityCode);
|
|
}
|
|
return;
|
|
}
|
|
leftovers.push({
|
|
name: doc.name,
|
|
code: doc.code || doc.name,
|
|
mandatory: !!doc.mandatory,
|
|
category: extraCategory ? `${extraCategory} documents` : 'Additional documents'
|
|
});
|
|
});
|
|
|
|
const base: KycDocumentType[] = ENTITY_KYC_DOCUMENT_TYPES.map((d) => ({
|
|
name: d.name,
|
|
code: d.code,
|
|
mandatory: mandatoryCodes.has(d.code),
|
|
category: ENTITY_KYC_CATEGORY
|
|
}));
|
|
|
|
return [
|
|
...base,
|
|
...leftovers
|
|
];
|
|
}
|