Skip to content

Commit

Permalink
Merge pull request #70 from RedFroggy/feature/issue-69
Browse files Browse the repository at this point in the history
feat: #69 - Support spanish company identifier
  • Loading branch information
michaeldesigaud committed Aug 20, 2021
2 parents b1931ed + 7c011c9 commit 8ca46fa
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,5 @@ console.log(info);
`npm run lint`. Will check your code based on `tslint.json` config file

## Roadmap
- For now only 6 countries are supported: FR, BE, CH, AD, CN, IT
- For now 7 countries are supported: FR, BE, CH, AD, CN, IT, ES
- Need to add many more countries based on the [ptdnum python library](https://github.com/arthurdejong/python-stdnum)
10 changes: 10 additions & 0 deletions src/data/es/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"companyIdFullName": "Código de identificación fiscal",
"companyIdName": "CIF",
"trustedSourceUrl": "https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal",
"parentLevel": true,
"pattern": "^([ABCDEFGHJUV][0-9]{8})|([NPQRSW][0-9]{7}[A-Z]{1})$",
"companyIdDescription": "The Spanish VAT number is a 9-digit number where either the first, last\ndigits or both can be letters.\nThe number is either a DNI (Documento Nacional de Identidad, for\nSpaniards), a NIE (Número de Identificación de Extranjero, for\nforeigners) or a CIF (Código de Identificación Fiscal, for legal\nentities and others)."
}
]
4 changes: 2 additions & 2 deletions src/lib/company-id.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export abstract class CompanyIdService {
companyInfo = Object.assign({}, companyInfo, matchedCompanyInfo);
}

const validCompanyId = Boolean(matchedCompanyInfo && this.validate(sanitizedQuery));
const validCompanyId = Boolean(matchedCompanyInfo && this.validate(sanitizedQuery, matchedCompanyInfo));

if (validCompanyId) {

Expand Down Expand Up @@ -102,5 +102,5 @@ export abstract class CompanyIdService {
* Validate the given {@see companyId}
* based on local algorithm
*/
abstract validate(companyId: string): boolean;
abstract validate(companyId: string, info?: CompanyInfo): boolean;
}
2 changes: 1 addition & 1 deletion src/lib/company-id.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe('Company validation tests', () => {
});

it('should get validator based on country code', () => {
const validationService = CompanyId.getValidator('FR');
const validationService = CompanyId.info('FR', '802070748');
expect(validationService).not.toBeNull();
expect((validationService as any).countryCode).toBe('FR');
});
Expand Down
6 changes: 1 addition & 5 deletions src/lib/company-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,13 @@ export class CompanyId {
throw new Error('Invalid isoAlpha2 country code');
}

return CompanyId.getValidator(countryCode).info(companyId);
}

static getValidator(countryCode: string): CompanyIdService {
const validator: CompanyIdService = IocContainer
.findByName(`${countryCode.toLowerCase()}companyidservice`);

if (!validator) {
throw new Error('Unsupported countryCode: ' + countryCode);
}

return validator;
return validator.info(companyId);
}
}
31 changes: 31 additions & 0 deletions src/lib/validators/es/es-company-id.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {COMPANY_ES_DATA} from "../../../data/es/es.model";
import {EsCompanyIdService} from "./es-company-id.service";

describe('Spanish company validation', () => {

const companyInfo = COMPANY_ES_DATA[0];
const validationService = new EsCompanyIdService();

it('should not validate if identifier type is unknown', () => {
expect(validationService.validate('J99216582')).toBeFalsy();
expect(validationService.validate('J99216582', {companyIdName: 'UNKNOWN'})).toBeFalsy();
});

it('should validate CIF', () => {
expect(validationService.validate('J99216582', companyInfo)).toBeTruthy();
expect(validationService.validate('A08437642', companyInfo)).toBeTruthy();
expect(validationService.validate('Q0818003F', companyInfo)).toBeTruthy();

// Invalid check digit
expect(validationService.validate('J99216583', companyInfo)).toBeFalsy();

// Too long format
expect(validationService.validate('J992165831', companyInfo)).toBeFalsy();

// Valid NIF but not valid CIF
expect(validationService.validate('M-1234567-L', companyInfo)).toBeFalsy();

// Invalid first character
expect(validationService.validate('O-1234567-L', companyInfo)).toBeFalsy();
});
});
57 changes: 57 additions & 0 deletions src/lib/validators/es/es-company-id.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {COMPANY_ES_DATA} from "../../../data/es/es.model";
import {CompanyInfo} from "../../../types/company-info.model";
import {CompanyIdService} from "../../company-id.service";
import {Injectable} from "../../injectable.decorator";

@Injectable
export class EsCompanyIdService extends CompanyIdService {

constructor() {
super('ES', COMPANY_ES_DATA);
}

validate(companyId: string, companyInfo?: CompanyInfo): boolean {
if (companyInfo && companyInfo.companyIdName === 'CIF') {
return this.validateCIF(companyId);
}

return false;
}

private validateCIF(companyId: string): boolean {
const firstDigit = companyId[0];
const digits = companyId.substring(1, companyId.length -1);
const checkDigit = companyId.substring(companyId.length -1);

let even_sum = 0, odd_sum = 0, n;

for ( let i = 0; i < digits.length; i++) {
n = parseInt( digits.charAt(i), 10 );
if ( i % 2 === 0 ) {
n *= 2;
odd_sum += n < 10 ? n : n - 9;
} else {
even_sum += n;
}
}

const last_digit = parseInt((even_sum + odd_sum).toString().slice(-1), 10);
// const control_digit = last_digit != 0 ? (10 - last_digit ) : last_digit;
const control_digit = (10 - last_digit );
const control_letter = 'JABCDEFGHI'.substr( control_digit, 1 );

// Control must be a digit
if ( firstDigit.match( /[ABEH]/ ) ) {
return checkDigit.toString() == control_digit.toString();

// Control must be a letter
} else if ( firstDigit.match( /[PQSW]/ ) ) {
return checkDigit == control_letter;

// Can be either
} else {
return checkDigit.toString() == control_digit.toString()
|| checkDigit == control_letter;
}
}
}
2 changes: 2 additions & 0 deletions src/lib/validators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export {BeCompanyIdService} from './be/be-company-id.service';
export {ChCompanyIdService} from './ch/ch-company-id.service';
export {AdCompanyIdService} from './ad/ad-company-id.service';
export {ItCompanyIdService} from './it/it-company-id.service';
export {CnCompanyIdService} from './cn/cn-company-id.service';
export {EsCompanyIdService} from './es/es-company-id.service';

0 comments on commit 8ca46fa

Please sign in to comment.