From f602fa7928bea977ef9bd3172f3679bc9df3c9d0 Mon Sep 17 00:00:00 2001 From: David Koblas Date: Sun, 25 Jun 2023 06:14:09 -0400 Subject: [PATCH] feat: add es/nss --- .github/workflows/release.yaml | 22 ++++++ README.md | 1 + src/es/dni.ts | 12 ++- src/es/index.ts | 1 + src/es/nss.spec.ts | 28 +++++++ src/es/nss.ts | 132 +++++++++++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 src/es/nss.spec.ts create mode 100644 src/es/nss.ts diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d293c767..d5cff722 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,6 +16,9 @@ jobs: release: name: Release runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.test }} + steps: - name: Checkout @@ -42,3 +45,22 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} run: npx semantic-release + - name: Get Version + id: version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + run: echo "version=$(npx semantic-release --version)" >> "$GITHUB_OUTPUT" + - name: GitHub Release update + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + # body_path: ${{ github.workspace }}-CHANGELOG.txt + # note you'll typically need to create a personal access token + # with permissions to create releases in the other repoj + #token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} + files: | + LICENSE.md + README.md + lib + package.json diff --git a/README.md b/README.md index b0db8b1e..87f14425 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ All country validators are in the "namespace" of the ISO country code. | Spain | ES | DNI | Person | Identity code (Documento Nacional de Identidad) | | Spain | ES | NIE | Person | Identity code foreigner (Número de Identificación de Extranjero) | | Spain | ES | NIF | Tax | Tax Identifier (Número de Identificación Fiscal) | +| Spain | ES | NSS | Person | El número de Seguridad Social, Social Security Number | | Uruguay | UY | RUT | Tax/Vat | Tax Identifier (Registro Único Tributario) | | Uruguay | UY | CEDULA | Person | Person Identifier (Cédula de Residencia) | | Uruguay | UY | NIE | Person | ForeignersI identification Number | diff --git a/src/es/dni.ts b/src/es/dni.ts index 49c3592b..c08bf827 100644 --- a/src/es/dni.ts +++ b/src/es/dni.ts @@ -8,6 +8,7 @@ * de Extranjeros, Foreigner's Identity Number) instead. * * Sources: + * https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/SPAIN-TIN.pdf * * PERSON */ @@ -58,11 +59,14 @@ const impl: Validator = { const [body, check] = strings.splitAt(value, 8); - if (!strings.isdigits(body)) { + if ('KLM'.includes(body[0]) && strings.isdigits(body.substring(1))) { + // Currently no test data for these cases, so + // we're assuming they're good based on format + } else if (!strings.isdigits(body)) { + // Not all digits in the body, it's not valid return { isValid: false, error: new exceptions.InvalidComponent() }; - } - - if (calcCheckDigit(body) !== check) { + } else if (calcCheckDigit(body) !== check) { + // Check the checksum on a non-[KLM] person return { isValid: false, error: new exceptions.InvalidChecksum() }; } diff --git a/src/es/index.ts b/src/es/index.ts index c0d5e134..1709ad96 100644 --- a/src/es/index.ts +++ b/src/es/index.ts @@ -2,3 +2,4 @@ export * as cif from './cif'; export * as dni from './dni'; export * as nie from './nie'; export * as nif from './nif'; +export * as nss from './nss'; diff --git a/src/es/nss.spec.ts b/src/es/nss.spec.ts new file mode 100644 index 00000000..ca1712aa --- /dev/null +++ b/src/es/nss.spec.ts @@ -0,0 +1,28 @@ +import { validate, format } from './nss'; +import { InvalidLength, InvalidComponent } from '../exceptions'; + +describe('es/nss', () => { + it('format:281234567840', () => { + const result = format('281234567840'); + + expect('28-1234567840').toEqual(result); + }); + + it.each(['281234567840'])('validate:%s', (value: string) => { + const result = validate(value); + + expect(value).toEqual(result.isValid && result.compact); + }); + + it('validate:77 12345678 40', () => { + const result = validate('77 12345678 40'); + + expect(result.error).toBeInstanceOf(InvalidComponent); + }); + + it('validate:28 12345678 4', () => { + const result = validate('28 12345678 4'); + + expect(result.error).toBeInstanceOf(InvalidLength); + }); +}); diff --git a/src/es/nss.ts b/src/es/nss.ts new file mode 100644 index 00000000..a4d1fbea --- /dev/null +++ b/src/es/nss.ts @@ -0,0 +1,132 @@ +/** + * NSS (El número de Seguridad Social, Social Security Number). + * + * The SSN is a tax identification number for individuals entities. It has 12 digits + * where where the first 2 digits indicate province, followed by 8 digits and + * a 2 digit checksum + * + * Sources: + * https://cis.ier.hit-u.ac.jp/English/society/conference1001/delgado-paper.pdf + * https://www.grupoalquerque.es/ferias/2012/archivos/digitos/codigo_seguridad_social.pdf + * + * TAX/PERSON + */ + +import * as exceptions from '../exceptions'; +import { strings } from '../util'; +import { Validator, ValidateReturn } from '../types'; + +const PROVINCES = { + ÁLAVA: '01', + ALBACETE: '02', + ALICANTE: '03', + ALMERÍA: '04', + ASTURIAS: '33', + ÁVILA: '05', + BADAJOZ: '06', + BALEARES: '07', + BARCELONA: '08', + BURGOS: '09', + CÁCERES: '10', + CÁDIZ: '11', + CANTABRIA: '39', + CASTELLÓN: '12', + 'CIUDAD REAL': '13', + CÓRDOBA: '14', + CORUÑA: '15', + CUENCA: '16', + GERONA: '17', + GRANADA: '18', + GUADALAJARA: '19', + GUIPÚZCOA: '20', + HUELVA: '21', + HUESCA: '22', + JAÉN: '23', + LEÓN: '24', + LÉRIDA: '25', + LUGO: '27', + MADRID: '28', + MÁLAGA: '29', + MURCIA: '30', + NAVARRA: '31', + ORENSE: '32', + PALENCIA: '34', + 'PALMAS (LAS)': '35', + PONTEVEDRA: '36', + 'RIOJA (LA)': '26', + SALAMANCA: '37', + 'SANTA CRUZ DE TENERIFE': '38', + SEGOVIA: '40', + SEVILLA: '41', + SORIA: '42', + TARRAGONA: '43', + TERUEL: '44', + TOLEDO: '45', + VALENCIA: '46', + VALLADOLID: '47', + VIZCAYA: '48', + ZAMORA: '49', + ZARAGOZA: '50', + 'OTROS TERRITORIOS': '53', + EXTRANJERO: '66', +}; +const VALID_PROVINCES = Object.values(PROVINCES); + +function clean(input: string): ReturnType { + return strings.cleanUnicode(input, ' -/'); +} + +const impl: Validator = { + name: 'Spanish Social Security Number', + localName: 'Número de Seguridad Social', + abbreviation: 'NSS', + compact(input: string): string { + const [value, err] = clean(input); + + if (err) { + throw err; + } + + return value; + }, + + format(input: string): string { + const [value] = clean(input); + + return strings.splitAt(value, 2).join('-'); + }, + + validate(input: string): ValidateReturn { + const [value, error] = clean(input); + + if (error) { + return { isValid: false, error }; + } + if (value.length !== 12) { + return { isValid: false, error: new exceptions.InvalidLength() }; + } + + if (!strings.isdigits(value)) { + return { isValid: false, error: new exceptions.InvalidComponent() }; + } + + const [province, body, check] = strings.splitAt(value, 2, 10); + if (!VALID_PROVINCES.includes(province)) { + return { isValid: false, error: new exceptions.InvalidComponent() }; + } + + if (parseInt(province + body, 10) % 97 !== parseInt(check, 10)) { + return { isValid: false, error: new exceptions.InvalidChecksum() }; + } + + return { + isValid: true, + compact: value, + isIndividual: true, + isCompany: false, + }; + }, +}; + +export const { name, localName, abbreviation, validate, format, compact } = + impl;