Bug fix and example completion

This commit is contained in:
Valentina Vandro 2018-10-08 11:27:13 +02:00
parent cf50ad76c1
commit 50524230e9
40 changed files with 17701 additions and 20320 deletions

View File

@ -1,3 +0,0 @@
{
"presets": ["env"]
}

0
LICENSE Normal file → Executable file
View File

4
bower.json Normal file → Executable file
View File

@ -1,7 +1,7 @@
{
"name": "codice-fiscale-js",
"description": "Calcolo del codice fiscale",
"main": "dist/codice.fiscale.var.js",
"main": "dist/codice.fiscale.js",
"authors": [
"Luca Vandro",
"Walter Barbagallo"
@ -25,4 +25,4 @@
"test",
"tests"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,103 +0,0 @@
export const MONTH_CODES = [
'A',
'B',
'C',
'D',
'E',
'H',
'L',
'M',
'P',
'R',
'S',
'T',
];
export const CHECK_CODE_ODD = {
0: 1,
1: 0,
2: 5,
3: 7,
4: 9,
5: 13,
6: 15,
7: 17,
8: 19,
9: 21,
A: 1,
B: 0,
C: 5,
D: 7,
E: 9,
F: 13,
G: 15,
H: 17,
I: 19,
J: 21,
K: 2,
L: 4,
M: 18,
N: 20,
O: 11,
P: 3,
Q: 6,
R: 8,
S: 12,
T: 14,
U: 16,
V: 10,
W: 22,
X: 25,
Y: 24,
Z: 23,
};
export const CHECK_CODE_EVEN = {
0: 0,
1: 1,
2: 2,
3: 3,
4: 4,
5: 5,
6: 6,
7: 7,
8: 8,
9: 9,
A: 0,
B: 1,
C: 2,
D: 3,
E: 4,
F: 5,
G: 6,
H: 7,
I: 8,
J: 9,
K: 10,
L: 11,
M: 12,
N: 13,
O: 14,
P: 15,
Q: 16,
R: 17,
S: 18,
T: 19,
U: 20,
V: 21,
W: 22,
X: 23,
Y: 24,
Z: 25,
};
export const OMOCODIA_TABLE = {
0: 'L',
1: 'M',
2: 'N',
3: 'P',
4: 'Q',
5: 'R',
6: 'S',
7: 'T',
8: 'U',
9: 'V',
};
export const CHECK_CODE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

View File

@ -1,47 +0,0 @@
export function normalizeString(str) {
return str.trim()
.replace(new RegExp(/[àá]/g), 'a\'')
.replace(new RegExp(/[èé]/g), 'e\'')
.replace(new RegExp(/[ìí]/g), 'i\'')
.replace(new RegExp(/[òó]/g), 'o\'')
.replace(new RegExp(/[ùú]/g), 'u\'')
.toUpperCase();
}
export function daysInMonth(m, y) {
switch (m) {
case 1:
return (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0) ? 29 : 28;
case 8:
case 3:
case 5:
case 10:
return 30;
default:
return 31;
}
}
export function isValidDate(d, m, y) {
const month = m - 1;
return month >= 0 && month < 12 && d > 0 && d <= daysInMonth(month, y);
}
export function getValidDate(d, m, y) {
if (isValidDate(d, m, y)) {
return new Date(y, m - 1, d, 0, 0, 0, 0);
}
else {
throw new Error(`The date ${y}/${m}/${d} is not a valid date`);
}
}
export function extractVowels(str) {
return str.replace(/[^AEIOU]/gi, '');
}
export function extractConsonants(str) {
return str.replace(/[^BCDFGHJKLMNPQRSTVWXYZ]/gi, '');
}
export function pad(n, size = 2) {
let s = String(n);
while (s.length < size) {
s = `0${s}`;
}
return s;
}

2
dist/codice.fiscale.amd.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

2
dist/codice.fiscale.commonjs2.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

2
dist/codice.fiscale.umd.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

2
dist/codice.fiscale.var.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

7
example/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

12
example/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1
example/codice.fiscale.var.js Executable file

File diff suppressed because one or more lines are too long

View File

@ -1,29 +1,273 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="../dist/codice.fiscale.var.js"></script>
<link rel="stylesheet" href="bootstrap.min.css">
<title>CodiceFiscaleJS</title>
<script src="jquery-3.3.1.min.js"></script>
<script src="bootstrap.bundle.min.js"></script>
<script src="codice.fiscale.var.js"></script>
<!-- Place this tag in your head or just before your close body tag. -->
<script async defer src="https://buttons.github.io/buttons.js"></script>
<style>
body{
background-color: #f7f7f9;
}
</style>
</head>
<body>
<form action="">
<input type="text" name="cf" id="cf" placeholder="name"><br>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Codice Fiscale JS</a>
</nav>
<div class="jumbotron jumbotron-fluid text-center">
<div class="container">
<h1 class="display-4 text-success">Codice Fiscale JS</h1>
<p class="lead">The Italian Tax Code Library for Javascript and Typescript</p>
<hr class="my-4">
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/lucavandro/CodiceFiscaleJs" data-size="large" data-show-count="true" aria-label="Star lucavandro/CodiceFiscaleJs on GitHub">Star</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/lucavandro/CodiceFiscaleJs/issues" data-size="large" data-show-count="true" aria-label="Issue lucavandro/CodiceFiscaleJs on GitHub">Issue</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/lucavandro/CodiceFiscaleJs/archive/master.zip" data-size="large" aria-label="Download lucavandro/CodiceFiscaleJs on GitHub">Download</a>
</div>
</div>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1>Codice Fiscale </h1>
<form id="cf-direct">
<div class="form-row">
<div class="form-group col-md-6">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" placeholder="Name" name="name" value="John" required>
</div>
<div class="form-group col-md-6">
<label for="surname">Surname</label>
<input type="text" class="form-control" id="surname" placeholder="Surname" name="surname" value="Doe" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="birthday">Gender</label>
<select class="custom-select" name="gender" id="gender" required>
<option selected value="M">Male</option>
<option value="F">Female</option>
</select>
</div>
<div class="form-group col-md-6">
<label for="birthday">Birthday</label>
<input type="date" class="form-control" id="birthday" name="birthday" value="1987-04-07" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="birthday">Birthplace province</label>
<select class="custom-select" name="birthplace_province" id="birthplace_province" required>
</select>
</div>
<div class="form-group col-md-6">
<label for="birthday">Birthplace</label>
<select class="custom-select" name="birthplace" id="birthplace" required>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-danger">Reset</button>
</form>
<pre></pre>
<hr>
<div class="form-row">
<div class="form-group col-md-12">
<label for="cf">Codice Fiscale</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="cf-1" readonly>
<div class="input-group-append">
<button class="btn btn-outline-primary" type="button" id="copy">Copy</button>
</div>
</div>
</div>
</div>
<div class="alert alert-success alert-dismissible fade" id="alert">
Codice fiscale copied to your clipboard!!!
<button type="button" class="close" id="copy-alert-btn" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<hr>
</div>
</div>
<div class="jumbotron jumbotron-fluid" id="reverse">
<div class="container">
<h1>Reverse Codice Fiscale</h1>
<form id="cf-reverse">
<div class="form-row">
<div class="form-group col-md-12">
<label for="cf">Codice Fiscale</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="cf-2">
<div class="input-group-append">
<button class="btn btn-outline-primary" type="submit">Reverse</button>
</div>
<div class="invalid-feedback">
Invalid Codice Fiscale
</div>
</div>
</div>
</div>
</form>
<div class="form-row">
<div class="form-group col-md-6">
<label for="name">Name</label>
<input type="text" class="form-control" id="name-reverse" readonly>
</div>
<div class="form-group col-md-6">
<label for="surname">Surname</label>
<input type="text" class="form-control" id="surname-reverse" readonly>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="birthday">Gender</label>
<input type="text" class="form-control" id="gender-reverse" readonly>
</div>
<div class="form-group col-md-6">
<label for="birthday">Birthday</label>
<input type="text" class="form-control" id="birthday-reverse" readonly>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="birthday">Birthplace province</label>
<input type="text" class="form-control" id="birthplace_province-reverse" readonly>
</div>
<div class="form-group col-md-6">
<label for="birthday">Birthplace</label>
<input type="text" class="form-control" id="birthplace-reverse" readonly>
</div>
</div>
<hr>
</div>
</div>
<div class="jumbotron jumbotron-fluid" id="validate">
<div class="container">
<h1>Validate Codice Fiscale</h1>
<form id="cf-validate">
<div class="form-row">
<div class="form-group col-md-12">
<label for="cf">Codice Fiscale</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="cf-3">
<div class="input-group-append">
<button class="btn btn-outline-primary" type="submit">Validate</button>
</div>
<div class="invalid-feedback">
Invalid Codice Fiscale
</div>
<div class="valid-feedback">
Valid Codice Fiscale
</div>
</div>
</div>
</div>
</form>
<hr>
</div>
</div>
<script>
$(function(){
$("#cf").change(function(){
var cf = new CodiceFiscale($(this).val());
$('pre').text(cf.toJSON());
/*
* COMPUTE CODICE FISCALE
*/
CodiceFiscale.utils.birthplaceFields("#birthplace_province", "#birthplace");
$("#birthplace_province").val("EE").change();
setTimeout(()=>$("#birthplace").val("Z404"), 1000);
$('form#cf-direct').submit(function(ev){
ev.preventDefault();
let cf = new CodiceFiscale({
name : $("#name").val(),
surname : $("#surname").val(),
gender : $("#gender").val(),
birthday : $("#birthday").val(),
birthplace : $("#birthplace").val()
});
$("#cf-1").val(cf.toString());
});
$("#copy").click(function(){
copyToClipboard($("#cf-1").val());
$('#alert').alert().addClass("show");
setTimeout(()=>$('#alert').removeClass("show"), 3000);
});
$("#copy-alert-btn").click(function(){
$('#alert').removeClass("show");
})
/*
* REVERSE CODICE FISCALE
*/
$("#cf-reverse").submit(function(e){
e.preventDefault();
let cf = $("#cf-reverse #cf-2").val();
if(CodiceFiscale.check(cf)){
$("#cf-reverse #cf-2").removeClass('is-invalid');
let cfData = CodiceFiscale.computeInverse(cf);
$("#name-reverse").val(cfData.name);
$("#surname-reverse").val(cfData.surname);
$("#gender-reverse").val(cfData.gender);
$("#birthplace-reverse").val(cfData.birthplace);
$("#birthplace_province-reverse").val(cfData.birthplaceProvincia);
$("#birthday-reverse").val(cfData.birthday);
} else {
$("#cf-reverse #cf-2").addClass('is-invalid');
$("#cf-reverse").reset();
}
});
/*
* Validate CODICE FISCALE
*/
$("#cf-validate").submit(function(e){
e.preventDefault();
$("#cf-validate #cf-3").removeClass('is-valid is-invalid');
let cf = $("#cf-validate #cf-3").val();
if(CodiceFiscale.check(cf)){
$("#cf-validate #cf-3").addClass('is-valid');
} else {
$("#cf-validate #cf-3").addClass('is-invalid');
}
});
});
function copyToClipboard (str) {
const el = document.createElement('textarea');
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
};
</script>
</body>
</html>

2
example/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,11 +0,0 @@
{
"spec_dir": "test",
"spec_files": [
"**/*[sS]pec.ts"
],
"helpers": [
"../node_modules/ts-node/register/type-check.js"
],
"stopSpecOnExpectationFailure": false,
"random": true
}

7988
package-lock.json generated

File diff suppressed because it is too large Load Diff

47
package.json Normal file → Executable file
View File

@ -1,43 +1,34 @@
{
"name": "codice-fiscale-js",
"version": "2.0.1",
"description": "CodiceFiscale.js is a utility library to compute and validate Italian Tax code (codice fiscale).",
"version": "2.1.0",
"description": "The Italian Tax Code Library for Javascript and Typescript",
"main": "dist/codice.fiscale.umd.js",
"types": "types/main.d.ts",
"repository": {
"type": "git",
"url": "git://github.com/lucavandro/CodiceFiscaleJS.git"
},
"directories": {
"test": "test"
},
"engines": {
"node": ">= 8.9.0"
"test": "tests"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@types/jasmine": "^2.8.8",
"@types/node": "^8.10.29",
"babel-loader": "^8.0.2",
"babel-preset-minify": "^0.4.3",
"jasmine": "^3.2.0",
"parallel-webpack": "^2.3.0",
"rimraf": "~2.6.2",
"ts-loader": "^4.5.0",
"ts-node": "^7.0.1",
"tslint": "~5.11.0",
"tslint-microsoft-contrib": "~5.1.0",
"tsutils": "~3.0.0",
"typescript": "^3.0.3",
"webpack": "^4.17.1",
"webpack-cli": "^3.1.0"
"babel-core": "^6.26.3",
"babel-jest": "^22.0.1",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.7.0",
"eslint": "^3.0.0",
"jest": "^22.0.1",
"parallel-webpack": "^2.2.0",
"regenerator-runtime": "^0.11.1",
"request": "^2.88.0",
"standard": "^10.0.3",
"webpack": "^4.20.2"
},
"scripts": {
"clean": "rimraf build types dist",
"build": "npm run clean && tsc -p tsconfig.json && webpack",
"lint": "tslint -t stylish --project \"tsconfig.json\"",
"test": "jasmine JASMINE_CONFIG_PATH=jasmine.json"
"build": "webpack && cp ./dist/codice.fiscale.var.js ./example/",
"clean": "rm ./example/codice.fiscale.var.js",
"pretest": "npm run build",
"test": "standard && jest",
"fix": "standard --fix"
},
"author": "Luca Vandro <lucavandro@gmail.com>",
"contributors": [

23
build/js/main.js → src/codice-fiscale.js Normal file → Executable file
View File

@ -1,7 +1,8 @@
import { Comune } from './comune';
import { CHECK_CODE_CHARS, CHECK_CODE_EVEN, CHECK_CODE_ODD, MONTH_CODES, OMOCODIA_TABLE } from './constants';
import { extractConsonants, extractVowels, getValidDate } from './utils';
export class CodiceFiscale {
import { extractConsonants, extractVowels, getValidDate, birthplaceFields } from './utils';
class CodiceFiscale {
get day() {
return this.birthday.getDate();
}
@ -47,7 +48,7 @@ export class CodiceFiscale {
this.name = cfData.name;
this.surname = cfData.surname;
this.gender = this.checkGender(cfData.gender);
this.birthday = getValidDate(cfData.day, cfData.month, cfData.year);
this.birthday = cfData.birthday ? getValidDate(cfData.birthday) : getValidDate(cfData.day, cfData.month, cfData.year);
this.birthplace = new Comune(cfData.birthplace, cfData.birthplaceProvincia);
this.compute();
}
@ -96,9 +97,7 @@ export class CodiceFiscale {
name: this.name,
surname: this.surname,
gender: this.gender,
day: this.day,
month: this.month,
year: this.year,
birthday: this.birthday.toISOString().slice(0,10),
birthplace: this.birthplace.nome,
birthplaceProvincia: this.birthplace.prov,
cf: this.code,
@ -137,12 +136,10 @@ export class CodiceFiscale {
this.code = code;
}
reverse() {
if (this.name !== undefined) {
this.name = this.code.substr(3, 3);
}
if (this.surname !== undefined) {
this.surname = this.code.substr(0, 3);
}
const yearCode = this.code.substr(6, 2);
const year19XX = parseInt(`19${yearCode}`, 10);
const year20XX = parseInt(`20${yearCode}`, 10);
@ -199,3 +196,9 @@ export class CodiceFiscale {
return String(year + month + dayStr);
}
}
CodiceFiscale.utils = {
birthplaceFields : birthplaceFields
}
module.exports = CodiceFiscale

49
build/js/comune.js → src/comune.js Normal file → Executable file
View File

@ -1,26 +1,33 @@
import { COMUNI } from './comuni';
import { COMUNI } from './geo-data';
import { normalizeString } from './utils';
export class Comune {
get nomeNorm() {
return normalizeString(this.nome);
}
constructor(nome, prov, cc, check = true) {
this.nome = nome;
this.prov = prov;
this.cc = cc;
if (check || this.cc === undefined || this.prov === undefined) {
if (check || cc === undefined || prov === undefined) {
let comune;
comune = this.prov !== undefined ? this.searchByNameAndProvince(this.nome, this.prov) : this.searchByName(this.nome);
if (comune === undefined) {
throw new Error(`Comune with name ${this.nome} doesn't exist`);
comune = prov !== undefined ? this.searchByNameAndProvince(nome, prov) : this.searchByName(nome);
if (comune === undefined && nome.length === 4) {
comune = this.searchByCC(nome);
}
else if (this.cc !== undefined && comune.cc !== this.cc) {
throw new Error(`Comune with cc ${this.cc} doesn't exist`);
if (comune === undefined) {
throw new Error(`Comune with name ${nome} doesn't exist`);
}
else if (cc !== undefined && comune.cc !== cc) {
throw new Error(`Comune with cc ${cc} doesn't exist`);
}
else {
this.nome = comune.nome;
this.prov = comune.prov;
this.cc = comune.cc;
}
} else {
this.nome = nome;
this.prov = prov;
this.cc = cc;
}
}
static GetByName(name, prov) {
@ -39,6 +46,16 @@ export class Comune {
}
throw new Error(`Comune with cc ${cc} doesn't exist`);
}
searchByCC(cc) {
let result;
try{
result = Comune.GetByCC(cc);
}catch(e){}
if (result !== undefined) {
return result.toJSON();
}
}
searchByName(nome) {
const query = normalizeString(nome);
let left = 0;
@ -70,9 +87,6 @@ export class Comune {
else if (result.length > 1) {
throw new Error(`Comune with name of ${nome} is found in more than one province. Please specify the province code`);
}
else {
throw new Error(`Comune with name of ${nome} doesn't exists`);
}
}
searchByNameAndProvince(nome, prov) {
const query = normalizeString(nome);
@ -108,4 +122,13 @@ export class Comune {
throw new Error(`Comune with name of ${nome} and prov ${prov} doesn't exists`);
}
}
toJSON(){
return {
cc : this.cc,
nome : this.nome,
prov : this.prov
}
}
}

View File

@ -1,125 +0,0 @@
import { COMUNI } from './comuni';
import { normalizeString } from './utils';
export interface IComuneObject {
nome: string;
prov?: string;
cc?: string;
}
/**
* Comune class
*/
export class Comune {
public nome: string;
public prov: string;
public cc: string;
public get nomeNorm() : string {
return normalizeString(this.nome);
}
constructor (nome: string, prov?: string, cc?: string, check: boolean = true) {
this.nome = nome;
this.prov = prov;
this.cc = cc;
if (check || this.cc === undefined || this.prov === undefined) {
let comune : IComuneObject;
comune = this.prov !== undefined ? this.searchByNameAndProvince(this.nome, this.prov) : this.searchByName(this.nome);
if (comune === undefined) {
throw new Error(`Comune with name ${this.nome} doesn't exist`);
} else if (this.cc !== undefined && comune.cc !== this.cc) {
throw new Error(`Comune with cc ${this.cc} doesn't exist`);
} else {
this.prov = comune.prov;
this.cc = comune.cc;
}
}
}
// tslint:disable-next-line:function-name member-access
static GetByName(name: string, prov? : string): Comune {
return new Comune(name, prov);
}
// tslint:disable-next-line:function-name member-access
static GetByCC(cc: string): Comune {
let result;
for (const item of COMUNI) {
if (item[0] === cc) {
result = item;
break;
}
}
if (result !== undefined) {
return new Comune(result[2], result[1], result[0], false);
}
throw new Error(`Comune with cc ${cc} doesn't exist`);
}
private searchByName(nome: string): IComuneObject {
const query = normalizeString(nome);
let left : number = 0;
let right : number = COMUNI.length - 1;
const result = [];
while (left <= right) {
const middle = Math.floor((left + right) / 2);
const currentItem = COMUNI[middle];
if (query === currentItem[2]) {
result.push(currentItem);
// Search for comuni with the same name in the neighbourhood +/- 1
if (middle > 0 && COMUNI[middle - 1][2] === query) {
result.push(COMUNI[middle - 1]);
} else if (middle < COMUNI.length - 1 && COMUNI[middle + 1][2] === query) {
result.push(COMUNI[middle + 1]);
}
break;
} else if (query < currentItem[2]) {
right = middle - 1;
} else {
left = middle + 1;
}
}
if (result.length === 1) {
return { cc : result[0][0], prov : result[0][1], nome: result[0][2] };
} else if (result.length > 1) {
throw new Error(`Comune with name of ${nome} is found in more than one province. Please specify the province code`);
} else {
throw new Error(`Comune with name of ${nome} doesn't exists`);
}
}
private searchByNameAndProvince(nome: string, prov: string): IComuneObject {
const query = normalizeString(nome);
let left : number = 0;
let right : number = COMUNI.length - 1;
let result;
while (left <= right) {
const middle = Math.floor((left + right) / 2);
const currentItem = COMUNI[middle];
if (query === currentItem[2]) {
if (prov === currentItem[1]) {
result = currentItem;
// Search for comuni with the same name in the neighbourhood +/- 1
} else if (middle > 0 && COMUNI[middle - 1][2] === query && prov === COMUNI[middle - 1][1]) {
result = COMUNI[middle - 1];
} else if (middle < COMUNI.length - 1 && COMUNI[middle + 1][2] === query && prov === COMUNI[middle + 1][1]) {
result = COMUNI[middle + 1];
}
break;
} else if (query < currentItem[2]) {
right = middle - 1;
} else {
left = middle + 1;
}
}
if (result !== undefined) {
return { cc : result[0], prov : result[1], nome: result[2] };
} else {
throw new Error(`Comune with name of ${nome} and prov ${prov} doesn't exists`);
}
}
}

File diff suppressed because it is too large Load Diff

18
src/constants.ts → src/constants.js Normal file → Executable file
View File

@ -10,8 +10,8 @@ export const MONTH_CODES = [
'P',
'R',
'S',
'T',
];
'T'
]
export const CHECK_CODE_ODD = {
0: 1,
@ -49,8 +49,8 @@ export const CHECK_CODE_ODD = {
W: 22,
X: 25,
Y: 24,
Z: 23,
};
Z: 23
}
export const CHECK_CODE_EVEN = {
0: 0,
@ -88,8 +88,8 @@ export const CHECK_CODE_EVEN = {
W: 22,
X: 23,
Y: 24,
Z: 25,
};
Z: 25
}
export const OMOCODIA_TABLE = {
0: 'L',
@ -101,7 +101,7 @@ export const OMOCODIA_TABLE = {
6: 'S',
7: 'T',
8: 'U',
9: 'V',
};
9: 'V'
}
export const CHECK_CODE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
export const CHECK_CODE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

8567
src/geo-data.js Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,264 +0,0 @@
import { Comune } from './comune';
import { CHECK_CODE_CHARS, CHECK_CODE_EVEN, CHECK_CODE_ODD, MONTH_CODES, OMOCODIA_TABLE} from './constants';
import { extractConsonants, extractVowels, getValidDate } from './utils';
export interface ICodiceFiscaleObject {
name: string;
surname: string;
gender: string;
day: number;
month: number;
year: number;
birthplace: string;
birthplaceProvincia: string;
cf?: string;
}
/**
* Compute and validate Italian Italian Tax code (codice fiscale).
*/
export class CodiceFiscale {
public birthday: Date;
public birthplace : Comune;
public name: string;
public surname: string;
public gender: string;
private code: string;
get day(): number {
return this.birthday.getDate();
}
set day(d: number) {
this.birthday.setDate(d);
}
get month(): number {
return this.birthday.getMonth() + 1;
}
set month(m: number) {
this.birthday.setMonth(m - 1);
}
get year(): number {
return this.birthday.getFullYear();
}
set year(y: number) {
this.birthday.setFullYear(y);
}
get cf() : string {
return this.code;
}
get nameCode(): string {
return this.code.substr(3, 3);
}
get surnameCode(): string {
return this.code.substr(0, 3);
}
get checkCode() : string {
return this.code.substr(15, 1);
}
// tslint:disable-next-line:no-any
constructor(data: string | ICodiceFiscaleObject | object) {
if (typeof data === 'string') {
if (CodiceFiscale.check(data)) {
this.code = data;
this.reverse();
} else {
throw new Error('Provided input is not a valid Codice Fiscale');
}
} else if (typeof data === 'object') {
const cfData = <ICodiceFiscaleObject>data;
this.name = cfData.name;
this.surname = cfData.surname;
this.gender = this.checkGender(cfData.gender);
this.birthday = getValidDate(cfData.day, cfData.month, cfData.year);
this.birthplace = new Comune(cfData.birthplace, cfData.birthplaceProvincia);
this.compute();
} else {
throw new Error('Comune constructor accept either a string or a plain object. Check the documentation');
}
}
// tslint:disable-next-line:function-name member-access
static getCheckCode (codiceFiscale: string) : string {
let val = 0 ;
for (let i = 0; i < 15; i = i + 1) {
const c = codiceFiscale[i];
val += i % 2 !== 0 ? <number>CHECK_CODE_EVEN[c] : <number>CHECK_CODE_ODD[c];
}
val = val % 26;
return CHECK_CODE_CHARS.charAt(val);
}
// tslint:disable-next-line:function-name member-access
static findLocationCode (name : string, prov?: string) {
return new Comune(name, prov).cc;
}
// tslint:disable-next-line:function-name member-access
static computeInverse (codiceFiscale: string) : ICodiceFiscaleObject {
return new CodiceFiscale(codiceFiscale).toJSON();
}
// tslint:disable-next-line:function-name member-access
static compute (obj : object) {
return new CodiceFiscale(obj).toString();
}
// tslint:disable-next-line:function-name member-access
static check (codiceFiscale: string) {
if (typeof codiceFiscale !== 'string') {
return false;
}
let cf = codiceFiscale.toUpperCase();
if (cf.length !== 16) {
return false;
}
const expectedCheckCode = codiceFiscale.charAt(15);
cf = codiceFiscale.slice(0, 15);
return CodiceFiscale.getCheckCode(cf) === expectedCheckCode;
}
// tslint:disable-next-line:function-name member-access
static getOmocodie(cf : string) {
return new CodiceFiscale(cf).omocodie();
}
public toString(): string {
return this.code;
}
public toJSON(): ICodiceFiscaleObject {
return {
name: this.name,
surname: this.surname,
gender: this.gender,
day: this.day,
month: this.month,
year: this.year,
birthplace: this.birthplace.nome,
birthplaceProvincia: this.birthplace.prov,
cf: this.code,
};
}
public isValid () : boolean {
if (typeof this.code !== 'string') {
return false;
}
this.code = this.code.toUpperCase();
if (this.code.length !== 16) {
return false;
}
const expectedCheckCode = this.code.charAt(15);
const cf = this.code.slice(0, 15);
return CodiceFiscale.getCheckCode(cf) === expectedCheckCode;
}
public omocodie () : string[] {
const results = [];
let lastOmocode = (this.code = this.code.slice(0, 15));
for (let i = this.code.length - 1; i >= 0; i = i - 1) {
const char = this.code[i];
if (char.match(/\d/) !== null) {
lastOmocode = `${lastOmocode.substr(0, i)}${OMOCODIA_TABLE[char]}${lastOmocode.substr(i + 1)}`;
results.push(lastOmocode + CodiceFiscale.getCheckCode(lastOmocode));
}
}
return results;
}
public compute() {
let code = this.getSurnameCode();
code += this.getNameCode();
code += this.dateCode();
code += this.birthplace.cc;
code += CodiceFiscale.getCheckCode(code);
this.code = code;
}
public reverse() {
if (this.name !== undefined) {
this.name = this.code.substr(3, 3);
}
if (this.surname !== undefined) {
this.surname = this.code.substr(0, 3);
}
const yearCode = this.code.substr(6, 2);
const year19XX = parseInt(`19${yearCode}`, 10);
const year20XX = parseInt(`20${yearCode}`, 10);
const currentYear20XX = new Date().getFullYear();
const year = year20XX > currentYear20XX ? year19XX : year20XX;
const monthChar = this.code.substr(8, 1);
const month = MONTH_CODES.indexOf(monthChar);
this.gender = 'M';
let day = parseInt(this.code.substr(9, 2), 10);
if (day > 31) {
this.gender = 'F';
day = day - 40;
}
this.birthday = new Date(year, month, day, 0, 0, 0, 0);
const cc = this.code.substr(11, 4);
this.birthplace = Comune.GetByCC(cc);
return this.toJSON();
}
private checkGender(gender?: string) {
this.gender = gender !== undefined ? gender.toUpperCase() : this.gender.toUpperCase();
if (typeof this.gender !== 'string') {
throw new Error('Gender must be a string');
}
if (this.gender !== 'M' && this.gender !== 'F') {
throw new Error('Gender must be either \'M\' or \'F\'');
}
return gender;
}
private getSurnameCode () : string {
const codeSurname = `${extractConsonants(this.surname)}${extractVowels(this.surname)}XXX`;
return codeSurname.substr(0, 3).toUpperCase();
}
private getNameCode () : string {
let codNome = extractConsonants(this.name);
if (codNome.length >= 4) {
codNome = codNome.charAt(0) + codNome.charAt(2) + codNome.charAt(3);
} else {
codNome += `${extractVowels(this.name)}XXX`;
codNome = codNome.substr(0, 3);
}
return codNome.toUpperCase();
}
private dateCode () : string {
// Padding year
let year = `0${this.birthday.getFullYear()}`;
year = year.substr(year.length - 2, 2);
const month = MONTH_CODES[this.birthday.getMonth()];
let day = this.birthday.getDate();
if (this.gender.toUpperCase() === 'F') {
day += 40;
}
// Padding day
let dayStr = `0${day}`;
dayStr = dayStr.substr(dayStr.length - 2, 2);
return String(year + month + dayStr);
}
}

91
src/utils.js Normal file
View File

@ -0,0 +1,91 @@
import { PROVINCE, COMUNI } from './geo-data';
export function normalizeString(str) {
return str.trim()
.replace(new RegExp(/[àá]/g), 'a\'')
.replace(new RegExp(/[èé]/g), 'e\'')
.replace(new RegExp(/[ìí]/g), 'i\'')
.replace(new RegExp(/[òó]/g), 'o\'')
.replace(new RegExp(/[ùú]/g), 'u\'')
.toUpperCase();
}
export function daysInMonth(m, y) {
switch (m) {
case 1:
return (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0) ? 29 : 28;
case 8:
case 3:
case 5:
case 10:
return 30;
default:
return 31;
}
}
export function isValidDate(d, m, y) {
const month = m - 1;
return month >= 0 && month < 12 && d > 0 && d <= daysInMonth(month, y);
}
export function getValidDate(d, m, y) {
if(typeof d === 'string' && m === undefined && y === undefined){
return new Date(d);
}else if (isValidDate(d, m, y)) {
return new Date(y, m - 1, d, 0, 0, 0, 0);
}
else {
throw new Error(`The date ${y}/${m}/${d} is not a valid date`);
}
}
export function extractVowels(str) {
return str.replace(/[^AEIOU]/gi, '');
}
export function extractConsonants(str) {
return str.replace(/[^BCDFGHJKLMNPQRSTVWXYZ]/gi, '');
}
export function pad(n, size = 2) {
let s = String(n);
while (s.length < size) {
s = `0${s}`;
}
return s;
}
export function birthplaceFields(provinceSelector, birthplaceSelector) {
const provinceSelect = document.querySelector(provinceSelector);
const birthplaceSelect = document.querySelector(birthplaceSelector);
const optGroupProv = document.createElement('optgroup');
const optGroupEE = document.createElement('optgroup');
optGroupEE.label = "-----------";
provinceSelect.appendChild(optGroupProv);
provinceSelect.appendChild(optGroupEE);
Object.keys(PROVINCE).forEach((code, i) => {
const name = PROVINCE[code];
const option = document.createElement('option');
option.value = code;
option.textContent = name;
if(code === 'EE'){
optGroupEE.appendChild(option);
} else {
optGroupProv.appendChild(option);
}
});
provinceSelect.onchange = (e)=>{
let province = provinceSelect.value;
while (birthplaceSelect.firstChild) {
birthplaceSelect.removeChild(birthplaceSelect.firstChild);
}
COMUNI.forEach((comune)=>{
let cc = comune[0], nome = comune[2], prov = comune[1];
if(prov === province){
let option = document.createElement('option');
option.value = cc;
option.textContent = nome.toLowerCase().replace(/\b\w/g, l => l.toUpperCase());
birthplaceSelect.appendChild(option);
}
});
}
provinceSelect.selectedIndex = "0";
provinceSelect.onchange();
}

View File

@ -1,74 +0,0 @@
export function normalizeString (str: string) {
return str.trim()
.replace(new RegExp(/[àá]/g), 'a\'')
.replace(new RegExp(/[èé]/g), 'e\'')
.replace(new RegExp(/[ìí]/g), 'i\'')
.replace(new RegExp(/[òó]/g), 'o\'')
.replace(new RegExp(/[ùú]/g), 'u\'')
.toUpperCase();
}
/**
* Get the number of days in any particular month
* @link https://stackoverflow.com/a/1433119/1293256
* @param {integer} m The month (valid: 0-11)
* @param {integer} y The year
* @return {integer} The number of days in the month
*/
export function daysInMonth(m: number, y: number) {
switch (m) {
case 1 :
return (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0) ? 29 : 28;
case 8 : case 3 : case 5 : case 10 :
return 30;
default :
return 31;
}
}
/**
* Check if a date is valid
* @link https://stackoverflow.com/a/1433119/1293256
* @param {[type]} d The day
* @param {[type]} m The month
* @param {[type]} y The year
* @return {Boolean} Returns true if valid
*/
export function isValidDate(d: number, m: number, y: number) {
const month = m - 1;
return month >= 0 && month < 12 && d > 0 && d <= daysInMonth(month, y);
}
/**
* Check if a date is valid
* @link https://stackoverflow.com/a/1433119/1293256
* @param {[type]} d The day
* @param {[type]} m The month
* @param {[type]} y The year
* @return {Boolean} Returns true if valid
*/
export function getValidDate(d: number, m: number, y: number) {
if (isValidDate(d, m, y)) {
return new Date(y, m - 1, d, 0, 0, 0, 0);
} else {
throw new Error(`The date ${y}/${m}/${d} is not a valid date`);
}
}
export function extractVowels (str: string) {
return str.replace(/[^AEIOU]/gi, '');
}
export function extractConsonants (str : string) {
return str.replace(/[^BCDFGHJKLMNPQRSTVWXYZ]/gi, '');
}
export function pad(n: number, size : number = 2) {
let s = String(n);
while (s.length < size) {
s = `0${s}`;
}
return s;
}

View File

@ -1,157 +0,0 @@
import { Comune } from '../src/comune';
import { CodiceFiscale } from '../src/main';
describe('Gli oggetti appartenenti alla classe Codice Fiscale', () => {
const c1 : CodiceFiscale = new CodiceFiscale({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Roma',
birthplaceProvincia: 'RM',
});
it('possono essere creati utilizzando una oggetto', () => {
const creaOggetto = () => new CodiceFiscale({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Roma',
birthplaceProvincia: 'RM',
});
expect(creaOggetto).not.toThrow();
});
it('una volta creati, calcaloano il codice fiscale, che può essere letto attraversi la proprietà cf o il metodo toString', () => {
expect(c1.cf).toBe('MRNLCU00A01H501J');
expect(c1.toString()).toBe('MRNLCU00A01H501J');
});
it('hanno la proprietà name', () => {
expect(c1.name).toBe('Luca');
});
it('hanno la proprietà surname', () => {
expect(c1.surname).toBe('Moreno');
});
it('hanno la proprietà gender', () => {
expect(c1.gender).toBe('M');
});
it('hanno la proprietà day', () => {
expect(c1.day).toBe(1);
});
it('hanno la proprietà month', () => {
expect(c1.month).toBe(1);
});
it('hanno la proprietà year', () => {
expect(c1.year).toBe(2000);
});
it('hanno la proprietà birtday', () => {
expect(c1.birthday).toEqual(new Date(2000, 0, 1, 0, 0, 0, 0));
});
it('hanno la proprietà comune', () => {
const comune = new Comune('Roma');
expect(c1.birthplace).toEqual(comune);
});
it('tutte le proprietà possono essere esportate utilizando il metodo toJSON()', () => {
expect(c1.toJSON()).toEqual({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Roma',
birthplaceProvincia: 'RM',
cf: 'MRNLCU00A01H501J',
});
});
});
describe('Gli oggetti appartenenti alla classe Codice Fiscale creati utilizzando una stringa con un codice fiscale', () => {
const c1 = new CodiceFiscale('MRNLCU00A01H501J');
it('possono essere creati', () => {
const creaOggetto1 = () => new CodiceFiscale('MRNLCU00A01H501J');
expect(creaOggetto1).not.toThrow();
});
it('lanciano un eccezione se la stringa passata al costruttore non è un codice fiscale valido', () => {
const creaOggetto2 = () => new CodiceFiscale('MRNLCU00A01H501K');
expect(creaOggetto2).toThrow();
});
it('non hanno la proprietà name', () => {
expect(c1.name).toBeUndefined();
});
it('non hanno la proprietà surname', () => {
expect(c1.surname).toBeUndefined();
});
it('hanno la proprietà gender', () => {
expect(c1.gender).toBe('M');
});
it('hanno la proprietà day', () => {
expect(c1.day).toBe(1);
});
it('hanno la proprietà month', () => {
expect(c1.month).toBe(1);
});
it('hanno la proprietà year', () => {
expect(c1.year).toBe(2000);
});
it('hanno la proprietà birtday', () => {
expect(c1.birthday).toEqual(new Date(2000, 0, 1));
});
it('hanno la proprietà comune', () => {
const comune = new Comune('ROMA');
expect(c1.birthplace).toEqual(comune);
});
it('hanno la proprietà year', () => {
expect(c1.year).toBe(2000);
});
it('calcola le omocodie dato un codice fiscale', () => {
const c2 = new CodiceFiscale('BNZVCN32S10E573Z');
const omocodie = c2.omocodie();
expect(omocodie).toContain('BNZVCN32S10E57PV');
expect(omocodie).toContain('BNZVCNPNSMLERTPX');
});
it('hanno la proprietà surnameCode', () => {
const c2 = new CodiceFiscale('BNZVCN32S10E573Z');
expect(c2.surnameCode).toEqual('BNZ');
});
it('hanno la proprietà nameCode', () => {
const c2 = new CodiceFiscale('BNZVCN32S10E573Z');
expect(c2.nameCode).toEqual('VCN');
});
it('hanno la proprietà checkCode', () => {
const c2 = new CodiceFiscale('BNZVCN32S10E573Z');
expect(c2.checkCode).toEqual('Z');
});
});

View File

@ -1,149 +0,0 @@
import { CodiceFiscale } from '../src/main';
describe('Codice Fiscale', () => {
it('esiste un oggetto chiamato CodiceFiscale', () => {
expect(CodiceFiscale).not.toBe(undefined);
});
});
describe('CodiceFiscale.getCheckCode', () => {
it('è definito', () => {
expect(CodiceFiscale.getCheckCode).not.toBe(undefined);
});
it('restituisce il corretto risultato', () => {
expect(CodiceFiscale.getCheckCode('MRNLCU00A01H501')).toBe('J');
});
});
describe('CodiceFiscale.compute', () => {
it('è definito', () => {
expect(CodiceFiscale.compute).not.toBe(undefined);
});
it('calcola il codice fiscale', () => {
expect(CodiceFiscale.compute({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Roma',
birthplaceProvincia: 'RM',
}))
.toBe('MRNLCU00A01H501J');
});
it('calcola il codice fiscale di persone nate all\'estero', () => {
expect(CodiceFiscale.compute({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Albania',
birthplaceProvincia: 'EE',
}))
.toBe('MRNLCU00A01Z100P');
});
it('se il comune non esiste lancia un eccezione', () => {
const comuneInventato = () => {
CodiceFiscale.compute({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Foo',
birthplaceProvincia: 'EE',
});
};
expect(comuneInventato).toThrow();
});
});
describe('CodiceFiscale.findLocationCode', () => {
it('è definito', () => {
expect(CodiceFiscale.findLocationCode).not.toBe(undefined);
});
it('trova il codice del comune', () => {
expect(CodiceFiscale.findLocationCode('Roma', 'RM')).toBe('H501');
});
});
describe('CodiceFiscale.check', () => {
it('è definito', () => {
expect(CodiceFiscale.check).not.toBe(undefined);
});
it('controlla se il codice fiscale è valido', () => {
expect(CodiceFiscale.check('MRNLCU00A01H501J')).toBe(true);
});
it('controlla che sia composto dal non più 16 valori alfanumerici', () => {
expect(CodiceFiscale.check('MRNLCU00A01H501JK')).toBe(false);
});
it('controlla che sia composto dal almeno 16 valori alfanumerici', () => {
expect(CodiceFiscale.check('MRNLCU00A01H501J3')).toBe(false);
});
it('controlla che il carattere di controllo sia esatto', () => {
expect(CodiceFiscale.check('VNDLDL87D07B963G')).toBe(false);
});
it('funziona anche in caso di omocodie', () => {
expect(CodiceFiscale.check('BNZVCN32S10E57PV')).toBe(true);
expect(CodiceFiscale.check('BNZVCNPNSMLERTPX')).toBe(true);
});
});
describe('CodiceFiscale.getOmocodie', () => {
it('è definito', () => {
expect(CodiceFiscale.getOmocodie).not.toBe(undefined);
});
it('calcola le omocodie dato un codice fiscale', () => {
const omocodie = CodiceFiscale.getOmocodie('BNZVCN32S10E573Z');
expect(omocodie).toContain('BNZVCN32S10E57PV');
expect(omocodie).toContain('BNZVCNPNSMLERTPX');
});
});
describe('Calcolo codice fiscale inverso -> metodo .computeInverse', () => {
it('è definito', () => {
expect(CodiceFiscale.computeInverse).not.toBe(undefined);
});
it('restituisce falso se l\'input è stringa formattata male', () => {
const notValid = () => {
CodiceFiscale.computeInverse('BNZVCN32SC0E573Z');
};
expect(notValid).toThrowError('Provided input is not a valid Codice Fiscale');
});
it('restituisce il genere corretto', () => {
expect(CodiceFiscale.computeInverse('MRNLCU00A01H501J').gender).toEqual('M');
});
it('restituisce la città natale corretta', () => {
expect(CodiceFiscale.computeInverse('MRNLCU00A01H501J').birthplace).toEqual('ROMA');
});
it('restituisce la provincia della città natale corretta', () => {
expect(CodiceFiscale.computeInverse('MRNLCU00A01H501J').birthplaceProvincia).toEqual('RM');
});
it('restituisce il giorno di nascita come numero compreso tra 1 e 31', () => {
expect(
CodiceFiscale.computeInverse('MRNLCU00A01H501J').day >= 1 &&
CodiceFiscale.computeInverse('MRNLCU00A01H501J').day <= 31,
).toBe(true);
});
});

View File

@ -1,62 +0,0 @@
import { Comune } from '../src/comune';
describe('Comune', () => {
it('crea nuove istanze utilizzando nome, provincia e codice catastale', () => {
// tslint:disable-next-line:no-console
const c1 = new Comune('Caserta', 'CE', 'B963');
expect(c1.nome).toEqual('Caserta');
expect(c1.prov).toEqual('CE');
expect(c1.cc).toEqual('B963');
});
it('esiste una classe chiamata comune', () => {
expect(Comune).toBeDefined();
expect(Comune.name).toEqual('Comune');
});
it('se nome, provincia o codice catastale sono errati genera un errore', () => {
expect(() => new Comune('Caserta', 'CE', 'A000')).toThrow();
expect(() => new Comune('Caserta', 'CU')).toThrow();
});
it('è possibile creare un\'istanza utilizzando solo il nome del comune', () => {
const c1 = Comune.GetByName('Caserta');
expect(c1.nome).toEqual('Caserta');
expect(c1.prov).toEqual('CE');
expect(c1.cc).toEqual('B963');
});
it('se ci sono due comuni con lo stesso nome e la provincia non viene specificata, il metodo .getByName genera un errore', () => {
expect(() => Comune.GetByName('Castro'))
.toThrowError('Comune with name of Castro is found in more than one province. Please specify the province code');
expect(() => Comune.GetByName('Castro', 'LE')).not.toThrowError();
expect(() => Comune.GetByName('Castro', 'BG')).not.toThrowError();
});
it('è possibile creare un\'istanza utilizzando solo il codice catastale', () => {
const c1 = Comune.GetByCC('B963');
expect(c1.nomeNorm).toEqual('CASERTA');
expect(c1.nome).toEqual('CASERTA');
expect(c1.prov).toEqual('CE');
expect(c1.cc).toEqual('B963');
});
it('trova il codice di un comune che contiene lettere accentate', () => {
const comune = new Comune('Riccò del golfo di Spezia', 'SP');
expect(comune.nome).toBe('Riccò del golfo di Spezia');
expect(comune.nomeNorm).toBe('RICCO\' DEL GOLFO DI SPEZIA');
expect(comune.prov).toBe('SP');
expect(comune.cc).toBe('H275');
});
it('trova il codice di un comune che contiene apostrofi', () => {
const comune = new Comune('Sant\'Angelo Romano', 'RM');
expect(comune.cc).toBe('I284');
});
it('consente di inserire stati esteri', () => {
const stato = new Comune('Albania', 'EE');
expect(stato.cc).toBe('Z100');
});
});

View File

@ -1,11 +0,0 @@
import { normalizeString } from '../src/utils';
describe('normalizeString', () => {
it('sostituisce gli accenti con gli apostrofi', () => {
expect(normalizeString('papà')).toBe('PAPA\'');
expect(normalizeString('purè')).toBe('PURE\'');
expect(normalizeString('pipì')).toBe('PIPI\'');
expect(normalizeString('popò')).toBe('POPO\'');
expect(normalizeString('pupù')).toBe('PUPU\'');
});
});

212
tests/index.spec.js Executable file
View File

@ -0,0 +1,212 @@
import CodiceFiscale from '../src/index'
let { describe, test, expect } = global
describe('Codice Fiscale', () => {
test('esiste un oggetto chiamato CodiceFiscale', () => {
expect(CodiceFiscale).not.toBe(undefined)
})
})
describe('CodiceFiscale.surnameCode', () => {
test('è definito', () => {
expect(CodiceFiscale.surnameCode).not.toBe(undefined)
})
test('restituisce il corretto risultato in caso di sufficienti consonanti', () => {
expect(CodiceFiscale.surnameCode('Moreno')).toBe('MRN')
})
test('restituisce il corretto risultato in caso di insufficienti consonanti', () => {
expect(CodiceFiscale.surnameCode('Julea')).toBe('JLU')
})
})
describe('CodiceFiscale.nameCode', () => {
test('è definito', () => {
expect(CodiceFiscale.nameCode).not.toBe(undefined)
})
test('restituisce il corretto risultato in caso di sufficienti consonanti', () => {
expect(CodiceFiscale.nameCode('Marco')).toBe('MRC')
})
test('restituisce il corretto risultato in caso di insufficienti consonanti', () => {
expect(CodiceFiscale.nameCode('Luca')).toBe('LCU')
})
})
describe('CodiceFiscale.dateCode', () => {
test('è definito', () => {
expect(CodiceFiscale.dateCode).not.toBe(undefined)
})
test('restituisce il corretto risultato', () => {
expect(CodiceFiscale.dateCode(1, 1, 2000, 'M')).toBe('00A01')
})
})
describe('CodiceFiscale.getCheckCode', () => {
test('è definito', () => {
expect(CodiceFiscale.getCheckCode).not.toBe(undefined)
})
test('restituisce il corretto risultato', () => {
expect(CodiceFiscale.getCheckCode('MRNLCU00A01H501')).toBe('J')
})
})
describe('CodiceFiscale.compute', () => {
test('è definito', () => {
expect(CodiceFiscale.compute).not.toBe(undefined)
})
test('calcola il codice fiscale', () => {
expect(CodiceFiscale.compute({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Roma',
birthplaceProvincia: 'RM'
}))
.toBe('MRNLCU00A01H501J')
})
test("calcola il codice fiscale di persone nate all'estero", () => {
expect(CodiceFiscale.compute({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Albania',
birthplaceProvincia: 'EE'
}))
.toBe('MRNLCU00A01Z100P')
})
test('se il comune non esiste lancia un eccezione', () => {
var comuneInventato = function () {
CodiceFiscale.compute({
name: 'Luca',
surname: 'Moreno',
gender: 'M',
day: 1,
month: 1,
year: 2000,
birthplace: 'Foo',
birthplaceProvincia: 'EE'
})
}
expect(comuneInventato).toThrowError('Location not found')
})
})
describe('CodiceFiscale.findLocationCode', () => {
test('è definito', () => {
expect(CodiceFiscale.findLocationCode).not.toBe(undefined)
})
test('trova il codice del comune', () => {
expect(CodiceFiscale.findLocationCode('Roma', 'RM')).toBe('H501')
})
test('trova il codice di un comune che contiene apostrofi', () => {
expect(CodiceFiscale.findLocationCode("Sant'Angelo Romano", 'RM')).toBe('I284')
})
test('trova il codice di un comune che contiene lettere accentate', () => {
expect(CodiceFiscale.findLocationCode('Riccò del Golfo di Spezia', 'SP')).toBe('H275')
})
test('se la provincia non esiste lancia un eccezione', () => {
var comuneInventato = function () {
CodiceFiscale.findLocationCode('Foo', 'Bar')
}
expect(comuneInventato).toThrowError('Area code not found')
})
test('se il comune non esiste lancia un eccezione', () => {
var comuneInventato = function () {
CodiceFiscale.findLocationCode('Foo', 'RM')
}
expect(comuneInventato).toThrowError('Location not found')
})
})
describe('CodiceFiscale.check', () => {
test('è definito', () => {
expect(CodiceFiscale.check).not.toBe(undefined)
})
test('controlla se il codice fiscale è valido', () => {
expect(CodiceFiscale.check('MRNLCU00A01H501J')).toBe(true)
})
test('controlla che sia composto dal non più 16 valori alfanumerici', () => {
expect(CodiceFiscale.check('MRNLCU00A01H501JK')).toBe(false)
})
test('controlla che sia composto dal almeno 16 valori alfanumerici', () => {
expect(CodiceFiscale.check('MRNLCU00A01H501J3')).toBe(false)
})
test('controlla che il carattere di controllo sia esatto', () => {
expect(CodiceFiscale.check('VNDLDL87D07B963G')).toBe(false)
})
test('funziona anche in caso di omocodie', () => {
expect(CodiceFiscale.check('BNZVCN32S10E57PV')).toBe(true)
expect(CodiceFiscale.check('BNZVCNPNSMLERTPX')).toBe(true)
})
})
describe('CodiceFiscale.getOmocodie', () => {
test('è definito', () => {
expect(CodiceFiscale.getOmocodie).not.toBe(undefined)
})
test('calcola le omocodie dato un codice fiscale', () => {
expect(CodiceFiscale.getOmocodie('BNZVCN32S10E573Z'))
.toEqual(expect.arrayContaining(['BNZVCN32S10E57PV', 'BNZVCNPNSMLERTPX']))
})
})
describe('Calcolo codice fiscale inverso -> metodo .computeInverse', () => {
test('è definito', () => {
expect(CodiceFiscale.computeInverse).not.toBe(undefined)
})
test("se l'input non è una stringa lancia un eccezione", () => {
var notAString = function () {
CodiceFiscale.computeInverse(0)
}
expect(notAString).toThrowError('Provided input is not a valid Codice Fiscale')
})
test("restituisce falso se l'input è stringa formattata male", () => {
var notValid = function () {
CodiceFiscale.computeInverse('BNZVCN32SC0E573Z')
}
expect(notValid).toThrowError('Provided input is not a valid Codice Fiscale')
})
test('restituisce il genere corretto', () => {
expect(CodiceFiscale.computeInverse('MRNLCU00A01H501J').gender).toEqual('M')
})
test('restituisce la città natale corretta', () => {
expect(CodiceFiscale.computeInverse('MRNLCU00A01H501J').birthplace).toEqual('ROMA')
})
test('restituisce la provincia della città natale corretta', () => {
expect(CodiceFiscale.computeInverse('MRNLCU00A01H501J').birthplaceProvincia).toEqual('RM')
})
test('restituisce il giorno di nascita come numero compreso tra 1 e 31', () => {
expect(CodiceFiscale.computeInverse('MRNLCU00A01H501J').day >= 1 && CodiceFiscale.computeInverse('MRNLCU00A01H501J').day <= 31).toBe(true)
})
})

View File

@ -1,17 +0,0 @@
{
"includes": [
"src/*.ts"
],
"exclude": [
"test/*.ts"
],
"compilerOptions": {
"noImplicitAny": false,
"target": "es6",
"declaration": true,
"declarationDir": "types",
"rootDir": "./src/",
"outDir": "build/js",
"removeComments": true
}
}

View File

@ -1,35 +0,0 @@
/**
* This starter project recommended using Microsoft TSLint rules.
* Please see https://github.com/Microsoft/tslint-microsoft-contrib for more details.
*/
{
"extends": [
"tslint-microsoft-contrib"
],
"rules": {
"no-unsafe-any": false,
"mocha-no-side-effect-code": false,
"missing-jsdoc": false,
"no-relative-imports": false,
"export-name": false,
"promise-function-async": false,
"no-void-expression": false,
"no-redundant-jsdoc": false,
"prefer-type-cast": false,
"trailing-comma": [ // https://palantir.github.io/tslint/rules/trailing-comma/
true,
{
"multiline": "always",
"singleline": "never"
}
],
"typedef": [
true,
"parameter",
"arrow-parameter",
"property-declaration",
"member-variable-declaration"
]
}
}

View File

@ -1,5 +1,6 @@
import { Comune } from './comune';
export interface ICodiceFiscaleObject {
import { birthplaceFields } from './utils';
interface ICodiceFiscaleObject {
name: string;
surname: string;
gender: string;
@ -10,13 +11,7 @@ export interface ICodiceFiscaleObject {
birthplaceProvincia: string;
cf?: string;
}
export declare class CodiceFiscale {
birthday: Date;
birthplace: Comune;
name: string;
surname: string;
gender: string;
private code;
declare class CodiceFiscale {
day: number;
month: number;
year: number;
@ -24,6 +19,15 @@ export declare class CodiceFiscale {
readonly nameCode: string;
readonly surnameCode: string;
readonly checkCode: string;
static utils: {
birthplaceFields: typeof birthplaceFields;
};
birthday: Date;
birthplace: Comune;
name: string;
surname: string;
gender: string;
private code;
constructor(data: string | ICodiceFiscaleObject | object);
static getCheckCode(codiceFiscale: string): string;
static findLocationCode(name: string, prov?: string): string;
@ -42,3 +46,4 @@ export declare class CodiceFiscale {
private getNameCode;
private dateCode;
}
export default CodiceFiscale;

1
types/comuni.d.ts vendored
View File

@ -1 +0,0 @@
export declare const COMUNI: string[][];

113
types/geo-data.d.ts vendored Normal file
View File

@ -0,0 +1,113 @@
export declare const COMUNI: string[][];
export declare const PROVINCE: {
AG: string;
AL: string;
AN: string;
AO: string;
AP: string;
AQ: string;
AR: string;
AT: string;
AV: string;
BA: string;
BG: string;
BI: string;
BL: string;
BN: string;
BO: string;
BR: string;
BS: string;
BT: string;
BZ: string;
CA: string;
CB: string;
CE: string;
CH: string;
CI: string;
CL: string;
CN: string;
CO: string;
CR: string;
CS: string;
CT: string;
CZ: string;
EN: string;
FC: string;
FE: string;
FG: string;
FI: string;
FM: string;
FR: string;
GE: string;
GO: string;
GR: string;
IM: string;
IS: string;
KR: string;
LC: string;
LE: string;
LI: string;
LO: string;
LT: string;
LU: string;
MB: string;
MC: string;
ME: string;
MI: string;
MN: string;
MO: string;
MS: string;
MT: string;
NA: string;
NO: string;
NU: string;
OG: string;
OR: string;
OT: string;
PA: string;
PC: string;
PD: string;
PE: string;
PG: string;
PI: string;
PN: string;
PO: string;
PR: string;
PT: string;
PU: string;
PV: string;
PZ: string;
RA: string;
RC: string;
RE: string;
RG: string;
RI: string;
RM: string;
RN: string;
RO: string;
SA: string;
SI: string;
SO: string;
SP: string;
SR: string;
SS: string;
SV: string;
TA: string;
TE: string;
TN: string;
TO: string;
TP: string;
TR: string;
TS: string;
TV: string;
UD: string;
VA: string;
VB: string;
VC: string;
VE: string;
VI: string;
VR: string;
VS: string;
VT: string;
VV: string;
};

1
types/utils.d.ts vendored
View File

@ -5,3 +5,4 @@ export declare function getValidDate(d: number, m: number, y: number): Date;
export declare function extractVowels(str: string): string;
export declare function extractConsonants(str: string): string;
export declare function pad(n: number, size?: number): string;
export declare function birthplaceFields(selector: string): void;

14
webpack.config.js Normal file → Executable file
View File

@ -5,7 +5,7 @@ const createVariants = require('parallel-webpack').createVariants
function createConfig (options) {
return {
entry: {
'codice.fiscale': './build/js/main.js'
'codice.fiscale': './src/codice-fiscale.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
@ -16,17 +16,25 @@ function createConfig (options) {
module: {
rules: [
{
test: /\.json$/,
loader: 'json-loader'
}, {
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env", "babel-preset-minify"]
presets: ['babel-preset-env']
}
}
}
]
}
},
plugins: [
new webpack
.optimize
.UglifyJsPlugin()
]
}
}

2484
yarn.lock Executable file

File diff suppressed because it is too large Load Diff