feat(clients, alm): add CIN/MCA verification & authorized signatories tab; refactor ALM report print and export
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s

This commit is contained in:
2026-08-13 15:05:16 +05:30
parent 75df8865dd
commit 5581d4180d
29 changed files with 3002 additions and 2453 deletions
@@ -0,0 +1,55 @@
/**
* 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 { toProperNameCase } from './name-case.util';
describe('toProperNameCase', () => {
it('raises a lower-case first letter on every word', () => {
expect(toProperNameCase('keshav Singh sisodiya')).toBe('Keshav Singh Sisodiya');
});
it('leaves the rest of each word exactly as typed', () => {
expect(toProperNameCase('lincpay lTD')).toBe('Lincpay LTD');
expect(toProperNameCase('lincpay pvt ltd')).toBe('Lincpay Pvt Ltd');
});
it('does not rewrite a name that is already correct', () => {
expect(toProperNameCase('Technoludium Solutions Private Limited')).toBe('Technoludium Solutions Private Limited');
});
it('leaves an upper-case name shouted rather than title-casing it', () => {
expect(toProperNameCase('TECHNOLUDIUM SOLUTIONS')).toBe('TECHNOLUDIUM SOLUTIONS');
expect(toProperNameCase('KESHAV singh')).toBe('KESHAV Singh');
});
it('capitalises after a hyphen', () => {
expect(toProperNameCase('jean-marie traders')).toBe('Jean-Marie Traders');
});
it('capitalises name prefixes after an apostrophe but not possessives', () => {
expect(toProperNameCase("o'brien d'souza")).toBe("O'Brien D'Souza");
expect(toProperNameCase("lincpay's agency")).toBe("Lincpay's Agency");
});
it('leaves punctuation and non-letters untouched', () => {
expect(toProperNameCase('abc pvt. ltd.')).toBe('Abc Pvt. Ltd.');
expect(toProperNameCase('tata & sons')).toBe('Tata & Sons');
expect(toProperNameCase('3m india')).toBe('3m India');
});
it('trims and collapses whitespace', () => {
expect(toProperNameCase(' lincpay holdings ')).toBe('Lincpay Holdings');
});
it('returns an empty string for blank or non-string input', () => {
expect(toProperNameCase('')).toBe('');
expect(toProperNameCase(' ')).toBe('');
expect(toProperNameCase(null)).toBe('');
expect(toProperNameCase(undefined)).toBe('');
});
});
+64
View File
@@ -0,0 +1,64 @@
/**
* 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/.
*/
/**
* Casing for client names: the name is stored exactly as the officer typed it, except that a word
* starting with a lower-case letter has that first letter raised. Nothing else is touched.
*
* 'keshav Singh sisodiya' → 'Keshav Singh Sisodiya'
* 'lincpay lTD' → 'Lincpay LTD'
* 'Technoludium Solutions Private Limited' → unchanged
* 'TECHNOLUDIUM SOLUTIONS' → unchanged
*
* Deliberately NOT full title-casing. Lower-casing the rest of each word would rewrite an entity
* name the officer entered as it appears on the certificate — 'TECHNOLUDIUM' becoming
* 'Technoludium', 'Ltd' becoming a forced 'LTD'. That name is matched character-for-character
* against the PAN holder's registered name during KYC verification, so the typed spelling is the
* thing to preserve; only an unintended lower-case first letter is corrected.
*/
/** Characters after which a new word starts: 'jean-marie' → 'Jean-Marie'. */
const SEGMENT_STARTERS = new Set([
'-',
"'",
'’'
]);
/**
* Raises the first letter of each word in a single space-delimited token, leaving every other
* character as typed.
*/
function capitalizeWord(word: string): string {
let result = '';
for (let i = 0; i < word.length; i++) {
const previous = i === 0 ? '' : word[i - 1];
let startsWord = i === 0 || SEGMENT_STARTERS.has(previous);
if (previous === "'" || previous === '’') {
// 'o'brien' / 'd'souza' capitalise after the apostrophe, but a possessive
// ('lincpay's') must not — only a single leading letter marks the name prefix.
startsWord = i === 2;
}
result += startsWord ? word[i].toUpperCase() : word[i];
}
return result;
}
/**
* Applies the casing rule above to a person or entity name.
* Trims and collapses runs of whitespace; non-string input yields ''.
*/
export function toProperNameCase(value: string | null | undefined): string {
if (typeof value !== 'string') {
return '';
}
const collapsed = value.trim().replace(/\s+/g, ' ');
if (!collapsed) {
return '';
}
return collapsed.split(' ').map(capitalizeWord).join(' ');
}