Skip to content

Commit

Permalink
Additional: Refine cifES and nieES algorithms (#1826)
Browse files Browse the repository at this point in the history
  • Loading branch information
alfonsomartinde authored and staabm committed Dec 1, 2016
1 parent 5c0c8ff commit e7422d6
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 83 deletions.
130 changes: 91 additions & 39 deletions src/additional/cifES.js
@@ -1,59 +1,111 @@
/*
* Código de identificación fiscal ( CIF ) is the tax identification code for Spanish legal entities
* Further rules can be found in Spanish on http://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal
*
* Spanish CIF structure:
*
* [ T ][ P ][ P ][ N ][ N ][ N ][ N ][ N ][ C ]
*
* Where:
*
* T: 1 character. Kind of Organization Letter: [ABCDEFGHJKLMNPQRSUVW]
* P: 2 characters. Province.
* N: 5 characters. Secuencial Number within the province.
* C: 1 character. Control Digit: [0-9A-J].
*
* [ T ]: Kind of Organizations. Possible values:
*
* A. Corporations
* B. LLCs
* C. General partnerships
* D. Companies limited partnerships
* E. Communities of goods
* F. Cooperative Societies
* G. Associations
* H. Communities of homeowners in horizontal property regime
* J. Civil Societies
* K. Old format
* L. Old format
* M. Old format
* N. Nonresident entities
* P. Local authorities
* Q. Autonomous bodies, state or not, and the like, and congregations and religious institutions
* R. Congregations and religious institutions (since 2008 ORDER EHA/451/2008)
* S. Organs of State Administration and regions
* V. Agrarian Transformation
* W. Permanent establishments of non-resident in Spain
*
* [ C ]: Control Digit. It can be a number or a letter depending on T value:
* [ T ] --> [ C ]
* ------ ----------
* A Number
* B Number
* E Number
* H Number
* K Letter
* P Letter
* Q Letter
* S Letter
*
*/
$.validator.addMethod( "cifES", function( value ) {
"use strict";

var num = [],
controlDigit, sum, i, count, tmp, secondDigit;
var cifRegEx = new RegExp( /^([ABCDEFGHJKLMNPQRSUVW])(\d{7})([0-9A-J])$/gi );
var letter = value.substring( 0, 1 ), // [ T ]
number = value.substring( 1, 8 ), // [ P ][ P ][ N ][ N ][ N ][ N ][ N ]
control = value.substring( 8, 9 ), // [ C ]
all_sum = 0,
even_sum = 0,
odd_sum = 0,
i, n,
control_digit,
control_letter;

value = value.toUpperCase();
function isOdd( n ) {
return n % 2 === 0;
}

// Quick format test
if ( !value.match( "((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)" ) ) {
if ( value.length !== 9 || !cifRegEx.test( value ) ) {
return false;
}

for ( i = 0; i < 9; i++ ) {
num[ i ] = parseInt( value.charAt( i ), 10 );
}
for ( i = 0; i < number.length; i++ ) {
n = parseInt( number[ i ], 10 );

// Odd positions
if ( isOdd( i ) ) {

// Odd positions are multiplied first.
n *= 2;

// Algorithm for checking CIF codes
sum = num[ 2 ] + num[ 4 ] + num[ 6 ];
for ( count = 1; count < 8; count += 2 ) {
tmp = ( 2 * num[ count ] ).toString();
secondDigit = tmp.charAt( 1 );
// If the multiplication is bigger than 10 we need to adjust
odd_sum += n < 10 ? n : n - 9;

sum += parseInt( tmp.charAt( 0 ), 10 ) + ( secondDigit === "" ? 0 : parseInt( secondDigit, 10 ) );
// Even positions
// Just sum them
} else {
even_sum += n;
}
}

/* The first (position 1) is a letter following the following criteria:
* A. Corporations
* B. LLCs
* C. General partnerships
* D. Companies limited partnerships
* E. Communities of goods
* F. Cooperative Societies
* G. Associations
* H. Communities of homeowners in horizontal property regime
* J. Civil Societies
* K. Old format
* L. Old format
* M. Old format
* N. Nonresident entities
* P. Local authorities
* Q. Autonomous bodies, state or not, and the like, and congregations and religious institutions
* R. Congregations and religious institutions (since 2008 ORDER EHA/451/2008)
* S. Organs of State Administration and regions
* V. Agrarian Transformation
* W. Permanent establishments of non-resident in Spain
*/
if ( /^[ABCDEFGHJNPQRSUVW]{1}/.test( value ) ) {
sum += "";
controlDigit = 10 - parseInt( sum.charAt( sum.length - 1 ), 10 );
value += controlDigit;
return ( num[ 8 ].toString() === String.fromCharCode( 64 + controlDigit ) || num[ 8 ].toString() === value.charAt( value.length - 1 ) );
all_sum = even_sum + odd_sum;
control_digit = ( 10 - ( all_sum ).toString().substr( -1 ) ).toString();
control_digit = parseInt( control_digit, 10 ) > 9 ? "0" : control_digit;
control_letter = "JABCDEFGHI".substr( control_digit, 1 ).toString();

// Control must be a digit
if ( letter.match( /[ABEH]/ ) ) {
return control === control_digit;

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

// Can be either
} else {
return control === control_digit || control === control_letter;
}

return false;
Expand Down
43 changes: 22 additions & 21 deletions src/additional/nieES.js
@@ -1,34 +1,35 @@
/*
* The número de identidad de extranjero ( NIE )is a code used to identify the non-nationals in Spain
* The NIE (Número de Identificación de Extranjero) is a Spanish tax identification number assigned by the Spanish
* authorities to any foreigner.
*
* The NIE is the equivalent of a Spaniards Número de Identificación Fiscal (NIF) which serves as a fiscal
* identification number. The CIF number (Certificado de Identificación Fiscal) is equivalent to the NIF, but applies to
* companies rather than individuals. The NIE consists of an 'X' or 'Y' followed by 7 or 8 digits then another letter.
*/
$.validator.addMethod( "nieES", function( value ) {
"use strict";

value = value.toUpperCase();
var nieRegEx = new RegExp( /^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi );
var validChars = "TRWAGMYFPDXBNJZSQVHLCKET",
letter = value.substr( value.length - 1 ).toUpperCase(),
number;

// Basic format test
if ( !value.match( "((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)" ) ) {
value = value.toString().toUpperCase();

// Quick format test
if ( value.length > 10 || value.length < 9 || !nieRegEx.test( value ) ) {
return false;
}

// Test NIE
//T
if ( /^[T]{1}/.test( value ) ) {
return ( value[ 8 ] === /^[T]{1}[A-Z0-9]{8}$/.test( value ) );
}
// X means same number
// Y means number + 10000000
// Z means number + 20000000
value = value.replace( /^[X]/, "0" )
.replace( /^[Y]/, "1" )
.replace( /^[Z]/, "2" );

//XYZ
if ( /^[XYZ]{1}/.test( value ) ) {
return (
value[ 8 ] === "TRWAGMYFPDXBNJZSQVHLCKE".charAt(
value.replace( "X", "0" )
.replace( "Y", "1" )
.replace( "Z", "2" )
.substring( 0, 8 ) % 23
)
);
}
number = value.length === 9 ? value.substr( 0, 8 ) : value.substr( 0, 9 );

return false;
return validChars.charAt( parseInt( number, 10 ) % 23 ) === letter;

}, "Please specify a valid NIE number." );
144 changes: 121 additions & 23 deletions test/methods.js
Expand Up @@ -1344,45 +1344,143 @@ QUnit.test( "nieES", function( assert ) {
assert.ok( method( "Y1524243R" ), "NIE valid" );
assert.ok( method( "X3744072V" ), "NIE valid" );
assert.ok( method( "X7436800A" ), "NIE valid" );
assert.ok( method( "X00002153Z" ), "NIE valid" );
assert.ok( method( "X02323232W" ), "NIE valid" );
assert.ok( method( "Z0569549M" ), "NIE valid" );
assert.ok( method( "X0479906B" ), "NIE valid" );
assert.ok( method( "y7875935j" ), "NIE valid: lower case" );
assert.ok( !method( "X0093999 K" ), "NIE inválido: white space" );
assert.ok( !method( "X 0093999 K" ), "NIE inválido: white space" );
assert.ok( !method( "11441059" ), "NIE inválido: no letter" );
assert.ok( !method( "11441059PR" ), "NIE inválido: two letters" );
assert.ok( !method( "11440059R" ), "NIE inválido: wrong number" );
assert.ok( !method( "11441059S" ), "NIE inválido: wrong letter" );
assert.ok( !method( "114410598R" ), "NIE inválido: > 8 digits" );
assert.ok( !method( "11441059-R" ), "NIE inválido: dash" );
assert.ok( !method( "asdasdasd" ), "NIE inválido: all letters" );
assert.ok( !method( "11.144.059R" ), "NIE inválido: two dots" );
assert.ok( !method( "05.122.654R" ), "NIE inválido: starts with 0 and dots" );
assert.ok( !method( "5.122.654-R" ), "NIE inválido: dots and dash" );
assert.ok( !method( "05.122.654-R" ), "NIE inválido: starts with zero and dot and dash" );

assert.ok( !method( "X0093999 K" ), "NIE invalid: white space" );
assert.ok( !method( "X 0093999 K" ), "NIE invalid: white space" );
assert.ok( !method( "11441059" ), "NIE invalid: no letter" );
assert.ok( !method( "11441059PR" ), "NIE invalid: two letters" );
assert.ok( !method( "11440059R" ), "NIE invalid: wrong number" );
assert.ok( !method( "11441059S" ), "NIE invalid: wrong letter" );
assert.ok( !method( "114410598R" ), "NIE invalid: > 8 digits" );
assert.ok( !method( "11441059-R" ), "NIE invalid: dash" );
assert.ok( !method( "asdasdasd" ), "NIE invalid: all letters" );
assert.ok( !method( "11.144.059R" ), "NIE invalid: two dots" );
assert.ok( !method( "05.122.654R" ), "NIE invalid: starts with 0 and dots" );
assert.ok( !method( "5.122.654-R" ), "NIE invalid: dots and dash" );
assert.ok( !method( "05.122.654-R" ), "NIE invalid: starts with zero and dot and dash" );
} );

QUnit.test( "cifES", function( assert ) {
var method = methodTest( "cifES" );
assert.ok( method( "A58818501" ), "CIF valid" );
assert.ok( method( "A79082244" ), "CIF valid" );
assert.ok( method( "A60917978" ), "CIF valid" );
assert.ok( method( "A39000013" ), "CIF valid" );
assert.ok( method( "A28315182" ), "CIF valid" );
assert.ok( method( "A75409573" ), "CIF valid" );
assert.ok( method( "A34396994" ), "CIF valid" );
assert.ok( method( "A08153538" ), "CIF valid" );
assert.ok( method( "A09681396" ), "CIF valid" );
assert.ok( method( "A06706303" ), "CIF valid" );
assert.ok( method( "A66242173" ), "CIF valid" );
assert.ok( method( "A61416699" ), "CIF valid" );
assert.ok( method( "A99545444" ), "CIF valid" );
assert.ok( method( "A10407252" ), "CIF valid" );
assert.ok( method( "A76170885" ), "CIF valid" );
assert.ok( method( "A83535047" ), "CIF valid" );
assert.ok( method( "A46031969" ), "CIF valid" );
assert.ok( method( "A97252910" ), "CIF valid" );
assert.ok( method( "A79082244" ), "CIF valid" );
assert.ok( !method( "A7908224D" ), "CIF invalid: digit control must be a number (4)" );

assert.ok( method( "B71413892" ), "CIF valid" );
assert.ok( method( "B37484755" ), "CIF valid" );
assert.ok( method( "B15940893" ), "CIF valid" );
assert.ok( method( "B55429161" ), "CIF valid" );
assert.ok( method( "B93337087" ), "CIF valid" );
assert.ok( method( "B43522192" ), "CIF valid" );
assert.ok( method( "B38624334" ), "CIF valid" );
assert.ok( method( "G72102064" ), "CIF valid" );
assert.ok( method( "F41190612" ), "CIF valid" );
assert.ok( method( "J85081081" ), "CIF valid" );
assert.ok( method( "S98038813" ), "CIF valid" );
assert.ok( method( "G32937757" ), "CIF valid" );
assert.ok( method( "B21920426" ), "CIF valid" );
assert.ok( method( "B74940156" ), "CIF valid" );
assert.ok( method( "B46125746" ), "CIF valid" );
assert.ok( method( "C27827559" ), "CIF valid" );
assert.ok( method( "B67077537" ), "CIF valid" );
assert.ok( method( "B21283155" ), "CIF valid" );
assert.ok( method( "B57104176" ), "CIF valid" );
assert.ok( method( "B25060179" ), "CIF valid" );
assert.ok( method( "B06536338" ), "CIF valid" );
assert.ok( method( "B50964592" ), "CIF valid" );
assert.ok( method( "B15653330" ), "CIF valid" );
assert.ok( method( "B83524710" ), "CIF valid" );
assert.ok( !method( "B8352471J" ), "CIF invalid: digit control must be a number (0)" );

assert.ok( !method( "C27827551" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827552" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827553" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827554" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827555" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827556" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827557" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827558" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C27827550" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755A" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755B" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755C" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755D" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755E" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755F" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755G" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755H" ), "CIF invalid: wrong digit control" );
assert.ok( !method( "C2782755J" ), "CIF invalid: wrong digit control" );
assert.ok( method( "C2782755I" ), "CIF valid. Digit control can be either a number or letter" );
assert.ok( method( "C27827559" ), "CIF valid. Digit control can be either a number or letter" );

assert.ok( method( "E48911572" ), "CIF valid" );
assert.ok( method( "E93928703" ), "CIF valid" );
assert.ok( method( "E17472952" ), "CIF valid" );
assert.ok( !method( "E1747295B" ), "CIF invalid: digit control must be a number (2)" );

assert.ok( method( "F41190612" ), "CIF valid" );
assert.ok( method( "F4119061B" ), "CIF valid. Digit control can be either a number or letter" );

assert.ok( method( "G72102064" ), "CIF valid" );
assert.ok( method( "G32937757" ), "CIF valid" );
assert.ok( method( "G8984953C" ), "CIF valid" );
assert.ok( method( "G3370454E" ), "CIF valid" );
assert.ok( method( "G33704545" ), "CIF valid. Digit control can be either a number or letter" );

assert.ok( method( "H48911572" ), "CIF valid" );
assert.ok( method( "H93928703" ), "CIF valid" );
assert.ok( method( "H17472952" ), "CIF valid" );
assert.ok( !method( "H1747295B" ), "CIF invalid: digit control must be a number (2)" );

assert.ok( !method( "I48911572" ), "CIF invalid: starts with I" );

assert.ok( method( "J85081081" ), "CIF valid" );
assert.ok( method( "J8508108A" ), "CIF valid" );

assert.ok( method( "K3902238I" ), "CIF valid" );
assert.ok( !method( "K39022389" ), "CIF invalid. Digit control must be a letter (I)" );

assert.ok( method( "M9916080F" ), "CIF valid" );
assert.ok( method( "M1566151E" ), "CIF valid" );
assert.ok( method( "M15661515" ), "CIF valid" );
assert.ok( method( "M4778730D" ), "CIF valid" );

assert.ok( method( "N1172218H" ), "CIF valid" );
assert.ok( method( "N4094938J" ), "CIF valid" );
assert.ok( method( "N40949380" ), "CIF valid. Digit control can be either a number or letter" );

assert.ok( method( "P5141387J" ), "CIF valid" );
assert.ok( method( "P9803881C" ), "CIF valid" );
assert.ok( !method( "P98038813" ), "CIF invalid: digit control must be a letter (C)" );

assert.ok( method( "Q5141387J" ), "CIF valid" );
assert.ok( method( "Q9803881C" ), "CIF valid" );
assert.ok( !method( "Q98038813" ), "CIF invalid: digit control must be a letter (C)" );

assert.ok( method( "S5141387J" ), "CIF valid" );
assert.ok( method( "S9803881C" ), "CIF valid" );
assert.ok( !method( "S98038813" ), "CIF invalid: digit control must be a letter (C)" );
assert.ok( method( "s98038813" ), "CIF valid: lower case" );
assert.ok( !method( "K48911572" ), "CIF invalid: starts with K" );
assert.ok( !method( "L48911572" ), "CIF invalid: starts with L" );
assert.ok( !method( "M48911572" ), "CIF invalid: starts with M" );

assert.ok( !method( "X48911572" ), "CIF invalid: starts with X" );
assert.ok( !method( "Y48911572" ), "CIF invalid: starts with Y" );
assert.ok( !method( "Z48911572" ), "CIF invalid: starts with Z" );
assert.ok( !method( "M15661515" ), "CIF invalid" );
assert.ok( !method( "Z98038813" ), "CIF invalid: wrong letter" );
assert.ok( !method( "B 43522192" ), "CIF invalid: white spaces" );
assert.ok( !method( "43522192" ), "CIF invalid: missing letter" );
Expand Down

0 comments on commit e7422d6

Please sign in to comment.