se-panfilov/jsvat

View on GitHub
src/lib/countries/czechRepublic.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { Country } from '../jsvat';

export const czechRepublic: Country = {
  name: 'Czech Republic',
  codes: ['CZ', 'CZE', '203'],
  calcFn: (vat: string): boolean => {
    const { rules } = czechRepublic;
    const { multipliers, additional, lookup } = rules;
    if (!additional) return false;

    return (
      isLegalEntities(vat, multipliers.common, additional) ||
      isIndividualType2(vat, multipliers.common, additional, lookup) ||
      isIndividualType3(vat, additional) ||
      isIndividualType1(vat, additional)
    );
  },
  rules: {
    multipliers: {
      common: [8, 7, 6, 5, 4, 3, 2]
    },
    lookup: [8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8],
    regex: [/^(CZ)(\d{8,10})(\d{3})?$/],
    additional: [/^\d{8}$/, /^[0-5][0-9][0|1|5|6]\d[0-3]\d\d{3}$/, /^6\d{8}$/, /^\d{2}[0-3|5-8]\d[0-3]\d\d{4}$/]
  }
};

function isLegalEntities(vat: string, multipliers: ReadonlyArray<number>, additional: ReadonlyArray<RegExp>): boolean {
  let total = 0;
  if (additional[0].test(vat)) {
    // Extract the next digit and multiply by the counter.
    for (let i = 0; i < 7; i++) {
      total += Number(vat.charAt(i)) * multipliers[i];
    }

    // Establish check digit.
    total = 11 - (total % 11);
    if (total === 10) total = 0;
    if (total === 11) total = 1;

    // Compare it with the last character of the VAT number. If it's the same, then it's valid.
    const expect = Number(vat.slice(7, 8));
    return total === expect;
  }

  return false;
}

function isIndividualType1(vat: string, additional: ReadonlyArray<RegExp>): boolean {
  if (additional[1].test(vat)) {
    return Number(vat.slice(0, 2)) <= 62;
  }

  return false;
}

function isIndividualType2(
  vat: string,
  multipliers: ReadonlyArray<number>,
  additional: ReadonlyArray<RegExp>,
  lookup?: ReadonlyArray<number>
): boolean {
  let total = 0;

  if (additional[2].test(vat)) {
    // Extract the next digit and multiply by the counter.
    for (let j = 0; j < 7; j++) {
      total += Number(vat.charAt(j + 1)) * multipliers[j];
    }

    // Establish check digit.
    let a;

    if (total % 11 === 0) {
      a = total + 11;
    } else {
      a = Math.ceil(total / 11) * 11;
    }

    const pointer = a - total - 1;

    // Convert calculated check digit according to a lookup table
    const expect = Number(vat.slice(8, 9));
    if (!lookup) return false;
    return lookup[pointer] === expect;
  }

  return false;
}

function isIndividualType3(vat: string, additional: ReadonlyArray<RegExp>): boolean {
  if (additional[3].test(vat)) {
    const temp: number =
      Number(vat.slice(0, 2)) +
      Number(vat.slice(2, 4)) +
      Number(vat.slice(4, 6)) +
      Number(vat.slice(6, 8)) +
      Number(vat.slice(8));
    const expect: boolean = Number(vat) % 11 === 0;
    return !!(temp % 11 === 0 && expect);
  }
  return false;
}