Skip to content

Commit

Permalink
Merge pull request #88 from koblas/tn_mf
Browse files Browse the repository at this point in the history
fix: added Tunisa MF number
  • Loading branch information
koblas committed Jul 10, 2023
2 parents 693e7f9 + 74d3b4f commit 2fac402
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ All country validators are in the "namespace" of the ISO country code.
| Thailand | TH | IDNR | Person | Thai National ID (บัตรประจำตัวประชาชนไทย) |
| Thailand | TH | MOA | Company | Thailand Memorandum of Association Number |
| Thailand | TH | TIN | Company/Person | Thai Tax ID (Moa or Idnr) |
| Tunisia | TN | MF | Vat | Matricule Fiscal, Tunisia tax number |
| Taiwan | TW | BAN | Company | Taiwanese Unified Business Number (統一編號) |
| Taiwan | TW | NATID | Person | National ID Card Number |
| Taiwan | TW | TAX_CODE | Person | Tax Code |
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import * as SK from './sk';
import * as SM from './sm';
import * as SV from './sv';
import * as TH from './th';
import * as TN from './tn';
import * as TR from './tr';
import * as TW from './tw';
import * as UA from './ua';
Expand Down Expand Up @@ -172,6 +173,7 @@ export const stdnum: Record<string, Record<string, Validator>> = {
SM,
SV,
TH,
TN,
TR,
TW,
UA,
Expand Down
1 change: 1 addition & 0 deletions src/tn/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as mf from './mf';
37 changes: 37 additions & 0 deletions src/tn/mf.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { validate, format } from './mf';
import { InvalidComponent, InvalidFormat } from '../exceptions';

describe('tn/mf', () => {
it('format:1496298 T P N 000', () => {
const result = format('1496298 T P N 000');

expect(result).toEqual('1496298/T/P/N/000');
});

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

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

test.each(['1234567/M/A/E/001', '1282182 W', '121J'])(
'validate:%s',
value => {
const result = validate(value);

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

it('validate:aaaaaaa/M/A/E/abc', () => {
const result = validate('aaaaaaa/M/A/E/abc');

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

it('validate:1234567/M/A/X/000', () => {
const result = validate('1234567/M/A/X/000');

expect(result.error).toBeInstanceOf(InvalidComponent);
});
});
169 changes: 169 additions & 0 deletions src/tn/mf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* MF (Matricule Fiscal, Tunisia tax number).
*
* The MF consists of 4 parts: the "identifiant fiscal", the "code TVA", the "code
* catégorie" and the "numéro d'etablissement secondaire".
*
* The "identifiant fiscal" consists of 2 parts: the "identifiant unique" and the
* "clef de contrôle". The "identifiant unique" is composed of 7 digits. The "clef
* de contrôle" is a letter, excluding "I", "O" and "U" because of their
* similarity to "1", "0" and "4".
*
* The "code TVA" is a letter that tells which VAT regime is being used. The valid
* values are "A", "P", "B", "D" and "N".
*
* The "code catégorie" is a letter that tells the category the contributor
* belongs to. The valid values are "M", "P", "C", "N" and "E".
*
* The "numéro d'etablissement secondaire" consists of 3 digits. It is usually
* "000", but it can be "001", "002"... depending on the branches. If it is not
* "000" then "code catégorie" must be "E".
*
* Source
* https://futurexpert.tn/2019/10/22/structure-et-utilite-du-matricule-fiscal/
* https://www.registre-entreprises.tn/
*
* VAT
*/

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

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

const validRe = /^(\d+)(.*)$/;

function compactImpl(input: string): ReturnType<typeof strings.cleanUnicode> {
const [value, err] = clean(input);

if (err) {
return ['', err];
}

const match = value.match(validRe);

if (match && match.length === 3) {
return [match[1].padStart(7, '0') + match[2], null];
}

return [value, null];
}

const VALID_CONTROL_KEYS = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'J',
'K',
'L',
'M',
'N',
'P',
'Q',
'R',
'S',
'T',
'V',
'W',
'X',
'Y',
'Z',
];
const VALID_TVA_CODES = ['A', 'P', 'B', 'D', 'N'];
const VALID_CATEGORY_CODES = ['M', 'P', 'C', 'N', 'E'];

const impl: Validator = {
name: 'Tunisia tax number',
localName: 'Matricule Fiscal',
abbreviation: 'MF',

compact(input: string): string {
const [value, error] = compactImpl(input);

if (error) {
throw error;
}

return value;
},

format(input: string): string {
const [value, error] = compactImpl(input);

if (error) {
return input;
}

const [front, key, tva, category, rest] = strings.splitAt(
value,
7,
8,
9,
10,
);

if (value.length === 8) {
return `${front}/${key}`;
}

return `${front}/${key}/${tva}/${category}/${rest}`;
},

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

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

const [front, key, tva, category, rest] = strings.splitAt(
value,
7,
8,
9,
10,
);

if (!strings.isdigits(front)) {
return { isValid: false, error: new exceptions.InvalidFormat() };
}
if (!VALID_CONTROL_KEYS.includes(key)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (value.length !== 8) {
if (!VALID_TVA_CODES.includes(tva)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (!VALID_CATEGORY_CODES.includes(category)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (!strings.isdigits(rest)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (rest !== '000' && category !== 'E') {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
}

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

export const { name, localName, abbreviation, validate, format, compact } =
impl;

0 comments on commit 2fac402

Please sign in to comment.