diff --git a/docs/utils/cnsValidator.md b/docs/utils/cnsValidator.md new file mode 100644 index 0000000..ee30370 --- /dev/null +++ b/docs/utils/cnsValidator.md @@ -0,0 +1,74 @@ +# CnsValidator + +Utilitário para validar CNS com e sem máscara. + +## Instalação e Importação + +```typescript +import { cnsValidator } from '@sysvale/foundry'; +``` + +## Função + +### `cnsValidator()` + +Valida CNS com e sem máscara, indicando se os mesmos são válidos. + +#### Sintaxes + +```typescript +cnsValidator(value: string): boolean +``` + +#### Parâmetros + +**Assinatura 1:** + +- **`value`** (`string`): CNS (com ou sem máscara) a ser validado + +
+ +#### Retorno + +`boolean` - Resultado da validação, `true` para CNS válido e `false` para inválido + +
+ +#### Exemplos + +**Usando CNS com máscara:** + +```typescript +cnsValidator('728 1376 2535 3587'); // → true + +cnsValidator('111 1111 1111 1111'); // → false +``` + +
+ +**Usando CNS sem máscara:** + +```typescript +cnsValidator('728137625353587'); // → true + +cnsValidator('111111111111111'); // → false +``` + +
+ +#### Tratamento de Erros + +A função lança um erro quando os parâmetros obrigatórios não são fornecidos: + +```typescript +// ❌ Erro: tipagem do parâmetro é inválida +cnsValidator(728137625353587); +// → Error: O tipo do parâmetro passado é inválido. + +// ✅ Correto +cnsValidator('728137625353587'); +``` + +## Notas + +- A função é **type-safe** diff --git a/docs/utils/index.md b/docs/utils/index.md index c15b6b9..1db465b 100644 --- a/docs/utils/index.md +++ b/docs/utils/index.md @@ -23,3 +23,9 @@ Função para sanitizar dados de formulário e aplicar transformações antes de Função para validar CPFs com e sem máscara. - [Documentação](./cpfValidator.md) + +### cnsValidator() + +Função para validar CNS com e sem máscara. + +- [Documentação](./cnsValidator.md) diff --git a/package.json b/package.json index 9ce040e..8c70325 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sysvale/foundry", - "version": "1.6.0", + "version": "1.7.0", "description": "A forge for composables, helpers, and front-end utilities.", "type": "module", "main": "./dist/foundry.cjs.js", diff --git a/src/index.ts b/src/index.ts index d4501ee..92d318e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from './utils/pluralize'; export * from './utils/commaline'; export * from './utils/sanitizeForm'; export * from './utils/cpfValidator'; +export * from './utils/cnsValidator'; export { maskCpf, removeCpfMask } from './formatters/cpf'; export { maskCns, removeCnsMask } from './formatters/cns'; export { maskPhone, removePhoneMask } from './formatters/phone'; diff --git a/src/utils/cnsValidator.ts b/src/utils/cnsValidator.ts new file mode 100644 index 0000000..5f5961e --- /dev/null +++ b/src/utils/cnsValidator.ts @@ -0,0 +1,128 @@ +function checkCnsValue(value: string) { + let pis; + let rest; + let sum; + + pis = value.substring(0, 15); + + if (pis === '') { + return false; + } + + if ( + value.substring(0, 1) !== '7' && + value.substring(0, 1) !== '8' && + value.substring(0, 1) !== '9' + ) { + return false; + } + + sum = + parseInt(pis.substring(0, 1), 10) * 15 + + parseInt(pis.substring(1, 2), 10) * 14 + + parseInt(pis.substring(2, 3), 10) * 13 + + parseInt(pis.substring(3, 4), 10) * 12 + + parseInt(pis.substring(4, 5), 10) * 11 + + parseInt(pis.substring(5, 6), 10) * 10 + + parseInt(pis.substring(6, 7), 10) * 9 + + parseInt(pis.substring(7, 8), 10) * 8 + + parseInt(pis.substring(8, 9), 10) * 7 + + parseInt(pis.substring(9, 10), 10) * 6 + + parseInt(pis.substring(10, 11), 10) * 5 + + parseInt(pis.substring(11, 12), 10) * 4 + + parseInt(pis.substring(12, 13), 10) * 3 + + parseInt(pis.substring(13, 14), 10) * 2 + + parseInt(pis.substring(14, 15), 10) * 1; + + rest = sum % 11; + + if (!rest) { + return true; + } + + return false; +} + +function checkCnsFirstElevenDigits(value: string) { + let sum = 0; + let rest = 0; + let validatorDigit = 0; + let pis = ''; + let result = ''; + const cnsSize = value.length; + + if (cnsSize !== 15) { + return false; + } + + pis = value.substring(0, 11); + sum = + Number(pis.substring(0, 1)) * 15 + + Number(pis.substring(1, 2)) * 14 + + Number(pis.substring(2, 3)) * 13 + + Number(pis.substring(3, 4)) * 12 + + Number(pis.substring(4, 5)) * 11 + + Number(pis.substring(5, 6)) * 10 + + Number(pis.substring(6, 7)) * 9 + + Number(pis.substring(7, 8)) * 8 + + Number(pis.substring(8, 9)) * 7 + + Number(pis.substring(9, 10)) * 6 + + Number(pis.substring(10, 11)) * 5; + rest = sum % 11; + validatorDigit = 11 - rest; + + if (validatorDigit === 11) { + validatorDigit = 0; + } + + if (validatorDigit === 10) { + sum = + Number(pis.substring(0, 1)) * 15 + + Number(pis.substring(1, 2)) * 14 + + Number(pis.substring(2, 3)) * 13 + + Number(pis.substring(3, 4)) * 12 + + Number(pis.substring(4, 5)) * 11 + + Number(pis.substring(5, 6)) * 10 + + Number(pis.substring(6, 7)) * 9 + + Number(pis.substring(7, 8)) * 8 + + Number(pis.substring(8, 9)) * 7 + + Number(pis.substring(9, 10)) * 6 + + Number(pis.substring(10, 11)) * 5 + + 2; + rest = sum % 11; + validatorDigit = 11 - rest; + result = `${pis}001${String(validatorDigit)}`; + } else { + result = `${pis}000${String(validatorDigit)}`; + } + + if (value !== result) { + return false; + } + + return true; +} + +/** + * Valida CNS com e sem máscara. + * + * @param { string } value + * @returns { boolean } + */ +export function cnsValidator(value: string) { + if (typeof value !== 'string') { + throw new Error('O tipo do parâmetro passado é inválido.'); + } + + const unmaskedValue = value.replace(/\D/g, ''); + + if (unmaskedValue.length !== 15) { + return false; + } + + if ([1, 2].indexOf(parseInt(unmaskedValue.substring(0, 1))) != -1) { + return checkCnsFirstElevenDigits(unmaskedValue); + } + + return checkCnsValue(unmaskedValue); +} diff --git a/tests/cnsValidator.test.ts b/tests/cnsValidator.test.ts new file mode 100644 index 0000000..be7fe35 --- /dev/null +++ b/tests/cnsValidator.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from 'vitest'; +import { cnsValidator } from '../src/utils/cnsValidator'; + +describe('cnsValidator()', () => { + test('retorna false quando string vazia é passada', () => { + expect(cnsValidator('')).toBe(false); + }); + + test('retorna false quando cns inválido com máscara é passado', () => { + expect(cnsValidator('111 1111 1111 1111')).toBe(false); + }); + + test('retorna true quando cns válido com máscara é passado', () => { + expect(cnsValidator('728 1376 2535 3587')).toBe(true); + }); + + test('retorna false quando cns inválido sem máscara é passado', () => { + expect(cnsValidator('111111111111111')).toBe(false); + }); + + test('retorna true quando cns válido sem máscara é passado', () => { + expect(cnsValidator('728137625353587')).toBe(true); + }); + + test('retorna false quando cns possui menos que 15 dígitos', () => { + expect(cnsValidator('11111111111')).toBe(false); + }); + + test('retorna false quando cns possui mais que 15 dígitos', () => { + expect(cnsValidator('7281376253535879')).toBe(false); + }); + + test('retorna false quando cns possui uma letra', () => { + expect(cnsValidator('11111111111111a')).toBe(false); + }); + + test('lança erro quando parâmetro é do tipo number', () => { + expect(() => cnsValidator(12341789324)).toThrowError(); + }); +});