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"]
}

2
LICENSE Normal file → Executable file
View File

@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

54
bower.json Normal file → Executable file
View File

@ -1,28 +1,28 @@
{
"name": "codice-fiscale-js",
"description": "Calcolo del codice fiscale",
"main": "dist/codice.fiscale.var.js",
"authors": [
"Luca Vandro",
"Walter Barbagallo"
],
"license": "MIT",
"keywords": [
"codice",
"fiscale",
"italian",
"social",
"security",
"number"
],
"homepage": "https://github.com/lucavandro/CodiceFiscaleJS",
"moduleType": [],
"ignore": [
"**/.*",
"lib",
"node_modules",
"bower_components",
"test",
"tests"
]
}
"name": "codice-fiscale-js",
"description": "Calcolo del codice fiscale",
"main": "dist/codice.fiscale.js",
"authors": [
"Luca Vandro",
"Walter Barbagallo"
],
"license": "MIT",
"keywords": [
"codice",
"fiscale",
"italian",
"social",
"security",
"number"
],
"homepage": "https://github.com/lucavandro/CodiceFiscaleJS",
"moduleType": [],
"ignore": [
"**/.*",
"lib",
"node_modules",
"bower_components",
"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>
</form>
<pre></pre>
<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>
<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
}

8048
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": [

27
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);
}
this.name = this.code.substr(3, 3);
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;

16
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()
]
}
}
@ -37,4 +45,4 @@ module.exports = createVariants({
'umd',
'amd'
]
}, createConfig)
}, createConfig)

2484
yarn.lock Executable file

File diff suppressed because it is too large Load Diff