Skip to content

Commit

Permalink
Merge pull request #84 from koblas/eg_tn
Browse files Browse the repository at this point in the history
fix: added EG/TN checking
  • Loading branch information
koblas committed Jul 7, 2023
2 parents 5c91eb8 + 15f5839 commit ea75bb0
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ All country validators are in the "namespace" of the ISO country code.
| Estonia | EE | IK | Person | Isikukood (Estonian Personcal ID number). |
| Estonia | EE | KMKR | Company | KMKR (Käibemaksukohuslase, Estonian VAT number) |
| Estonia | EE | Registrikood | Company | Registrikood (Estonian organisation registration code) |
| Egypt | EG | TN | Company | Tax Registration Number (الرقم الضريبي, Egypt tax number) |
| Ecuador | EC | RUC | Tax/Vat | Ecuadorian company tax number (Registro Único de Contribuyentes) |
| El Salvador | SV | NIT | Tax | Tax Identifier (Número de Identificación Tributaria) |
| Finland | FI | ALV | Company | ALV nro (Arvonlisäveronumero, Finnish VAT number) |
Expand Down
58 changes: 58 additions & 0 deletions bin/create-validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3

import os
import re
import os.path
import sys

basedir = os.path.dirname(__file__)

RE = r'{{\s*([a-zA-Z_]+[a-zA-Z0-9_])\s*}}'

def fatal(msg):
print(msg)
sys.exit(1)

def replace(tmpl, params):
return re.sub(RE, lambda match: params.get(match.group(1),''), tmpl)

def main(kind):
with open(os.path.join(basedir, 'tmpl', 'tin.ts')) as fd:
tinTmpl = fd.read()
with open(os.path.join(basedir, 'tmpl', 'tin.spec.ts')) as fd:
specTmpl = fd.read()

parts = os.path.split(kind)
if len(parts) != 2:
fatal("expected path should be COUNTRY/TIN")
if '.' in parts[1]:
fatal("found . in path")

root = os.path.join(basedir, '..', 'src', parts[0])

if not os.path.isdir(root):
os.mkdir(root)

tinFile = os.path.join(root, "{}.ts".format(parts[1]))
specFile = os.path.join(root, "{}.spec.ts".format(parts[1]))

if os.path.isfile(tinFile):
fatal("TIN file exists {}".format(os.path.normpath(tinFile)))
if os.path.isfile(specFile):
fatal("SPEC file exists {}".format(os.path.normpath(specFile)))

params = {
'country': parts[0],
'tincode': parts[1],
'tincode_upper': parts[1].upper(),
}

with open(tinFile, 'w') as fd:
fd.write(replace(tinTmpl, params))
with open(specFile, 'w') as fd:
fd.write(replace(specTmpl, params))
with open(os.path.join(root, 'index.ts'), 'a') as fd:
fd.write("export * as {tincode} from './{tincode}';\n".format(**params))


main(sys.argv[1])
34 changes: 34 additions & 0 deletions bin/tmpl/tin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { validate, format } from './{{tincode}}';
import { InvalidLength, InvalidChecksum } from '../exceptions';

describe('{{country}}/{{tincode}}', () => {
it('format:VALUE', () => {
const result = format('VALUE');

expect(result).toEqual('VALUE');
});

it('fvalidate:VALUE', () => {
const result = validate('VALUE');

expect(result.isValid && result.compact).toEqual('VALUE');
});

test.each(['VALUE1', 'VALUE2'])('validate:%s', value => {
const result = validate(value);

expect(result.isValid).toEqual(true);
});

it('validate:VALUE', () => {
const result = validate('VALUE');

expect(result.error).toBeInstanceOf(InvalidLength);
});

it('validate:VALUE', () => {
const result = validate('VALUE');

expect(result.error).toBeInstanceOf(InvalidChecksum);
});
});
81 changes: 81 additions & 0 deletions bin/tmpl/tin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* XYZZY (description).
*
* DESCRIPTION
*
* Source
* HERE
*
* ENTITY/PERSON
*/

import * as exceptions from '../exceptions';
import { strings } from '../util';
import { Validator, ValidateReturn } from '../types';
import { weightedSum } from '../util/checksum';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
return strings.cleanUnicode(input, ' -');
}

// const validRe = /^[PCGQV]{1}00[A-Z0-9]{8}$/;

// const ALPHABET = '0123456789X';

const impl: Validator = {
name: 'NAME',
localName: 'NAME',
abbreviation: '{{tincode_upper}}',

compact(input: string): string {
const [value, err] = clean(input);

if (err) {
throw err;
}

return value;
},

format(input: string): string {
const [value] = clean(input);

return value;
},

validate(input: string): ValidateReturn {
const [value, error] = clean(input);

if (error) {
return { isValid: false, error };
}
if (value.length !== 11) {
return { isValid: false, error: new exceptions.InvalidLength() };
}

// if (!validRe.test(value)) {
// return { isValid: false, error: new exceptions.InvalidFormat() };
// }

const [, front, check] = strings.splitAt(value, 1, 10);

const sum = weightedSum(front, {
weights: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
modulus: 11,
});

// if (ALPHABET[sum] !== check) {
// return { isValid: false, error: new exceptions.InvalidChecksum() };
// }

return {
isValid: true,
compact: value,
isIndividual: value[0] === 'P',
isCompany: value[0] !== 'P',
};
},
};

export const { name, localName, abbreviation, validate, format, compact } =
impl;
1 change: 1 addition & 0 deletions src/eg/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as tn from './tn';
44 changes: 44 additions & 0 deletions src/eg/tn.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { validate, format } from './tn';
import { InvalidLength, InvalidFormat } from '../exceptions';

describe('eg/tn', () => {
it('format:100531385', () => {
const result = format('100531385');

expect(result).toEqual('100-531-385');
});

it('fvalidate:100-531-385', () => {
const result = validate('100-531-385');

expect(result.isValid && result.compact).toEqual('100531385');
});

test.each([
'100-531-385',
'٣٣١-١٠٥-٢٦٨',
'421 – 159 – 723',
'431-134-189',
'432-600-132',
'455-466-138',
'455273677',
'٩٤٦-١٤٩-٢٠٠',
'۹٤۹-۸۹۱-۲۰٤',
])('validate:%s', value => {
const result = validate(value);

expect(result.isValid).toEqual(true);
});

it('validate:VV3456789', () => {
const result = validate('VV3456789');

expect(result.error).toBeInstanceOf(InvalidFormat);
});

it('validate:12345', () => {
const result = validate('12345');

expect(result.error).toBeInstanceOf(InvalidLength);
});
});
96 changes: 96 additions & 0 deletions src/eg/tn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Tax Registration Number (الرقم الضريبي, Egypt tax number).
*
* This number consists of 9 digits, usually separated into three groups
* using hyphens to make it easier to read, like XXX-XXX-XXX.
*
* Source
* https://emsp.mts.gov.eg:8181/EMDB-web/faces/authoritiesandcompanies/authority/website/SearchAuthority.xhtml?lang=en
*
* ENTITY
*/

import * as exceptions from '../exceptions';
import { strings } from '../util';
import { Validator, ValidateReturn } from '../types';

const ARABIC_NUMBERS_MAP: Record<string, string> = {
// Arabic-indic digits.
'٠': '0',
'١': '1',
'٢': '2',
'٣': '3',
'٤': '4',
'٥': '5',
'٦': '6',
'٧': '7',
'٨': '8',
'٩': '9',
// Extended arabic-indic digits.
'۰': '0',
'۱': '1',
'۲': '2',
'۳': '3',
'۴': '4',
'۵': '5',
'۶': '6',
'۷': '7',
'۸': '8',
'۹': '9',
};

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
// Normalize the arabic characters to ascii digits
const norm = input
.split('')
.map(c => ARABIC_NUMBERS_MAP[c] ?? c)
.join('');
return strings.cleanUnicode(norm, ' -/');
}

const impl: Validator = {
name: 'NAME',
localName: 'NAME',
abbreviation: 'TN',

compact(input: string): string {
const [value, err] = clean(input);

if (err) {
throw err;
}

return value;
},

format(input: string): string {
const [value] = clean(input);

return strings.splitAt(value, 3, 6).join('-');
},

validate(input: string): ValidateReturn {
const [value, error] = clean(input);

if (error) {
return { isValid: false, error };
}
if (value.length !== 9) {
return { isValid: false, error: new exceptions.InvalidLength() };
}

if (!strings.isdigits(value)) {
return { isValid: false, error: new exceptions.InvalidFormat() };
}

return {
isValid: true,
compact: value,
isIndividual: false,
isCompany: false,
};
},
};

export const { name, localName, abbreviation, validate, format, compact } =
impl;
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as DK from './dk';
import * as DO from './do';
import * as EC from './ec';
import * as EE from './ee';
import * as EG from './eg';
import * as ES from './es';
import * as FI from './fi';
import * as FR from './fr';
Expand Down Expand Up @@ -114,6 +115,7 @@ export const stdnum: Record<string, Record<string, Validator>> = {
DK,
EC,
EE,
EG,
ES,
FI,
FR,
Expand Down

0 comments on commit ea75bb0

Please sign in to comment.